From 799757ccf1d03c33c75bc597cd5ef77741dcb6a7 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Fri, 3 Jun 2011 09:17:04 +0000 Subject: Imported upstream 4.91 --- AUTHORS | 56 + COPYING | 340 + COPYING.LIB | 504 ++ ChangeLog | 1614 ++++ INSTALL | 236 + Makefile.am | 431 + Makefile.in | 3477 ++++++++ Makefile.tools | 239 + NEWS | 0 README | 38 + TODO | 254 + acinclude.m4 | 405 + aclocal.m4 | 9192 +++++++++++++++++++++ attrib/att.c | 968 +++ attrib/att.h | 306 + attrib/client.c | 1116 +++ attrib/client.h | 28 + attrib/example.c | 341 + attrib/example.h | 26 + attrib/gatt.c | 577 ++ attrib/gatt.h | 53 + attrib/gattrib.c | 636 ++ attrib/gattrib.h | 80 + attrib/gatttool.c | 634 ++ attrib/gatttool.h | 28 + attrib/interactive.c | 842 ++ attrib/main.c | 60 + attrib/manager.c | 105 + attrib/manager.h | 26 + attrib/utils.c | 126 + audio/a2dp-codecs.h | 116 + audio/a2dp.c | 2361 ++++++ audio/a2dp.h | 164 + audio/audio.conf | 45 + audio/avdtp.c | 3914 +++++++++ audio/avdtp.h | 316 + audio/bluetooth.conf | 36 + audio/control.c | 1193 +++ audio/control.h | 50 + audio/ctl_bluetooth.c | 384 + audio/device.c | 862 ++ audio/device.h | 94 + audio/gateway.c | 711 ++ audio/gateway.h | 51 + audio/gsta2dpsink.c | 730 ++ audio/gsta2dpsink.h | 85 + audio/gstavdtpsink.c | 2029 +++++ audio/gstavdtpsink.h | 107 + audio/gstbluetooth.c | 109 + audio/gstpragma.h | 24 + audio/gstrtpsbcpay.c | 352 + audio/gstrtpsbcpay.h | 66 + audio/gstsbcdec.c | 223 + audio/gstsbcdec.h | 66 + audio/gstsbcenc.c | 603 ++ audio/gstsbcenc.h | 75 + audio/gstsbcparse.c | 220 + audio/gstsbcparse.h | 69 + audio/gstsbcutil.c | 521 ++ audio/gstsbcutil.h | 75 + audio/headset.c | 2951 +++++++ audio/headset.h | 109 + audio/ipc.c | 133 + audio/ipc.h | 360 + audio/main.c | 194 + audio/manager.c | 1415 ++++ audio/manager.h | 56 + audio/media.c | 714 ++ audio/media.h | 54 + audio/pcm_bluetooth.c | 1784 ++++ audio/rtp.h | 76 + audio/sink.c | 743 ++ audio/sink.h | 49 + audio/source.c | 633 ++ audio/source.h | 50 + audio/telephony-dummy.c | 433 + audio/telephony-maemo5.c | 2104 +++++ audio/telephony-maemo6.c | 1993 +++++ audio/telephony-ofono.c | 1627 ++++ audio/telephony.h | 244 + audio/transport.c | 927 +++ audio/transport.h | 38 + audio/unix.c | 1917 +++++ audio/unix.h | 30 + bluez.pc.in | 10 + btio/btio.c | 1319 +++ btio/btio.h | 98 + compat/bnep.c | 339 + compat/dun.c | 334 + compat/dund.1 | 72 + compat/dund.c | 645 ++ compat/dund.h | 40 + compat/fakehid.c | 669 ++ compat/hidd.1 | 41 + compat/hidd.c | 862 ++ compat/hidd.h | 30 + compat/lib.h | 86 + compat/msdun.c | 153 + compat/pand.1 | 77 + compat/pand.c | 811 ++ compat/pand.h | 36 + compat/sdp.c | 720 ++ compat/sdp.h | 39 + compile | 143 + config.guess | 1501 ++++ config.h.in | 96 + config.sub | 1705 ++++ configure | 16522 ++++++++++++++++++++++++++++++++++++++ configure.ac | 60 + cups/cups.h | 38 + cups/hcrp.c | 353 + cups/main.c | 876 ++ cups/sdp.c | 119 + cups/spp.c | 118 + depcomp | 630 ++ doc/adapter-api.txt | 286 + doc/agent-api.txt | 91 + doc/assigned-numbers.txt | 23 + doc/attribute-api.txt | 164 + doc/audio-api.txt | 458 ++ doc/control-api.txt | 142 + doc/device-api.txt | 199 + doc/health-api.txt | 166 + doc/hfp-api.txt | 86 + doc/input-api.txt | 44 + doc/manager-api.txt | 74 + doc/media-api.txt | 165 + doc/network-api.txt | 88 + doc/sap-api.txt | 34 + doc/serial-api.txt | 41 + doc/service-api.txt | 62 + doc/version.xml.in | 1 + gdbus/gdbus.h | 177 + gdbus/mainloop.c | 383 + gdbus/object.c | 869 ++ gdbus/polkit.c | 202 + gdbus/watch.c | 748 ++ health/hdp.c | 2223 +++++ health/hdp.h | 35 + health/hdp_main.c | 62 + health/hdp_manager.c | 100 + health/hdp_manager.h | 27 + health/hdp_types.h | 129 + health/hdp_util.c | 1211 +++ health/hdp_util.h | 61 + health/mcap.c | 2195 +++++ health/mcap.h | 169 + health/mcap_internal.h | 141 + health/mcap_lib.h | 228 + health/mcap_sync.c | 1015 +++ input/device.c | 1265 +++ input/device.h | 56 + input/fakehid.c | 411 + input/fakehid.h | 40 + input/input.conf | 9 + input/main.c | 86 + input/manager.c | 207 + input/manager.h | 25 + input/server.c | 237 + input/server.h | 25 + install-sh | 520 ++ lib/bluetooth.c | 470 ++ lib/bluetooth.h | 219 + lib/bnep.h | 153 + lib/cmtp.h | 69 + lib/hci.c | 2913 +++++++ lib/hci.h | 2367 ++++++ lib/hci_lib.h | 233 + lib/hidp.h | 85 + lib/l2cap.h | 208 + lib/mgmt.h | 271 + lib/rfcomm.h | 99 + lib/sco.h | 62 + lib/sdp.c | 4791 +++++++++++ lib/sdp.h | 518 ++ lib/sdp_lib.h | 631 ++ lib/uuid.c | 273 + lib/uuid.h | 65 + ltmain.sh | 8406 +++++++++++++++++++ missing | 376 + network/common.c | 262 + network/common.h | 42 + network/connection.c | 622 ++ network/connection.h | 28 + network/main.c | 59 + network/manager.c | 222 + network/manager.h | 25 + network/network.conf | 6 + network/server.c | 809 ++ network/server.h | 29 + plugins/dbusoob.c | 234 + plugins/echo.c | 167 + plugins/formfactor.c | 148 + plugins/hal.c | 144 + plugins/hciops.c | 3744 +++++++++ plugins/maemo6.c | 252 + plugins/mgmtops.c | 1937 +++++ plugins/pnat.c | 526 ++ plugins/service.c | 824 ++ plugins/storage.c | 43 + sap/main.c | 55 + sap/manager.c | 93 + sap/manager.h | 22 + sap/sap-dummy.c | 330 + sap/sap.h | 186 + sap/server.c | 1438 ++++ sap/server.h | 26 + sbc/formats.h | 55 + sbc/sbc.c | 1234 +++ sbc/sbc.h | 113 + sbc/sbc_math.h | 61 + sbc/sbc_primitives.c | 554 ++ sbc/sbc_primitives.h | 80 + sbc/sbc_primitives_armv6.c | 299 + sbc/sbc_primitives_armv6.h | 52 + sbc/sbc_primitives_iwmmxt.c | 304 + sbc/sbc_primitives_iwmmxt.h | 42 + sbc/sbc_primitives_mmx.c | 375 + sbc/sbc_primitives_mmx.h | 41 + sbc/sbc_primitives_neon.c | 893 ++ sbc/sbc_primitives_neon.h | 41 + sbc/sbc_tables.h | 660 ++ sbc/sbcdec.c | 293 + sbc/sbcenc.c | 308 + sbc/sbcinfo.c | 322 + sbc/sbctester.c | 358 + scripts/bluetooth-hid2hci.rules | 36 + scripts/bluetooth-serial.rules | 35 + scripts/bluetooth.rules | 4 + scripts/bluetooth.rules.in | 4 + scripts/bluetooth_serial | 39 + serial/main.c | 59 + serial/manager.c | 176 + serial/manager.h | 25 + serial/port.c | 622 ++ serial/port.h | 29 + serial/proxy.c | 1278 +++ serial/proxy.h | 25 + serial/serial.conf | 10 + src/adapter.c | 3752 +++++++++ src/adapter.h | 302 + src/agent.c | 794 ++ src/agent.h | 77 + src/attrib-server.c | 1310 +++ src/attrib-server.h | 35 + src/bluetooth.conf | 33 + src/bluetooth.ver | 10 + src/bluetoothd.8.in | 91 + src/dbus-common.c | 254 + src/dbus-common.h | 47 + src/device.c | 2388 ++++++ src/device.h | 119 + src/error.c | 116 + src/error.h | 43 + src/event.c | 731 ++ src/event.h | 44 + src/genbuiltin | 17 + src/glib-helper.c | 587 ++ src/glib-helper.h | 36 + src/hcid.h | 66 + src/log.c | 136 + src/log.h | 57 + src/main.c | 509 ++ src/main.conf | 66 + src/manager.c | 431 + src/manager.h | 43 + src/oob.c | 41 + src/oob.h | 32 + src/oui.c | 101 + src/oui.h | 25 + src/plugin.c | 249 + src/plugin.h | 47 + src/ppoll.h | 16 + src/rfkill.c | 173 + src/sdp-xml.c | 789 ++ src/sdp-xml.h | 59 + src/sdpd-database.c | 346 + src/sdpd-request.c | 1094 +++ src/sdpd-server.c | 292 + src/sdpd-service.c | 537 ++ src/sdpd.h | 97 + src/storage.c | 1345 ++++ src/storage.h | 92 + src/textfile.c | 492 ++ src/textfile.h | 43 + src/uinput.h | 724 ++ test/agent.c | 700 ++ test/apitest | 448 ++ test/attest.c | 183 + test/avtest.c | 869 ++ test/bdaddr.8 | 68 + test/bdaddr.c | 460 ++ test/btiotest.c | 555 ++ test/dbusdef.py | 16 + test/gaptest.c | 335 + test/hciemu.1 | 31 + test/hciemu.c | 1343 ++++ test/hsmicro | 20 + test/hsplay | 22 + test/hstest.c | 308 + test/ipctest.c | 1129 +++ test/l2test.c | 1379 ++++ test/list-devices | 87 + test/lmptest.c | 175 + test/monitor-bluetooth | 56 + test/rctest.1 | 90 + test/rctest.c | 781 ++ test/sap-client | 943 +++ test/scotest.c | 434 + test/sdptest.c | 146 + test/service-did.xml | 33 + test/service-ftp.xml | 37 + test/service-opp.xml | 50 + test/service-record.dtd | 66 + test/service-spp.xml | 25 + test/simple-agent | 120 + test/simple-endpoint | 126 + test/simple-service | 127 + test/test-adapter | 120 + test/test-attrib | 108 + test/test-audio | 45 + test/test-device | 207 + test/test-discovery | 57 + test/test-input | 45 + test/test-manager | 38 + test/test-network | 57 + test/test-sap-server | 138 + test/test-serial | 56 + test/test-service | 47 + test/test-telephony | 176 + test/test-textfile.c | 188 + test/uuidtest.c | 319 + tools/avctrl.8 | 39 + tools/avctrl.c | 248 + tools/avinfo.c | 672 ++ tools/bccmd.8 | 130 + tools/bccmd.c | 1254 +++ tools/ciptool.1 | 68 + tools/ciptool.c | 498 ++ tools/csr.c | 2853 +++++++ tools/csr.h | 553 ++ tools/csr_3wire.c | 62 + tools/csr_bcsp.c | 255 + tools/csr_h4.c | 165 + tools/csr_hci.c | 160 + tools/csr_usb.c | 180 + tools/dfu.c | 168 + tools/dfu.h | 107 + tools/dfubabel.1 | 38 + tools/dfubabel.c | 211 + tools/dfutool.1 | 53 + tools/dfutool.c | 791 ++ tools/hciattach.8 | 155 + tools/hciattach.c | 1446 ++++ tools/hciattach.h | 56 + tools/hciattach_ath3k.c | 1049 +++ tools/hciattach_qualcomm.c | 275 + tools/hciattach_st.c | 278 + tools/hciattach_ti.c | 529 ++ tools/hciattach_tialt.c | 244 + tools/hciconfig.8 | 277 + tools/hciconfig.c | 2036 +++++ tools/hcieventmask.c | 130 + tools/hcisecfilter.c | 155 + tools/hcitool.1 | 209 + tools/hcitool.c | 3007 +++++++ tools/hid2hci.8 | 51 + tools/hid2hci.c | 375 + tools/kword.c | 65 + tools/kword.h | 46 + tools/l2ping.8 | 76 + tools/l2ping.c | 324 + tools/lexer.c | 1834 +++++ tools/lexer.l | 120 + tools/parser.c | 1768 ++++ tools/parser.h | 94 + tools/parser.y | 171 + tools/ppporc.c | 271 + tools/rfcomm.1 | 137 + tools/rfcomm.c | 849 ++ tools/rfcomm.conf | 17 + tools/sdptool.1 | 130 + tools/sdptool.c | 4274 ++++++++++ tools/ubcsp.c | 1180 +++ tools/ubcsp.h | 208 + tracer/main.c | 152 + ylwrap | 222 + 387 files changed, 205620 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 COPYING.LIB create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 Makefile.in create mode 100644 Makefile.tools create mode 100644 NEWS create mode 100644 README create mode 100644 TODO create mode 100644 acinclude.m4 create mode 100644 aclocal.m4 create mode 100644 attrib/att.c create mode 100644 attrib/att.h create mode 100644 attrib/client.c create mode 100644 attrib/client.h create mode 100644 attrib/example.c create mode 100644 attrib/example.h create mode 100644 attrib/gatt.c create mode 100644 attrib/gatt.h create mode 100644 attrib/gattrib.c create mode 100644 attrib/gattrib.h create mode 100644 attrib/gatttool.c create mode 100644 attrib/gatttool.h create mode 100644 attrib/interactive.c create mode 100644 attrib/main.c create mode 100644 attrib/manager.c create mode 100644 attrib/manager.h create mode 100644 attrib/utils.c create mode 100644 audio/a2dp-codecs.h create mode 100644 audio/a2dp.c create mode 100644 audio/a2dp.h create mode 100644 audio/audio.conf create mode 100644 audio/avdtp.c create mode 100644 audio/avdtp.h create mode 100644 audio/bluetooth.conf create mode 100644 audio/control.c create mode 100644 audio/control.h create mode 100644 audio/ctl_bluetooth.c create mode 100644 audio/device.c create mode 100644 audio/device.h create mode 100644 audio/gateway.c create mode 100644 audio/gateway.h create mode 100644 audio/gsta2dpsink.c create mode 100644 audio/gsta2dpsink.h create mode 100644 audio/gstavdtpsink.c create mode 100644 audio/gstavdtpsink.h create mode 100644 audio/gstbluetooth.c create mode 100644 audio/gstpragma.h create mode 100644 audio/gstrtpsbcpay.c create mode 100644 audio/gstrtpsbcpay.h create mode 100644 audio/gstsbcdec.c create mode 100644 audio/gstsbcdec.h create mode 100644 audio/gstsbcenc.c create mode 100644 audio/gstsbcenc.h create mode 100644 audio/gstsbcparse.c create mode 100644 audio/gstsbcparse.h create mode 100644 audio/gstsbcutil.c create mode 100644 audio/gstsbcutil.h create mode 100644 audio/headset.c create mode 100644 audio/headset.h create mode 100644 audio/ipc.c create mode 100644 audio/ipc.h create mode 100644 audio/main.c create mode 100644 audio/manager.c create mode 100644 audio/manager.h create mode 100644 audio/media.c create mode 100644 audio/media.h create mode 100644 audio/pcm_bluetooth.c create mode 100644 audio/rtp.h create mode 100644 audio/sink.c create mode 100644 audio/sink.h create mode 100644 audio/source.c create mode 100644 audio/source.h create mode 100644 audio/telephony-dummy.c create mode 100644 audio/telephony-maemo5.c create mode 100644 audio/telephony-maemo6.c create mode 100644 audio/telephony-ofono.c create mode 100644 audio/telephony.h create mode 100644 audio/transport.c create mode 100644 audio/transport.h create mode 100644 audio/unix.c create mode 100644 audio/unix.h create mode 100644 bluez.pc.in create mode 100644 btio/btio.c create mode 100644 btio/btio.h create mode 100644 compat/bnep.c create mode 100644 compat/dun.c create mode 100644 compat/dund.1 create mode 100644 compat/dund.c create mode 100644 compat/dund.h create mode 100644 compat/fakehid.c create mode 100644 compat/hidd.1 create mode 100644 compat/hidd.c create mode 100644 compat/hidd.h create mode 100644 compat/lib.h create mode 100644 compat/msdun.c create mode 100644 compat/pand.1 create mode 100644 compat/pand.c create mode 100644 compat/pand.h create mode 100644 compat/sdp.c create mode 100644 compat/sdp.h create mode 100755 compile create mode 100755 config.guess create mode 100644 config.h.in create mode 100755 config.sub create mode 100755 configure create mode 100644 configure.ac create mode 100644 cups/cups.h create mode 100644 cups/hcrp.c create mode 100644 cups/main.c create mode 100644 cups/sdp.c create mode 100644 cups/spp.c create mode 100755 depcomp create mode 100644 doc/adapter-api.txt create mode 100644 doc/agent-api.txt create mode 100644 doc/assigned-numbers.txt create mode 100644 doc/attribute-api.txt create mode 100644 doc/audio-api.txt create mode 100644 doc/control-api.txt create mode 100644 doc/device-api.txt create mode 100644 doc/health-api.txt create mode 100644 doc/hfp-api.txt create mode 100644 doc/input-api.txt create mode 100644 doc/manager-api.txt create mode 100644 doc/media-api.txt create mode 100644 doc/network-api.txt create mode 100644 doc/sap-api.txt create mode 100644 doc/serial-api.txt create mode 100644 doc/service-api.txt create mode 100644 doc/version.xml.in create mode 100644 gdbus/gdbus.h create mode 100644 gdbus/mainloop.c create mode 100644 gdbus/object.c create mode 100644 gdbus/polkit.c create mode 100644 gdbus/watch.c create mode 100644 health/hdp.c create mode 100644 health/hdp.h create mode 100644 health/hdp_main.c create mode 100644 health/hdp_manager.c create mode 100644 health/hdp_manager.h create mode 100644 health/hdp_types.h create mode 100644 health/hdp_util.c create mode 100644 health/hdp_util.h create mode 100644 health/mcap.c create mode 100644 health/mcap.h create mode 100644 health/mcap_internal.h create mode 100644 health/mcap_lib.h create mode 100644 health/mcap_sync.c create mode 100644 input/device.c create mode 100644 input/device.h create mode 100644 input/fakehid.c create mode 100644 input/fakehid.h create mode 100644 input/input.conf create mode 100644 input/main.c create mode 100644 input/manager.c create mode 100644 input/manager.h create mode 100644 input/server.c create mode 100644 input/server.h create mode 100755 install-sh create mode 100644 lib/bluetooth.c create mode 100644 lib/bluetooth.h create mode 100644 lib/bnep.h create mode 100644 lib/cmtp.h create mode 100644 lib/hci.c create mode 100644 lib/hci.h create mode 100644 lib/hci_lib.h create mode 100644 lib/hidp.h create mode 100644 lib/l2cap.h create mode 100644 lib/mgmt.h create mode 100644 lib/rfcomm.h create mode 100644 lib/sco.h create mode 100644 lib/sdp.c create mode 100644 lib/sdp.h create mode 100644 lib/sdp_lib.h create mode 100644 lib/uuid.c create mode 100644 lib/uuid.h create mode 100755 ltmain.sh create mode 100755 missing create mode 100644 network/common.c create mode 100644 network/common.h create mode 100644 network/connection.c create mode 100644 network/connection.h create mode 100644 network/main.c create mode 100644 network/manager.c create mode 100644 network/manager.h create mode 100644 network/network.conf create mode 100644 network/server.c create mode 100644 network/server.h create mode 100644 plugins/dbusoob.c create mode 100644 plugins/echo.c create mode 100644 plugins/formfactor.c create mode 100644 plugins/hal.c create mode 100644 plugins/hciops.c create mode 100644 plugins/maemo6.c create mode 100644 plugins/mgmtops.c create mode 100644 plugins/pnat.c create mode 100644 plugins/service.c create mode 100644 plugins/storage.c create mode 100644 sap/main.c create mode 100644 sap/manager.c create mode 100644 sap/manager.h create mode 100644 sap/sap-dummy.c create mode 100644 sap/sap.h create mode 100644 sap/server.c create mode 100644 sap/server.h create mode 100644 sbc/formats.h create mode 100644 sbc/sbc.c create mode 100644 sbc/sbc.h create mode 100644 sbc/sbc_math.h create mode 100644 sbc/sbc_primitives.c create mode 100644 sbc/sbc_primitives.h create mode 100644 sbc/sbc_primitives_armv6.c create mode 100644 sbc/sbc_primitives_armv6.h create mode 100644 sbc/sbc_primitives_iwmmxt.c create mode 100644 sbc/sbc_primitives_iwmmxt.h create mode 100644 sbc/sbc_primitives_mmx.c create mode 100644 sbc/sbc_primitives_mmx.h create mode 100644 sbc/sbc_primitives_neon.c create mode 100644 sbc/sbc_primitives_neon.h create mode 100644 sbc/sbc_tables.h create mode 100644 sbc/sbcdec.c create mode 100644 sbc/sbcenc.c create mode 100644 sbc/sbcinfo.c create mode 100644 sbc/sbctester.c create mode 100644 scripts/bluetooth-hid2hci.rules create mode 100644 scripts/bluetooth-serial.rules create mode 100644 scripts/bluetooth.rules create mode 100644 scripts/bluetooth.rules.in create mode 100644 scripts/bluetooth_serial create mode 100644 serial/main.c create mode 100644 serial/manager.c create mode 100644 serial/manager.h create mode 100644 serial/port.c create mode 100644 serial/port.h create mode 100644 serial/proxy.c create mode 100644 serial/proxy.h create mode 100644 serial/serial.conf create mode 100644 src/adapter.c create mode 100644 src/adapter.h create mode 100644 src/agent.c create mode 100644 src/agent.h create mode 100644 src/attrib-server.c create mode 100644 src/attrib-server.h create mode 100644 src/bluetooth.conf create mode 100644 src/bluetooth.ver create mode 100644 src/bluetoothd.8.in create mode 100644 src/dbus-common.c create mode 100644 src/dbus-common.h create mode 100644 src/device.c create mode 100644 src/device.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/event.c create mode 100644 src/event.h create mode 100755 src/genbuiltin create mode 100644 src/glib-helper.c create mode 100644 src/glib-helper.h create mode 100644 src/hcid.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/main.c create mode 100644 src/main.conf create mode 100644 src/manager.c create mode 100644 src/manager.h create mode 100644 src/oob.c create mode 100644 src/oob.h create mode 100644 src/oui.c create mode 100644 src/oui.h create mode 100644 src/plugin.c create mode 100644 src/plugin.h create mode 100644 src/ppoll.h create mode 100644 src/rfkill.c create mode 100644 src/sdp-xml.c create mode 100644 src/sdp-xml.h create mode 100644 src/sdpd-database.c create mode 100644 src/sdpd-request.c create mode 100644 src/sdpd-server.c create mode 100644 src/sdpd-service.c create mode 100644 src/sdpd.h create mode 100644 src/storage.c create mode 100644 src/storage.h create mode 100644 src/textfile.c create mode 100644 src/textfile.h create mode 100644 src/uinput.h create mode 100644 test/agent.c create mode 100755 test/apitest create mode 100644 test/attest.c create mode 100644 test/avtest.c create mode 100644 test/bdaddr.8 create mode 100644 test/bdaddr.c create mode 100644 test/btiotest.c create mode 100644 test/dbusdef.py create mode 100644 test/gaptest.c create mode 100644 test/hciemu.1 create mode 100644 test/hciemu.c create mode 100755 test/hsmicro create mode 100755 test/hsplay create mode 100644 test/hstest.c create mode 100644 test/ipctest.c create mode 100644 test/l2test.c create mode 100755 test/list-devices create mode 100644 test/lmptest.c create mode 100755 test/monitor-bluetooth create mode 100644 test/rctest.1 create mode 100644 test/rctest.c create mode 100644 test/sap-client create mode 100644 test/scotest.c create mode 100644 test/sdptest.c create mode 100644 test/service-did.xml create mode 100644 test/service-ftp.xml create mode 100644 test/service-opp.xml create mode 100644 test/service-record.dtd create mode 100644 test/service-spp.xml create mode 100755 test/simple-agent create mode 100755 test/simple-endpoint create mode 100755 test/simple-service create mode 100755 test/test-adapter create mode 100755 test/test-attrib create mode 100755 test/test-audio create mode 100755 test/test-device create mode 100755 test/test-discovery create mode 100755 test/test-input create mode 100755 test/test-manager create mode 100755 test/test-network create mode 100755 test/test-sap-server create mode 100755 test/test-serial create mode 100755 test/test-service create mode 100755 test/test-telephony create mode 100644 test/test-textfile.c create mode 100644 test/uuidtest.c create mode 100644 tools/avctrl.8 create mode 100644 tools/avctrl.c create mode 100644 tools/avinfo.c create mode 100644 tools/bccmd.8 create mode 100644 tools/bccmd.c create mode 100644 tools/ciptool.1 create mode 100644 tools/ciptool.c create mode 100644 tools/csr.c create mode 100644 tools/csr.h create mode 100644 tools/csr_3wire.c create mode 100644 tools/csr_bcsp.c create mode 100644 tools/csr_h4.c create mode 100644 tools/csr_hci.c create mode 100644 tools/csr_usb.c create mode 100644 tools/dfu.c create mode 100644 tools/dfu.h create mode 100644 tools/dfubabel.1 create mode 100644 tools/dfubabel.c create mode 100644 tools/dfutool.1 create mode 100644 tools/dfutool.c create mode 100644 tools/hciattach.8 create mode 100644 tools/hciattach.c create mode 100644 tools/hciattach.h create mode 100644 tools/hciattach_ath3k.c create mode 100644 tools/hciattach_qualcomm.c create mode 100644 tools/hciattach_st.c create mode 100644 tools/hciattach_ti.c create mode 100644 tools/hciattach_tialt.c create mode 100644 tools/hciconfig.8 create mode 100644 tools/hciconfig.c create mode 100644 tools/hcieventmask.c create mode 100644 tools/hcisecfilter.c create mode 100644 tools/hcitool.1 create mode 100644 tools/hcitool.c create mode 100644 tools/hid2hci.8 create mode 100644 tools/hid2hci.c create mode 100644 tools/kword.c create mode 100644 tools/kword.h create mode 100644 tools/l2ping.8 create mode 100644 tools/l2ping.c create mode 100644 tools/lexer.c create mode 100644 tools/lexer.l create mode 100644 tools/parser.c create mode 100644 tools/parser.h create mode 100644 tools/parser.y create mode 100644 tools/ppporc.c create mode 100644 tools/rfcomm.1 create mode 100644 tools/rfcomm.c create mode 100644 tools/rfcomm.conf create mode 100644 tools/sdptool.1 create mode 100644 tools/sdptool.c create mode 100644 tools/ubcsp.c create mode 100644 tools/ubcsp.h create mode 100644 tracer/main.c create mode 100755 ylwrap diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..939a0a8 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,56 @@ +Maxim Krasnyansky +Marcel Holtmann +Stephen Crane +Jean Tourrilhes +Jan Beutel +Ilguiz Latypov +Thomas Moser +Nils Faerber +Martin Leopold +Wolfgang Heidrich +Fabrizio Gennari +Brad Midgley +Henryk Ploetz +Philip Blundell +Johan Hedberg +Claudio Takahasi +Eduardo Rocha +Denis Kenzior +Frederic Dalleau +Frederic Danis +Luiz Augusto von Dentz +Fabien Chevalier +Ohad Ben-Cohen +Daniel Gollub +Tom Patzig +Kai Vehmanen +Vinicius Gomes +Alok Barsode +Bastien Nocera +Albert Huang +Glenn Durfee +David Woodhouse +Christian Hoene +Pekka Pessi +Siarhei Siamashka +Nick Pelly +Lennart Poettering +Gustavo F. Padovan +Marc-Andre Lureau +Bea Lam +Zygo Blaxell +Forrest Zhao +Scott Talbot +Ilya Rubtsov +Mario Limonciello +Filippo Giunchedi +Jaikumar Ganesh +Elvis Pfutzenreuter +Santiago Carot-Nemesio +José Antonio Santos Cadenas +Francisco Alecrim +Daniel Orstadius +Anderson Briglia +Anderson Lizardo +Bruna Moreira +Brian Gix diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..6d45519 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000..1f7c8cc --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..5796bf3 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1614 @@ +ver 4.91: + Fix issue with LMP version string and hciconfig. + Fix issue with missing discovery signal when scanning. + Fix issue with wrong state and canceling name resolving. + Fix issue with missing check during adapter initialization. + Fix issue with missing protocol not supported error and A2DP. + Fix issue with crash during driver unregistering and A2DP. + Fix issue with crash when receiving AVDTP close command. + Fix issue with remote SEP handling when A2DP codec changes. + Fix issue with SCO hangup handling and state changes. + Fix issue with security level and MCAP instances. + Fix issue with memory leak and HDP data channels. + Add support for discover characteristics by UUID to gatttool. + Add initial support for Out-of-Band association model. + Add initial support for SIM Access Profile. + +ver 4.90: + Fix issue with setting of global mode property. + Fix issue with handling of RequestSession responses. + Fix issue with TP_BNEP_CTRL_BV_01_C qualification test. + Fix issue with too short AVDTP request timeout. + Add support for SIM Access Profile manager. + Add support for new UUID utility functions. + Add support for attribute server notifications. + Add support for client characteristic configuration. + Update support for interactive GATT utility. + +ver 4.89: + Fix issue with name resolving when discovery is suspended. + Fix issue with parsing flags of advertising report. + Fix issue with SEP handling if interface is disabled. + Fix issue with device object creation on disconnect event. + Fix issue with indicators whenever the driver is initialized. + Fix issue with call indicator when parsing call info reply. + Fix issue with crash and allowed GATT MTU was too large. + Add support for SDP record of Primary GATT services. + Add support for interactive mode for GATT utility. + +ver 4.88: + Fix issue with HID channel reference count handling. + Fix issue with daemon exit on badly formatted AT+VTS. + Fix issue with crash while parsing of endpoint properties. + Fix issue with possible crash on AVDTP Suspend request timeout. + Fix issue with stopping inquiry before adapter is initialized. + Fix issue with creating device object when connection fails. + Fix issue with sending HCIDEVUP when adapter is already up. + Fix issue with handling bonding IO channel closing. + Fix agent cancellation in security mode 3 situations. + Update pairing code to support management interface. + +ver 4.87: + Fix issue with initialization when adapter is already up. + Fix issue with attribute server MTU and incoming connections. + Fix issue with duplicate characteristics after discovery. + +ver 4.86: + Revert wrong fix for SDP PDU size error response. + Fix various memory leaks in A2DP and AVDTP support. + Add Routing property to MediaTransport interface + Add proper tracking mechanism to NREC status. + Add READ_BLOB_REQUEST support to attribute server. + +ver 4.85: + Fix issue with event mask setting for older adapters. + Fix issue with device creation and pairing failures. + Add support for telephony support via oFono. + Add support for characteristic security level. + Update support for service registration. + +ver 4.84: + Fix issue with wrong parameters and device found signals. + Fix issue with leaking EIR data if RSSI does not change. + Fix issue with adapter initialization state. + Fix issue with closing of SDP server sockets. + +ver 4.83: + Fix issue with already connected HFP/HSP endpoints. + Fix missing reply when create device is canceled. + Fix memory leak within the attribute server. + Fix memory leak with unused extended inquiry name. + Fix setting paired state when device->authr is false. + Fix clearing authentication request for renewed keys. + Add support for storing link keys in runtime memory. + Update support for primary service discovery. + +ver 4.82: + Fix crash with mmap of files with multiples of page size. + Fix HFP response and hold (AT+BTRH) command response. + Fix device creation error response when powered off. + Fix device removal when connecting/browsing fails. + Add initial attribute permission implementation. + Add AVDTP SRC stream send buffer size verification. + Add support for setting link policy based on features. + +ver 4.81: + Fix issue with telephony driver initialization. + Fix issue with adapter services list initialization. + Fix crash after simultaneous authentication requests. + Add support for primary service search on device creation. + +ver 4.80: + Fix legacy link key storing for some buggy adapters. + Fix invalid memory access when EIR field length is zero. + Fix adapter initialization to wait for kernel HCI commands. + Fix initialization of adapters which are already up. + Fix possible race condition when initializing adapters. + Fix possible crashes when attempting to connect AVDTP. + Fix not aborting sink stream configuration on disconnect. + Fix not indicating disconnected state when connecting to AVDTP. + Fix not dropping AVDTP session when canceling stream setup. + Fix AVDTP abort not being send when the state is idle. + Fix regression with Low Energy and interleave discovery. + Add a new configuration option to disable Low Energy support. + Add iwmmxt optimization for SBC for ARM PXA series CPUs. + Update support for GATT Primary Service Discovery. + Update MCAP and HDP support. + +ver 4.79: + Fix issue with adapter initialization race condition. + Update new Bluetooth Management interface support. + +ver 4.78: + Fix various issues with AVDTP timer handling. + Fix various issues with handling of mode changes. + Fix issue with audio disconnect watch in connecting state. + Fix issue with handling call waiting indicators in telephony. + Fix issue with handling UUID parameter and RegisterEndpoint. + Add initial support for Bluetooth Management interface. + Add support for Application property to HealthChannel. + +ver 4.77: + Fix issue with device name and accessing already freed memory. + Fix issue with handling CHLD=0 command for handsfree. + Fix issue with manager properties and no adapters. + Fix issue with properties and broken service records. + Fix issue with A2DP playback and sample rate changes. + Update MCAP and HDP support. + +ver 4.76: + Fix issue in telephony driver with hanging up held call. + Fix issue in telephony driver with notifications when on hold. + Fix issue with blocking on setconf confirmation callback. + Fix issue with not always signaling new streams as sinks. + Fix issue with errors in case of endpoint request timeout. + Fix issue with HFP/HSP microphone and speaker gain values. + Add source if the device attempt to configure local sink stream. + Add PSM option for GATT/ATT over BR/EDR on gatttool. + Add support for GATT/ATT Attribute Write Request. + Update MCAP and HDP support. + +ver 4.75: + Fix use of uninitialized variable on legacy pairing. + Fix mismatch of attribute protocol opcode. + +ver 4.74: + Fix regression for Legacy Pairing. + Fix wrong PSM value for attribute protocol. + Fix issue with RSSI field in advertising reports. + Add support for Add BR/EDR and LE interleaved discovery. + Add support for GATT write characteristic value option. + Add support for specifying download address for AR300x. + +ver 4.73: + Fix problem with EIR data when setting the name. + Fix reading local name from command complete event. + Fix registering local endpoints with disabled socket interface. + Add support for more HCI operations using ops infrastructure. + Add support for GATT characteristic hierarchy. + Add support for GATT indications. + +ver 4.72: + Fix memory leak while connecting BTIO channels. + Fix crash with GStreamer plugin if SBC is not supported. + Fix issue with GATT server stop sending notifications. + Fix issue with GATT and dealing with the minimum MTU size. + Fix issue with file descriptor leak in GATT client. + Add support for UUID 128-bit handling in attribute client. + Add support for encoders/decoders for MTU Exchange. + Add support for the MTU Exchange procedure to the server. + Add support for a per channel MTU to the ATT server. + Add support for Characteristic interface. + Add support for new Media API and framework. + Add initial support for HDP plugin. + +ver 4.71: + Fix compilation when SBC support in not enabled. + Fix crash with RequestSession and application disconnects. + Fix memory leak and possible crash when removing audio device. + Fix issue with closing stream of locked sep when reconfiguring. + Fix issue where discovery could interfere with bonding. + Fix issue with Connected status when PS3 BD remote connects. + Fix issue with lifetime of fake input devices. + Add support for compile time option of oui.txt path. + Add support for printing IEEE1284 device ID for CUPS. + Add plugin for setting adapter class via DMI. + Add more features for attribute protocol and profile. + Add initial support for MCAP. + +ver 4.70: + Fix incoming call indication handling when in WAITING state. + Fix various SDP related qualification test case issues. + Fix logic to write EIR when SDP records are changed. + Fix UTF-8 validity check for remote names in EIR. + Add support for UUID-128 extended inquiry response. + Add service UUIDs from EIR to the DeviceFound signal. + Add fast connectable feature for Handsfree profile. + Add HCI command and event definitions for AMP support. + Add firmware download support for Qualcommh devices. + Add host level support for Atheros AR300x device. + Add initial support of ATT and GATT for basic rate. + +ver 4.69: + Fix issue with calling g_option_context_free() twice. + Fix inconsistencies with initial LE commands and events. + Add support for telephony ClearLastNumber method. + Add support for network server interface. + +ver 4.68: + Fix initialization of adapters in RAW mode. + Fix signal strength for HFP in Maemo's telephony support. + Add support for following the radio state via Maemo's MCE. + Add initial set of LE commands and events definitions. + Add mode option for L2CAP sockets to the BtIO API. + +ver 4.67: + Fix issue with authentication reply when bonding already completed. + Fix issue with not canceling authentication when bonding fails. + Fix issue with changed combination keys and temporary storage. + Fix issue with sdp_get_supp_feat library function. + Fix issue with missing unblock on device removal. + Fix issue with not waiting for mode change completion. + Add ARMv6 optimized version of analysis filter for SBC encoder. + +ver 4.66: + Fix regression with full debug enabling via SIGUSR2. + Fix redundant speaker/microphone gains being sent. + Fix not emitting PropertyChanged for SpeakerGain/MicrophoneGain. + Fix issue with storage usage when a record is not found in memory. + Fix issue with DiscoverServices not retrieving any records. + Fix audio profile disconnection order to match whitepaper. + Fix auto-accept confirmation when local agent has NoInputNoOutput. + Fix remote just-works SSP when MITM protection is required. + Fix performing dedicated bonding without MITM requirement. + Add support for storing debug link keys in runtime memory. + +ver 4.65: + Fix issues with general bonding being default setting now. + Fix driver removal upon device removal. + Add new "Blocked" property to device objects. + Add hciconfig support for blacklisting. + Add support for dynamic debug feature. + +ver 4.64: + Fix invalid memory access in headset_get_nrec function. + Fix issue with disconnect event on higher protocol layers. + Fix issue with list parsing in sdp_set_supp_features function. + Fix device object reference counting for SDP browse requests. + Add missing memory checks whenever memory is allocated for SDP. + Add support for exporting local services via D-Bus. + Add more L2CAP Enhanced Retransmission test options. + +ver 4.63: + Fix avdtp_abort not canceling pending requests. + Fix stale connection when abort gets rejected. + +ver 4.62: + Fix accidental symbol breakage with inquiry transmit power. + Fix using invalid data from previous headset connection. + Fix double free on AVDTP Abort response. + Fix possible crash while verifying AVDTP version. + Fix missing inuse flag when AVDTP stream is configured. + Add support for Bluetooth controller types. + +ver 4.61: + Fix issues with Read Inquiry Response Transmit Power Level. + Fix possible invalid read when removing a temporary device. + Fix mode restoration when remember_powered is false. + Fix conference call releasing in telephony-maemo. + Fix segmentation fault with authorization during headset disconnects. + Add support for handling unanswered AVDTP request on disconnect. + Add support for handling Inquiry Response Transmit Power Level. + Add support for caching of remote host features. + Add preliminary voice dialing support for HSP. + +ver 4.60: + Fix voice mailbox number reading from SIM. + Fix some races with D-Bus mainloop integration. + Add helpers for D-Bus signal watches. + +ver 4.59: + Add values for Bluetooth 4.0 specification. + Add SDP functions for HDP support. + Add test scripts for input and audio. + Fix missing close on BtIO create_io function. + Fix sending incorrect AVDTP commands after timeout occurs. + Fix timer removal when device disconnects unexpectedly. + Fix Extended Inquiry Response record for Device ID. + +ver 4.58: + Fix crash when adapter agent exists during authentication. + Fix CK-20W quirks for play and pause events. + +ver 4.57: + Fix unloading of drivers for uninitialized adapters. + Fix debug message to use requested and not opened SEID. + Fix codec selection for GStreamer plugin. + Fix deleting of SDP records during service updates. + Fix deleting of SDP records when a device is removed. + Fix handling when the SDP record is modified on remote device. + Fix potential buffer overflow by using snprintf instead of sprintf. + Fix const declarations for some storage function parameters. + +ver 4.56: + Add missing values from Bluetooth 3.0 specification. + Add proper tracking of device paired status. + Fix tracking of devices without permanently stored link key. + Fix issue with link key removal after connection failures. + Fix legacy pairing information based on remote host features. + Fix off-by-one issue with AVDTP capability parsing. + Fix AVRCP, AVCTP, AVDTP, A2DP and HFP version numbers. + Fix agent canceling before calling agent_destroy. + Fix service record parsing with an empty UUID list. + Fix various SDP related memory leaks. + +ver 4.55: + Add support for POSIX capabilities dropping. + Add special quirk for the Nokia CK-20W car kit. + Fix error code handling for AVDTP SetConfiguration response. + Fix updating out of range list when RSSI hasn't changed. + Fix various memory leaks and unnecessary error checks. + +ver 4.54: + Add introspection interface to output of introspection calls. + Fix stream handling when media transport disconnects prematurely. + Fix command timeout handling when there's no stream. + Fix headset_suspend_stream behavior for invalid states + Fix issue with AVDTP ABORTING state transition. + Fix issue with AVDTP suspend while closing. + +ver 4.53: + Fix issue with telephony connection state notifications. + Fix AVDTP stream leak for invalid media transport config. + Fix audio connection authorization handling with timeouts. + Fix race condition in authorizing audio connections. + Fix device authorized setting for AVRCP-only connections. + Fix duplicate attempts from device to connect signal channel. + +ver 4.52: + Add AVCTP support to test utility. + Fix AVDTP Abort when transport closes before response. + Fix authorization when the audio profiles are slow to connect. + Fix potential AVDTP reference leaks. + +ver 4.51: + Add utility for basic AVDTP testing. + Add support for configuring L2CAP FCS option. + Fix discovery mode for CUPS 1.4.x and later. + Fix global state tracking of audio service. + Fix last issues with the new build system. + +ver 4.50: + Fix issue with missing manual pages in distribution. + Fix issue with the configuration and state directories. + Fix issue with creating include directory. + Fix dependencies of include file generation. + +ver 4.49: + Add simple test program for basic GAP testing. + Add support for confirmation requests to agent example. + Add support for full non-recursive build. + Add five millisecond delay for Simple Pairing auto-accept. + Fix Class of Device setting when InitiallyPowered=false. + +ver 4.48: + Add library function for comparing UUID values. + Add support for creating all plugins as builtins. + Add support for async handling of service class changes. + Add support for source interface to audio IPC. + Fix device name settings when device is off or down. + Fix issue with enabled SCO server when not necessary. + Fix missing D-Bus access policy for CUPS backend. + Fix discovery results of CUPS backend. + Fix initialization handling of Maemo telephony. + +ver 4.47: + Add support for RFKILL unblock handling. + Add support for serial proxy configurations. + Add support for caching service class updates. + Fix issues with updating SDP service records. + Fix usage of limited discoverable mode. + Remove deprecated methods and signals for AudioSource. + +ver 4.46: + Add support for A2DP sink role. + Fix clearing svc_cache before the adapter is up. + Fix various pointer after free usages. + Fix various memory leaks. + +ver 4.45: + Fix UDEV_DATADIR fallback if pkg-config fails. + Fix adapter cleanup and setup prototypes. + Fix double-free with out-of-range devices. + Fix inband ring setting to be per-headset. + Fix handling of Maemo CSD startup. + +ver 4.44: + Add some missing manual pages. + Fix missing number prefix when installing udev rules. + Fix program prefix used in Bluetooth udev rules. + Fix three-way calling indicator order. + Fix downgrade/upgrade of callheld indicator. + Fix +CIEV sending when indicator value changes. + Fix signal handling for Maemo telephony driver. + Fix parsing issues with messages from Maemo CSD. + Fix issue with duplicate active calls. + +ver 4.43: + Add support for udev based on-demand startup. + Fix verbose error reporting of CUPS backend. + Fix various string length issues. + Fix issues with Maemo telephony driver. + Fix another device setup and temporary flag issue. + Fix and update example agent implementation. + +ver 4.42: + Add TI WL1271 to Texas Instruments chip list. + Add special udev mode to bluetoothd. + Fix regression when there is no agent registered. + Fix error return when bonding socket hang up. + Fix SCO server socket for HFP handsfree role. + Fix shutdown on SCO socket before closing. + Fix shutdown on A2DP audio stream channel before closing. + Fix issue with asserting on AVDTP reference count bugs. + Fix authorization denied issue with certain headsets. + Fix AVRCP UNITINFO and SUBUNIT INFO responses. + Fix discovery cancel issues in case SDP discovery fails. + +ver 4.41: + Fix pairing even if the ACL gets dropped before successful SDP. + Fix regression which caused device to be removed after pairing. + Fix HSP record fetching when remote device doesn't support it. + Fix SDP discovery canceling when clearing hs->pending. + Fix headset never connecting on the first attempt. + Fix headset state tracking if bt_search_service() fails. + Fix maximum headset connection count check. + Fix AVDTP Discover timeout handling. + Fix also UI_SET_KEYBIT for the new pause and play key codes. + +ver 4.40: + Add telephony driver for oFono telephony stack. + Add support for Dell specific HID proxy switching. + Add support for running hid2hci from udev. + Add mapping for AVRCP Play and Pause to dedicated key codes. + Fix AVRCP keycodes to better match existing X keymap support. + Fix various quoting issues within telephony support. + Fix memory allocation issue when generating PDUs for SDP. + Fix race condition on device removal. + Fix non-cancelable issue with CreateDevice method. + Fix non-working CancelDiscovery method call. + +ver 4.39: + Add workaround for dealing with unknown inquiry complete. + Fix discovering when using software scheduler. + Fix wrong NoInputNoOutput IO capability string. + Fix race condition with agent during pairing. + Fix agent cancellation for security mode 3 acceptor failure. + Fix temporary flag removal when device creation fails. + Fix hciattach to use ppoll instead of poll. + Fix service class update when adapter is down. + Fix service classes race condition during startup. + Fix release of audio client before freeing the device. + +ver 4.38: + Add support for builtin plugins. + Add framework for adapter operations. + Add constants for Enhanced Retransmission modes. + Fix HCI socket leak in device_remove_bonding. + Fix various format string issues. + Fix crashes with various free functions. + Fix issues with Headset and A2DP drivers to load again. + Fix sending AVRCP button released passthrough messages + Fix bug which prevent input devices to work after restart. + Fix issue with interpretation of UUID-128 as channel. + +ver 4.37: + Add version value for Bluetooth 3.0 devices. + Add additional L2CAP extended feature mask bits. + Add support for loading plugins in priority order. + Add support for more detailed usage of disconnect watches. + Add support for AVRCP volume control. + Add saturated clipping of SBC decoder output to 16-bit. + Fix potentially infinite recursion of adapter_up. + Fix SCO handling in the case of an incoming call. + Fix input service to use confirm callback. + Fix cleanup of temporary device entries from storage. + +ver 4.36: + Add proper tracking of AVCTP connect attempts. + Add support to channel pattern in Serial interface. + Fix A2DP sink crash if removing device while connecting. + Fix error handling if HFP indicators aren't initialized. + Fix segfault while handling an incoming SCO connection. + Fix Serial.Disconnect to abort connection attempt. + +ver 4.35: + Add support for Handsfree profile headset role. + Add additional checks for open SEIDs from clients. + Fix device removal while audio IPC client is connected. + Fix device removal when an authorization request is pending. + Fix incoming AVDTP connect while authorization in progress. + Fix disconnection timers for audio support. + Fix various potential NULL pointer deferences. + Fix callheld indicator value for multiple calls. + Fix voice number type usage. + Fix GDBus watch handling. + +ver 4.34: + Add support for version checks of plugins. + Add support for class property on adapter interface. + Add support for second SDP attempt after connection reset. + Add support for more detailed audio states. + Add support for HFP+A2DP auto connection feature. + Add support for new and improved audio IPC. + Add program for testing audio IPC interface. + Fix various AVDTP qualification related issues. + Fix broken SDP AttributeIdList parsing. + Fix invalid memory access of SDP URL handling. + Fix local class of device race conditions. + Fix issue with periodic inquiry on startup. + Fix missing temporary devices in some situations. + Fix SBC alignment issue for encoding with four subbands. + +ver 4.33: + Add Paired property to the DeviceFound signals. + Add support for Headset profile 1.2 version. + Fix broken network configuration when IPv6 is disabled. + Fix network regression that caused disconnection. + Fix SDP truncation of strings with NULL values. + Fix service discovery handling of CUPS helper. + +ver 4.32: + Fix broken SDP record handling. + Fix SDP data buffer parsing. + Fix more SDP memory leaks. + Fix read scan enable calls. + Fix A2DP stream handling. + +ver 4.31: + Add support for new BtIO helper library. + Fix AVDTP session close issue. + Fix SDP memory leaks. + Fix various uninitialized memory issues. + Fix duplicate signal emissions. + Fix property changes request handling. + Fix class of device storage handling. + +ver 4.30: + Add CID field to L2CAP socket address structure. + Fix reset of authentication requirements after bonding. + Fix storing of link keys when using dedicated bonding. + Fix storing of pre-Bluetooth 2.1 link keys. + Fix resetting trust settings on every reboot. + Fix handling of local name changes. + Fix memory leaks in hciconfig and hcitool + +ver 4.29: + Use AVRCP version 1.0 for now. + Decrease AVDTP idle timeout to one second. + Delay AVRCP connection when remote device connects A2DP. + Add workaround for AVDTP stream setup with broken headsets. + Add missing three-way calling feature bit for Handsfree. + Fix handsfree callheld indicator updating. + Fix parsing of all AT commands within the buffer. + Fix authentication replies when disconnected. + Fix handling of debug combination keys. + Fix handling of changed combination keys. + Fix handling of link keys when using no bonding. + Fix handling of invalid/unknown authentication requirements. + Fix closing of L2CAP raw socket used for dedicated bonding. + +ver 4.28: + Add AVDTP signal fragmentation support. + Add more SBC performance optimizations. + Add more SBC audio quality improvements. + Use native byte order for audio plugins. + Set the adapter alias only after checking the EIR data. + Fix auto-disconnect issue with explicit A2DP connections. + Fix invalid memory access of ALSA plugin. + Fix compilation with -Wsign-compare. + +ver 4.27: + Add more SBC optimization (MMX and ARM NEON). + Add BT_SECURITY and BT_DEFER_SETUP definitions. + Add support for deferred connection setup. + Add support for fragmentation of data packets. + Add option to trigger dedicated bonding. + Follow MITM requirements from remote device. + Require MITM for dedicated bonding if capabilities allow it. + Fix IO capabilities for non-pairing and pairing cases. + Fix no-bonding connections in non-bondable mode. + Fix new pairing detection with SSP. + Fix bonding with pre-2.1 devices and newer kernels. + Fix LIAC setting while toggling Pairable property. + Fix device creation for incoming security mode 3 connects. + Fix crash within A2DP with bogus pointer. + Fix issue with sdp_copy_record() function. + Fix crash with extract_des() if sdp_uuid_extract() fails. + +ver 4.26: + Use of constant shift in SBC quantization code. + Add possibility to analyze 4 blocks at once in encoder. + Fix correct handling of frame sizes in the encoder. + Fix for big endian problems in SBC codec. + Fix audio client socket to always be non-blocking. + Update telephony support for Maemo. + +ver 4.25: + Fix receiving data over the audio control socket. + Fix subbands selection for joint-stereo in SBC encoder. + Add new SBC analysis filter function. + +ver 4.24: + Fix signal emissions when removing adapters. + Fix missing adapter signals on exit. + Add support for bringing adapters down on exit. + Add support for RememberPowered option. + Add support for verbose compiler warnings. + Add more options to SBC encoder. + +ver 4.23: + Update audio IPC for better codec handling. + Fix bitstream optimization for SBC encoder. + Fix length header values of IPC messages. + Fix multiple coding style violations. + Fix FindDevice to handle temporary devices. + Add configuration option for DeviceID. + Add support for InitiallyPowered option. + Add missing signals for manager properties. + Add telephony support for Maemo. + +ver 4.22: + Add deny statements to D-Bus access policy. + Add support for LegacyPairing property. + Add support for global properties. + Add more commands to telephony testing script. + Add sender checks for serial and network interfaces. + Remove deprecated methods and signals from input interface. + Remove deprecated methods and signals from network interface. + Remove OffMode option and always use device down. + +ver 4.21: + Fix adapter initialization logic. + Fix adapter setup and start security manager early. + Fix usage issue with first_init variable. + +ver 4.20: + Cleanup session handling. + Cleanup mode setting handling. + Fix issue with concurrent audio clients. + Fix issue with HFP/HSP suspending. + Fix AT result code syntax handling. + Add Handsfree support for AT+NREC. + Add PairableTimeout adapter property. + +ver 4.19: + Fix installation of manual pages for old daemons. + Fix D-Bus signal emmissions for CreateDevice. + Fix issues with UUID probing. + Fix +BSRF syntax issue. + Add Pairable adapter property. + Add sdp_copy_record() library function. + +ver 4.18: + Fix release before close issue with RFCOMM TTYs. + Fix Connected property on input interface. + Fix DeviceFound signals during initial name resolving. + Fix service discovery handling. + Fix duplicate UUID detection. + Fix SBC gain mismatch and decoding handling. + Add more options to SBC encoder and decoder. + Add special any adapter object for service interface. + Add variable prefix to adapter and device object paths. + +ver 4.17: + Fix SBC encoder not writing last frame. + Fix missing timer for A2DP suspend. + Add more supported devices to hid2hci utility. + Add additional functionality to Handsfree support. + +ver 4.16: + Fix wrong parameter usage of watch callbacks. + Fix parameters for callback upon path removal. + Fix unloading of adapter drivers. + +ver 4.15: + Fix various A2DP state machine issues. + Fix some issues with the Handsfree error reporting. + Fix format string warnings with recent GCC versions. + Remove dependency on GModule. + +ver 4.14: + Fix types of property arrays. + Fix potential crash with input devices. + Fix PS3 BD remote input event generation. + Allow dynamic adapter driver registration. + Update udev rules. + +ver 4.13: + Fix service discovery and UUID handling. + Fix bonding issues with Simple Pairing. + Fix file descriptor misuse of SCO connections. + Fix various memory leaks in the device handling. + Fix AVCTP disconnect handling. + Fix GStreamer modes for MP3 encoding. + Add operator selection to Handsfree support. + +ver 4.12: + Fix crash with missing icon value. + Fix error checks of HAL plugin. + Fix SCO server socket cleanup on exit. + Fix memory leaks from DBusPendingCall. + Fix handling of pending authorization requests. + Fix missing protocol UUIDs in record pattern. + +ver 4.11: + Change SCO server socket into a generic one. + Add test script for dummy telephony plugin. + Fix uninitialized reply of multiple GetProperties methods. + +ver 4.10: + Fix memory leaks with HAL messages. + Add more advanced handsfree features. + Add properties to audio, input and network interfaces. + Stop device discovery timer on device removal. + +ver 4.9: + Fix signals for Powered and Discoverable properties. + Fix handling of Alias and Icon properties. + Fix duplicate entries for service UUIDs. + +ver 4.8: + Fix retrieving of formfactor value. + Fix retrieving of local and remote extended features. + Fix potential NULL pointer dereference during pairing. + Fix crash with browsing due to a remotely initated pairing. + +ver 4.7: + Fix pairing and service discovery logic. + Fix crashes during suspend and resume. + Fix race condition within devdown mode. + Add RequestSession and ReleaseSession methods. + Add Powered and Discoverable properties. + Add Devices property and deprecate ListDevices. + Add workaround for a broken carkit from Nokia. + +ver 4.6: + Fix Device ID record handling. + Fix service browsing and storage. + Fix authentication and encryption for input devices. + Fix adapter name initialization. + +ver 4.5: + Fix initialization issue with new adapters. + Send HID authentication request without blocking. + Hide the verbose SDP debug behind SDP_DEBUG. + Add extra UUIDs for service discovery. + Add SCO server socket listener. + Add authorization support to service plugin. + +ver 4.4: + Add temporary fix for the CUPS compile issue. + Add service-api.txt to distribution. + Mention the variable prefix of an object path + +ver 4.3: + Add dummy driver for telephony support. + Add support for discovery sessions. + Add service plugin for external services. + Various cleanups. + +ver 4.2: + Avoid memory copies in A2DP write routine. + Fix broken logic with Simple Pairing check and old kernels. + Allow non-bondable and outgoing SDP without agent. + Only remove the bonding for non-temporary devices. + Cleanup various unnecessary includes. + Make more unexported functions static. + Add basic infrastructure for gtk-doc support. + +ver 4.1: + Add 30 seconds timeout to BNEP connection setup phase. + Avoid memory copies in A2DP write routine for ALSA. + Make sure to include compat/sdp.h in the distribution. + +ver 4.0: + Initial public release. + +ver 3.36: + Add init routines for TI BRF chips. + Add extra attributes to the serial port record. + Add example record for headset audio gateway record. + Use Handsfree version 0x0105 for the gateway role. + Fix SDP record registration with specific record handles. + Fix BCSP sent/receive handling. + Fix various includes for cross-compilation. + Allow link mode settings for outgoing connections. + Allow bonding during periodic inquiry. + +ver 3.35: + Add two additional company identifiers. + Add UUID-128 support for service discovery. + Fix usage of friendly names for service discovery. + Fix authorization when experiemental is disabled. + Fix uninitialized variable in passkey request handling. + Enable output of timestamps for l2test and rctest. + +ver 3.34: + Replace various SDP functions with safe versions. + Add additional length validation for incoming SDP packets. + Use safe function versions for SDP client handling. + Fix issue with RemoveDevice during discovery procedure. + Fix collect for non-persistent service records. + +ver 3.33: + Add functions for reading and writing the link policy settings. + Add definition for authentication requirements. + Add support for handling Simple Pairing. + Add Simple Pairing support to Agent interface. + Add ReleaseMode method to Adapter interface. + Add DiscoverServices method to Device interface. + Remove obsolete code and cleanup the repository. + Move over to use the libgdbus API. + Enable PIE by default if supported. + +ver 3.32: + Add OCF constants for synchronous flow control enabling. + Add support for switching HID proxy devices from Dell. + Add more Bluetooth client/server helper functions. + Add support for input service idle timeout option. + Fix BNEP reconnection handling. + Fix return value for snd_pcm_hw_params() calls. + Use upper-case addresses for object paths. + Remove HAL support helpers. + Remove inotify support. + Remove service daemon activation handling. + Remove uneeded D-Bus API extension. + +ver 3.31: + Create device object for all pairing cases. + Convert authorization to internal function calls. + Add initial support for Headset Audio Gateway role. + Add generic Bluetooth helper functions for GLib. + Fix endiannes handling of connection handles. + Don't optimize when debug is enabled. + +ver 3.30: + Convert audio service into a plugin. + Convert input service into a plugin. + Convert serial service into a plugin. + Convert network service into a plugin. + Emit old device signals when a property is changed. + Fix missing DiscoverDevices and CancelDiscovery methods. + Add another company identifier. + Add basic support for Bluetooth sessions. + Add avinfo utility for AVDTP/A2DP classification. + Remove build option for deprecated sdpd binary. + +ver 3.29: + Introduce new D-Bus based API. + Add more SBC optimizations. + Add support for PS3 remote devices. + Fix alignment trap in SDP server. + Fix memory leak in sdp_get_uuidseq_attr function. + +ver 3.28: + Add support for MCAP UUIDs. + Add support for role switch for audio service. + Add disconnect timer for audio service. + Add disconnect detection to ALSA plugin. + Add more SBC optimizations. + Fix alignment issue of SDP server. + Remove support for SDP parsing via expat. + +ver 3.27: + Update uinput.h with extra key definitions. + Add support for input connect/disconnect callbacks. + Add ifdefs around some baud rate definitions. + Add another company identifier. + Add proper HFP service level connection handling. + Add basic headset automatic disconnect support. + Add support for new SBC API. + Fix SBC decoder noise at high bitpools. + Use 32-bit multipliers for further SBC optimization. + Check for RFCOMM connection state in SCO connect callback. + Make use of parameters selected in ALSA plugin. + +ver 3.26: + Fix compilation issues with UCHAR_MAX, USHRT_MAX and UINT_MAX. + Improve handling of different audio transports. + Enable services by default and keep old daemons disabled. + +ver 3.25: + Add limited support for Handsfree profile. + Add limited support for MPEG12/MP3 codec. + Add basic support for UNITINFO and SUBUNITINFO. + Add more SBC optimizations. + Fix external service (un)registration. + Allow GetInfo and GetAddress to fail. + +ver 3.24: + Add definitions for MDP. + Add TCP connection support for serial proxy. + Add fix for Logitech HID proxy switching. + Add missing macros, MIN, MAX, ABS and CLAMP. + Add more SBC encoder optimizations. + Add initial mechanism to handle headset commands. + Fix connecting to handsfree profile headsets. + Use proper function for checking signal name. + +ver 3.23: + Fix remote name request handling bug. + Fix key search function to honor the mmap area size. + Fix Avahi integration of network service. + Add new plugin communication for audio service. + Enable basic AVRCP support by default. + More optimizations to the SBC library. + Create common error definitions. + +ver 3.22: + Add missing include file from audio service. + Add SBC conformance test utility. + Add basic uinput support for AVRCP. + Fix L2CAP socket leak in audio service. + Fix buffer usage in GStreamer plugin. + Fix remote name request event handling. + +ver 3.21: + Add constant for Bluetooth socket options level. + Add initial AVRCP support. + Add A2DP sink support to GStreamer plugin. + Fix interoperability with A2DP suspend. + Fix sign error in 8-subband encoder. + Fix handling of service classes length size. + Store Extended Inquiry Response data information. + Publish device id information through EIR. + Support higher baud rates for Ericcson based chips. + +ver 3.20: + Fix GStreamer plugin file type detection. + Fix potential infinite loop in inotify support. + Fix D-Bus signatures for dict handling. + Fix issues with service activation. + Fix SDP failure handling of audio service. + Fix various memory leaks in input service. + Add secure device creation method to input service. + Add service information methods to serial service. + Add config file support to network service. + Add scripting capability to network service. + Add special on-mode handling. + Add optimization for SBC encoder. + Add tweaks for D-Bus 1.1.x libraries. + Add support for inquiry transmit power level. + +ver 3.19: + Limit range of bitpool announced while in ACP side. + Use poll instead of usleep to wait for worker thread. + Use default event mask from the specification. + Add L2CAP mode constants. + Add HID proxy support for Logitech diNovo Edge dongle. + Add refresh option to re-request device names. + Show correct connection link type. + +ver 3.18: + Don't allocate memory for the Bluetooth base UUID. + Implement proper locking for headsets. + Fix various A2DP SEP locking issues. + Fix and cleanup audio stream handling. + Fix stream starting if suspend request is pending. + Fix A2DP and AVDTP endianess problems. + Add network timeout and retransmission support. + Add more detailed decoding of EIR elements. + +ver 3.17: + Fix supported commands bit calculation. + Fix crashes in audio and network services. + Check PAN source and destination roles. + Only export the needed symbols for the plugins. + +ver 3.16: + Update company identifier list. + Add support for headsets with SCO audio over HCI. + Add support for auto-create through ALSA plugin. + Add support for ALSA plugin parameters. + Add GStreamer plugin with SBC decoder and encoder. + Fix network service NAP, GN and PANU servers. + Set EIR information from SDP database. + +ver 3.15: + Add A2DP support to the audio service. + Add proxy support to the serial service. + Extract main service class for later use. + Set service classes value from SDP database. + +ver 3.14: + Add missing signals for the adapter interface. + Add definitions and functions for Simple Pairing. + Add basic commands for Simple Pairing. + Add correct Simple Pairing and EIR interaction. + Add missing properties for remote information. + Add EPoX endian quirk to the input service. + Fix HID descriptor import and storage functions. + Fix handling of adapters in raw mode. + Fix remote device listing methods. + +ver 3.13: + Fix some issues with the headset support. + Fix concurrent pending connection attempts. + Fix usage of devname instead of netdev. + Add identifier for Nokia SyncML records. + Add command for reading the CSR chip revision. + Add generic CSR radio test support. + Update HCI command table. + +ver 3.12: + Add missing HCI command text descriptions + Add missing HCI commands structures. + Add missing HCI event structures. + Add common bachk() function. + Add support for limited discovery mode. + Add support for setting of event mask. + Add GetRemoteServiceIdentifiers method. + Add skeleton for local D-Bus server. + Add headset gain control methods. + Fix various headset implementation issues. + Fix various serial port service issues. + Fix various input service issues. + Let CUPS plugin discover printers in range. + Improve the BCM2035 UART init routine. + Ignore connection events for non-ACL links. + +ver 3.11: + Update API documentation. + Minimize SDP root records and browse groups. + Use same decoder for text and URL strings. + Fix URL data size handling. + Fix SDP pattern extraction for XML. + Fix network connection persistent state. + Add network connection helper methods. + Add initial version of serial port support. + Add class of device tracking. + +ver 3.10.1: + Add option to disable installation of manual pages. + Fix input service encryption setup. + Fix serial service methods. + Fix network service connection handling. + Provide a simple init script. + +ver 3.10: + Add initial version of network service. + Add initial version of serial service. + Add initial version of input service. + Add initial version of audio service. + Add authorization framework. + Add integer based SBC library. + Add version code for Bluetooth 2.1 specification. + Add ESCO_LINK connection type constant. + Export sdp_uuid32_to_uuid128() function. + +ver 3.9: + Add RemoteDeviceDisconnectRequested signal. + Add updated service framework. + Add embedded GLib library. + Add support for using system GLib library. + Create internal SDP server library. + +ver 3.8: + Sort discovered devices list based on their RSSI. + Send DiscoverableTimeoutChanged signal. + Fix local and remote name validity checking. + Add ListRemoteDevices and ListRecentRemoteDevices methods. + Add basic integration of confirmation concept. + Add support for service record description via XML. + Add support for external commands to the RFCOMM utility. + Add experimental service and authorization API. + Add functions for registering binary records. + +ver 3.7: + Fix class of device handling. + Fix error replies with pairing and security mode 3. + Fix disconnect method for RFCOMM connections. + Add match pattern for service searches. + Add support for prioritized watches. + Add additional PDU length checks. + Fix CSRC value for partial responses. + +ver 3.6.1: + Fix IO channel race conditions. + Fix pairing issues on big endian systems. + Fix pairing issues with page timeout errors. + Fix pairing state for security mode 3 requests. + Switch to user as default security manager mode. + +ver 3.6: + Update D-Bus based RFCOMM interface support. + Use L2CAP raw sockets for HCI connection creation. + Add periodic discovery support to the D-Bus interface. + Add initial support for device names via EIR. + Add proper UTF-8 validation of device names. + Add support for the J-Three keyboard. + Fix issues with the asynchronous API for SDP. + +ver 3.5: + Fix and cleanup watch functionality. + Add support for periodic inquiry mode. + Add support for asynchronous SDP requests. + Add more request owner tracking. + Add asynchronous API for SDP. + Document pageto and discovto options. + +ver 3.4: + Improve error reporting for failed HCI commands. + Improve handling of CancelBonding. + Fixed bonding reply message when disconnected. + Fix UUID128 string lookup handling. + Fix malloc() versus bt_malloc() usage. + +ver 3.3: + Don't change inquiry mode for Bluetooth 1.1 adapters. + Add udev rules for Bluetooth serial PCMCIA cards. + Add Cancel and Release methods for passkey agents. + Add GetRemoteClass method. + Convert to using ppoll() and pselect(). + Initialize allocated memory to zero. + Remove bcm203x firmware loader. + Remove kernel specific timeouts. + Add additional private data field for SDP sessions. + Add host controller to host flow control defines. + Add host number of completed packets defines. + Initialize various memory to zero before usage. + +ver 3.2: + Only check for the low-level D-Bus library. + Update possible device minor classes. + Fix timeout for pending reply. + Add more Inquiry with RSSI quirks. + Sleep only 100 msecs for device detection. + Don't send BondingCreated on link key renewal. + Allow storing of all UTF-8 remote device names. + Create storage filenames with a generic function. + Fix handling of SDP strings. + Add adapter type for SDIO cards. + Add features bit for link supervision timeout. + +ver 3.1: + Add missing placeholders for feature bits. + Fix handling of raw mode devices. + Fix busy loop in UUID extraction routine. + Remove inquiry mode setting. + Remove auth and encrypt settings. + +ver 3.0: + Implement the new BlueZ D-Bus API. + Fix broken behavior with EVT_CMD_STATUS. + Add features bit for pause encryption. + Add additional EIR error code. + Add more company identifiers. + Add another Phonebook Access identifier. + Update sniff subrating data structures. + +ver 2.25: + Use %jx instead of %llx for uint64_t and int64_t. + Allow null-terminated text strings. + Add UUID for N-Gage games. + Add UUID for Apple Macintosh Attributes. + Add Apple attributes and iSync records. + Add definitions for Apple Agent. + Add support for the Handsfree Audio Gateway service. + Add support for choosing a specific record handle. + Add support for dialup/telephone connections. + Add definitions for Apple Agent. + Add support for record handle on service registration. + +ver 2.24: + Fix display of SDP text and data strings. + Add support for device scan property. + Add support for additional access protocols. + Update the D-Bus policy configuration file. + +ver 2.23: + Update the new D-Bus interface. + Make dfutool ready for big endian architectures. + Add support for AVRCP specific service records. + Add support for writing complex BCCMD commands. + Add the new BCCMD interface utility. + Add MicroBCSP implementation from CSR. + Add constants and definitions for sniff subrating. + Add support for allocation of binary text elements. + Add HCI emulation tool. + Add fake HID support for old EPoX presenters. + Reject connections from unknown HID devices. + Fix service discovery deadlocks with Samsung D600 phones. + +ver 2.22: + Remove D-Bus 0.23 support. + Add initial version of the new D-Bus interface. + Add support for extended inquiry response commands. + Add support for the Logitech diNovo Media Desktop Laser. + Add compile time buffer checks (FORTIFY SOURCE). + Decode reserved LMP feature bits. + Fix errno overwrite problems. + Fix profile descriptor problem with Samsung phones. + +ver 2.21: + Move create_dirs() and create_file() into the textfile library. + Let textfile_put() also replace the last key value pair. + Fix memory leaks with textfile_get() usage. + Fix infinite loops and false positive matches. + Don't retrieve stored link keys for RAW devices. + Document the putkey and delkey commands. + Show supported commands also in clear text. + Support volatile changes of the BD_ADDR for CSR chips. + Add support for identification of supported commands. + Add missing OCF declarations for the security filter. + Add two new company identifiers. + +ver 2.20: + Add UUIDs for video distribution profile. + Add UUIDs for phonebook access profile. + Add attribute identifier for supported repositories. + Add definitions for extended inquiry response. + Add functions for extended inquiry response. + Add support for extended inquiry response. + Add support for HotSync service record. + Add support for ActiveSync service record. + Add ActiveSync networking support. + Fix D-Bus crashes with new API versions. + +ver 2.19: + Fix the GCC 4.0 warnings. + Fix the routing for dealing with raw devices. + Fix off by one memory allocation error. + Fix security problem with escape characters in device name. + Add per device service record functions. + Send D-Bus signals for inquiry results and remote name resolves. + Add support for device specific SDP records. + +ver 2.18: + Support D-Bus 0.23 and 0.33 API versions. + Support reading of complex BCCMD values. + Support minimum and maximum encryption key length. + Add support for reading and writing the inquiry scan type. + Add definitions for connection accept timeout and scan enable. + Add support for inquiry scan type. + Add tool for the CSR BCCMD interface. + Add first draft of the Audio/Video control utility. + Add disconnect timer support for the A2DP ALSA plugin. + Make SBC parameters configurable. + Replace non-printable characters in device names. + Remove hci_vhci.h header file. + Remove hci_uart.h header file. + +ver 2.17: + Set the storage directory through ${localstatedir}. + Add the textfile library for ASCII based file access. + Add support for return link keys event. + Add support for voice setting configuration. + Add support for page scan timeout configuration. + Add support for storing and deleting of stored link keys. + Add support for searching for services with UUID-128. + Add support for retrieving all possible service records. + Add support for a raw mode view of service records. + Add support for HID information caching in hidd. + Add support for authentication in pand and dund. + Add support for changing BD_ADDR of CSR chips. + Add pskey utility for changing CSR persistent storage values. + Add the firmware upgrade utility. + Add connection caching for the A2DP ALSA plugin. + Add functions for stored link keys. + Add definitions for PIN type and unit key. + Add SDP_WAIT_ON_CLOSE flag for sdp_connect(). + Include stdio.h in bluetooth.h header file. + Include sys/socket.h in the header files. + +ver 2.16: + Store link keys in ASCII based file format. + Support device name caching. + Support zero length data sizes in l2test. + Change default l2ping data size to 44 bytes. + Hide the server record and the public browse group root. + Read BD_ADDR if not set and if it is a raw device. + Add SDP language attributes. + Add support for browsing the L2CAP group. + Add support for stored pin codes for outgoing connections. + Add support for local commands and extended features. + Add support for reading CSR panic and fault codes. + Add config option for setting the inquiry mode. + Add OUI decoding support. + Use unlimited inquiry responses as default. + Use cached device names for PIN request. + Use the clock offset when getting the remote names. + Add function for reading local supported commands. + Add function for reading local extended features. + Add function for reading remote extended features. + Add function for getting the remote name with a clock offset. + Add function for extracting the OUI from a BD_ADDR. + Add inquiry info structure with RSSI and page scan mode. + Fix buffer allocation for features to string conversion. + Support inquiry with unlimited number of responses. + +ver 2.15: + Enable the RFCOMM service level security. + Add deprecated functions for reading the name. + Add command for reading the clock offset. + Add command for reading the clock. + Add function for reading the clock. + Add function for reading the local Bluetooth address. + Add function for reading the local supported features. + Don't configure raw devices. + Don't set inquiry scan or page scan on raw devices. + Don't show extended information for raw devices. + Support L2CAP signal sizes bigger than 2048 bytes. + Cleanup of the socket handling code of the test programs. + Use better way for unaligned access. + Remove sdp_internal.h and its usage. + +ver 2.14: + Make use of additional connection information. + Use library function for reading the RSSI. + Use library function for reading the link quality. + Use library function for reading the transmit power level. + Use library functions for the link supervision timeout. + Add tool for changing the device address. + Add function for reading the RSSI. + Add function for reading the link quality. + Add function for reading the transmit power level. + Add functions for the link supervision timeout. + Remove deprecated functions. + Update AM_PATH_BLUEZ macro. + +ver 2.13: + Use file permission 0600 for the link key file. + Add support for HID attribute descriptions. + Add support for Device ID attributes. + Add Device ID and HID attribute definitions. + Update the UUID constants and its translations. + Update L2CAP socket option definitions. + Update connection information definitions. + Various whitespace cleanups. + +ver 2.12: + Inherit the device specific options from the default. + Use --device for selecting the source device. + Add --nosdp option for devices with resource limitation. + Add support and parameter option for secure mode. + Add a lot of build ids and hardware revisions. + Add service classes and profile ids for WAP. + Add simple AM_PATH_BLUEZ macro. + Update UUID translation tables. + Correct kernel interface for CMTP and HIDP support. + +ver 2.11: + Initial support for the kernel security manager. + Various cleanups to avoid inclusion of kernel headers. + Fix output when the CUPS backend is called without arguments. + Fix problems with a 64 bit userland. + Use Bluetooth library functions if available. + Use standard numbering scheme of SDP record handles. + Use bit zero for vendor packets in the filter type bitmask. + Add SIM Access types for service discovery. + Add more audio/video profile translations. + Add another company identifier. + Add the missing HCI error codes. + Add RFCOMM socket options. + Add definition for the SECURE link mode. + Add functions for reading and writing the inquiry mode. + Add functions for AFH related settings and information. + Add version identifier for the Bluetooth 2.0 specification. + Add a master option to the hidd. + Add support for changing the link key of a connection. + Add support for requesting encryption on keyboards. + Add support for revision information of Digianswer devices. + Add support for the Zoom, IBM and TDK PCMCIA cards. + Add checks for the OpenOBEX and the ALSA libraries. + Add experimental mRouter support. + +ver 2.10: + Use a define for the configuration directory. + Fix string initialization for flags translation. + Fix and extend the unaligned access macros. + Make compiling with debug information optional. + Don't override CFLAGS from configure. + Check for usb_get_busses() and usb_interrupt_read(). + Add optional support for compiling with PIE. + Make installation of the init scripts optional. + Make compiling with debug information optional. + Don't override CFLAGS from configure. + +ver 2.9: + Retry SDP connect if busy in the CUPS backend. + Use packet type and allow role switch in hcitool. + Use the functions from the USB library for hid2hci. + Add Broadcom firmware loader. + Add EPoX endian quirk for buggy keyboards. + Add L2CAP info type and info result definitions. + Add value for L2CAP_CONF_RFC_MODE. + Change RSSI value to signed instead of unsigned. + Allow UUID32 values as protocol identifiers. + Update the autoconf/automake scripts. + +ver 2.8: + Use LIBS and LDADD instead of LDFLAGS. + Use HIDP subclass field for HID boot protocol. + Set olen before calling getsockopt() in pand. + Restore signals for dev-up script. + Add PID file support for pand. + Add size parameter to expand_name() in hcid. + Add support for audio source and audio sink SDP records. + Add support for HID virtual cable unplug. + Add support for AmbiCom BT2000C card. + Add defines and UUID's for audio/video profiles. + Add AVDTP protocol identifier. + Add HIDP subclass field. + Add PKGConfig support. + Fix the event code of inquiry with RSSI. + Remove dummy SDP library. + +ver 2.7: + Fix display of decoded LMP features. + Update company identifiers. + Add AFH related types. + Add first bits from EDR prototyping specification. + Add support for inquiry with RSSI. + Add HCRP related SDP functions. + Add HIDP header file. + Add support for getting the AFH channel map. + Add support for AFH mode. + Add support for inquiry mode. + Add Bluetooth backend for CUPS. + Add the hid2hci utility. + Add the hidd utility. + Add the pand utility. + Add the dund utility. + More endian bug fixes. + Give udev some time to create the RFCOMM device nodes. + Release the TTY if no device node is found. + New startup script for the Bluetooth subsystem. + Update to the autoconf stuff. + +ver 2.6: + Change default prefix to /usr. + Add manpages for hcid and hcid.conf. + Add the sdpd server daemon. + Add the sdptool utility. + Add the ciptool utility. + Add new company identifiers. + Add BNEP and CMTP header files. + Add the SDP library. + Use R2 for default value of pscan_rep_mode. + +ver 2.5: + Add decoding of Bluetooth 1.2 features. + Add link manager version parameter for Bluetooth 1.2. + Add new company identifiers. + Add D-Bus support for PIN request. + Support for transmit power level. + Support for park, sniff and hold mode. + Support for role switch. + Support for reading the clock offset. + Support for requesting authentication. + Support for setting connection encryption. + Show revision information for Broadcom devices. + Replace unprintable characters in device name. + Use R1 for default value of pscan_rep_mode. + Fix some 64-bit problems. + Fix some endian problems. + Report an error on PIN helper failure. + Update bluepin script for GTK2. + +ver 2.4: + Increase number of inquiry responses. + Support for transmit power level. + Display all 8 bytes of the features. + Add support for reading and writing of IAC. + Correct decoding class of device. + Use Ericsson revision command for ST Microelectronics devices. + Display AVM firmware version with 'revision' command. + New code for CSR specific revision information. + Support for ST Microelectronics specific initialization. + Support for 3Com card version 3.0. + Support for TDK, IBM and Socket cards. + Support for initial baud rate. + Update man pages. + Fixes for some memory leaks. + +ver 2.3: + Added const qualifiers to appropriate function arguments. + Minor fixes. + CSR firmware version is now displayed by 'revision' command. + Voice command is working properly on big endian machines. + Added support for Texas Bluetooth modules. + Added support for high UART baud rates on Ericsson modules. + BCSP initialization fixes. + Support for role switch command (hcitool). + RFCOMM config file parser fixes. + Update man pages. + Removed GLib dependency. + +ver 2.2: + Updated RFCOMM header file. + Additional HCI command and event defines. + Support for voice settings (hciconfig). + Minor hcitool fixes. + Improved configure script. + Added Headset testing tool. + Updated man pages. + RPM package. + +ver 2.1.1: + Resurrect hci_remote_name. + +ver 2.1: + Added hci_{read, write}_class_of_dev(). + Added hci_{read, write}_current_iac_lap(). + Added hci_write_local_name(). + Added RFCOMM header file. + Minor fixes. + Improved BCSP initialization (hciattach). + Support for displaying link quality (hcitool). + Support for changing link supervision timeout (hcitool). + New RFCOMM TTY configuration tool (rfcomm). + Minor fixes and updates. + +ver 2.0: + Additional company IDs. + BCSP initialization (hciattach). + Minor hciconfig fixes. + +ver 2.0-pr13: + Support for multiple pairing modes. + Link key database handling fixes. + +ver 2.0-pre12: + Removed max link key limit. Keys never expire. + Link key database is always updated. Reread PIN on SIGHUP (hcid). + Bluetooth script starts SDPd, if installed. + Other minor fixes. + +ver 2.0-pre11: + Improved link key management and more verbose logging (hcid). + Fixed scan command (hcitool). + +ver 2.0-pre10: + Fix hci_inquiry function to return errors and accept user buffers. + New functions hci_devba, hci_devid, hci_for_each_dev and hci_get_route. + Additional company IDs. + Makefile and other minor fixes. + Support for reading RSSI, remote name and changing + connection type (hcitool). + Device initialization fixes (hcid). + Other minor fixes and improvements. + Build environment cleanup and fixes. + +ver 2.0-pre9: + Improved bluepin. Working X authentication. + Improved hcitool. New flexible cmd syntax, additional commands. + Human readable display of the device features. + LMP features to string translation support. + Additional HCI command and event defines. + Extended hci_filter API. + +ver 2.0-pre8: + Additional HCI ioctls and defines. + All strings and buffers are allocated dynamically. + ba2str, str2ba automatically swap bdaddress. + Additional hciconfig commands. Support for ACL and SCO MTU ioctls. + Support for Inventel and COM1 UART based devices. + Minor hcitool fixes. + Improved l2test. New L2CAP test modes. + Minor fixes and cleanup. + +ver 2.0-pre7: + Bluetooth libraries and header files is now a separate package. + New build environment uses automake and libtool. + Massive header files cleanup. + Bluetooth utilities is now a separate package. + New build environment uses automake. + Moved all config files and security data to /etc/bluetooth. + Various cleanups. + +ver 2.0-pre6: + API cleanup and additions. + Improved hcitool. + l2test minor output fixes. + hciattach opt to display list of supported devices. + +ver 2.0-pre4: + HCI filter enhancements. + +ver 2.0-pre3: + Cleanup. + +ver 2.0-pre2: + Additional HCI library functions. + Improved CSR baud rate initialization. + PCMCIA scripts fixes and enhancements. + Documentation update. + +ver 2.0-pre1: + New UART initialization utility. + Hot plugging support for UART based PCMCIA devices. + SCO testing utility. + New authentication utility (bluepin). + Minor fixes and improvements. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..56b077d --- /dev/null +++ b/INSTALL @@ -0,0 +1,236 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free +Software Foundation, Inc. + +This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + +These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that the +`configure' script does not know about. Run `./configure --help' for +details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + +You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + +By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PREFIX', the package will +use PREFIX as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + +Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + +There may be some features `configure' cannot figure out automatically, +but needs to determine by the type of machine the package will run on. +Usually, assuming the package is built to be run on the _same_ +architectures, `configure' can figure that out, but if it prints a +message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the `--target=TYPE' option to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, you +can create a site shell script called `config.site' that gives default +values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + +Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). Here is a another example: + + /bin/bash ./configure CONFIG_SHELL=/bin/bash + +Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent +configuration-related scripts to be executed by `/bin/bash'. + +`configure' Invocation +====================== + +`configure' recognizes the following options to control how it operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..a23cc60 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,431 @@ + +AM_MAKEFLAGS = --no-print-directory + +lib_LTLIBRARIES = + +noinst_LTLIBRARIES = + +bin_PROGRAMS = + +sbin_PROGRAMS = + +noinst_PROGRAMS = + +dist_man_MANS = + +dist_noinst_MANS = + +CLEANFILES = + +EXTRA_DIST = + +includedir = @includedir@/bluetooth + +include_HEADERS = + +if CONFIGFILES +dbusdir = $(sysconfdir)/dbus-1/system.d + +dbus_DATA = src/bluetooth.conf + +confdir = $(sysconfdir)/bluetooth + +conf_DATA = + +statedir = $(localstatedir)/lib/bluetooth + +state_DATA = +endif + +plugindir = $(libdir)/bluetooth/plugins + +plugin_LTLIBRARIES = + + +lib_headers = lib/bluetooth.h lib/hci.h lib/hci_lib.h lib/mgmt.h \ + lib/sco.h lib/l2cap.h lib/sdp.h lib/sdp_lib.h lib/uuid.h \ + lib/rfcomm.h lib/bnep.h lib/cmtp.h lib/hidp.h +local_headers = $(foreach file,$(lib_headers), lib/bluetooth/$(notdir $(file))) + +include_HEADERS += $(lib_headers) + +lib_LTLIBRARIES += lib/libbluetooth.la + +lib_libbluetooth_la_SOURCES = $(lib_headers) \ + lib/bluetooth.c lib/hci.c lib/sdp.c lib/uuid.c +lib_libbluetooth_la_LDFLAGS = -version-info 14:0:11 +lib_libbluetooth_la_DEPENDENCIES = $(local_headers) + +CLEANFILES += $(local_headers) + + +if SBC +noinst_LTLIBRARIES += sbc/libsbc.la + +sbc_libsbc_la_SOURCES = sbc/sbc.h sbc/sbc.c sbc/sbc_math.h sbc/sbc_tables.h \ + sbc/sbc_primitives.h sbc/sbc_primitives.c \ + sbc/sbc_primitives_mmx.h sbc/sbc_primitives_mmx.c \ + sbc/sbc_primitives_iwmmxt.h sbc/sbc_primitives_iwmmxt.c \ + sbc/sbc_primitives_neon.h sbc/sbc_primitives_neon.c \ + sbc/sbc_primitives_armv6.h sbc/sbc_primitives_armv6.c + +sbc_libsbc_la_CFLAGS = -finline-functions -fgcse-after-reload \ + -funswitch-loops -funroll-loops + +noinst_PROGRAMS += sbc/sbcinfo sbc/sbcdec sbc/sbcenc + +sbc_sbcdec_SOURCES = sbc/sbcdec.c sbc/formats.h +sbc_sbcdec_LDADD = sbc/libsbc.la + +sbc_sbcenc_SOURCES = sbc/sbcenc.c sbc/formats.h +sbc_sbcenc_LDADD = sbc/libsbc.la + +if SNDFILE +noinst_PROGRAMS += sbc/sbctester + +sbc_sbctester_LDADD = @SNDFILE_LIBS@ -lm +sbc_sbctest_CFLAGS = @SNDFILE_CFLAGS@ +endif +endif + +attrib_sources = attrib/att.h attrib/att.c attrib/gatt.h attrib/gatt.c \ + attrib/gattrib.h attrib/gattrib.c + +gdbus_sources = gdbus/gdbus.h gdbus/mainloop.c gdbus/watch.c \ + gdbus/object.c gdbus/polkit.c + +btio_sources = btio/btio.h btio/btio.c + +builtin_modules = +builtin_sources = +builtin_nodist = +mcap_sources = + +if MCAP +mcap_sources += health/mcap_lib.h health/mcap_internal.h \ + health/mcap.h health/mcap.c \ + health/mcap_sync.c +endif + +if PNATPLUGIN +builtin_modules += pnat +builtin_sources += plugins/pnat.c +endif + +if ECHOPLUGIN +builtin_modules += echo +builtin_sources += plugins/echo.c +endif + +if AUDIOPLUGIN +builtin_modules += audio +builtin_sources += audio/main.c \ + audio/manager.h audio/manager.c \ + audio/gateway.h audio/gateway.c \ + audio/headset.h audio/headset.c \ + audio/control.h audio/control.c \ + audio/device.h audio/device.c \ + audio/source.h audio/source.c \ + audio/sink.h audio/sink.c \ + audio/a2dp.h audio/a2dp.c \ + audio/avdtp.h audio/avdtp.c \ + audio/ipc.h audio/ipc.c \ + audio/unix.h audio/unix.c \ + audio/media.h audio/media.c \ + audio/transport.h audio/transport.c \ + audio/telephony.h audio/a2dp-codecs.h +builtin_nodist += audio/telephony.c + +noinst_LIBRARIES = audio/libtelephony.a + +audio_libtelephony_a_SOURCES = audio/telephony.h audio/telephony-dummy.c \ + audio/telephony-maemo5.c audio/telephony-ofono.c \ + audio/telephony-maemo6.c +endif + +if SAPPLUGIN +builtin_modules += sap +builtin_sources += sap/main.c \ + sap/manager.h sap/manager.c \ + sap/server.h sap/server.c \ + sap/sap.h + +builtin_nodist += sap/sap.c + +noinst_LIBRARIES = sap/libsap.a + +sap_libsap_a_SOURCES = sap/sap.h sap/sap-dummy.c +endif + +if INPUTPLUGIN +builtin_modules += input +builtin_sources += input/main.c \ + input/manager.h input/manager.c \ + input/server.h input/server.c \ + input/device.h input/device.c \ + input/fakehid.c input/fakehid.h +endif + +if SERIALPLUGIN +builtin_modules += serial +builtin_sources += serial/main.c \ + serial/manager.h serial/manager.c \ + serial/proxy.h serial/proxy.c \ + serial/port.h serial/port.c +endif + +if NETWORKPLUGIN +builtin_modules += network +builtin_sources += network/main.c \ + network/manager.h network/manager.c \ + network/common.h network/common.c \ + network/server.h network/server.c \ + network/connection.h network/connection.c +endif + +if SERVICEPLUGIN +builtin_modules += service +builtin_sources += plugins/service.c +endif + +if ATTRIBPLUGIN + +if READLINE +bin_PROGRAMS += attrib/gatttool + +attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \ + attrib/gattrib.c btio/btio.c \ + src/glib-helper.h src/glib-helper.c \ + attrib/gatttool.h attrib/interactive.c \ + attrib/utils.c +attrib_gatttool_LDADD = lib/libbluetooth.la @GLIB_LIBS@ @READLINE_LIBS@ +endif + +builtin_modules += attrib +builtin_sources += attrib/main.c \ + attrib/manager.h attrib/manager.c \ + attrib/client.h attrib/client.c \ + attrib/example.h attrib/example.c +endif + +if HEALTHPLUGIN +builtin_modules += health +builtin_sources += health/hdp_main.c health/hdp_types.h \ + health/hdp_manager.h health/hdp_manager.c \ + health/hdp.h health/hdp.c \ + health/hdp_util.h health/hdp_util.c +endif + +builtin_modules += hciops mgmtops +builtin_sources += plugins/hciops.c plugins/mgmtops.c + +if HAL +builtin_modules += hal +builtin_sources += plugins/hal.c +else +builtin_modules += formfactor +builtin_sources += plugins/formfactor.c +endif + +EXTRA_DIST += plugins/hal.c plugins/formfactor.c + +builtin_modules += storage +builtin_sources += plugins/storage.c + +if MAEMO6PLUGIN +builtin_modules += maemo6 +builtin_sources += plugins/maemo6.c +endif + +if DBUSOOBPLUGIN +builtin_modules += dbusoob +builtin_sources += plugins/dbusoob.c +endif + +sbin_PROGRAMS += src/bluetoothd + +src_bluetoothd_SOURCES = $(gdbus_sources) $(builtin_sources) \ + $(attrib_sources) $(btio_sources) \ + $(mcap_sources) src/bluetooth.ver \ + src/main.c src/log.h src/log.c \ + src/rfkill.c src/hcid.h src/sdpd.h \ + src/sdpd-server.c src/sdpd-request.c \ + src/sdpd-service.c src/sdpd-database.c \ + src/attrib-server.h src/attrib-server.c \ + src/sdp-xml.h src/sdp-xml.c \ + src/textfile.h src/textfile.c \ + src/glib-helper.h src/glib-helper.c \ + src/oui.h src/oui.c src/uinput.h src/ppoll.h \ + src/plugin.h src/plugin.c \ + src/storage.h src/storage.c \ + src/agent.h src/agent.c \ + src/error.h src/error.c \ + src/manager.h src/manager.c \ + src/adapter.h src/adapter.c \ + src/device.h src/device.c \ + src/dbus-common.c src/dbus-common.h \ + src/event.h src/event.c \ + src/oob.h src/oob.c +src_bluetoothd_LDADD = lib/libbluetooth.la @GLIB_LIBS@ @DBUS_LIBS@ \ + @CAPNG_LIBS@ -ldl -lrt +src_bluetoothd_LDFLAGS = -Wl,--export-dynamic \ + -Wl,--version-script=$(srcdir)/src/bluetooth.ver + +src_bluetoothd_DEPENDENCIES = lib/libbluetooth.la + +builtin_files = src/builtin.h $(builtin_nodist) + +nodist_src_bluetoothd_SOURCES = $(builtin_files) + +CLEANFILES += $(builtin_files) + +man_MANS = src/bluetoothd.8 + +if CONFIGFILES +conf_DATA += src/main.conf +endif + +EXTRA_DIST += src/genbuiltin src/bluetooth.conf \ + src/main.conf network/network.conf \ + input/input.conf serial/serial.conf \ + audio/audio.conf audio/telephony-dummy.c \ + audio/telephony-maemo5.c audio/telephony-ofono.c \ + audio/telephony-maemo6.c sap/sap-dummy.c + + +if ALSA +alsadir = $(libdir)/alsa-lib + +alsa_LTLIBRARIES = audio/libasound_module_pcm_bluetooth.la \ + audio/libasound_module_ctl_bluetooth.la + +audio_libasound_module_pcm_bluetooth_la_SOURCES = audio/pcm_bluetooth.c \ + audio/rtp.h audio/ipc.h audio/ipc.c +audio_libasound_module_pcm_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_pcm_.* +audio_libasound_module_pcm_bluetooth_la_LIBADD = sbc/libsbc.la \ + lib/libbluetooth.la @ALSA_LIBS@ +audio_libasound_module_pcm_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ + +audio_libasound_module_ctl_bluetooth_la_SOURCES = audio/ctl_bluetooth.c \ + audio/rtp.h audio/ipc.h audio/ipc.c +audio_libasound_module_ctl_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_ctl_.* +audio_libasound_module_ctl_bluetooth_la_LIBADD = lib/libbluetooth.la @ALSA_LIBS@ +audio_libasound_module_ctl_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ + +if CONFIGFILES +alsaconfdir = $(datadir)/alsa + +alsaconf_DATA = audio/bluetooth.conf +endif +endif + +if AUDIOPLUGIN +if GSTREAMER +gstreamerdir = $(libdir)/gstreamer-0.10 + +gstreamer_LTLIBRARIES = audio/libgstbluetooth.la + +audio_libgstbluetooth_la_SOURCES = audio/gstbluetooth.c audio/gstpragma.h \ + audio/gstsbcenc.h audio/gstsbcenc.c \ + audio/gstsbcdec.h audio/gstsbcdec.c \ + audio/gstsbcparse.h audio/gstsbcparse.c \ + audio/gstavdtpsink.h audio/gstavdtpsink.c \ + audio/gsta2dpsink.h audio/gsta2dpsink.c \ + audio/gstsbcutil.h audio/gstsbcutil.c \ + audio/gstrtpsbcpay.h audio/gstrtpsbcpay.c \ + audio/rtp.h audio/ipc.h audio/ipc.c +audio_libgstbluetooth_la_LDFLAGS = -module -avoid-version +audio_libgstbluetooth_la_LIBADD = sbc/libsbc.la lib/libbluetooth.la \ + @DBUS_LIBS@ @GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10 +audio_libgstbluetooth_la_CFLAGS = -fvisibility=hidden -fno-strict-aliasing \ + $(AM_CFLAGS) @DBUS_CFLAGS@ @GSTREAMER_CFLAGS@ +endif +endif + +EXTRA_DIST += audio/bluetooth.conf + + +include Makefile.tools + +if UDEVRULES +rulesdir = @UDEV_DATADIR@ + +udev_files = scripts/bluetooth.rules + +if HID2HCI +udev_files += scripts/bluetooth-hid2hci.rules +endif + +if PCMCIA +udev_files += scripts/bluetooth-serial.rules +endif + +rules_DATA = $(foreach file,$(udev_files), scripts/97-$(notdir $(file))) +endif + +CLEANFILES += $(rules_DATA) + +EXTRA_DIST += scripts/bluetooth.rules \ + scripts/bluetooth-hid2hci.rules scripts/bluetooth-serial.rules + +if PCMCIA +udevdir = $(libexecdir)/udev + +dist_udev_SCRIPTS = scripts/bluetooth_serial +endif + +EXTRA_DIST += doc/manager-api.txt \ + doc/adapter-api.txt doc/device-api.txt \ + doc/service-api.txt doc/agent-api.txt doc/attribute-api.txt \ + doc/serial-api.txt doc/network-api.txt \ + doc/input-api.txt doc/audio-api.txt doc/control-api.txt \ + doc/hfp-api.txt doc/health-api.txt doc/sap-api.txt \ + doc/media-api.txt doc/assigned-numbers.txt + +AM_YFLAGS = -d + +AM_CFLAGS = @DBUS_CFLAGS@ @GLIB_CFLAGS@ @CAPNG_CFLAGS@ \ + -DBLUETOOTH_PLUGIN_BUILTIN -DPLUGINDIR=\""$(plugindir)"\" + +INCLUDES = -I$(builddir)/lib -I$(builddir)/src -I$(srcdir)/src \ + -I$(srcdir)/audio -I$(srcdir)/sbc -I$(srcdir)/gdbus \ + -I$(srcdir)/attrib -I$(srcdir)/btio + +if MCAP +INCLUDES += -I$(builddir)/health +endif + +pkgconfigdir = $(libdir)/pkgconfig + +pkgconfig_DATA = bluez.pc + +DISTCHECK_CONFIGURE_FLAGS = --disable-udevrules --enable-attrib + +DISTCLEANFILES = $(pkgconfig_DATA) + +MAINTAINERCLEANFILES = Makefile.in \ + aclocal.m4 configure config.h.in config.sub config.guess \ + ltmain.sh depcomp compile missing install-sh mkinstalldirs ylwrap + +src/plugin.$(OBJEXT): src/builtin.h + +src/builtin.h: src/genbuiltin $(builtin_sources) + $(AM_V_GEN)$(srcdir)/src/genbuiltin $(builtin_modules) > $@ + +audio/telephony.c: audio/@TELEPHONY_DRIVER@ + $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@ + +sap/sap.c: sap/@SAP_DRIVER@ + $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@ + +scripts/%.rules: + $(AM_V_GEN)cp $(subst 97-,,$@) $@ + +$(lib_libbluetooth_la_OBJECTS): $(local_headers) + +lib/bluetooth/%.h: lib/%.h + $(AM_V_at)$(MKDIR_P) lib/bluetooth + $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@ + +clean-local: + $(RM) -r lib/bluetooth diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..0e3933e --- /dev/null +++ b/Makefile.in @@ -0,0 +1,3477 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + + + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) $(am__EXEEXT_3) \ + $(am__EXEEXT_4) $(am__EXEEXT_5) $(am__EXEEXT_6) \ + $(am__EXEEXT_7) +sbin_PROGRAMS = src/bluetoothd$(EXEEXT) $(am__EXEEXT_13) \ + $(am__EXEEXT_14) $(am__EXEEXT_15) $(am__EXEEXT_16) \ + $(am__EXEEXT_17) +noinst_PROGRAMS = $(am__EXEEXT_8) $(am__EXEEXT_9) $(am__EXEEXT_10) \ + $(am__EXEEXT_11) $(am__EXEEXT_12) +@SBC_TRUE@am__append_1 = sbc/libsbc.la +@SBC_TRUE@am__append_2 = sbc/sbcinfo sbc/sbcdec sbc/sbcenc +@SBC_TRUE@@SNDFILE_TRUE@am__append_3 = sbc/sbctester +@MCAP_TRUE@am__append_4 = health/mcap_lib.h health/mcap_internal.h \ +@MCAP_TRUE@ health/mcap.h health/mcap.c \ +@MCAP_TRUE@ health/mcap_sync.c + +@PNATPLUGIN_TRUE@am__append_5 = pnat +@PNATPLUGIN_TRUE@am__append_6 = plugins/pnat.c +@ECHOPLUGIN_TRUE@am__append_7 = echo +@ECHOPLUGIN_TRUE@am__append_8 = plugins/echo.c +@AUDIOPLUGIN_TRUE@am__append_9 = audio +@AUDIOPLUGIN_TRUE@am__append_10 = audio/main.c \ +@AUDIOPLUGIN_TRUE@ audio/manager.h audio/manager.c \ +@AUDIOPLUGIN_TRUE@ audio/gateway.h audio/gateway.c \ +@AUDIOPLUGIN_TRUE@ audio/headset.h audio/headset.c \ +@AUDIOPLUGIN_TRUE@ audio/control.h audio/control.c \ +@AUDIOPLUGIN_TRUE@ audio/device.h audio/device.c \ +@AUDIOPLUGIN_TRUE@ audio/source.h audio/source.c \ +@AUDIOPLUGIN_TRUE@ audio/sink.h audio/sink.c \ +@AUDIOPLUGIN_TRUE@ audio/a2dp.h audio/a2dp.c \ +@AUDIOPLUGIN_TRUE@ audio/avdtp.h audio/avdtp.c \ +@AUDIOPLUGIN_TRUE@ audio/ipc.h audio/ipc.c \ +@AUDIOPLUGIN_TRUE@ audio/unix.h audio/unix.c \ +@AUDIOPLUGIN_TRUE@ audio/media.h audio/media.c \ +@AUDIOPLUGIN_TRUE@ audio/transport.h audio/transport.c \ +@AUDIOPLUGIN_TRUE@ audio/telephony.h audio/a2dp-codecs.h + +@AUDIOPLUGIN_TRUE@am__append_11 = audio/telephony.c +@SAPPLUGIN_TRUE@am__append_12 = sap +@SAPPLUGIN_TRUE@am__append_13 = sap/main.c \ +@SAPPLUGIN_TRUE@ sap/manager.h sap/manager.c \ +@SAPPLUGIN_TRUE@ sap/server.h sap/server.c \ +@SAPPLUGIN_TRUE@ sap/sap.h + +@SAPPLUGIN_TRUE@am__append_14 = sap/sap.c +@INPUTPLUGIN_TRUE@am__append_15 = input +@INPUTPLUGIN_TRUE@am__append_16 = input/main.c \ +@INPUTPLUGIN_TRUE@ input/manager.h input/manager.c \ +@INPUTPLUGIN_TRUE@ input/server.h input/server.c \ +@INPUTPLUGIN_TRUE@ input/device.h input/device.c \ +@INPUTPLUGIN_TRUE@ input/fakehid.c input/fakehid.h + +@SERIALPLUGIN_TRUE@am__append_17 = serial +@SERIALPLUGIN_TRUE@am__append_18 = serial/main.c \ +@SERIALPLUGIN_TRUE@ serial/manager.h serial/manager.c \ +@SERIALPLUGIN_TRUE@ serial/proxy.h serial/proxy.c \ +@SERIALPLUGIN_TRUE@ serial/port.h serial/port.c + +@NETWORKPLUGIN_TRUE@am__append_19 = network +@NETWORKPLUGIN_TRUE@am__append_20 = network/main.c \ +@NETWORKPLUGIN_TRUE@ network/manager.h network/manager.c \ +@NETWORKPLUGIN_TRUE@ network/common.h network/common.c \ +@NETWORKPLUGIN_TRUE@ network/server.h network/server.c \ +@NETWORKPLUGIN_TRUE@ network/connection.h network/connection.c + +@SERVICEPLUGIN_TRUE@am__append_21 = service +@SERVICEPLUGIN_TRUE@am__append_22 = plugins/service.c +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@am__append_23 = attrib/gatttool +@ATTRIBPLUGIN_TRUE@am__append_24 = attrib +@ATTRIBPLUGIN_TRUE@am__append_25 = attrib/main.c \ +@ATTRIBPLUGIN_TRUE@ attrib/manager.h attrib/manager.c \ +@ATTRIBPLUGIN_TRUE@ attrib/client.h attrib/client.c \ +@ATTRIBPLUGIN_TRUE@ attrib/example.h attrib/example.c + +@HEALTHPLUGIN_TRUE@am__append_26 = health +@HEALTHPLUGIN_TRUE@am__append_27 = health/hdp_main.c health/hdp_types.h \ +@HEALTHPLUGIN_TRUE@ health/hdp_manager.h health/hdp_manager.c \ +@HEALTHPLUGIN_TRUE@ health/hdp.h health/hdp.c \ +@HEALTHPLUGIN_TRUE@ health/hdp_util.h health/hdp_util.c + +@HAL_TRUE@am__append_28 = hal +@HAL_TRUE@am__append_29 = plugins/hal.c +@HAL_FALSE@am__append_30 = formfactor +@HAL_FALSE@am__append_31 = plugins/formfactor.c +@MAEMO6PLUGIN_TRUE@am__append_32 = maemo6 +@MAEMO6PLUGIN_TRUE@am__append_33 = plugins/maemo6.c +@DBUSOOBPLUGIN_TRUE@am__append_34 = dbusoob +@DBUSOOBPLUGIN_TRUE@am__append_35 = plugins/dbusoob.c +DIST_COMMON = README $(am__configure_deps) \ + $(am__dist_udev_SCRIPTS_DIST) $(dist_man_MANS) \ + $(include_HEADERS) $(srcdir)/Makefile.am $(srcdir)/Makefile.in \ + $(srcdir)/Makefile.tools $(srcdir)/bluez.pc.in \ + $(srcdir)/config.h.in $(top_srcdir)/configure \ + $(top_srcdir)/doc/version.xml.in \ + $(top_srcdir)/scripts/bluetooth.rules.in \ + $(top_srcdir)/src/bluetoothd.8.in AUTHORS COPYING COPYING.LIB \ + ChangeLog INSTALL NEWS TODO compile config.guess config.sub \ + depcomp install-sh ltmain.sh missing tools/lexer.c \ + tools/parser.c tools/parser.h ylwrap +@CONFIGFILES_TRUE@@TOOLS_TRUE@am__append_36 = tools/rfcomm.conf +@TOOLS_TRUE@am__append_37 = tools/rfcomm tools/l2ping \ +@TOOLS_TRUE@ tools/hcitool tools/sdptool tools/ciptool + +@TOOLS_TRUE@am__append_38 = tools/hciattach tools/hciconfig +@TOOLS_TRUE@am__append_39 = tools/avinfo tools/ppporc \ +@TOOLS_TRUE@ tools/hcieventmask tools/hcisecfilter + +@TOOLS_TRUE@am__append_40 = tools/rfcomm.1 tools/l2ping.8 \ +@TOOLS_TRUE@ tools/hciattach.8 tools/hciconfig.8 \ +@TOOLS_TRUE@ tools/hcitool.1 tools/sdptool.1 tools/ciptool.1 + +@TOOLS_FALSE@am__append_41 = tools/rfcomm.1 tools/l2ping.8 \ +@TOOLS_FALSE@ tools/hciattach.8 tools/hciconfig.8 \ +@TOOLS_FALSE@ tools/hcitool.1 tools/sdptool.1 tools/ciptool.1 + +@TRACER_TRUE@am__append_42 = tracer/hcitrace +@BCCMD_TRUE@am__append_43 = tools/bccmd +@BCCMD_TRUE@@USB_TRUE@am__append_44 = tools/csr_usb.c +@BCCMD_TRUE@@USB_TRUE@am__append_45 = @USB_LIBS@ +@BCCMD_TRUE@am__append_46 = tools/bccmd.8 +@BCCMD_FALSE@am__append_47 = tools/bccmd.8 +@HID2HCI_TRUE@am__append_48 = tools/hid2hci +@HID2HCI_TRUE@am__append_49 = tools/hid2hci.8 +@HID2HCI_FALSE@am__append_50 = tools/hid2hci.8 +@DFUTOOL_TRUE@am__append_51 = tools/dfutool +@DFUTOOL_TRUE@am__append_52 = tools/dfutool.1 +@DFUTOOL_FALSE@am__append_53 = tools/dfutool.1 +@USB_TRUE@am__append_54 = tools/dfubabel tools/avctrl +@CUPS_TRUE@cups_PROGRAMS = cups/bluetooth$(EXEEXT) +@TEST_TRUE@am__append_55 = test/hciemu +@TEST_TRUE@am__append_56 = test/l2test test/rctest +@TEST_TRUE@am__append_57 = test/gaptest test/sdptest test/scotest \ +@TEST_TRUE@ test/attest test/hstest test/avtest test/ipctest \ +@TEST_TRUE@ test/lmptest test/bdaddr test/agent \ +@TEST_TRUE@ test/btiotest test/test-textfile \ +@TEST_TRUE@ test/uuidtest + +@TEST_TRUE@am__append_58 = test/rctest.1 test/hciemu.1 +@TEST_TRUE@am__append_59 = test/bdaddr.8 +@TEST_FALSE@am__append_60 = test/rctest.1 test/hciemu.1 test/bdaddr.8 +@HIDD_TRUE@am__append_61 = compat/hidd +@HIDD_TRUE@am__append_62 = compat/hidd.1 +@HIDD_FALSE@am__append_63 = compat/hidd.1 +@PAND_TRUE@am__append_64 = compat/pand +@PAND_TRUE@am__append_65 = compat/pand.1 +@PAND_FALSE@am__append_66 = compat/pand.1 +@DUND_TRUE@am__append_67 = compat/dund +@DUND_TRUE@am__append_68 = compat/dund.1 +@DUND_FALSE@am__append_69 = compat/dund.1 +@HID2HCI_TRUE@@UDEVRULES_TRUE@am__append_70 = scripts/bluetooth-hid2hci.rules +@PCMCIA_TRUE@@UDEVRULES_TRUE@am__append_71 = scripts/bluetooth-serial.rules +@MCAP_TRUE@am__append_72 = -I$(builddir)/health +subdir = . +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ + configure.lineno config.status.lineno +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = config.h +CONFIG_CLEAN_FILES = scripts/bluetooth.rules doc/version.xml \ + src/bluetoothd.8 bluez.pc +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +ARFLAGS = cru +AM_V_AR = $(am__v_AR_$(V)) +am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY)) +am__v_AR_0 = @echo " AR " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +audio_libtelephony_a_AR = $(AR) $(ARFLAGS) +audio_libtelephony_a_LIBADD = +am__audio_libtelephony_a_SOURCES_DIST = audio/telephony.h \ + audio/telephony-dummy.c audio/telephony-maemo5.c \ + audio/telephony-ofono.c audio/telephony-maemo6.c +am__dirstamp = $(am__leading_dot)dirstamp +@AUDIOPLUGIN_TRUE@am_audio_libtelephony_a_OBJECTS = \ +@AUDIOPLUGIN_TRUE@ audio/telephony-dummy.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/telephony-maemo5.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/telephony-ofono.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/telephony-maemo6.$(OBJEXT) +audio_libtelephony_a_OBJECTS = $(am_audio_libtelephony_a_OBJECTS) +sap_libsap_a_AR = $(AR) $(ARFLAGS) +sap_libsap_a_LIBADD = +am__sap_libsap_a_SOURCES_DIST = sap/sap.h sap/sap-dummy.c +@SAPPLUGIN_TRUE@am_sap_libsap_a_OBJECTS = sap/sap-dummy.$(OBJEXT) +sap_libsap_a_OBJECTS = $(am_sap_libsap_a_OBJECTS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__installdirs = "$(DESTDIR)$(alsadir)" "$(DESTDIR)$(gstreamerdir)" \ + "$(DESTDIR)$(libdir)" "$(DESTDIR)$(plugindir)" \ + "$(DESTDIR)$(bindir)" "$(DESTDIR)$(cupsdir)" \ + "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(udevdir)" \ + "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(man8dir)" \ + "$(DESTDIR)$(alsaconfdir)" "$(DESTDIR)$(confdir)" \ + "$(DESTDIR)$(dbusdir)" "$(DESTDIR)$(pkgconfigdir)" \ + "$(DESTDIR)$(rulesdir)" "$(DESTDIR)$(statedir)" \ + "$(DESTDIR)$(includedir)" +LTLIBRARIES = $(alsa_LTLIBRARIES) $(gstreamer_LTLIBRARIES) \ + $(lib_LTLIBRARIES) $(noinst_LTLIBRARIES) $(plugin_LTLIBRARIES) +@ALSA_TRUE@audio_libasound_module_ctl_bluetooth_la_DEPENDENCIES = \ +@ALSA_TRUE@ lib/libbluetooth.la +am__audio_libasound_module_ctl_bluetooth_la_SOURCES_DIST = \ + audio/ctl_bluetooth.c audio/rtp.h audio/ipc.h audio/ipc.c +@ALSA_TRUE@am_audio_libasound_module_ctl_bluetooth_la_OBJECTS = audio/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.lo \ +@ALSA_TRUE@ audio/audio_libasound_module_ctl_bluetooth_la-ipc.lo +audio_libasound_module_ctl_bluetooth_la_OBJECTS = \ + $(am_audio_libasound_module_ctl_bluetooth_la_OBJECTS) +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +audio_libasound_module_ctl_bluetooth_la_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(audio_libasound_module_ctl_bluetooth_la_CFLAGS) \ + $(CFLAGS) $(audio_libasound_module_ctl_bluetooth_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +@ALSA_TRUE@am_audio_libasound_module_ctl_bluetooth_la_rpath = -rpath \ +@ALSA_TRUE@ $(alsadir) +@ALSA_TRUE@audio_libasound_module_pcm_bluetooth_la_DEPENDENCIES = \ +@ALSA_TRUE@ sbc/libsbc.la lib/libbluetooth.la +am__audio_libasound_module_pcm_bluetooth_la_SOURCES_DIST = \ + audio/pcm_bluetooth.c audio/rtp.h audio/ipc.h audio/ipc.c +@ALSA_TRUE@am_audio_libasound_module_pcm_bluetooth_la_OBJECTS = audio/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.lo \ +@ALSA_TRUE@ audio/audio_libasound_module_pcm_bluetooth_la-ipc.lo +audio_libasound_module_pcm_bluetooth_la_OBJECTS = \ + $(am_audio_libasound_module_pcm_bluetooth_la_OBJECTS) +audio_libasound_module_pcm_bluetooth_la_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(audio_libasound_module_pcm_bluetooth_la_CFLAGS) \ + $(CFLAGS) $(audio_libasound_module_pcm_bluetooth_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +@ALSA_TRUE@am_audio_libasound_module_pcm_bluetooth_la_rpath = -rpath \ +@ALSA_TRUE@ $(alsadir) +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@audio_libgstbluetooth_la_DEPENDENCIES = \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ sbc/libsbc.la \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ lib/libbluetooth.la +am__audio_libgstbluetooth_la_SOURCES_DIST = audio/gstbluetooth.c \ + audio/gstpragma.h audio/gstsbcenc.h audio/gstsbcenc.c \ + audio/gstsbcdec.h audio/gstsbcdec.c audio/gstsbcparse.h \ + audio/gstsbcparse.c audio/gstavdtpsink.h audio/gstavdtpsink.c \ + audio/gsta2dpsink.h audio/gsta2dpsink.c audio/gstsbcutil.h \ + audio/gstsbcutil.c audio/gstrtpsbcpay.h audio/gstrtpsbcpay.c \ + audio/rtp.h audio/ipc.h audio/ipc.c +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@am_audio_libgstbluetooth_la_OBJECTS = audio/audio_libgstbluetooth_la-gstbluetooth.lo \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/audio_libgstbluetooth_la-gstsbcenc.lo \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/audio_libgstbluetooth_la-gstsbcdec.lo \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/audio_libgstbluetooth_la-gstsbcparse.lo \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/audio_libgstbluetooth_la-gstavdtpsink.lo \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/audio_libgstbluetooth_la-gsta2dpsink.lo \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/audio_libgstbluetooth_la-gstsbcutil.lo \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/audio_libgstbluetooth_la-gstrtpsbcpay.lo \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/audio_libgstbluetooth_la-ipc.lo +audio_libgstbluetooth_la_OBJECTS = \ + $(am_audio_libgstbluetooth_la_OBJECTS) +audio_libgstbluetooth_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) \ + $(audio_libgstbluetooth_la_LDFLAGS) $(LDFLAGS) -o $@ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@am_audio_libgstbluetooth_la_rpath = \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ -rpath $(gstreamerdir) +lib_libbluetooth_la_LIBADD = +am__objects_1 = +am_lib_libbluetooth_la_OBJECTS = $(am__objects_1) lib/bluetooth.lo \ + lib/hci.lo lib/sdp.lo lib/uuid.lo +lib_libbluetooth_la_OBJECTS = $(am_lib_libbluetooth_la_OBJECTS) +lib_libbluetooth_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib_libbluetooth_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +sbc_libsbc_la_LIBADD = +am__sbc_libsbc_la_SOURCES_DIST = sbc/sbc.h sbc/sbc.c sbc/sbc_math.h \ + sbc/sbc_tables.h sbc/sbc_primitives.h sbc/sbc_primitives.c \ + sbc/sbc_primitives_mmx.h sbc/sbc_primitives_mmx.c \ + sbc/sbc_primitives_iwmmxt.h sbc/sbc_primitives_iwmmxt.c \ + sbc/sbc_primitives_neon.h sbc/sbc_primitives_neon.c \ + sbc/sbc_primitives_armv6.h sbc/sbc_primitives_armv6.c +@SBC_TRUE@am_sbc_libsbc_la_OBJECTS = sbc/sbc_libsbc_la-sbc.lo \ +@SBC_TRUE@ sbc/sbc_libsbc_la-sbc_primitives.lo \ +@SBC_TRUE@ sbc/sbc_libsbc_la-sbc_primitives_mmx.lo \ +@SBC_TRUE@ sbc/sbc_libsbc_la-sbc_primitives_iwmmxt.lo \ +@SBC_TRUE@ sbc/sbc_libsbc_la-sbc_primitives_neon.lo \ +@SBC_TRUE@ sbc/sbc_libsbc_la-sbc_primitives_armv6.lo +sbc_libsbc_la_OBJECTS = $(am_sbc_libsbc_la_OBJECTS) +sbc_libsbc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(sbc_libsbc_la_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +@SBC_TRUE@am_sbc_libsbc_la_rpath = +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@am__EXEEXT_1 = \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/gatttool$(EXEEXT) +@TOOLS_TRUE@am__EXEEXT_2 = tools/rfcomm$(EXEEXT) tools/l2ping$(EXEEXT) \ +@TOOLS_TRUE@ tools/hcitool$(EXEEXT) tools/sdptool$(EXEEXT) \ +@TOOLS_TRUE@ tools/ciptool$(EXEEXT) +@DFUTOOL_TRUE@am__EXEEXT_3 = tools/dfutool$(EXEEXT) +@TEST_TRUE@am__EXEEXT_4 = test/l2test$(EXEEXT) test/rctest$(EXEEXT) +@HIDD_TRUE@am__EXEEXT_5 = compat/hidd$(EXEEXT) +@PAND_TRUE@am__EXEEXT_6 = compat/pand$(EXEEXT) +@DUND_TRUE@am__EXEEXT_7 = compat/dund$(EXEEXT) +@SBC_TRUE@am__EXEEXT_8 = sbc/sbcinfo$(EXEEXT) sbc/sbcdec$(EXEEXT) \ +@SBC_TRUE@ sbc/sbcenc$(EXEEXT) +@SBC_TRUE@@SNDFILE_TRUE@am__EXEEXT_9 = sbc/sbctester$(EXEEXT) +@TOOLS_TRUE@am__EXEEXT_10 = tools/avinfo$(EXEEXT) \ +@TOOLS_TRUE@ tools/ppporc$(EXEEXT) tools/hcieventmask$(EXEEXT) \ +@TOOLS_TRUE@ tools/hcisecfilter$(EXEEXT) +@USB_TRUE@am__EXEEXT_11 = tools/dfubabel$(EXEEXT) \ +@USB_TRUE@ tools/avctrl$(EXEEXT) +@TEST_TRUE@am__EXEEXT_12 = test/gaptest$(EXEEXT) test/sdptest$(EXEEXT) \ +@TEST_TRUE@ test/scotest$(EXEEXT) test/attest$(EXEEXT) \ +@TEST_TRUE@ test/hstest$(EXEEXT) test/avtest$(EXEEXT) \ +@TEST_TRUE@ test/ipctest$(EXEEXT) test/lmptest$(EXEEXT) \ +@TEST_TRUE@ test/bdaddr$(EXEEXT) test/agent$(EXEEXT) \ +@TEST_TRUE@ test/btiotest$(EXEEXT) test/test-textfile$(EXEEXT) \ +@TEST_TRUE@ test/uuidtest$(EXEEXT) +@TOOLS_TRUE@am__EXEEXT_13 = tools/hciattach$(EXEEXT) \ +@TOOLS_TRUE@ tools/hciconfig$(EXEEXT) +@TRACER_TRUE@am__EXEEXT_14 = tracer/hcitrace$(EXEEXT) +@BCCMD_TRUE@am__EXEEXT_15 = tools/bccmd$(EXEEXT) +@HID2HCI_TRUE@am__EXEEXT_16 = tools/hid2hci$(EXEEXT) +@TEST_TRUE@am__EXEEXT_17 = test/hciemu$(EXEEXT) +PROGRAMS = $(bin_PROGRAMS) $(cups_PROGRAMS) $(noinst_PROGRAMS) \ + $(sbin_PROGRAMS) +am__attrib_gatttool_SOURCES_DIST = attrib/gatttool.c attrib/att.c \ + attrib/gatt.c attrib/gattrib.c btio/btio.c src/glib-helper.h \ + src/glib-helper.c attrib/gatttool.h attrib/interactive.c \ + attrib/utils.c +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@am_attrib_gatttool_OBJECTS = \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/gatttool.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/att.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/gatt.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/gattrib.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ btio/btio.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ src/glib-helper.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/interactive.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/utils.$(OBJEXT) +attrib_gatttool_OBJECTS = $(am_attrib_gatttool_OBJECTS) +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@attrib_gatttool_DEPENDENCIES = \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ lib/libbluetooth.la +am__compat_dund_SOURCES_DIST = compat/dund.c compat/dund.h \ + compat/lib.h compat/sdp.h compat/sdp.c compat/dun.c \ + compat/msdun.c src/textfile.h src/textfile.c +@DUND_TRUE@am_compat_dund_OBJECTS = compat/dund.$(OBJEXT) \ +@DUND_TRUE@ compat/sdp.$(OBJEXT) compat/dun.$(OBJEXT) \ +@DUND_TRUE@ compat/msdun.$(OBJEXT) src/textfile.$(OBJEXT) +compat_dund_OBJECTS = $(am_compat_dund_OBJECTS) +@DUND_TRUE@compat_dund_DEPENDENCIES = lib/libbluetooth.la +am__compat_hidd_SOURCES_DIST = compat/hidd.c compat/hidd.h \ + src/uinput.h compat/sdp.h compat/sdp.c compat/fakehid.c \ + src/textfile.h src/textfile.c +@HIDD_TRUE@am_compat_hidd_OBJECTS = compat/hidd.$(OBJEXT) \ +@HIDD_TRUE@ compat/sdp.$(OBJEXT) compat/fakehid.$(OBJEXT) \ +@HIDD_TRUE@ src/textfile.$(OBJEXT) +compat_hidd_OBJECTS = $(am_compat_hidd_OBJECTS) +@HIDD_TRUE@compat_hidd_DEPENDENCIES = lib/libbluetooth.la +am__compat_pand_SOURCES_DIST = compat/pand.c compat/pand.h \ + compat/bnep.c compat/sdp.h compat/sdp.c src/textfile.h \ + src/textfile.c +@PAND_TRUE@am_compat_pand_OBJECTS = compat/pand.$(OBJEXT) \ +@PAND_TRUE@ compat/bnep.$(OBJEXT) compat/sdp.$(OBJEXT) \ +@PAND_TRUE@ src/textfile.$(OBJEXT) +compat_pand_OBJECTS = $(am_compat_pand_OBJECTS) +@PAND_TRUE@compat_pand_DEPENDENCIES = lib/libbluetooth.la +am__cups_bluetooth_SOURCES_DIST = gdbus/gdbus.h gdbus/mainloop.c \ + gdbus/watch.c gdbus/object.c gdbus/polkit.c cups/main.c \ + cups/cups.h cups/sdp.c cups/spp.c cups/hcrp.c +am__objects_2 = gdbus/mainloop.$(OBJEXT) gdbus/watch.$(OBJEXT) \ + gdbus/object.$(OBJEXT) gdbus/polkit.$(OBJEXT) +@CUPS_TRUE@am_cups_bluetooth_OBJECTS = $(am__objects_2) \ +@CUPS_TRUE@ cups/main.$(OBJEXT) cups/sdp.$(OBJEXT) \ +@CUPS_TRUE@ cups/spp.$(OBJEXT) cups/hcrp.$(OBJEXT) +cups_bluetooth_OBJECTS = $(am_cups_bluetooth_OBJECTS) +@CUPS_TRUE@cups_bluetooth_DEPENDENCIES = lib/libbluetooth.la +am__sbc_sbcdec_SOURCES_DIST = sbc/sbcdec.c sbc/formats.h +@SBC_TRUE@am_sbc_sbcdec_OBJECTS = sbc/sbcdec.$(OBJEXT) +sbc_sbcdec_OBJECTS = $(am_sbc_sbcdec_OBJECTS) +@SBC_TRUE@sbc_sbcdec_DEPENDENCIES = sbc/libsbc.la +am__sbc_sbcenc_SOURCES_DIST = sbc/sbcenc.c sbc/formats.h +@SBC_TRUE@am_sbc_sbcenc_OBJECTS = sbc/sbcenc.$(OBJEXT) +sbc_sbcenc_OBJECTS = $(am_sbc_sbcenc_OBJECTS) +@SBC_TRUE@sbc_sbcenc_DEPENDENCIES = sbc/libsbc.la +sbc_sbcinfo_SOURCES = sbc/sbcinfo.c +sbc_sbcinfo_OBJECTS = sbc/sbcinfo.$(OBJEXT) +sbc_sbcinfo_LDADD = $(LDADD) +sbc_sbctester_SOURCES = sbc/sbctester.c +sbc_sbctester_OBJECTS = sbc/sbctester.$(OBJEXT) +sbc_sbctester_DEPENDENCIES = +am__src_bluetoothd_SOURCES_DIST = gdbus/gdbus.h gdbus/mainloop.c \ + gdbus/watch.c gdbus/object.c gdbus/polkit.c plugins/pnat.c \ + plugins/echo.c audio/main.c audio/manager.h audio/manager.c \ + audio/gateway.h audio/gateway.c audio/headset.h \ + audio/headset.c audio/control.h audio/control.c audio/device.h \ + audio/device.c audio/source.h audio/source.c audio/sink.h \ + audio/sink.c audio/a2dp.h audio/a2dp.c audio/avdtp.h \ + audio/avdtp.c audio/ipc.h audio/ipc.c audio/unix.h \ + audio/unix.c audio/media.h audio/media.c audio/transport.h \ + audio/transport.c audio/telephony.h audio/a2dp-codecs.h \ + sap/main.c sap/manager.h sap/manager.c sap/server.h \ + sap/server.c sap/sap.h input/main.c input/manager.h \ + input/manager.c input/server.h input/server.c input/device.h \ + input/device.c input/fakehid.c input/fakehid.h serial/main.c \ + serial/manager.h serial/manager.c serial/proxy.h \ + serial/proxy.c serial/port.h serial/port.c network/main.c \ + network/manager.h network/manager.c network/common.h \ + network/common.c network/server.h network/server.c \ + network/connection.h network/connection.c plugins/service.c \ + attrib/main.c attrib/manager.h attrib/manager.c \ + attrib/client.h attrib/client.c attrib/example.h \ + attrib/example.c health/hdp_main.c health/hdp_types.h \ + health/hdp_manager.h health/hdp_manager.c health/hdp.h \ + health/hdp.c health/hdp_util.h health/hdp_util.c \ + plugins/hciops.c plugins/mgmtops.c plugins/hal.c \ + plugins/formfactor.c plugins/storage.c plugins/maemo6.c \ + plugins/dbusoob.c attrib/att.h attrib/att.c attrib/gatt.h \ + attrib/gatt.c attrib/gattrib.h attrib/gattrib.c btio/btio.h \ + btio/btio.c health/mcap_lib.h health/mcap_internal.h \ + health/mcap.h health/mcap.c health/mcap_sync.c \ + src/bluetooth.ver src/main.c src/log.h src/log.c src/rfkill.c \ + src/hcid.h src/sdpd.h src/sdpd-server.c src/sdpd-request.c \ + src/sdpd-service.c src/sdpd-database.c src/attrib-server.h \ + src/attrib-server.c src/sdp-xml.h src/sdp-xml.c src/textfile.h \ + src/textfile.c src/glib-helper.h src/glib-helper.c src/oui.h \ + src/oui.c src/uinput.h src/ppoll.h src/plugin.h src/plugin.c \ + src/storage.h src/storage.c src/agent.h src/agent.c \ + src/error.h src/error.c src/manager.h src/manager.c \ + src/adapter.h src/adapter.c src/device.h src/device.c \ + src/dbus-common.c src/dbus-common.h src/event.h src/event.c \ + src/oob.h src/oob.c +@PNATPLUGIN_TRUE@am__objects_3 = plugins/pnat.$(OBJEXT) +@ECHOPLUGIN_TRUE@am__objects_4 = plugins/echo.$(OBJEXT) +@AUDIOPLUGIN_TRUE@am__objects_5 = audio/main.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/manager.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/gateway.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/headset.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/control.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/device.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/source.$(OBJEXT) audio/sink.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/a2dp.$(OBJEXT) audio/avdtp.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/ipc.$(OBJEXT) audio/unix.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/media.$(OBJEXT) \ +@AUDIOPLUGIN_TRUE@ audio/transport.$(OBJEXT) +@SAPPLUGIN_TRUE@am__objects_6 = sap/main.$(OBJEXT) \ +@SAPPLUGIN_TRUE@ sap/manager.$(OBJEXT) sap/server.$(OBJEXT) +@INPUTPLUGIN_TRUE@am__objects_7 = input/main.$(OBJEXT) \ +@INPUTPLUGIN_TRUE@ input/manager.$(OBJEXT) \ +@INPUTPLUGIN_TRUE@ input/server.$(OBJEXT) \ +@INPUTPLUGIN_TRUE@ input/device.$(OBJEXT) \ +@INPUTPLUGIN_TRUE@ input/fakehid.$(OBJEXT) +@SERIALPLUGIN_TRUE@am__objects_8 = serial/main.$(OBJEXT) \ +@SERIALPLUGIN_TRUE@ serial/manager.$(OBJEXT) \ +@SERIALPLUGIN_TRUE@ serial/proxy.$(OBJEXT) \ +@SERIALPLUGIN_TRUE@ serial/port.$(OBJEXT) +@NETWORKPLUGIN_TRUE@am__objects_9 = network/main.$(OBJEXT) \ +@NETWORKPLUGIN_TRUE@ network/manager.$(OBJEXT) \ +@NETWORKPLUGIN_TRUE@ network/common.$(OBJEXT) \ +@NETWORKPLUGIN_TRUE@ network/server.$(OBJEXT) \ +@NETWORKPLUGIN_TRUE@ network/connection.$(OBJEXT) +@SERVICEPLUGIN_TRUE@am__objects_10 = plugins/service.$(OBJEXT) +@ATTRIBPLUGIN_TRUE@am__objects_11 = attrib/main.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@ attrib/manager.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@ attrib/client.$(OBJEXT) \ +@ATTRIBPLUGIN_TRUE@ attrib/example.$(OBJEXT) +@HEALTHPLUGIN_TRUE@am__objects_12 = health/hdp_main.$(OBJEXT) \ +@HEALTHPLUGIN_TRUE@ health/hdp_manager.$(OBJEXT) \ +@HEALTHPLUGIN_TRUE@ health/hdp.$(OBJEXT) \ +@HEALTHPLUGIN_TRUE@ health/hdp_util.$(OBJEXT) +@HAL_TRUE@am__objects_13 = plugins/hal.$(OBJEXT) +@HAL_FALSE@am__objects_14 = plugins/formfactor.$(OBJEXT) +@MAEMO6PLUGIN_TRUE@am__objects_15 = plugins/maemo6.$(OBJEXT) +@DBUSOOBPLUGIN_TRUE@am__objects_16 = plugins/dbusoob.$(OBJEXT) +am__objects_17 = $(am__objects_3) $(am__objects_4) $(am__objects_5) \ + $(am__objects_6) $(am__objects_7) $(am__objects_8) \ + $(am__objects_9) $(am__objects_10) $(am__objects_11) \ + $(am__objects_12) plugins/hciops.$(OBJEXT) \ + plugins/mgmtops.$(OBJEXT) $(am__objects_13) $(am__objects_14) \ + plugins/storage.$(OBJEXT) $(am__objects_15) $(am__objects_16) +am__objects_18 = attrib/att.$(OBJEXT) attrib/gatt.$(OBJEXT) \ + attrib/gattrib.$(OBJEXT) +am__objects_19 = btio/btio.$(OBJEXT) +@MCAP_TRUE@am__objects_20 = health/mcap.$(OBJEXT) \ +@MCAP_TRUE@ health/mcap_sync.$(OBJEXT) +am__objects_21 = $(am__objects_20) +am_src_bluetoothd_OBJECTS = $(am__objects_2) $(am__objects_17) \ + $(am__objects_18) $(am__objects_19) $(am__objects_21) \ + src/main.$(OBJEXT) src/log.$(OBJEXT) src/rfkill.$(OBJEXT) \ + src/sdpd-server.$(OBJEXT) src/sdpd-request.$(OBJEXT) \ + src/sdpd-service.$(OBJEXT) src/sdpd-database.$(OBJEXT) \ + src/attrib-server.$(OBJEXT) src/sdp-xml.$(OBJEXT) \ + src/textfile.$(OBJEXT) src/glib-helper.$(OBJEXT) \ + src/oui.$(OBJEXT) src/plugin.$(OBJEXT) src/storage.$(OBJEXT) \ + src/agent.$(OBJEXT) src/error.$(OBJEXT) src/manager.$(OBJEXT) \ + src/adapter.$(OBJEXT) src/device.$(OBJEXT) \ + src/dbus-common.$(OBJEXT) src/event.$(OBJEXT) \ + src/oob.$(OBJEXT) +@AUDIOPLUGIN_TRUE@am__objects_22 = audio/telephony.$(OBJEXT) +@SAPPLUGIN_TRUE@am__objects_23 = sap/sap.$(OBJEXT) +am__objects_24 = $(am__objects_22) $(am__objects_23) +am__objects_25 = $(am__objects_24) +nodist_src_bluetoothd_OBJECTS = $(am__objects_25) +src_bluetoothd_OBJECTS = $(am_src_bluetoothd_OBJECTS) \ + $(nodist_src_bluetoothd_OBJECTS) +src_bluetoothd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(src_bluetoothd_LDFLAGS) $(LDFLAGS) -o \ + $@ +test_agent_SOURCES = test/agent.c +test_agent_OBJECTS = test/agent.$(OBJEXT) +test_agent_DEPENDENCIES = +test_attest_SOURCES = test/attest.c +test_attest_OBJECTS = test/attest.$(OBJEXT) +@TEST_TRUE@test_attest_DEPENDENCIES = lib/libbluetooth.la +test_avtest_SOURCES = test/avtest.c +test_avtest_OBJECTS = test/avtest.$(OBJEXT) +@TEST_TRUE@test_avtest_DEPENDENCIES = lib/libbluetooth.la +am__test_bdaddr_SOURCES_DIST = test/bdaddr.c src/oui.h src/oui.c +@TEST_TRUE@am_test_bdaddr_OBJECTS = test/bdaddr.$(OBJEXT) \ +@TEST_TRUE@ src/oui.$(OBJEXT) +test_bdaddr_OBJECTS = $(am_test_bdaddr_OBJECTS) +@TEST_TRUE@test_bdaddr_DEPENDENCIES = lib/libbluetooth.la +am__test_btiotest_SOURCES_DIST = test/btiotest.c btio/btio.h \ + btio/btio.c +@TEST_TRUE@am_test_btiotest_OBJECTS = test/btiotest.$(OBJEXT) \ +@TEST_TRUE@ btio/btio.$(OBJEXT) +test_btiotest_OBJECTS = $(am_test_btiotest_OBJECTS) +@TEST_TRUE@test_btiotest_DEPENDENCIES = lib/libbluetooth.la +test_gaptest_SOURCES = test/gaptest.c +test_gaptest_OBJECTS = test/gaptest.$(OBJEXT) +test_gaptest_DEPENDENCIES = +test_hciemu_SOURCES = test/hciemu.c +test_hciemu_OBJECTS = test/hciemu.$(OBJEXT) +@TEST_TRUE@test_hciemu_DEPENDENCIES = lib/libbluetooth.la +test_hstest_SOURCES = test/hstest.c +test_hstest_OBJECTS = test/hstest.$(OBJEXT) +@TEST_TRUE@test_hstest_DEPENDENCIES = lib/libbluetooth.la +am__test_ipctest_SOURCES_DIST = test/ipctest.c audio/ipc.h audio/ipc.c +@TEST_TRUE@am_test_ipctest_OBJECTS = test/ipctest.$(OBJEXT) \ +@TEST_TRUE@ audio/ipc.$(OBJEXT) +test_ipctest_OBJECTS = $(am_test_ipctest_OBJECTS) +@TEST_TRUE@test_ipctest_DEPENDENCIES = sbc/libsbc.la +test_l2test_SOURCES = test/l2test.c +test_l2test_OBJECTS = test/l2test.$(OBJEXT) +@TEST_TRUE@test_l2test_DEPENDENCIES = lib/libbluetooth.la +test_lmptest_SOURCES = test/lmptest.c +test_lmptest_OBJECTS = test/lmptest.$(OBJEXT) +@TEST_TRUE@test_lmptest_DEPENDENCIES = lib/libbluetooth.la +test_rctest_SOURCES = test/rctest.c +test_rctest_OBJECTS = test/rctest.$(OBJEXT) +@TEST_TRUE@test_rctest_DEPENDENCIES = lib/libbluetooth.la +test_scotest_SOURCES = test/scotest.c +test_scotest_OBJECTS = test/scotest.$(OBJEXT) +@TEST_TRUE@test_scotest_DEPENDENCIES = lib/libbluetooth.la +test_sdptest_SOURCES = test/sdptest.c +test_sdptest_OBJECTS = test/sdptest.$(OBJEXT) +@TEST_TRUE@test_sdptest_DEPENDENCIES = lib/libbluetooth.la +am__test_test_textfile_SOURCES_DIST = test/test-textfile.c \ + src/textfile.h src/textfile.c +@TEST_TRUE@am_test_test_textfile_OBJECTS = \ +@TEST_TRUE@ test/test-textfile.$(OBJEXT) src/textfile.$(OBJEXT) +test_test_textfile_OBJECTS = $(am_test_test_textfile_OBJECTS) +test_test_textfile_LDADD = $(LDADD) +am__test_uuidtest_SOURCES_DIST = test/uuidtest.c +@TEST_TRUE@am_test_uuidtest_OBJECTS = test/uuidtest.$(OBJEXT) +test_uuidtest_OBJECTS = $(am_test_uuidtest_OBJECTS) +@TEST_TRUE@test_uuidtest_DEPENDENCIES = lib/libbluetooth.la +tools_avctrl_SOURCES = tools/avctrl.c +tools_avctrl_OBJECTS = tools/avctrl.$(OBJEXT) +tools_avctrl_DEPENDENCIES = +tools_avinfo_SOURCES = tools/avinfo.c +tools_avinfo_OBJECTS = tools/avinfo.$(OBJEXT) +@TOOLS_TRUE@tools_avinfo_DEPENDENCIES = lib/libbluetooth.la +am__tools_bccmd_SOURCES_DIST = tools/bccmd.c tools/csr.h tools/csr.c \ + tools/csr_hci.c tools/csr_h4.c tools/csr_3wire.c \ + tools/csr_bcsp.c tools/ubcsp.h tools/ubcsp.c tools/csr_usb.c +@BCCMD_TRUE@@USB_TRUE@am__objects_26 = tools/csr_usb.$(OBJEXT) +@BCCMD_TRUE@am_tools_bccmd_OBJECTS = tools/bccmd.$(OBJEXT) \ +@BCCMD_TRUE@ tools/csr.$(OBJEXT) tools/csr_hci.$(OBJEXT) \ +@BCCMD_TRUE@ tools/csr_h4.$(OBJEXT) tools/csr_3wire.$(OBJEXT) \ +@BCCMD_TRUE@ tools/csr_bcsp.$(OBJEXT) tools/ubcsp.$(OBJEXT) \ +@BCCMD_TRUE@ $(am__objects_26) +tools_bccmd_OBJECTS = $(am_tools_bccmd_OBJECTS) +am__DEPENDENCIES_1 = +@BCCMD_TRUE@tools_bccmd_DEPENDENCIES = lib/libbluetooth.la \ +@BCCMD_TRUE@ $(am__DEPENDENCIES_1) +tools_ciptool_SOURCES = tools/ciptool.c +tools_ciptool_OBJECTS = tools/ciptool.$(OBJEXT) +@TOOLS_TRUE@tools_ciptool_DEPENDENCIES = lib/libbluetooth.la +tools_dfubabel_SOURCES = tools/dfubabel.c +tools_dfubabel_OBJECTS = tools/dfubabel.$(OBJEXT) +tools_dfubabel_DEPENDENCIES = +am__tools_dfutool_SOURCES_DIST = tools/dfutool.c tools/dfu.h \ + tools/dfu.c +@DFUTOOL_TRUE@am_tools_dfutool_OBJECTS = tools/dfutool.$(OBJEXT) \ +@DFUTOOL_TRUE@ tools/dfu.$(OBJEXT) +tools_dfutool_OBJECTS = $(am_tools_dfutool_OBJECTS) +tools_dfutool_DEPENDENCIES = +am__tools_hciattach_SOURCES_DIST = tools/hciattach.c tools/hciattach.h \ + tools/hciattach_st.c tools/hciattach_ti.c \ + tools/hciattach_tialt.c tools/hciattach_ath3k.c \ + tools/hciattach_qualcomm.c +@TOOLS_TRUE@am_tools_hciattach_OBJECTS = tools/hciattach.$(OBJEXT) \ +@TOOLS_TRUE@ tools/hciattach_st.$(OBJEXT) \ +@TOOLS_TRUE@ tools/hciattach_ti.$(OBJEXT) \ +@TOOLS_TRUE@ tools/hciattach_tialt.$(OBJEXT) \ +@TOOLS_TRUE@ tools/hciattach_ath3k.$(OBJEXT) \ +@TOOLS_TRUE@ tools/hciattach_qualcomm.$(OBJEXT) +tools_hciattach_OBJECTS = $(am_tools_hciattach_OBJECTS) +@TOOLS_TRUE@tools_hciattach_DEPENDENCIES = lib/libbluetooth.la +am__tools_hciconfig_SOURCES_DIST = tools/hciconfig.c tools/csr.h \ + tools/csr.c src/textfile.h src/textfile.c +@TOOLS_TRUE@am_tools_hciconfig_OBJECTS = tools/hciconfig.$(OBJEXT) \ +@TOOLS_TRUE@ tools/csr.$(OBJEXT) src/textfile.$(OBJEXT) +tools_hciconfig_OBJECTS = $(am_tools_hciconfig_OBJECTS) +@TOOLS_TRUE@tools_hciconfig_DEPENDENCIES = lib/libbluetooth.la +tools_hcieventmask_SOURCES = tools/hcieventmask.c +tools_hcieventmask_OBJECTS = tools/hcieventmask.$(OBJEXT) +@TOOLS_TRUE@tools_hcieventmask_DEPENDENCIES = lib/libbluetooth.la +tools_hcisecfilter_SOURCES = tools/hcisecfilter.c +tools_hcisecfilter_OBJECTS = tools/hcisecfilter.$(OBJEXT) +tools_hcisecfilter_LDADD = $(LDADD) +am__tools_hcitool_SOURCES_DIST = tools/hcitool.c src/oui.h src/oui.c \ + src/textfile.h src/textfile.c +@TOOLS_TRUE@am_tools_hcitool_OBJECTS = tools/hcitool.$(OBJEXT) \ +@TOOLS_TRUE@ src/oui.$(OBJEXT) src/textfile.$(OBJEXT) +tools_hcitool_OBJECTS = $(am_tools_hcitool_OBJECTS) +@TOOLS_TRUE@tools_hcitool_DEPENDENCIES = lib/libbluetooth.la +tools_hid2hci_SOURCES = tools/hid2hci.c +tools_hid2hci_OBJECTS = tools/hid2hci.$(OBJEXT) +tools_hid2hci_DEPENDENCIES = +tools_l2ping_SOURCES = tools/l2ping.c +tools_l2ping_OBJECTS = tools/l2ping.$(OBJEXT) +@TOOLS_TRUE@tools_l2ping_DEPENDENCIES = lib/libbluetooth.la +tools_ppporc_SOURCES = tools/ppporc.c +tools_ppporc_OBJECTS = tools/ppporc.$(OBJEXT) +@TOOLS_TRUE@tools_ppporc_DEPENDENCIES = lib/libbluetooth.la +am__tools_rfcomm_SOURCES_DIST = tools/rfcomm.c tools/parser.y \ + tools/lexer.l tools/kword.h tools/kword.c +@TOOLS_TRUE@am_tools_rfcomm_OBJECTS = tools/rfcomm.$(OBJEXT) \ +@TOOLS_TRUE@ tools/parser.$(OBJEXT) tools/lexer.$(OBJEXT) \ +@TOOLS_TRUE@ tools/kword.$(OBJEXT) +am__EXTRA_tools_rfcomm_SOURCES_DIST = tools/parser.h tools/parser.c \ + tools/lexer.c +tools_rfcomm_OBJECTS = $(am_tools_rfcomm_OBJECTS) +@TOOLS_TRUE@tools_rfcomm_DEPENDENCIES = lib/libbluetooth.la +am__tools_sdptool_SOURCES_DIST = tools/sdptool.c src/sdp-xml.h \ + src/sdp-xml.c +@TOOLS_TRUE@am_tools_sdptool_OBJECTS = tools/sdptool.$(OBJEXT) \ +@TOOLS_TRUE@ src/sdp-xml.$(OBJEXT) +tools_sdptool_OBJECTS = $(am_tools_sdptool_OBJECTS) +@TOOLS_TRUE@tools_sdptool_DEPENDENCIES = lib/libbluetooth.la +am__tracer_hcitrace_SOURCES_DIST = tracer/main.c +@TRACER_TRUE@am_tracer_hcitrace_OBJECTS = tracer/main.$(OBJEXT) +tracer_hcitrace_OBJECTS = $(am_tracer_hcitrace_OBJECTS) +am__dist_udev_SCRIPTS_DIST = scripts/bluetooth_serial +SCRIPTS = $(dist_udev_SCRIPTS) +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +@MAINTAINER_MODE_FALSE@am__skiplex = test -f $@ || +LEXCOMPILE = $(LEX) $(LFLAGS) $(AM_LFLAGS) +LTLEXCOMPILE = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(LEX) $(LFLAGS) $(AM_LFLAGS) +AM_V_LEX = $(am__v_LEX_$(V)) +am__v_LEX_ = $(am__v_LEX_$(AM_DEFAULT_VERBOSITY)) +am__v_LEX_0 = @echo " LEX " $@; +YLWRAP = $(top_srcdir)/ylwrap +@MAINTAINER_MODE_FALSE@am__skipyacc = test -f $@ || +YACCCOMPILE = $(YACC) $(YFLAGS) $(AM_YFLAGS) +LTYACCCOMPILE = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(YACC) $(YFLAGS) $(AM_YFLAGS) +AM_V_YACC = $(am__v_YACC_$(V)) +am__v_YACC_ = $(am__v_YACC_$(AM_DEFAULT_VERBOSITY)) +am__v_YACC_0 = @echo " YACC " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(audio_libtelephony_a_SOURCES) $(sap_libsap_a_SOURCES) \ + $(audio_libasound_module_ctl_bluetooth_la_SOURCES) \ + $(audio_libasound_module_pcm_bluetooth_la_SOURCES) \ + $(audio_libgstbluetooth_la_SOURCES) \ + $(lib_libbluetooth_la_SOURCES) $(sbc_libsbc_la_SOURCES) \ + $(attrib_gatttool_SOURCES) $(compat_dund_SOURCES) \ + $(compat_hidd_SOURCES) $(compat_pand_SOURCES) \ + $(cups_bluetooth_SOURCES) $(sbc_sbcdec_SOURCES) \ + $(sbc_sbcenc_SOURCES) sbc/sbcinfo.c sbc/sbctester.c \ + $(src_bluetoothd_SOURCES) $(nodist_src_bluetoothd_SOURCES) \ + test/agent.c test/attest.c test/avtest.c \ + $(test_bdaddr_SOURCES) $(test_btiotest_SOURCES) test/gaptest.c \ + test/hciemu.c test/hstest.c $(test_ipctest_SOURCES) \ + test/l2test.c test/lmptest.c test/rctest.c test/scotest.c \ + test/sdptest.c $(test_test_textfile_SOURCES) \ + $(test_uuidtest_SOURCES) tools/avctrl.c tools/avinfo.c \ + $(tools_bccmd_SOURCES) tools/ciptool.c tools/dfubabel.c \ + $(tools_dfutool_SOURCES) $(tools_hciattach_SOURCES) \ + $(tools_hciconfig_SOURCES) tools/hcieventmask.c \ + tools/hcisecfilter.c $(tools_hcitool_SOURCES) tools/hid2hci.c \ + tools/l2ping.c tools/ppporc.c $(tools_rfcomm_SOURCES) \ + $(EXTRA_tools_rfcomm_SOURCES) $(tools_sdptool_SOURCES) \ + $(tracer_hcitrace_SOURCES) +DIST_SOURCES = $(am__audio_libtelephony_a_SOURCES_DIST) \ + $(am__sap_libsap_a_SOURCES_DIST) \ + $(am__audio_libasound_module_ctl_bluetooth_la_SOURCES_DIST) \ + $(am__audio_libasound_module_pcm_bluetooth_la_SOURCES_DIST) \ + $(am__audio_libgstbluetooth_la_SOURCES_DIST) \ + $(lib_libbluetooth_la_SOURCES) \ + $(am__sbc_libsbc_la_SOURCES_DIST) \ + $(am__attrib_gatttool_SOURCES_DIST) \ + $(am__compat_dund_SOURCES_DIST) \ + $(am__compat_hidd_SOURCES_DIST) \ + $(am__compat_pand_SOURCES_DIST) \ + $(am__cups_bluetooth_SOURCES_DIST) \ + $(am__sbc_sbcdec_SOURCES_DIST) $(am__sbc_sbcenc_SOURCES_DIST) \ + sbc/sbcinfo.c sbc/sbctester.c \ + $(am__src_bluetoothd_SOURCES_DIST) test/agent.c test/attest.c \ + test/avtest.c $(am__test_bdaddr_SOURCES_DIST) \ + $(am__test_btiotest_SOURCES_DIST) test/gaptest.c test/hciemu.c \ + test/hstest.c $(am__test_ipctest_SOURCES_DIST) test/l2test.c \ + test/lmptest.c test/rctest.c test/scotest.c test/sdptest.c \ + $(am__test_test_textfile_SOURCES_DIST) \ + $(am__test_uuidtest_SOURCES_DIST) tools/avctrl.c \ + tools/avinfo.c $(am__tools_bccmd_SOURCES_DIST) tools/ciptool.c \ + tools/dfubabel.c $(am__tools_dfutool_SOURCES_DIST) \ + $(am__tools_hciattach_SOURCES_DIST) \ + $(am__tools_hciconfig_SOURCES_DIST) tools/hcieventmask.c \ + tools/hcisecfilter.c $(am__tools_hcitool_SOURCES_DIST) \ + tools/hid2hci.c tools/l2ping.c tools/ppporc.c \ + $(am__tools_rfcomm_SOURCES_DIST) \ + $(am__EXTRA_tools_rfcomm_SOURCES_DIST) \ + $(am__tools_sdptool_SOURCES_DIST) \ + $(am__tracer_hcitrace_SOURCES_DIST) +man1dir = $(mandir)/man1 +man8dir = $(mandir)/man8 +NROFF = nroff +MANS = $(dist_man_MANS) $(man_MANS) +DATA = $(alsaconf_DATA) $(conf_DATA) $(dbus_DATA) $(pkgconfig_DATA) \ + $(rules_DATA) $(state_DATA) +HEADERS = $(include_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +distdir = $(PACKAGE)-$(VERSION) +top_distdir = $(distdir) +am__remove_distdir = \ + { test ! -d "$(distdir)" \ + || { find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -fr "$(distdir)"; }; } +DIST_ARCHIVES = $(distdir).tar.gz +GZIP_ENV = --best +distuninstallcheck_listfiles = find . -type f -print +distcleancheck_listfiles = find . -type f -print +ACLOCAL = @ACLOCAL@ +ALSA_CFLAGS = @ALSA_CFLAGS@ +ALSA_LIBS = @ALSA_LIBS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CAPNG_CFLAGS = @CAPNG_CFLAGS@ +CAPNG_LIBS = @CAPNG_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONFIGDIR = @CONFIGDIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_LIBS = @DBUS_LIBS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_LIBS = @GLIB_LIBS@ +GREP = @GREP@ +GSTREAMER_CFLAGS = @GSTREAMER_CFLAGS@ +GSTREAMER_LIBS = @GSTREAMER_LIBS@ +GSTREAMER_PLUGINSDIR = @GSTREAMER_PLUGINSDIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SAP_DRIVER = @SAP_DRIVER@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SNDFILE_CFLAGS = @SNDFILE_CFLAGS@ +SNDFILE_LIBS = @SNDFILE_LIBS@ +STORAGEDIR = @STORAGEDIR@ +STRIP = @STRIP@ +TELEPHONY_DRIVER = @TELEPHONY_DRIVER@ +UDEV_DATADIR = @UDEV_DATADIR@ +USB_CFLAGS = @USB_CFLAGS@ +USB_LIBS = @USB_LIBS@ +VERSION = @VERSION@ +YACC = @YACC@ +YFLAGS = @YFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@/bluetooth +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +lt_ECHO = @lt_ECHO@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_MAKEFLAGS = --no-print-directory +lib_LTLIBRARIES = lib/libbluetooth.la +noinst_LTLIBRARIES = $(am__append_1) +dist_man_MANS = $(am__append_40) $(am__append_46) $(am__append_49) \ + $(am__append_52) $(am__append_58) $(am__append_62) \ + $(am__append_65) $(am__append_68) +dist_noinst_MANS = +CLEANFILES = $(local_headers) $(builtin_files) tools/lexer.c \ + tools/parser.c tools/parser.h $(rules_DATA) +EXTRA_DIST = plugins/hal.c plugins/formfactor.c src/genbuiltin \ + src/bluetooth.conf src/main.conf network/network.conf \ + input/input.conf serial/serial.conf audio/audio.conf \ + audio/telephony-dummy.c audio/telephony-maemo5.c \ + audio/telephony-ofono.c audio/telephony-maemo6.c \ + sap/sap-dummy.c audio/bluetooth.conf $(am__append_41) \ + tools/rfcomm.conf $(am__append_47) $(am__append_50) \ + $(am__append_53) tools/dfubabel.1 tools/avctrl.8 \ + $(am__append_59) $(am__append_60) test/apitest test/sap-client \ + test/hsplay test/hsmicro test/dbusdef.py \ + test/monitor-bluetooth test/list-devices test/test-discovery \ + test/test-manager test/test-adapter test/test-device \ + test/test-service test/test-serial test/test-telephony \ + test/test-network test/simple-agent test/simple-service \ + test/simple-endpoint test/test-audio test/test-input \ + test/test-attrib test/test-sap-server test/service-record.dtd \ + test/service-did.xml test/service-spp.xml test/service-opp.xml \ + test/service-ftp.xml $(am__append_63) $(am__append_66) \ + $(am__append_69) scripts/bluetooth.rules \ + scripts/bluetooth-hid2hci.rules scripts/bluetooth-serial.rules \ + doc/manager-api.txt doc/adapter-api.txt doc/device-api.txt \ + doc/service-api.txt doc/agent-api.txt doc/attribute-api.txt \ + doc/serial-api.txt doc/network-api.txt doc/input-api.txt \ + doc/audio-api.txt doc/control-api.txt doc/hfp-api.txt \ + doc/health-api.txt doc/sap-api.txt doc/media-api.txt \ + doc/assigned-numbers.txt +include_HEADERS = $(lib_headers) +@CONFIGFILES_TRUE@dbusdir = $(sysconfdir)/dbus-1/system.d +@CONFIGFILES_TRUE@dbus_DATA = src/bluetooth.conf +@CONFIGFILES_TRUE@confdir = $(sysconfdir)/bluetooth +@CONFIGFILES_TRUE@conf_DATA = src/main.conf $(am__append_36) +@CONFIGFILES_TRUE@statedir = $(localstatedir)/lib/bluetooth +@CONFIGFILES_TRUE@state_DATA = +plugindir = $(libdir)/bluetooth/plugins +plugin_LTLIBRARIES = +lib_headers = lib/bluetooth.h lib/hci.h lib/hci_lib.h lib/mgmt.h \ + lib/sco.h lib/l2cap.h lib/sdp.h lib/sdp_lib.h lib/uuid.h \ + lib/rfcomm.h lib/bnep.h lib/cmtp.h lib/hidp.h + +local_headers = $(foreach file,$(lib_headers), lib/bluetooth/$(notdir $(file))) +lib_libbluetooth_la_SOURCES = $(lib_headers) \ + lib/bluetooth.c lib/hci.c lib/sdp.c lib/uuid.c + +lib_libbluetooth_la_LDFLAGS = -version-info 14:0:11 +lib_libbluetooth_la_DEPENDENCIES = $(local_headers) +@SBC_TRUE@sbc_libsbc_la_SOURCES = sbc/sbc.h sbc/sbc.c sbc/sbc_math.h sbc/sbc_tables.h \ +@SBC_TRUE@ sbc/sbc_primitives.h sbc/sbc_primitives.c \ +@SBC_TRUE@ sbc/sbc_primitives_mmx.h sbc/sbc_primitives_mmx.c \ +@SBC_TRUE@ sbc/sbc_primitives_iwmmxt.h sbc/sbc_primitives_iwmmxt.c \ +@SBC_TRUE@ sbc/sbc_primitives_neon.h sbc/sbc_primitives_neon.c \ +@SBC_TRUE@ sbc/sbc_primitives_armv6.h sbc/sbc_primitives_armv6.c + +@SBC_TRUE@sbc_libsbc_la_CFLAGS = -finline-functions -fgcse-after-reload \ +@SBC_TRUE@ -funswitch-loops -funroll-loops + +@SBC_TRUE@sbc_sbcdec_SOURCES = sbc/sbcdec.c sbc/formats.h +@SBC_TRUE@sbc_sbcdec_LDADD = sbc/libsbc.la +@SBC_TRUE@sbc_sbcenc_SOURCES = sbc/sbcenc.c sbc/formats.h +@SBC_TRUE@sbc_sbcenc_LDADD = sbc/libsbc.la +@SBC_TRUE@@SNDFILE_TRUE@sbc_sbctester_LDADD = @SNDFILE_LIBS@ -lm +@SBC_TRUE@@SNDFILE_TRUE@sbc_sbctest_CFLAGS = @SNDFILE_CFLAGS@ +attrib_sources = attrib/att.h attrib/att.c attrib/gatt.h attrib/gatt.c \ + attrib/gattrib.h attrib/gattrib.c + +gdbus_sources = gdbus/gdbus.h gdbus/mainloop.c gdbus/watch.c \ + gdbus/object.c gdbus/polkit.c + +btio_sources = btio/btio.h btio/btio.c +builtin_modules = $(am__append_5) $(am__append_7) $(am__append_9) \ + $(am__append_12) $(am__append_15) $(am__append_17) \ + $(am__append_19) $(am__append_21) $(am__append_24) \ + $(am__append_26) hciops mgmtops $(am__append_28) \ + $(am__append_30) storage $(am__append_32) $(am__append_34) +builtin_sources = $(am__append_6) $(am__append_8) $(am__append_10) \ + $(am__append_13) $(am__append_16) $(am__append_18) \ + $(am__append_20) $(am__append_22) $(am__append_25) \ + $(am__append_27) plugins/hciops.c plugins/mgmtops.c \ + $(am__append_29) $(am__append_31) plugins/storage.c \ + $(am__append_33) $(am__append_35) +builtin_nodist = $(am__append_11) $(am__append_14) +mcap_sources = $(am__append_4) +@AUDIOPLUGIN_TRUE@noinst_LIBRARIES = audio/libtelephony.a +@SAPPLUGIN_TRUE@noinst_LIBRARIES = sap/libsap.a +@AUDIOPLUGIN_TRUE@audio_libtelephony_a_SOURCES = audio/telephony.h audio/telephony-dummy.c \ +@AUDIOPLUGIN_TRUE@ audio/telephony-maemo5.c audio/telephony-ofono.c \ +@AUDIOPLUGIN_TRUE@ audio/telephony-maemo6.c + +@SAPPLUGIN_TRUE@sap_libsap_a_SOURCES = sap/sap.h sap/sap-dummy.c +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/gattrib.c btio/btio.c \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ src/glib-helper.h src/glib-helper.c \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/gatttool.h attrib/interactive.c \ +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@ attrib/utils.c + +@ATTRIBPLUGIN_TRUE@@READLINE_TRUE@attrib_gatttool_LDADD = lib/libbluetooth.la @GLIB_LIBS@ @READLINE_LIBS@ +src_bluetoothd_SOURCES = $(gdbus_sources) $(builtin_sources) \ + $(attrib_sources) $(btio_sources) \ + $(mcap_sources) src/bluetooth.ver \ + src/main.c src/log.h src/log.c \ + src/rfkill.c src/hcid.h src/sdpd.h \ + src/sdpd-server.c src/sdpd-request.c \ + src/sdpd-service.c src/sdpd-database.c \ + src/attrib-server.h src/attrib-server.c \ + src/sdp-xml.h src/sdp-xml.c \ + src/textfile.h src/textfile.c \ + src/glib-helper.h src/glib-helper.c \ + src/oui.h src/oui.c src/uinput.h src/ppoll.h \ + src/plugin.h src/plugin.c \ + src/storage.h src/storage.c \ + src/agent.h src/agent.c \ + src/error.h src/error.c \ + src/manager.h src/manager.c \ + src/adapter.h src/adapter.c \ + src/device.h src/device.c \ + src/dbus-common.c src/dbus-common.h \ + src/event.h src/event.c \ + src/oob.h src/oob.c + +src_bluetoothd_LDADD = lib/libbluetooth.la @GLIB_LIBS@ @DBUS_LIBS@ \ + @CAPNG_LIBS@ -ldl -lrt + +src_bluetoothd_LDFLAGS = -Wl,--export-dynamic \ + -Wl,--version-script=$(srcdir)/src/bluetooth.ver + +src_bluetoothd_DEPENDENCIES = lib/libbluetooth.la +builtin_files = src/builtin.h $(builtin_nodist) +nodist_src_bluetoothd_SOURCES = $(builtin_files) +man_MANS = src/bluetoothd.8 +@ALSA_TRUE@alsadir = $(libdir)/alsa-lib +@ALSA_TRUE@alsa_LTLIBRARIES = audio/libasound_module_pcm_bluetooth.la \ +@ALSA_TRUE@ audio/libasound_module_ctl_bluetooth.la + +@ALSA_TRUE@audio_libasound_module_pcm_bluetooth_la_SOURCES = audio/pcm_bluetooth.c \ +@ALSA_TRUE@ audio/rtp.h audio/ipc.h audio/ipc.c + +@ALSA_TRUE@audio_libasound_module_pcm_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_pcm_.* +@ALSA_TRUE@audio_libasound_module_pcm_bluetooth_la_LIBADD = sbc/libsbc.la \ +@ALSA_TRUE@ lib/libbluetooth.la @ALSA_LIBS@ + +@ALSA_TRUE@audio_libasound_module_pcm_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ +@ALSA_TRUE@audio_libasound_module_ctl_bluetooth_la_SOURCES = audio/ctl_bluetooth.c \ +@ALSA_TRUE@ audio/rtp.h audio/ipc.h audio/ipc.c + +@ALSA_TRUE@audio_libasound_module_ctl_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_ctl_.* +@ALSA_TRUE@audio_libasound_module_ctl_bluetooth_la_LIBADD = lib/libbluetooth.la @ALSA_LIBS@ +@ALSA_TRUE@audio_libasound_module_ctl_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ +@ALSA_TRUE@@CONFIGFILES_TRUE@alsaconfdir = $(datadir)/alsa +@ALSA_TRUE@@CONFIGFILES_TRUE@alsaconf_DATA = audio/bluetooth.conf +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@gstreamerdir = $(libdir)/gstreamer-0.10 +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@gstreamer_LTLIBRARIES = audio/libgstbluetooth.la +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@audio_libgstbluetooth_la_SOURCES = audio/gstbluetooth.c audio/gstpragma.h \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/gstsbcenc.h audio/gstsbcenc.c \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/gstsbcdec.h audio/gstsbcdec.c \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/gstsbcparse.h audio/gstsbcparse.c \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/gstavdtpsink.h audio/gstavdtpsink.c \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/gsta2dpsink.h audio/gsta2dpsink.c \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/gstsbcutil.h audio/gstsbcutil.c \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/gstrtpsbcpay.h audio/gstrtpsbcpay.c \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ audio/rtp.h audio/ipc.h audio/ipc.c + +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@audio_libgstbluetooth_la_LDFLAGS = -module -avoid-version +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@audio_libgstbluetooth_la_LIBADD = sbc/libsbc.la lib/libbluetooth.la \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ @DBUS_LIBS@ @GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10 + +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@audio_libgstbluetooth_la_CFLAGS = -fvisibility=hidden -fno-strict-aliasing \ +@AUDIOPLUGIN_TRUE@@GSTREAMER_TRUE@ $(AM_CFLAGS) @DBUS_CFLAGS@ @GSTREAMER_CFLAGS@ + +@TOOLS_TRUE@tools_rfcomm_SOURCES = tools/rfcomm.c tools/parser.y tools/lexer.l \ +@TOOLS_TRUE@ tools/kword.h tools/kword.c + +@TOOLS_TRUE@EXTRA_tools_rfcomm_SOURCES = tools/parser.h tools/parser.c \ +@TOOLS_TRUE@ tools/lexer.c + +@TOOLS_TRUE@tools_rfcomm_LDADD = lib/libbluetooth.la +@TOOLS_TRUE@tools_l2ping_LDADD = lib/libbluetooth.la +@TOOLS_TRUE@tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \ +@TOOLS_TRUE@ tools/hciattach_st.c \ +@TOOLS_TRUE@ tools/hciattach_ti.c \ +@TOOLS_TRUE@ tools/hciattach_tialt.c \ +@TOOLS_TRUE@ tools/hciattach_ath3k.c \ +@TOOLS_TRUE@ tools/hciattach_qualcomm.c + +@TOOLS_TRUE@tools_hciattach_LDADD = lib/libbluetooth.la +@TOOLS_TRUE@tools_hciconfig_SOURCES = tools/hciconfig.c tools/csr.h tools/csr.c \ +@TOOLS_TRUE@ src/textfile.h src/textfile.c + +@TOOLS_TRUE@tools_hciconfig_LDADD = lib/libbluetooth.la +@TOOLS_TRUE@tools_hcitool_SOURCES = tools/hcitool.c src/oui.h src/oui.c \ +@TOOLS_TRUE@ src/textfile.h src/textfile.c + +@TOOLS_TRUE@tools_hcitool_LDADD = lib/libbluetooth.la +@TOOLS_TRUE@tools_sdptool_SOURCES = tools/sdptool.c src/sdp-xml.h src/sdp-xml.c +@TOOLS_TRUE@tools_sdptool_LDADD = lib/libbluetooth.la +@TOOLS_TRUE@tools_ciptool_LDADD = lib/libbluetooth.la +@TOOLS_TRUE@tools_avinfo_LDADD = lib/libbluetooth.la +@TOOLS_TRUE@tools_ppporc_LDADD = lib/libbluetooth.la +@TOOLS_TRUE@tools_hcieventmask_LDADD = lib/libbluetooth.la +@TRACER_TRUE@tracer_hcitrace_SOURCES = tracer/main.c +@TRACER_TRUE@tracer_hcitrace_LDADD = lib/libbluetooth.la \ +@TRACER_TRUE@ @GLIB_LIBS@ @DBUS_LIBS@ @CAPNG_LIBS@ + +@TRACER_TRUE@tracer_hcitrace_DEPENDENCIES = lib/libbluetooth.la +@BCCMD_TRUE@tools_bccmd_SOURCES = tools/bccmd.c tools/csr.h \ +@BCCMD_TRUE@ tools/csr.c tools/csr_hci.c tools/csr_h4.c \ +@BCCMD_TRUE@ tools/csr_3wire.c tools/csr_bcsp.c tools/ubcsp.h \ +@BCCMD_TRUE@ tools/ubcsp.c $(am__append_44) +@BCCMD_TRUE@tools_bccmd_LDADD = lib/libbluetooth.la $(am__append_45) +@HID2HCI_TRUE@tools_hid2hci_LDADD = @USB_LIBS@ +@DFUTOOL_TRUE@tools_dfutool_SOURCES = tools/dfutool.c tools/dfu.h tools/dfu.c +@DFUTOOL_TRUE@tools_dfutool_LDADD = @USB_LIBS@ +@USB_TRUE@tools_dfubabel_LDADD = @USB_LIBS@ +@USB_TRUE@tools_avctrl_LDADD = @USB_LIBS@ +@CUPS_TRUE@cupsdir = $(libdir)/cups/backend +@CUPS_TRUE@cups_bluetooth_SOURCES = $(gdbus_sources) cups/main.c cups/cups.h \ +@CUPS_TRUE@ cups/sdp.c cups/spp.c cups/hcrp.c + +@CUPS_TRUE@cups_bluetooth_LDADD = @GLIB_LIBS@ @DBUS_LIBS@ lib/libbluetooth.la +@TEST_TRUE@test_hciemu_LDADD = @GLIB_LIBS@ lib/libbluetooth.la +@TEST_TRUE@test_l2test_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_rctest_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_gaptest_LDADD = @DBUS_LIBS@ +@TEST_TRUE@test_sdptest_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_scotest_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_attest_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_hstest_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_avtest_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_lmptest_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_ipctest_SOURCES = test/ipctest.c audio/ipc.h audio/ipc.c +@TEST_TRUE@test_ipctest_LDADD = @GLIB_LIBS@ sbc/libsbc.la +@TEST_TRUE@test_bdaddr_SOURCES = test/bdaddr.c src/oui.h src/oui.c +@TEST_TRUE@test_bdaddr_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_agent_LDADD = @DBUS_LIBS@ +@TEST_TRUE@test_btiotest_SOURCES = test/btiotest.c btio/btio.h btio/btio.c +@TEST_TRUE@test_btiotest_LDADD = @GLIB_LIBS@ lib/libbluetooth.la +@TEST_TRUE@test_uuidtest_SOURCES = test/uuidtest.c +@TEST_TRUE@test_uuidtest_LDADD = lib/libbluetooth.la +@TEST_TRUE@test_test_textfile_SOURCES = test/test-textfile.c src/textfile.h src/textfile.c +@HIDD_TRUE@compat_hidd_SOURCES = compat/hidd.c compat/hidd.h src/uinput.h \ +@HIDD_TRUE@ compat/sdp.h compat/sdp.c compat/fakehid.c \ +@HIDD_TRUE@ src/textfile.h src/textfile.c + +@HIDD_TRUE@compat_hidd_LDADD = -lm lib/libbluetooth.la +@PAND_TRUE@compat_pand_SOURCES = compat/pand.c compat/pand.h \ +@PAND_TRUE@ compat/bnep.c compat/sdp.h compat/sdp.c \ +@PAND_TRUE@ src/textfile.h src/textfile.c + +@PAND_TRUE@compat_pand_LDADD = lib/libbluetooth.la +@DUND_TRUE@compat_dund_SOURCES = compat/dund.c compat/dund.h compat/lib.h \ +@DUND_TRUE@ compat/sdp.h compat/sdp.c compat/dun.c compat/msdun.c \ +@DUND_TRUE@ src/textfile.h src/textfile.c + +@DUND_TRUE@compat_dund_LDADD = lib/libbluetooth.la +@UDEVRULES_TRUE@rulesdir = @UDEV_DATADIR@ +@UDEVRULES_TRUE@udev_files = scripts/bluetooth.rules $(am__append_70) \ +@UDEVRULES_TRUE@ $(am__append_71) +@UDEVRULES_TRUE@rules_DATA = $(foreach file,$(udev_files), scripts/97-$(notdir $(file))) +@PCMCIA_TRUE@udevdir = $(libexecdir)/udev +@PCMCIA_TRUE@dist_udev_SCRIPTS = scripts/bluetooth_serial +AM_YFLAGS = -d +AM_CFLAGS = @DBUS_CFLAGS@ @GLIB_CFLAGS@ @CAPNG_CFLAGS@ \ + -DBLUETOOTH_PLUGIN_BUILTIN -DPLUGINDIR=\""$(plugindir)"\" + +INCLUDES = -I$(builddir)/lib -I$(builddir)/src -I$(srcdir)/src \ + -I$(srcdir)/audio -I$(srcdir)/sbc -I$(srcdir)/gdbus \ + -I$(srcdir)/attrib -I$(srcdir)/btio $(am__append_72) +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = bluez.pc +DISTCHECK_CONFIGURE_FLAGS = --disable-udevrules --enable-attrib +DISTCLEANFILES = $(pkgconfig_DATA) +MAINTAINERCLEANFILES = Makefile.in \ + aclocal.m4 configure config.h.in config.sub config.guess \ + ltmain.sh depcomp compile missing install-sh mkinstalldirs ylwrap + +all: config.h + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .l .lo .o .obj .y +am--refresh: + @: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(srcdir)/Makefile.tools $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ + $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + echo ' $(SHELL) ./config.status'; \ + $(SHELL) ./config.status;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + $(SHELL) ./config.status --recheck + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + $(am__cd) $(srcdir) && $(AUTOCONF) +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) +$(am__aclocal_m4_deps): + +config.h: stamp-h1 + @if test ! -f $@; then \ + rm -f stamp-h1; \ + $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \ + else :; fi + +stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status + @rm -f stamp-h1 + cd $(top_builddir) && $(SHELL) ./config.status config.h +$(srcdir)/config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) + rm -f stamp-h1 + touch $@ + +distclean-hdr: + -rm -f config.h stamp-h1 +scripts/bluetooth.rules: $(top_builddir)/config.status $(top_srcdir)/scripts/bluetooth.rules.in + cd $(top_builddir) && $(SHELL) ./config.status $@ +doc/version.xml: $(top_builddir)/config.status $(top_srcdir)/doc/version.xml.in + cd $(top_builddir) && $(SHELL) ./config.status $@ +src/bluetoothd.8: $(top_builddir)/config.status $(top_srcdir)/src/bluetoothd.8.in + cd $(top_builddir) && $(SHELL) ./config.status $@ +bluez.pc: $(top_builddir)/config.status $(srcdir)/bluez.pc.in + cd $(top_builddir) && $(SHELL) ./config.status $@ + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) +audio/$(am__dirstamp): + @$(MKDIR_P) audio + @: > audio/$(am__dirstamp) +audio/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) audio/$(DEPDIR) + @: > audio/$(DEPDIR)/$(am__dirstamp) +audio/telephony-dummy.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/telephony-maemo5.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/telephony-ofono.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/telephony-maemo6.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/libtelephony.a: $(audio_libtelephony_a_OBJECTS) $(audio_libtelephony_a_DEPENDENCIES) audio/$(am__dirstamp) + $(AM_V_at)-rm -f audio/libtelephony.a + $(AM_V_AR)$(audio_libtelephony_a_AR) audio/libtelephony.a $(audio_libtelephony_a_OBJECTS) $(audio_libtelephony_a_LIBADD) + $(AM_V_at)$(RANLIB) audio/libtelephony.a +sap/$(am__dirstamp): + @$(MKDIR_P) sap + @: > sap/$(am__dirstamp) +sap/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) sap/$(DEPDIR) + @: > sap/$(DEPDIR)/$(am__dirstamp) +sap/sap-dummy.$(OBJEXT): sap/$(am__dirstamp) \ + sap/$(DEPDIR)/$(am__dirstamp) +sap/libsap.a: $(sap_libsap_a_OBJECTS) $(sap_libsap_a_DEPENDENCIES) sap/$(am__dirstamp) + $(AM_V_at)-rm -f sap/libsap.a + $(AM_V_AR)$(sap_libsap_a_AR) sap/libsap.a $(sap_libsap_a_OBJECTS) $(sap_libsap_a_LIBADD) + $(AM_V_at)$(RANLIB) sap/libsap.a +install-alsaLTLIBRARIES: $(alsa_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(alsadir)" || $(MKDIR_P) "$(DESTDIR)$(alsadir)" + @list='$(alsa_LTLIBRARIES)'; test -n "$(alsadir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(alsadir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(alsadir)"; \ + } + +uninstall-alsaLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(alsa_LTLIBRARIES)'; test -n "$(alsadir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(alsadir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(alsadir)/$$f"; \ + done + +clean-alsaLTLIBRARIES: + -test -z "$(alsa_LTLIBRARIES)" || rm -f $(alsa_LTLIBRARIES) + @list='$(alsa_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +install-gstreamerLTLIBRARIES: $(gstreamer_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(gstreamerdir)" || $(MKDIR_P) "$(DESTDIR)$(gstreamerdir)" + @list='$(gstreamer_LTLIBRARIES)'; test -n "$(gstreamerdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(gstreamerdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(gstreamerdir)"; \ + } + +uninstall-gstreamerLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(gstreamer_LTLIBRARIES)'; test -n "$(gstreamerdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(gstreamerdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(gstreamerdir)/$$f"; \ + done + +clean-gstreamerLTLIBRARIES: + -test -z "$(gstreamer_LTLIBRARIES)" || rm -f $(gstreamer_LTLIBRARIES) + @list='$(gstreamer_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)" + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +install-pluginLTLIBRARIES: $(plugin_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(plugindir)" || $(MKDIR_P) "$(DESTDIR)$(plugindir)" + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(plugindir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(plugindir)"; \ + } + +uninstall-pluginLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(plugindir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(plugindir)/$$f"; \ + done + +clean-pluginLTLIBRARIES: + -test -z "$(plugin_LTLIBRARIES)" || rm -f $(plugin_LTLIBRARIES) + @list='$(plugin_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +audio/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.lo: \ + audio/$(am__dirstamp) audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libasound_module_ctl_bluetooth_la-ipc.lo: \ + audio/$(am__dirstamp) audio/$(DEPDIR)/$(am__dirstamp) +audio/libasound_module_ctl_bluetooth.la: $(audio_libasound_module_ctl_bluetooth_la_OBJECTS) $(audio_libasound_module_ctl_bluetooth_la_DEPENDENCIES) audio/$(am__dirstamp) + $(AM_V_CCLD)$(audio_libasound_module_ctl_bluetooth_la_LINK) $(am_audio_libasound_module_ctl_bluetooth_la_rpath) $(audio_libasound_module_ctl_bluetooth_la_OBJECTS) $(audio_libasound_module_ctl_bluetooth_la_LIBADD) $(LIBS) +audio/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.lo: \ + audio/$(am__dirstamp) audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libasound_module_pcm_bluetooth_la-ipc.lo: \ + audio/$(am__dirstamp) audio/$(DEPDIR)/$(am__dirstamp) +audio/libasound_module_pcm_bluetooth.la: $(audio_libasound_module_pcm_bluetooth_la_OBJECTS) $(audio_libasound_module_pcm_bluetooth_la_DEPENDENCIES) audio/$(am__dirstamp) + $(AM_V_CCLD)$(audio_libasound_module_pcm_bluetooth_la_LINK) $(am_audio_libasound_module_pcm_bluetooth_la_rpath) $(audio_libasound_module_pcm_bluetooth_la_OBJECTS) $(audio_libasound_module_pcm_bluetooth_la_LIBADD) $(LIBS) +audio/audio_libgstbluetooth_la-gstbluetooth.lo: audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libgstbluetooth_la-gstsbcenc.lo: audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libgstbluetooth_la-gstsbcdec.lo: audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libgstbluetooth_la-gstsbcparse.lo: audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libgstbluetooth_la-gstavdtpsink.lo: audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libgstbluetooth_la-gsta2dpsink.lo: audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libgstbluetooth_la-gstsbcutil.lo: audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libgstbluetooth_la-gstrtpsbcpay.lo: audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/audio_libgstbluetooth_la-ipc.lo: audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/libgstbluetooth.la: $(audio_libgstbluetooth_la_OBJECTS) $(audio_libgstbluetooth_la_DEPENDENCIES) audio/$(am__dirstamp) + $(AM_V_CCLD)$(audio_libgstbluetooth_la_LINK) $(am_audio_libgstbluetooth_la_rpath) $(audio_libgstbluetooth_la_OBJECTS) $(audio_libgstbluetooth_la_LIBADD) $(LIBS) +lib/$(am__dirstamp): + @$(MKDIR_P) lib + @: > lib/$(am__dirstamp) +lib/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) lib/$(DEPDIR) + @: > lib/$(DEPDIR)/$(am__dirstamp) +lib/bluetooth.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) +lib/hci.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) +lib/sdp.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) +lib/uuid.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) +lib/libbluetooth.la: $(lib_libbluetooth_la_OBJECTS) $(lib_libbluetooth_la_DEPENDENCIES) lib/$(am__dirstamp) + $(AM_V_CCLD)$(lib_libbluetooth_la_LINK) -rpath $(libdir) $(lib_libbluetooth_la_OBJECTS) $(lib_libbluetooth_la_LIBADD) $(LIBS) +sbc/$(am__dirstamp): + @$(MKDIR_P) sbc + @: > sbc/$(am__dirstamp) +sbc/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) sbc/$(DEPDIR) + @: > sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbc_libsbc_la-sbc.lo: sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbc_libsbc_la-sbc_primitives.lo: sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbc_libsbc_la-sbc_primitives_mmx.lo: sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbc_libsbc_la-sbc_primitives_iwmmxt.lo: sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbc_libsbc_la-sbc_primitives_neon.lo: sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbc_libsbc_la-sbc_primitives_armv6.lo: sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/libsbc.la: $(sbc_libsbc_la_OBJECTS) $(sbc_libsbc_la_DEPENDENCIES) sbc/$(am__dirstamp) + $(AM_V_CCLD)$(sbc_libsbc_la_LINK) $(am_sbc_libsbc_la_rpath) $(sbc_libsbc_la_OBJECTS) $(sbc_libsbc_la_LIBADD) $(LIBS) +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p || test -f $$p1; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-cupsPROGRAMS: $(cups_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(cupsdir)" || $(MKDIR_P) "$(DESTDIR)$(cupsdir)" + @list='$(cups_PROGRAMS)'; test -n "$(cupsdir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p || test -f $$p1; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(cupsdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(cupsdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-cupsPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(cups_PROGRAMS)'; test -n "$(cupsdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(cupsdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(cupsdir)" && rm -f $$files + +clean-cupsPROGRAMS: + @list='$(cups_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-sbinPROGRAMS: $(sbin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(sbindir)" || $(MKDIR_P) "$(DESTDIR)$(sbindir)" + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p || test -f $$p1; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sbinPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sbindir)" && rm -f $$files + +clean-sbinPROGRAMS: + @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +attrib/$(am__dirstamp): + @$(MKDIR_P) attrib + @: > attrib/$(am__dirstamp) +attrib/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) attrib/$(DEPDIR) + @: > attrib/$(DEPDIR)/$(am__dirstamp) +attrib/gatttool.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/att.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/gatt.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/gattrib.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +btio/$(am__dirstamp): + @$(MKDIR_P) btio + @: > btio/$(am__dirstamp) +btio/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) btio/$(DEPDIR) + @: > btio/$(DEPDIR)/$(am__dirstamp) +btio/btio.$(OBJEXT): btio/$(am__dirstamp) \ + btio/$(DEPDIR)/$(am__dirstamp) +src/$(am__dirstamp): + @$(MKDIR_P) src + @: > src/$(am__dirstamp) +src/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/$(DEPDIR) + @: > src/$(DEPDIR)/$(am__dirstamp) +src/glib-helper.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +attrib/interactive.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/utils.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/gatttool$(EXEEXT): $(attrib_gatttool_OBJECTS) $(attrib_gatttool_DEPENDENCIES) attrib/$(am__dirstamp) + @rm -f attrib/gatttool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(attrib_gatttool_OBJECTS) $(attrib_gatttool_LDADD) $(LIBS) +compat/$(am__dirstamp): + @$(MKDIR_P) compat + @: > compat/$(am__dirstamp) +compat/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) compat/$(DEPDIR) + @: > compat/$(DEPDIR)/$(am__dirstamp) +compat/dund.$(OBJEXT): compat/$(am__dirstamp) \ + compat/$(DEPDIR)/$(am__dirstamp) +compat/sdp.$(OBJEXT): compat/$(am__dirstamp) \ + compat/$(DEPDIR)/$(am__dirstamp) +compat/dun.$(OBJEXT): compat/$(am__dirstamp) \ + compat/$(DEPDIR)/$(am__dirstamp) +compat/msdun.$(OBJEXT): compat/$(am__dirstamp) \ + compat/$(DEPDIR)/$(am__dirstamp) +src/textfile.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +compat/dund$(EXEEXT): $(compat_dund_OBJECTS) $(compat_dund_DEPENDENCIES) compat/$(am__dirstamp) + @rm -f compat/dund$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(compat_dund_OBJECTS) $(compat_dund_LDADD) $(LIBS) +compat/hidd.$(OBJEXT): compat/$(am__dirstamp) \ + compat/$(DEPDIR)/$(am__dirstamp) +compat/fakehid.$(OBJEXT): compat/$(am__dirstamp) \ + compat/$(DEPDIR)/$(am__dirstamp) +compat/hidd$(EXEEXT): $(compat_hidd_OBJECTS) $(compat_hidd_DEPENDENCIES) compat/$(am__dirstamp) + @rm -f compat/hidd$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(compat_hidd_OBJECTS) $(compat_hidd_LDADD) $(LIBS) +compat/pand.$(OBJEXT): compat/$(am__dirstamp) \ + compat/$(DEPDIR)/$(am__dirstamp) +compat/bnep.$(OBJEXT): compat/$(am__dirstamp) \ + compat/$(DEPDIR)/$(am__dirstamp) +compat/pand$(EXEEXT): $(compat_pand_OBJECTS) $(compat_pand_DEPENDENCIES) compat/$(am__dirstamp) + @rm -f compat/pand$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(compat_pand_OBJECTS) $(compat_pand_LDADD) $(LIBS) +gdbus/$(am__dirstamp): + @$(MKDIR_P) gdbus + @: > gdbus/$(am__dirstamp) +gdbus/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) gdbus/$(DEPDIR) + @: > gdbus/$(DEPDIR)/$(am__dirstamp) +gdbus/mainloop.$(OBJEXT): gdbus/$(am__dirstamp) \ + gdbus/$(DEPDIR)/$(am__dirstamp) +gdbus/watch.$(OBJEXT): gdbus/$(am__dirstamp) \ + gdbus/$(DEPDIR)/$(am__dirstamp) +gdbus/object.$(OBJEXT): gdbus/$(am__dirstamp) \ + gdbus/$(DEPDIR)/$(am__dirstamp) +gdbus/polkit.$(OBJEXT): gdbus/$(am__dirstamp) \ + gdbus/$(DEPDIR)/$(am__dirstamp) +cups/$(am__dirstamp): + @$(MKDIR_P) cups + @: > cups/$(am__dirstamp) +cups/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) cups/$(DEPDIR) + @: > cups/$(DEPDIR)/$(am__dirstamp) +cups/main.$(OBJEXT): cups/$(am__dirstamp) \ + cups/$(DEPDIR)/$(am__dirstamp) +cups/sdp.$(OBJEXT): cups/$(am__dirstamp) \ + cups/$(DEPDIR)/$(am__dirstamp) +cups/spp.$(OBJEXT): cups/$(am__dirstamp) \ + cups/$(DEPDIR)/$(am__dirstamp) +cups/hcrp.$(OBJEXT): cups/$(am__dirstamp) \ + cups/$(DEPDIR)/$(am__dirstamp) +cups/bluetooth$(EXEEXT): $(cups_bluetooth_OBJECTS) $(cups_bluetooth_DEPENDENCIES) cups/$(am__dirstamp) + @rm -f cups/bluetooth$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(cups_bluetooth_OBJECTS) $(cups_bluetooth_LDADD) $(LIBS) +sbc/sbcdec.$(OBJEXT): sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbcdec$(EXEEXT): $(sbc_sbcdec_OBJECTS) $(sbc_sbcdec_DEPENDENCIES) sbc/$(am__dirstamp) + @rm -f sbc/sbcdec$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sbc_sbcdec_OBJECTS) $(sbc_sbcdec_LDADD) $(LIBS) +sbc/sbcenc.$(OBJEXT): sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbcenc$(EXEEXT): $(sbc_sbcenc_OBJECTS) $(sbc_sbcenc_DEPENDENCIES) sbc/$(am__dirstamp) + @rm -f sbc/sbcenc$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sbc_sbcenc_OBJECTS) $(sbc_sbcenc_LDADD) $(LIBS) +sbc/sbcinfo.$(OBJEXT): sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbcinfo$(EXEEXT): $(sbc_sbcinfo_OBJECTS) $(sbc_sbcinfo_DEPENDENCIES) sbc/$(am__dirstamp) + @rm -f sbc/sbcinfo$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sbc_sbcinfo_OBJECTS) $(sbc_sbcinfo_LDADD) $(LIBS) +sbc/sbctester.$(OBJEXT): sbc/$(am__dirstamp) \ + sbc/$(DEPDIR)/$(am__dirstamp) +sbc/sbctester$(EXEEXT): $(sbc_sbctester_OBJECTS) $(sbc_sbctester_DEPENDENCIES) sbc/$(am__dirstamp) + @rm -f sbc/sbctester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sbc_sbctester_OBJECTS) $(sbc_sbctester_LDADD) $(LIBS) +plugins/$(am__dirstamp): + @$(MKDIR_P) plugins + @: > plugins/$(am__dirstamp) +plugins/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) plugins/$(DEPDIR) + @: > plugins/$(DEPDIR)/$(am__dirstamp) +plugins/pnat.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/echo.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +audio/main.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/manager.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/gateway.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/headset.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/control.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/device.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/source.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/sink.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/a2dp.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/avdtp.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/ipc.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/unix.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/media.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +audio/transport.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +sap/main.$(OBJEXT): sap/$(am__dirstamp) sap/$(DEPDIR)/$(am__dirstamp) +sap/manager.$(OBJEXT): sap/$(am__dirstamp) \ + sap/$(DEPDIR)/$(am__dirstamp) +sap/server.$(OBJEXT): sap/$(am__dirstamp) \ + sap/$(DEPDIR)/$(am__dirstamp) +input/$(am__dirstamp): + @$(MKDIR_P) input + @: > input/$(am__dirstamp) +input/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) input/$(DEPDIR) + @: > input/$(DEPDIR)/$(am__dirstamp) +input/main.$(OBJEXT): input/$(am__dirstamp) \ + input/$(DEPDIR)/$(am__dirstamp) +input/manager.$(OBJEXT): input/$(am__dirstamp) \ + input/$(DEPDIR)/$(am__dirstamp) +input/server.$(OBJEXT): input/$(am__dirstamp) \ + input/$(DEPDIR)/$(am__dirstamp) +input/device.$(OBJEXT): input/$(am__dirstamp) \ + input/$(DEPDIR)/$(am__dirstamp) +input/fakehid.$(OBJEXT): input/$(am__dirstamp) \ + input/$(DEPDIR)/$(am__dirstamp) +serial/$(am__dirstamp): + @$(MKDIR_P) serial + @: > serial/$(am__dirstamp) +serial/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) serial/$(DEPDIR) + @: > serial/$(DEPDIR)/$(am__dirstamp) +serial/main.$(OBJEXT): serial/$(am__dirstamp) \ + serial/$(DEPDIR)/$(am__dirstamp) +serial/manager.$(OBJEXT): serial/$(am__dirstamp) \ + serial/$(DEPDIR)/$(am__dirstamp) +serial/proxy.$(OBJEXT): serial/$(am__dirstamp) \ + serial/$(DEPDIR)/$(am__dirstamp) +serial/port.$(OBJEXT): serial/$(am__dirstamp) \ + serial/$(DEPDIR)/$(am__dirstamp) +network/$(am__dirstamp): + @$(MKDIR_P) network + @: > network/$(am__dirstamp) +network/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) network/$(DEPDIR) + @: > network/$(DEPDIR)/$(am__dirstamp) +network/main.$(OBJEXT): network/$(am__dirstamp) \ + network/$(DEPDIR)/$(am__dirstamp) +network/manager.$(OBJEXT): network/$(am__dirstamp) \ + network/$(DEPDIR)/$(am__dirstamp) +network/common.$(OBJEXT): network/$(am__dirstamp) \ + network/$(DEPDIR)/$(am__dirstamp) +network/server.$(OBJEXT): network/$(am__dirstamp) \ + network/$(DEPDIR)/$(am__dirstamp) +network/connection.$(OBJEXT): network/$(am__dirstamp) \ + network/$(DEPDIR)/$(am__dirstamp) +plugins/service.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +attrib/main.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/manager.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/client.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/example.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +health/$(am__dirstamp): + @$(MKDIR_P) health + @: > health/$(am__dirstamp) +health/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) health/$(DEPDIR) + @: > health/$(DEPDIR)/$(am__dirstamp) +health/hdp_main.$(OBJEXT): health/$(am__dirstamp) \ + health/$(DEPDIR)/$(am__dirstamp) +health/hdp_manager.$(OBJEXT): health/$(am__dirstamp) \ + health/$(DEPDIR)/$(am__dirstamp) +health/hdp.$(OBJEXT): health/$(am__dirstamp) \ + health/$(DEPDIR)/$(am__dirstamp) +health/hdp_util.$(OBJEXT): health/$(am__dirstamp) \ + health/$(DEPDIR)/$(am__dirstamp) +plugins/hciops.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/mgmtops.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/hal.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/formfactor.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/storage.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/maemo6.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/dbusoob.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +health/mcap.$(OBJEXT): health/$(am__dirstamp) \ + health/$(DEPDIR)/$(am__dirstamp) +health/mcap_sync.$(OBJEXT): health/$(am__dirstamp) \ + health/$(DEPDIR)/$(am__dirstamp) +src/main.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +src/log.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +src/rfkill.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/sdpd-server.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/sdpd-request.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/sdpd-service.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/sdpd-database.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/attrib-server.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/sdp-xml.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/oui.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +src/plugin.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/storage.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/agent.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +src/error.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +src/manager.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/adapter.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/device.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/dbus-common.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/event.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +src/oob.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +audio/telephony.$(OBJEXT): audio/$(am__dirstamp) \ + audio/$(DEPDIR)/$(am__dirstamp) +sap/sap.$(OBJEXT): sap/$(am__dirstamp) sap/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd$(EXEEXT): $(src_bluetoothd_OBJECTS) $(src_bluetoothd_DEPENDENCIES) src/$(am__dirstamp) + @rm -f src/bluetoothd$(EXEEXT) + $(AM_V_CCLD)$(src_bluetoothd_LINK) $(src_bluetoothd_OBJECTS) $(src_bluetoothd_LDADD) $(LIBS) +test/$(am__dirstamp): + @$(MKDIR_P) test + @: > test/$(am__dirstamp) +test/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) test/$(DEPDIR) + @: > test/$(DEPDIR)/$(am__dirstamp) +test/agent.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/agent$(EXEEXT): $(test_agent_OBJECTS) $(test_agent_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/agent$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_agent_OBJECTS) $(test_agent_LDADD) $(LIBS) +test/attest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/attest$(EXEEXT): $(test_attest_OBJECTS) $(test_attest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/attest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_attest_OBJECTS) $(test_attest_LDADD) $(LIBS) +test/avtest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/avtest$(EXEEXT): $(test_avtest_OBJECTS) $(test_avtest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/avtest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_avtest_OBJECTS) $(test_avtest_LDADD) $(LIBS) +test/bdaddr.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/bdaddr$(EXEEXT): $(test_bdaddr_OBJECTS) $(test_bdaddr_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/bdaddr$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_bdaddr_OBJECTS) $(test_bdaddr_LDADD) $(LIBS) +test/btiotest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/btiotest$(EXEEXT): $(test_btiotest_OBJECTS) $(test_btiotest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/btiotest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_btiotest_OBJECTS) $(test_btiotest_LDADD) $(LIBS) +test/gaptest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/gaptest$(EXEEXT): $(test_gaptest_OBJECTS) $(test_gaptest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/gaptest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_gaptest_OBJECTS) $(test_gaptest_LDADD) $(LIBS) +test/hciemu.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/hciemu$(EXEEXT): $(test_hciemu_OBJECTS) $(test_hciemu_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/hciemu$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_hciemu_OBJECTS) $(test_hciemu_LDADD) $(LIBS) +test/hstest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/hstest$(EXEEXT): $(test_hstest_OBJECTS) $(test_hstest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/hstest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_hstest_OBJECTS) $(test_hstest_LDADD) $(LIBS) +test/ipctest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/ipctest$(EXEEXT): $(test_ipctest_OBJECTS) $(test_ipctest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/ipctest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_ipctest_OBJECTS) $(test_ipctest_LDADD) $(LIBS) +test/l2test.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/l2test$(EXEEXT): $(test_l2test_OBJECTS) $(test_l2test_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/l2test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_l2test_OBJECTS) $(test_l2test_LDADD) $(LIBS) +test/lmptest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/lmptest$(EXEEXT): $(test_lmptest_OBJECTS) $(test_lmptest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/lmptest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_lmptest_OBJECTS) $(test_lmptest_LDADD) $(LIBS) +test/rctest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/rctest$(EXEEXT): $(test_rctest_OBJECTS) $(test_rctest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/rctest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_rctest_OBJECTS) $(test_rctest_LDADD) $(LIBS) +test/scotest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/scotest$(EXEEXT): $(test_scotest_OBJECTS) $(test_scotest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/scotest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_scotest_OBJECTS) $(test_scotest_LDADD) $(LIBS) +test/sdptest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/sdptest$(EXEEXT): $(test_sdptest_OBJECTS) $(test_sdptest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/sdptest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_sdptest_OBJECTS) $(test_sdptest_LDADD) $(LIBS) +test/test-textfile.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/test-textfile$(EXEEXT): $(test_test_textfile_OBJECTS) $(test_test_textfile_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/test-textfile$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_test_textfile_OBJECTS) $(test_test_textfile_LDADD) $(LIBS) +test/uuidtest.$(OBJEXT): test/$(am__dirstamp) \ + test/$(DEPDIR)/$(am__dirstamp) +test/uuidtest$(EXEEXT): $(test_uuidtest_OBJECTS) $(test_uuidtest_DEPENDENCIES) test/$(am__dirstamp) + @rm -f test/uuidtest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_uuidtest_OBJECTS) $(test_uuidtest_LDADD) $(LIBS) +tools/$(am__dirstamp): + @$(MKDIR_P) tools + @: > tools/$(am__dirstamp) +tools/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) tools/$(DEPDIR) + @: > tools/$(DEPDIR)/$(am__dirstamp) +tools/avctrl.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/avctrl$(EXEEXT): $(tools_avctrl_OBJECTS) $(tools_avctrl_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/avctrl$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_avctrl_OBJECTS) $(tools_avctrl_LDADD) $(LIBS) +tools/avinfo.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/avinfo$(EXEEXT): $(tools_avinfo_OBJECTS) $(tools_avinfo_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/avinfo$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_avinfo_OBJECTS) $(tools_avinfo_LDADD) $(LIBS) +tools/bccmd.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_hci.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_h4.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_3wire.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_bcsp.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/ubcsp.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_usb.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/bccmd$(EXEEXT): $(tools_bccmd_OBJECTS) $(tools_bccmd_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/bccmd$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_bccmd_OBJECTS) $(tools_bccmd_LDADD) $(LIBS) +tools/ciptool.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/ciptool$(EXEEXT): $(tools_ciptool_OBJECTS) $(tools_ciptool_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/ciptool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_ciptool_OBJECTS) $(tools_ciptool_LDADD) $(LIBS) +tools/dfubabel.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/dfubabel$(EXEEXT): $(tools_dfubabel_OBJECTS) $(tools_dfubabel_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/dfubabel$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_dfubabel_OBJECTS) $(tools_dfubabel_LDADD) $(LIBS) +tools/dfutool.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/dfu.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/dfutool$(EXEEXT): $(tools_dfutool_OBJECTS) $(tools_dfutool_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/dfutool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_dfutool_OBJECTS) $(tools_dfutool_LDADD) $(LIBS) +tools/hciattach.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_st.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_ti.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_tialt.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_ath3k.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_qualcomm.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach$(EXEEXT): $(tools_hciattach_OBJECTS) $(tools_hciattach_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hciattach$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hciattach_OBJECTS) $(tools_hciattach_LDADD) $(LIBS) +tools/hciconfig.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciconfig$(EXEEXT): $(tools_hciconfig_OBJECTS) $(tools_hciconfig_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hciconfig$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hciconfig_OBJECTS) $(tools_hciconfig_LDADD) $(LIBS) +tools/hcieventmask.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hcieventmask$(EXEEXT): $(tools_hcieventmask_OBJECTS) $(tools_hcieventmask_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hcieventmask$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hcieventmask_OBJECTS) $(tools_hcieventmask_LDADD) $(LIBS) +tools/hcisecfilter.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hcisecfilter$(EXEEXT): $(tools_hcisecfilter_OBJECTS) $(tools_hcisecfilter_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hcisecfilter$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hcisecfilter_OBJECTS) $(tools_hcisecfilter_LDADD) $(LIBS) +tools/hcitool.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hcitool$(EXEEXT): $(tools_hcitool_OBJECTS) $(tools_hcitool_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hcitool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hcitool_OBJECTS) $(tools_hcitool_LDADD) $(LIBS) +tools/hid2hci.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hid2hci$(EXEEXT): $(tools_hid2hci_OBJECTS) $(tools_hid2hci_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hid2hci$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hid2hci_OBJECTS) $(tools_hid2hci_LDADD) $(LIBS) +tools/l2ping.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/l2ping$(EXEEXT): $(tools_l2ping_OBJECTS) $(tools_l2ping_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/l2ping$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_l2ping_OBJECTS) $(tools_l2ping_LDADD) $(LIBS) +tools/ppporc.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/ppporc$(EXEEXT): $(tools_ppporc_OBJECTS) $(tools_ppporc_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/ppporc$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_ppporc_OBJECTS) $(tools_ppporc_LDADD) $(LIBS) +tools/rfcomm.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/parser.h: tools/parser.c + @if test ! -f $@; then \ + rm -f tools/parser.c; \ + $(MAKE) $(AM_MAKEFLAGS) tools/parser.c; \ + else :; fi +tools/parser.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/lexer.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/kword.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/rfcomm$(EXEEXT): $(tools_rfcomm_OBJECTS) $(tools_rfcomm_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/rfcomm$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_rfcomm_OBJECTS) $(tools_rfcomm_LDADD) $(LIBS) +tools/sdptool.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/sdptool$(EXEEXT): $(tools_sdptool_OBJECTS) $(tools_sdptool_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/sdptool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_sdptool_OBJECTS) $(tools_sdptool_LDADD) $(LIBS) +tracer/$(am__dirstamp): + @$(MKDIR_P) tracer + @: > tracer/$(am__dirstamp) +tracer/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) tracer/$(DEPDIR) + @: > tracer/$(DEPDIR)/$(am__dirstamp) +tracer/main.$(OBJEXT): tracer/$(am__dirstamp) \ + tracer/$(DEPDIR)/$(am__dirstamp) +tracer/hcitrace$(EXEEXT): $(tracer_hcitrace_OBJECTS) $(tracer_hcitrace_DEPENDENCIES) tracer/$(am__dirstamp) + @rm -f tracer/hcitrace$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tracer_hcitrace_OBJECTS) $(tracer_hcitrace_LDADD) $(LIBS) +install-dist_udevSCRIPTS: $(dist_udev_SCRIPTS) + @$(NORMAL_INSTALL) + test -z "$(udevdir)" || $(MKDIR_P) "$(DESTDIR)$(udevdir)" + @list='$(dist_udev_SCRIPTS)'; test -n "$(udevdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n' \ + -e 'h;s|.*|.|' \ + -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) { files[d] = files[d] " " $$1; \ + if (++n[d] == $(am__install_max)) { \ + print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ + else { print "f", d "/" $$4, $$1 } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(udevdir)$$dir'"; \ + $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(udevdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-dist_udevSCRIPTS: + @$(NORMAL_UNINSTALL) + @list='$(dist_udev_SCRIPTS)'; test -n "$(udevdir)" || exit 0; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 's,.*/,,;$(transform)'`; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(udevdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(udevdir)" && rm -f $$files + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f attrib/att.$(OBJEXT) + -rm -f attrib/client.$(OBJEXT) + -rm -f attrib/example.$(OBJEXT) + -rm -f attrib/gatt.$(OBJEXT) + -rm -f attrib/gattrib.$(OBJEXT) + -rm -f attrib/gatttool.$(OBJEXT) + -rm -f attrib/interactive.$(OBJEXT) + -rm -f attrib/main.$(OBJEXT) + -rm -f attrib/manager.$(OBJEXT) + -rm -f attrib/utils.$(OBJEXT) + -rm -f audio/a2dp.$(OBJEXT) + -rm -f audio/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.$(OBJEXT) + -rm -f audio/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.lo + -rm -f audio/audio_libasound_module_ctl_bluetooth_la-ipc.$(OBJEXT) + -rm -f audio/audio_libasound_module_ctl_bluetooth_la-ipc.lo + -rm -f audio/audio_libasound_module_pcm_bluetooth_la-ipc.$(OBJEXT) + -rm -f audio/audio_libasound_module_pcm_bluetooth_la-ipc.lo + -rm -f audio/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.$(OBJEXT) + -rm -f audio/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.lo + -rm -f audio/audio_libgstbluetooth_la-gsta2dpsink.$(OBJEXT) + -rm -f audio/audio_libgstbluetooth_la-gsta2dpsink.lo + -rm -f audio/audio_libgstbluetooth_la-gstavdtpsink.$(OBJEXT) + -rm -f audio/audio_libgstbluetooth_la-gstavdtpsink.lo + -rm -f audio/audio_libgstbluetooth_la-gstbluetooth.$(OBJEXT) + -rm -f audio/audio_libgstbluetooth_la-gstbluetooth.lo + -rm -f audio/audio_libgstbluetooth_la-gstrtpsbcpay.$(OBJEXT) + -rm -f audio/audio_libgstbluetooth_la-gstrtpsbcpay.lo + -rm -f audio/audio_libgstbluetooth_la-gstsbcdec.$(OBJEXT) + -rm -f audio/audio_libgstbluetooth_la-gstsbcdec.lo + -rm -f audio/audio_libgstbluetooth_la-gstsbcenc.$(OBJEXT) + -rm -f audio/audio_libgstbluetooth_la-gstsbcenc.lo + -rm -f audio/audio_libgstbluetooth_la-gstsbcparse.$(OBJEXT) + -rm -f audio/audio_libgstbluetooth_la-gstsbcparse.lo + -rm -f audio/audio_libgstbluetooth_la-gstsbcutil.$(OBJEXT) + -rm -f audio/audio_libgstbluetooth_la-gstsbcutil.lo + -rm -f audio/audio_libgstbluetooth_la-ipc.$(OBJEXT) + -rm -f audio/audio_libgstbluetooth_la-ipc.lo + -rm -f audio/avdtp.$(OBJEXT) + -rm -f audio/control.$(OBJEXT) + -rm -f audio/device.$(OBJEXT) + -rm -f audio/gateway.$(OBJEXT) + -rm -f audio/headset.$(OBJEXT) + -rm -f audio/ipc.$(OBJEXT) + -rm -f audio/main.$(OBJEXT) + -rm -f audio/manager.$(OBJEXT) + -rm -f audio/media.$(OBJEXT) + -rm -f audio/sink.$(OBJEXT) + -rm -f audio/source.$(OBJEXT) + -rm -f audio/telephony-dummy.$(OBJEXT) + -rm -f audio/telephony-maemo5.$(OBJEXT) + -rm -f audio/telephony-maemo6.$(OBJEXT) + -rm -f audio/telephony-ofono.$(OBJEXT) + -rm -f audio/telephony.$(OBJEXT) + -rm -f audio/transport.$(OBJEXT) + -rm -f audio/unix.$(OBJEXT) + -rm -f btio/btio.$(OBJEXT) + -rm -f compat/bnep.$(OBJEXT) + -rm -f compat/dun.$(OBJEXT) + -rm -f compat/dund.$(OBJEXT) + -rm -f compat/fakehid.$(OBJEXT) + -rm -f compat/hidd.$(OBJEXT) + -rm -f compat/msdun.$(OBJEXT) + -rm -f compat/pand.$(OBJEXT) + -rm -f compat/sdp.$(OBJEXT) + -rm -f cups/hcrp.$(OBJEXT) + -rm -f cups/main.$(OBJEXT) + -rm -f cups/sdp.$(OBJEXT) + -rm -f cups/spp.$(OBJEXT) + -rm -f gdbus/mainloop.$(OBJEXT) + -rm -f gdbus/object.$(OBJEXT) + -rm -f gdbus/polkit.$(OBJEXT) + -rm -f gdbus/watch.$(OBJEXT) + -rm -f health/hdp.$(OBJEXT) + -rm -f health/hdp_main.$(OBJEXT) + -rm -f health/hdp_manager.$(OBJEXT) + -rm -f health/hdp_util.$(OBJEXT) + -rm -f health/mcap.$(OBJEXT) + -rm -f health/mcap_sync.$(OBJEXT) + -rm -f input/device.$(OBJEXT) + -rm -f input/fakehid.$(OBJEXT) + -rm -f input/main.$(OBJEXT) + -rm -f input/manager.$(OBJEXT) + -rm -f input/server.$(OBJEXT) + -rm -f lib/bluetooth.$(OBJEXT) + -rm -f lib/bluetooth.lo + -rm -f lib/hci.$(OBJEXT) + -rm -f lib/hci.lo + -rm -f lib/sdp.$(OBJEXT) + -rm -f lib/sdp.lo + -rm -f lib/uuid.$(OBJEXT) + -rm -f lib/uuid.lo + -rm -f network/common.$(OBJEXT) + -rm -f network/connection.$(OBJEXT) + -rm -f network/main.$(OBJEXT) + -rm -f network/manager.$(OBJEXT) + -rm -f network/server.$(OBJEXT) + -rm -f plugins/dbusoob.$(OBJEXT) + -rm -f plugins/echo.$(OBJEXT) + -rm -f plugins/formfactor.$(OBJEXT) + -rm -f plugins/hal.$(OBJEXT) + -rm -f plugins/hciops.$(OBJEXT) + -rm -f plugins/maemo6.$(OBJEXT) + -rm -f plugins/mgmtops.$(OBJEXT) + -rm -f plugins/pnat.$(OBJEXT) + -rm -f plugins/service.$(OBJEXT) + -rm -f plugins/storage.$(OBJEXT) + -rm -f sap/main.$(OBJEXT) + -rm -f sap/manager.$(OBJEXT) + -rm -f sap/sap-dummy.$(OBJEXT) + -rm -f sap/sap.$(OBJEXT) + -rm -f sap/server.$(OBJEXT) + -rm -f sbc/sbc_libsbc_la-sbc.$(OBJEXT) + -rm -f sbc/sbc_libsbc_la-sbc.lo + -rm -f sbc/sbc_libsbc_la-sbc_primitives.$(OBJEXT) + -rm -f sbc/sbc_libsbc_la-sbc_primitives.lo + -rm -f sbc/sbc_libsbc_la-sbc_primitives_armv6.$(OBJEXT) + -rm -f sbc/sbc_libsbc_la-sbc_primitives_armv6.lo + -rm -f sbc/sbc_libsbc_la-sbc_primitives_iwmmxt.$(OBJEXT) + -rm -f sbc/sbc_libsbc_la-sbc_primitives_iwmmxt.lo + -rm -f sbc/sbc_libsbc_la-sbc_primitives_mmx.$(OBJEXT) + -rm -f sbc/sbc_libsbc_la-sbc_primitives_mmx.lo + -rm -f sbc/sbc_libsbc_la-sbc_primitives_neon.$(OBJEXT) + -rm -f sbc/sbc_libsbc_la-sbc_primitives_neon.lo + -rm -f sbc/sbcdec.$(OBJEXT) + -rm -f sbc/sbcenc.$(OBJEXT) + -rm -f sbc/sbcinfo.$(OBJEXT) + -rm -f sbc/sbctester.$(OBJEXT) + -rm -f serial/main.$(OBJEXT) + -rm -f serial/manager.$(OBJEXT) + -rm -f serial/port.$(OBJEXT) + -rm -f serial/proxy.$(OBJEXT) + -rm -f src/adapter.$(OBJEXT) + -rm -f src/agent.$(OBJEXT) + -rm -f src/attrib-server.$(OBJEXT) + -rm -f src/dbus-common.$(OBJEXT) + -rm -f src/device.$(OBJEXT) + -rm -f src/error.$(OBJEXT) + -rm -f src/event.$(OBJEXT) + -rm -f src/glib-helper.$(OBJEXT) + -rm -f src/log.$(OBJEXT) + -rm -f src/main.$(OBJEXT) + -rm -f src/manager.$(OBJEXT) + -rm -f src/oob.$(OBJEXT) + -rm -f src/oui.$(OBJEXT) + -rm -f src/plugin.$(OBJEXT) + -rm -f src/rfkill.$(OBJEXT) + -rm -f src/sdp-xml.$(OBJEXT) + -rm -f src/sdpd-database.$(OBJEXT) + -rm -f src/sdpd-request.$(OBJEXT) + -rm -f src/sdpd-server.$(OBJEXT) + -rm -f src/sdpd-service.$(OBJEXT) + -rm -f src/storage.$(OBJEXT) + -rm -f src/textfile.$(OBJEXT) + -rm -f test/agent.$(OBJEXT) + -rm -f test/attest.$(OBJEXT) + -rm -f test/avtest.$(OBJEXT) + -rm -f test/bdaddr.$(OBJEXT) + -rm -f test/btiotest.$(OBJEXT) + -rm -f test/gaptest.$(OBJEXT) + -rm -f test/hciemu.$(OBJEXT) + -rm -f test/hstest.$(OBJEXT) + -rm -f test/ipctest.$(OBJEXT) + -rm -f test/l2test.$(OBJEXT) + -rm -f test/lmptest.$(OBJEXT) + -rm -f test/rctest.$(OBJEXT) + -rm -f test/scotest.$(OBJEXT) + -rm -f test/sdptest.$(OBJEXT) + -rm -f test/test-textfile.$(OBJEXT) + -rm -f test/uuidtest.$(OBJEXT) + -rm -f tools/avctrl.$(OBJEXT) + -rm -f tools/avinfo.$(OBJEXT) + -rm -f tools/bccmd.$(OBJEXT) + -rm -f tools/ciptool.$(OBJEXT) + -rm -f tools/csr.$(OBJEXT) + -rm -f tools/csr_3wire.$(OBJEXT) + -rm -f tools/csr_bcsp.$(OBJEXT) + -rm -f tools/csr_h4.$(OBJEXT) + -rm -f tools/csr_hci.$(OBJEXT) + -rm -f tools/csr_usb.$(OBJEXT) + -rm -f tools/dfu.$(OBJEXT) + -rm -f tools/dfubabel.$(OBJEXT) + -rm -f tools/dfutool.$(OBJEXT) + -rm -f tools/hciattach.$(OBJEXT) + -rm -f tools/hciattach_ath3k.$(OBJEXT) + -rm -f tools/hciattach_qualcomm.$(OBJEXT) + -rm -f tools/hciattach_st.$(OBJEXT) + -rm -f tools/hciattach_ti.$(OBJEXT) + -rm -f tools/hciattach_tialt.$(OBJEXT) + -rm -f tools/hciconfig.$(OBJEXT) + -rm -f tools/hcieventmask.$(OBJEXT) + -rm -f tools/hcisecfilter.$(OBJEXT) + -rm -f tools/hcitool.$(OBJEXT) + -rm -f tools/hid2hci.$(OBJEXT) + -rm -f tools/kword.$(OBJEXT) + -rm -f tools/l2ping.$(OBJEXT) + -rm -f tools/lexer.$(OBJEXT) + -rm -f tools/parser.$(OBJEXT) + -rm -f tools/ppporc.$(OBJEXT) + -rm -f tools/rfcomm.$(OBJEXT) + -rm -f tools/sdptool.$(OBJEXT) + -rm -f tools/ubcsp.$(OBJEXT) + -rm -f tracer/main.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/att.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/client.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/example.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/gatt.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/gattrib.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/gatttool.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/interactive.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/manager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/a2dp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libasound_module_ctl_bluetooth_la-ipc.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libasound_module_pcm_bluetooth_la-ipc.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libgstbluetooth_la-gsta2dpsink.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libgstbluetooth_la-gstavdtpsink.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libgstbluetooth_la-gstbluetooth.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libgstbluetooth_la-gstrtpsbcpay.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcdec.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcenc.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcparse.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcutil.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/audio_libgstbluetooth_la-ipc.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/avdtp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/control.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/device.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/gateway.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/headset.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/ipc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/manager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/media.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/sink.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/source.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/telephony-dummy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/telephony-maemo5.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/telephony-maemo6.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/telephony-ofono.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/telephony.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/transport.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@audio/$(DEPDIR)/unix.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@btio/$(DEPDIR)/btio.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/bnep.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/dun.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/dund.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/fakehid.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/hidd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/msdun.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/pand.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/sdp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@cups/$(DEPDIR)/hcrp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@cups/$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@cups/$(DEPDIR)/sdp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@cups/$(DEPDIR)/spp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/mainloop.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/object.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/polkit.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/watch.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@health/$(DEPDIR)/hdp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@health/$(DEPDIR)/hdp_main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@health/$(DEPDIR)/hdp_manager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@health/$(DEPDIR)/hdp_util.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@health/$(DEPDIR)/mcap.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@health/$(DEPDIR)/mcap_sync.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@input/$(DEPDIR)/device.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@input/$(DEPDIR)/fakehid.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@input/$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@input/$(DEPDIR)/manager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@input/$(DEPDIR)/server.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/bluetooth.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/hci.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/sdp.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/uuid.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@network/$(DEPDIR)/common.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@network/$(DEPDIR)/connection.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@network/$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@network/$(DEPDIR)/manager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@network/$(DEPDIR)/server.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/dbusoob.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/echo.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/formfactor.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/hal.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/hciops.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/maemo6.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/mgmtops.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/pnat.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/service.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/storage.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sap/$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sap/$(DEPDIR)/manager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sap/$(DEPDIR)/sap-dummy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sap/$(DEPDIR)/sap.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sap/$(DEPDIR)/server.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbc_libsbc_la-sbc.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_armv6.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_iwmmxt.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_mmx.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_neon.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbcdec.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbcenc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbcinfo.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@sbc/$(DEPDIR)/sbctester.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@serial/$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@serial/$(DEPDIR)/manager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@serial/$(DEPDIR)/port.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@serial/$(DEPDIR)/proxy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/adapter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/agent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/attrib-server.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/dbus-common.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/device.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/error.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/event.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/glib-helper.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/log.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/manager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/oob.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/oui.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/plugin.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/rfkill.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdp-xml.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-database.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-request.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-server.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-service.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/storage.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/textfile.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/agent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/attest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/avtest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/bdaddr.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/btiotest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/gaptest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/hciemu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/hstest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/ipctest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/l2test.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/lmptest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/rctest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/scotest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/sdptest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/test-textfile.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@test/$(DEPDIR)/uuidtest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/avctrl.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/avinfo.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bccmd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/ciptool.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_3wire.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_bcsp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_h4.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_hci.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_usb.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/dfu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/dfubabel.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/dfutool.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_ath3k.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_qualcomm.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_st.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_ti.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_tialt.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciconfig.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcieventmask.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcisecfilter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcitool.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hid2hci.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/kword.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/l2ping.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/lexer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/parser.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/ppporc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/rfcomm.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/sdptool.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/ubcsp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tracer/$(DEPDIR)/main.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +audio/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.lo: audio/ctl_bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libasound_module_ctl_bluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.lo -MD -MP -MF audio/$(DEPDIR)/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.Tpo -c -o audio/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.lo `test -f 'audio/ctl_bluetooth.c' || echo '$(srcdir)/'`audio/ctl_bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.Tpo audio/$(DEPDIR)/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/ctl_bluetooth.c' object='audio/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libasound_module_ctl_bluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libasound_module_ctl_bluetooth_la-ctl_bluetooth.lo `test -f 'audio/ctl_bluetooth.c' || echo '$(srcdir)/'`audio/ctl_bluetooth.c + +audio/audio_libasound_module_ctl_bluetooth_la-ipc.lo: audio/ipc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libasound_module_ctl_bluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libasound_module_ctl_bluetooth_la-ipc.lo -MD -MP -MF audio/$(DEPDIR)/audio_libasound_module_ctl_bluetooth_la-ipc.Tpo -c -o audio/audio_libasound_module_ctl_bluetooth_la-ipc.lo `test -f 'audio/ipc.c' || echo '$(srcdir)/'`audio/ipc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libasound_module_ctl_bluetooth_la-ipc.Tpo audio/$(DEPDIR)/audio_libasound_module_ctl_bluetooth_la-ipc.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/ipc.c' object='audio/audio_libasound_module_ctl_bluetooth_la-ipc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libasound_module_ctl_bluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libasound_module_ctl_bluetooth_la-ipc.lo `test -f 'audio/ipc.c' || echo '$(srcdir)/'`audio/ipc.c + +audio/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.lo: audio/pcm_bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libasound_module_pcm_bluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.lo -MD -MP -MF audio/$(DEPDIR)/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.Tpo -c -o audio/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.lo `test -f 'audio/pcm_bluetooth.c' || echo '$(srcdir)/'`audio/pcm_bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.Tpo audio/$(DEPDIR)/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/pcm_bluetooth.c' object='audio/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libasound_module_pcm_bluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libasound_module_pcm_bluetooth_la-pcm_bluetooth.lo `test -f 'audio/pcm_bluetooth.c' || echo '$(srcdir)/'`audio/pcm_bluetooth.c + +audio/audio_libasound_module_pcm_bluetooth_la-ipc.lo: audio/ipc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libasound_module_pcm_bluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libasound_module_pcm_bluetooth_la-ipc.lo -MD -MP -MF audio/$(DEPDIR)/audio_libasound_module_pcm_bluetooth_la-ipc.Tpo -c -o audio/audio_libasound_module_pcm_bluetooth_la-ipc.lo `test -f 'audio/ipc.c' || echo '$(srcdir)/'`audio/ipc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libasound_module_pcm_bluetooth_la-ipc.Tpo audio/$(DEPDIR)/audio_libasound_module_pcm_bluetooth_la-ipc.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/ipc.c' object='audio/audio_libasound_module_pcm_bluetooth_la-ipc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libasound_module_pcm_bluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libasound_module_pcm_bluetooth_la-ipc.lo `test -f 'audio/ipc.c' || echo '$(srcdir)/'`audio/ipc.c + +audio/audio_libgstbluetooth_la-gstbluetooth.lo: audio/gstbluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libgstbluetooth_la-gstbluetooth.lo -MD -MP -MF audio/$(DEPDIR)/audio_libgstbluetooth_la-gstbluetooth.Tpo -c -o audio/audio_libgstbluetooth_la-gstbluetooth.lo `test -f 'audio/gstbluetooth.c' || echo '$(srcdir)/'`audio/gstbluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libgstbluetooth_la-gstbluetooth.Tpo audio/$(DEPDIR)/audio_libgstbluetooth_la-gstbluetooth.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/gstbluetooth.c' object='audio/audio_libgstbluetooth_la-gstbluetooth.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libgstbluetooth_la-gstbluetooth.lo `test -f 'audio/gstbluetooth.c' || echo '$(srcdir)/'`audio/gstbluetooth.c + +audio/audio_libgstbluetooth_la-gstsbcenc.lo: audio/gstsbcenc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libgstbluetooth_la-gstsbcenc.lo -MD -MP -MF audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcenc.Tpo -c -o audio/audio_libgstbluetooth_la-gstsbcenc.lo `test -f 'audio/gstsbcenc.c' || echo '$(srcdir)/'`audio/gstsbcenc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcenc.Tpo audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcenc.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/gstsbcenc.c' object='audio/audio_libgstbluetooth_la-gstsbcenc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libgstbluetooth_la-gstsbcenc.lo `test -f 'audio/gstsbcenc.c' || echo '$(srcdir)/'`audio/gstsbcenc.c + +audio/audio_libgstbluetooth_la-gstsbcdec.lo: audio/gstsbcdec.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libgstbluetooth_la-gstsbcdec.lo -MD -MP -MF audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcdec.Tpo -c -o audio/audio_libgstbluetooth_la-gstsbcdec.lo `test -f 'audio/gstsbcdec.c' || echo '$(srcdir)/'`audio/gstsbcdec.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcdec.Tpo audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcdec.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/gstsbcdec.c' object='audio/audio_libgstbluetooth_la-gstsbcdec.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libgstbluetooth_la-gstsbcdec.lo `test -f 'audio/gstsbcdec.c' || echo '$(srcdir)/'`audio/gstsbcdec.c + +audio/audio_libgstbluetooth_la-gstsbcparse.lo: audio/gstsbcparse.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libgstbluetooth_la-gstsbcparse.lo -MD -MP -MF audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcparse.Tpo -c -o audio/audio_libgstbluetooth_la-gstsbcparse.lo `test -f 'audio/gstsbcparse.c' || echo '$(srcdir)/'`audio/gstsbcparse.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcparse.Tpo audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcparse.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/gstsbcparse.c' object='audio/audio_libgstbluetooth_la-gstsbcparse.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libgstbluetooth_la-gstsbcparse.lo `test -f 'audio/gstsbcparse.c' || echo '$(srcdir)/'`audio/gstsbcparse.c + +audio/audio_libgstbluetooth_la-gstavdtpsink.lo: audio/gstavdtpsink.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libgstbluetooth_la-gstavdtpsink.lo -MD -MP -MF audio/$(DEPDIR)/audio_libgstbluetooth_la-gstavdtpsink.Tpo -c -o audio/audio_libgstbluetooth_la-gstavdtpsink.lo `test -f 'audio/gstavdtpsink.c' || echo '$(srcdir)/'`audio/gstavdtpsink.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libgstbluetooth_la-gstavdtpsink.Tpo audio/$(DEPDIR)/audio_libgstbluetooth_la-gstavdtpsink.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/gstavdtpsink.c' object='audio/audio_libgstbluetooth_la-gstavdtpsink.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libgstbluetooth_la-gstavdtpsink.lo `test -f 'audio/gstavdtpsink.c' || echo '$(srcdir)/'`audio/gstavdtpsink.c + +audio/audio_libgstbluetooth_la-gsta2dpsink.lo: audio/gsta2dpsink.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libgstbluetooth_la-gsta2dpsink.lo -MD -MP -MF audio/$(DEPDIR)/audio_libgstbluetooth_la-gsta2dpsink.Tpo -c -o audio/audio_libgstbluetooth_la-gsta2dpsink.lo `test -f 'audio/gsta2dpsink.c' || echo '$(srcdir)/'`audio/gsta2dpsink.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libgstbluetooth_la-gsta2dpsink.Tpo audio/$(DEPDIR)/audio_libgstbluetooth_la-gsta2dpsink.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/gsta2dpsink.c' object='audio/audio_libgstbluetooth_la-gsta2dpsink.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libgstbluetooth_la-gsta2dpsink.lo `test -f 'audio/gsta2dpsink.c' || echo '$(srcdir)/'`audio/gsta2dpsink.c + +audio/audio_libgstbluetooth_la-gstsbcutil.lo: audio/gstsbcutil.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libgstbluetooth_la-gstsbcutil.lo -MD -MP -MF audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcutil.Tpo -c -o audio/audio_libgstbluetooth_la-gstsbcutil.lo `test -f 'audio/gstsbcutil.c' || echo '$(srcdir)/'`audio/gstsbcutil.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcutil.Tpo audio/$(DEPDIR)/audio_libgstbluetooth_la-gstsbcutil.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/gstsbcutil.c' object='audio/audio_libgstbluetooth_la-gstsbcutil.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libgstbluetooth_la-gstsbcutil.lo `test -f 'audio/gstsbcutil.c' || echo '$(srcdir)/'`audio/gstsbcutil.c + +audio/audio_libgstbluetooth_la-gstrtpsbcpay.lo: audio/gstrtpsbcpay.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libgstbluetooth_la-gstrtpsbcpay.lo -MD -MP -MF audio/$(DEPDIR)/audio_libgstbluetooth_la-gstrtpsbcpay.Tpo -c -o audio/audio_libgstbluetooth_la-gstrtpsbcpay.lo `test -f 'audio/gstrtpsbcpay.c' || echo '$(srcdir)/'`audio/gstrtpsbcpay.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libgstbluetooth_la-gstrtpsbcpay.Tpo audio/$(DEPDIR)/audio_libgstbluetooth_la-gstrtpsbcpay.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/gstrtpsbcpay.c' object='audio/audio_libgstbluetooth_la-gstrtpsbcpay.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libgstbluetooth_la-gstrtpsbcpay.lo `test -f 'audio/gstrtpsbcpay.c' || echo '$(srcdir)/'`audio/gstrtpsbcpay.c + +audio/audio_libgstbluetooth_la-ipc.lo: audio/ipc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -MT audio/audio_libgstbluetooth_la-ipc.lo -MD -MP -MF audio/$(DEPDIR)/audio_libgstbluetooth_la-ipc.Tpo -c -o audio/audio_libgstbluetooth_la-ipc.lo `test -f 'audio/ipc.c' || echo '$(srcdir)/'`audio/ipc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) audio/$(DEPDIR)/audio_libgstbluetooth_la-ipc.Tpo audio/$(DEPDIR)/audio_libgstbluetooth_la-ipc.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='audio/ipc.c' object='audio/audio_libgstbluetooth_la-ipc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(audio_libgstbluetooth_la_CFLAGS) $(CFLAGS) -c -o audio/audio_libgstbluetooth_la-ipc.lo `test -f 'audio/ipc.c' || echo '$(srcdir)/'`audio/ipc.c + +sbc/sbc_libsbc_la-sbc.lo: sbc/sbc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -MT sbc/sbc_libsbc_la-sbc.lo -MD -MP -MF sbc/$(DEPDIR)/sbc_libsbc_la-sbc.Tpo -c -o sbc/sbc_libsbc_la-sbc.lo `test -f 'sbc/sbc.c' || echo '$(srcdir)/'`sbc/sbc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) sbc/$(DEPDIR)/sbc_libsbc_la-sbc.Tpo sbc/$(DEPDIR)/sbc_libsbc_la-sbc.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='sbc/sbc.c' object='sbc/sbc_libsbc_la-sbc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -c -o sbc/sbc_libsbc_la-sbc.lo `test -f 'sbc/sbc.c' || echo '$(srcdir)/'`sbc/sbc.c + +sbc/sbc_libsbc_la-sbc_primitives.lo: sbc/sbc_primitives.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -MT sbc/sbc_libsbc_la-sbc_primitives.lo -MD -MP -MF sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives.Tpo -c -o sbc/sbc_libsbc_la-sbc_primitives.lo `test -f 'sbc/sbc_primitives.c' || echo '$(srcdir)/'`sbc/sbc_primitives.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives.Tpo sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='sbc/sbc_primitives.c' object='sbc/sbc_libsbc_la-sbc_primitives.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -c -o sbc/sbc_libsbc_la-sbc_primitives.lo `test -f 'sbc/sbc_primitives.c' || echo '$(srcdir)/'`sbc/sbc_primitives.c + +sbc/sbc_libsbc_la-sbc_primitives_mmx.lo: sbc/sbc_primitives_mmx.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -MT sbc/sbc_libsbc_la-sbc_primitives_mmx.lo -MD -MP -MF sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_mmx.Tpo -c -o sbc/sbc_libsbc_la-sbc_primitives_mmx.lo `test -f 'sbc/sbc_primitives_mmx.c' || echo '$(srcdir)/'`sbc/sbc_primitives_mmx.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_mmx.Tpo sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_mmx.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='sbc/sbc_primitives_mmx.c' object='sbc/sbc_libsbc_la-sbc_primitives_mmx.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -c -o sbc/sbc_libsbc_la-sbc_primitives_mmx.lo `test -f 'sbc/sbc_primitives_mmx.c' || echo '$(srcdir)/'`sbc/sbc_primitives_mmx.c + +sbc/sbc_libsbc_la-sbc_primitives_iwmmxt.lo: sbc/sbc_primitives_iwmmxt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -MT sbc/sbc_libsbc_la-sbc_primitives_iwmmxt.lo -MD -MP -MF sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_iwmmxt.Tpo -c -o sbc/sbc_libsbc_la-sbc_primitives_iwmmxt.lo `test -f 'sbc/sbc_primitives_iwmmxt.c' || echo '$(srcdir)/'`sbc/sbc_primitives_iwmmxt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_iwmmxt.Tpo sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_iwmmxt.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='sbc/sbc_primitives_iwmmxt.c' object='sbc/sbc_libsbc_la-sbc_primitives_iwmmxt.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -c -o sbc/sbc_libsbc_la-sbc_primitives_iwmmxt.lo `test -f 'sbc/sbc_primitives_iwmmxt.c' || echo '$(srcdir)/'`sbc/sbc_primitives_iwmmxt.c + +sbc/sbc_libsbc_la-sbc_primitives_neon.lo: sbc/sbc_primitives_neon.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -MT sbc/sbc_libsbc_la-sbc_primitives_neon.lo -MD -MP -MF sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_neon.Tpo -c -o sbc/sbc_libsbc_la-sbc_primitives_neon.lo `test -f 'sbc/sbc_primitives_neon.c' || echo '$(srcdir)/'`sbc/sbc_primitives_neon.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_neon.Tpo sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_neon.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='sbc/sbc_primitives_neon.c' object='sbc/sbc_libsbc_la-sbc_primitives_neon.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -c -o sbc/sbc_libsbc_la-sbc_primitives_neon.lo `test -f 'sbc/sbc_primitives_neon.c' || echo '$(srcdir)/'`sbc/sbc_primitives_neon.c + +sbc/sbc_libsbc_la-sbc_primitives_armv6.lo: sbc/sbc_primitives_armv6.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -MT sbc/sbc_libsbc_la-sbc_primitives_armv6.lo -MD -MP -MF sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_armv6.Tpo -c -o sbc/sbc_libsbc_la-sbc_primitives_armv6.lo `test -f 'sbc/sbc_primitives_armv6.c' || echo '$(srcdir)/'`sbc/sbc_primitives_armv6.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_armv6.Tpo sbc/$(DEPDIR)/sbc_libsbc_la-sbc_primitives_armv6.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='sbc/sbc_primitives_armv6.c' object='sbc/sbc_libsbc_la-sbc_primitives_armv6.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sbc_libsbc_la_CFLAGS) $(CFLAGS) -c -o sbc/sbc_libsbc_la-sbc_primitives_armv6.lo `test -f 'sbc/sbc_primitives_armv6.c' || echo '$(srcdir)/'`sbc/sbc_primitives_armv6.c + +.l.c: + $(AM_V_LEX)$(am__skiplex) $(SHELL) $(YLWRAP) $< $(LEX_OUTPUT_ROOT).c $@ -- $(LEXCOMPILE) + +.y.c: + $(AM_V_YACC)$(am__skipyacc) $(SHELL) $(YLWRAP) $< y.tab.c $@ y.tab.h $*.h y.output $*.output -- $(YACCCOMPILE) + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf attrib/.libs attrib/_libs + -rm -rf audio/.libs audio/_libs + -rm -rf compat/.libs compat/_libs + -rm -rf cups/.libs cups/_libs + -rm -rf lib/.libs lib/_libs + -rm -rf sbc/.libs sbc/_libs + -rm -rf src/.libs src/_libs + -rm -rf test/.libs test/_libs + -rm -rf tools/.libs tools/_libs + -rm -rf tracer/.libs tracer/_libs + +distclean-libtool: + -rm -f libtool config.lt +install-man1: $(dist_man_MANS) $(man_MANS) + @$(NORMAL_INSTALL) + test -z "$(man1dir)" || $(MKDIR_P) "$(DESTDIR)$(man1dir)" + @list=''; test -n "$(man1dir)" || exit 0; \ + { for i in $$list; do echo "$$i"; done; \ + l2='$(dist_man_MANS) $(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.1[a-z]*$$/p'; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \ + done; } + +uninstall-man1: + @$(NORMAL_UNINSTALL) + @list=''; test -n "$(man1dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + l2='$(dist_man_MANS) $(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.1[a-z]*$$/p'; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + test -z "$$files" || { \ + echo " ( cd '$(DESTDIR)$(man1dir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(man1dir)" && rm -f $$files; } +install-man8: $(dist_man_MANS) $(man_MANS) + @$(NORMAL_INSTALL) + test -z "$(man8dir)" || $(MKDIR_P) "$(DESTDIR)$(man8dir)" + @list=''; test -n "$(man8dir)" || exit 0; \ + { for i in $$list; do echo "$$i"; done; \ + l2='$(dist_man_MANS) $(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.8[a-z]*$$/p'; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \ + done; } + +uninstall-man8: + @$(NORMAL_UNINSTALL) + @list=''; test -n "$(man8dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + l2='$(dist_man_MANS) $(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.8[a-z]*$$/p'; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + test -z "$$files" || { \ + echo " ( cd '$(DESTDIR)$(man8dir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(man8dir)" && rm -f $$files; } +install-alsaconfDATA: $(alsaconf_DATA) + @$(NORMAL_INSTALL) + test -z "$(alsaconfdir)" || $(MKDIR_P) "$(DESTDIR)$(alsaconfdir)" + @list='$(alsaconf_DATA)'; test -n "$(alsaconfdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(alsaconfdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(alsaconfdir)" || exit $$?; \ + done + +uninstall-alsaconfDATA: + @$(NORMAL_UNINSTALL) + @list='$(alsaconf_DATA)'; test -n "$(alsaconfdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(alsaconfdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(alsaconfdir)" && rm -f $$files +install-confDATA: $(conf_DATA) + @$(NORMAL_INSTALL) + test -z "$(confdir)" || $(MKDIR_P) "$(DESTDIR)$(confdir)" + @list='$(conf_DATA)'; test -n "$(confdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(confdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(confdir)" || exit $$?; \ + done + +uninstall-confDATA: + @$(NORMAL_UNINSTALL) + @list='$(conf_DATA)'; test -n "$(confdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(confdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(confdir)" && rm -f $$files +install-dbusDATA: $(dbus_DATA) + @$(NORMAL_INSTALL) + test -z "$(dbusdir)" || $(MKDIR_P) "$(DESTDIR)$(dbusdir)" + @list='$(dbus_DATA)'; test -n "$(dbusdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(dbusdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(dbusdir)" || exit $$?; \ + done + +uninstall-dbusDATA: + @$(NORMAL_UNINSTALL) + @list='$(dbus_DATA)'; test -n "$(dbusdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(dbusdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(dbusdir)" && rm -f $$files +install-pkgconfigDATA: $(pkgconfig_DATA) + @$(NORMAL_INSTALL) + test -z "$(pkgconfigdir)" || $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \ + done + +uninstall-pkgconfigDATA: + @$(NORMAL_UNINSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(pkgconfigdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(pkgconfigdir)" && rm -f $$files +install-rulesDATA: $(rules_DATA) + @$(NORMAL_INSTALL) + test -z "$(rulesdir)" || $(MKDIR_P) "$(DESTDIR)$(rulesdir)" + @list='$(rules_DATA)'; test -n "$(rulesdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(rulesdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(rulesdir)" || exit $$?; \ + done + +uninstall-rulesDATA: + @$(NORMAL_UNINSTALL) + @list='$(rules_DATA)'; test -n "$(rulesdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(rulesdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(rulesdir)" && rm -f $$files +install-stateDATA: $(state_DATA) + @$(NORMAL_INSTALL) + test -z "$(statedir)" || $(MKDIR_P) "$(DESTDIR)$(statedir)" + @list='$(state_DATA)'; test -n "$(statedir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(statedir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(statedir)" || exit $$?; \ + done + +uninstall-stateDATA: + @$(NORMAL_UNINSTALL) + @list='$(state_DATA)'; test -n "$(statedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(statedir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(statedir)" && rm -f $$files +install-includeHEADERS: $(include_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(includedir)" || $(MKDIR_P) "$(DESTDIR)$(includedir)" + @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \ + done + +uninstall-includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(includedir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(includedir)" && rm -f $$files + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @list='$(MANS)'; if test -n "$$list"; then \ + list=`for p in $$list; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + if test -f "$$d$$p"; then echo "$$d$$p"; else :; fi; done`; \ + if test -n "$$list" && \ + grep 'ab help2man is required to generate this page' $$list >/dev/null; then \ + echo "error: found man pages containing the \`missing help2man' replacement text:" >&2; \ + grep -l 'ab help2man is required to generate this page' $$list | sed 's/^/ /' >&2; \ + echo " to fix them, install help2man, remove and regenerate the man pages;" >&2; \ + echo " typically \`make maintainer-clean' will remove them" >&2; \ + exit 1; \ + else :; fi; \ + else :; fi + $(am__remove_distdir) + test -d "$(distdir)" || mkdir "$(distdir)" + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + -test -n "$(am__skip_mode_fix)" \ + || find "$(distdir)" -type d ! -perm -755 \ + -exec chmod u+rwx,go+rx {} \; -o \ + ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ + || chmod -R a+r "$(distdir)" +dist-gzip: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + $(am__remove_distdir) + +dist-bzip2: distdir + tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 + $(am__remove_distdir) + +dist-lzma: distdir + tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma + $(am__remove_distdir) + +dist-xz: distdir + tardir=$(distdir) && $(am__tar) | xz -c >$(distdir).tar.xz + $(am__remove_distdir) + +dist-tarZ: distdir + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z + $(am__remove_distdir) + +dist-shar: distdir + shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz + $(am__remove_distdir) + +dist-zip: distdir + -rm -f $(distdir).zip + zip -rq $(distdir).zip $(distdir) + $(am__remove_distdir) + +dist dist-all: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + $(am__remove_distdir) + +# This target untars the dist file and tries a VPATH configuration. Then +# it guarantees that the distribution is self-contained by making another +# tarfile. +distcheck: dist + case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lzma*) \ + lzma -dc $(distdir).tar.lzma | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + esac + chmod -R a-w $(distdir); chmod a+w $(distdir) + mkdir $(distdir)/_build + mkdir $(distdir)/_inst + chmod a-w $(distdir) + test -d $(distdir)/_build || exit 0; \ + dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ + && am__cwd=`pwd` \ + && $(am__cd) $(distdir)/_build \ + && ../configure --srcdir=.. --prefix="$$dc_install_base" \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) check \ + && $(MAKE) $(AM_MAKEFLAGS) install \ + && $(MAKE) $(AM_MAKEFLAGS) installcheck \ + && $(MAKE) $(AM_MAKEFLAGS) uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ + distuninstallcheck \ + && chmod -R a-w "$$dc_install_base" \ + && ({ \ + (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ + distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ + } || { rm -rf "$$dc_destdir"; exit 1; }) \ + && rm -rf "$$dc_destdir" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && rm -rf $(DIST_ARCHIVES) \ + && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ + && cd "$$am__cwd" \ + || exit 1 + $(am__remove_distdir) + @(echo "$(distdir) archives ready for distribution: "; \ + list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' +distuninstallcheck: + @$(am__cd) '$(distuninstallcheck_dir)' \ + && test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \ + || { echo "ERROR: files left after uninstall:" ; \ + if test -n "$(DESTDIR)"; then \ + echo " (check DESTDIR support)"; \ + fi ; \ + $(distuninstallcheck_listfiles) ; \ + exit 1; } >&2 +distcleancheck: distclean + @if test '$(srcdir)' = . ; then \ + echo "ERROR: distcleancheck can only run from a VPATH build" ; \ + exit 1 ; \ + fi + @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left in build directory after distclean:" ; \ + $(distcleancheck_listfiles) ; \ + exit 1; } >&2 +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) $(LTLIBRARIES) $(PROGRAMS) $(SCRIPTS) \ + $(MANS) $(DATA) $(HEADERS) config.h +install-binPROGRAMS: install-libLTLIBRARIES + +installdirs: + for dir in "$(DESTDIR)$(alsadir)" "$(DESTDIR)$(gstreamerdir)" "$(DESTDIR)$(libdir)" "$(DESTDIR)$(plugindir)" "$(DESTDIR)$(bindir)" "$(DESTDIR)$(cupsdir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(udevdir)" "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(alsaconfdir)" "$(DESTDIR)$(confdir)" "$(DESTDIR)$(dbusdir)" "$(DESTDIR)$(pkgconfigdir)" "$(DESTDIR)$(rulesdir)" "$(DESTDIR)$(statedir)" "$(DESTDIR)$(includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f attrib/$(DEPDIR)/$(am__dirstamp) + -rm -f attrib/$(am__dirstamp) + -rm -f audio/$(DEPDIR)/$(am__dirstamp) + -rm -f audio/$(am__dirstamp) + -rm -f btio/$(DEPDIR)/$(am__dirstamp) + -rm -f btio/$(am__dirstamp) + -rm -f compat/$(DEPDIR)/$(am__dirstamp) + -rm -f compat/$(am__dirstamp) + -rm -f cups/$(DEPDIR)/$(am__dirstamp) + -rm -f cups/$(am__dirstamp) + -rm -f gdbus/$(DEPDIR)/$(am__dirstamp) + -rm -f gdbus/$(am__dirstamp) + -rm -f health/$(DEPDIR)/$(am__dirstamp) + -rm -f health/$(am__dirstamp) + -rm -f input/$(DEPDIR)/$(am__dirstamp) + -rm -f input/$(am__dirstamp) + -rm -f lib/$(DEPDIR)/$(am__dirstamp) + -rm -f lib/$(am__dirstamp) + -rm -f network/$(DEPDIR)/$(am__dirstamp) + -rm -f network/$(am__dirstamp) + -rm -f plugins/$(DEPDIR)/$(am__dirstamp) + -rm -f plugins/$(am__dirstamp) + -rm -f sap/$(DEPDIR)/$(am__dirstamp) + -rm -f sap/$(am__dirstamp) + -rm -f sbc/$(DEPDIR)/$(am__dirstamp) + -rm -f sbc/$(am__dirstamp) + -rm -f serial/$(DEPDIR)/$(am__dirstamp) + -rm -f serial/$(am__dirstamp) + -rm -f src/$(DEPDIR)/$(am__dirstamp) + -rm -f src/$(am__dirstamp) + -rm -f test/$(DEPDIR)/$(am__dirstamp) + -rm -f test/$(am__dirstamp) + -rm -f tools/$(DEPDIR)/$(am__dirstamp) + -rm -f tools/$(am__dirstamp) + -rm -f tracer/$(DEPDIR)/$(am__dirstamp) + -rm -f tracer/$(am__dirstamp) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -rm -f tools/lexer.c + -rm -f tools/parser.c + -rm -f tools/parser.h + -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES) +clean: clean-am + +clean-am: clean-alsaLTLIBRARIES clean-binPROGRAMS clean-cupsPROGRAMS \ + clean-generic clean-gstreamerLTLIBRARIES clean-libLTLIBRARIES \ + clean-libtool clean-local clean-noinstLIBRARIES \ + clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ + clean-pluginLTLIBRARIES clean-sbinPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf attrib/$(DEPDIR) audio/$(DEPDIR) btio/$(DEPDIR) compat/$(DEPDIR) cups/$(DEPDIR) gdbus/$(DEPDIR) health/$(DEPDIR) input/$(DEPDIR) lib/$(DEPDIR) network/$(DEPDIR) plugins/$(DEPDIR) sap/$(DEPDIR) sbc/$(DEPDIR) serial/$(DEPDIR) src/$(DEPDIR) test/$(DEPDIR) tools/$(DEPDIR) tracer/$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-hdr distclean-libtool distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-alsaLTLIBRARIES install-alsaconfDATA \ + install-confDATA install-cupsPROGRAMS install-dbusDATA \ + install-dist_udevSCRIPTS install-gstreamerLTLIBRARIES \ + install-includeHEADERS install-man install-pkgconfigDATA \ + install-pluginLTLIBRARIES install-rulesDATA install-stateDATA + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS install-libLTLIBRARIES \ + install-sbinPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: install-man1 install-man8 + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf $(top_srcdir)/autom4te.cache + -rm -rf attrib/$(DEPDIR) audio/$(DEPDIR) btio/$(DEPDIR) compat/$(DEPDIR) cups/$(DEPDIR) gdbus/$(DEPDIR) health/$(DEPDIR) input/$(DEPDIR) lib/$(DEPDIR) network/$(DEPDIR) plugins/$(DEPDIR) sap/$(DEPDIR) sbc/$(DEPDIR) serial/$(DEPDIR) src/$(DEPDIR) test/$(DEPDIR) tools/$(DEPDIR) tracer/$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-alsaLTLIBRARIES uninstall-alsaconfDATA \ + uninstall-binPROGRAMS uninstall-confDATA \ + uninstall-cupsPROGRAMS uninstall-dbusDATA \ + uninstall-dist_udevSCRIPTS uninstall-gstreamerLTLIBRARIES \ + uninstall-includeHEADERS uninstall-libLTLIBRARIES \ + uninstall-man uninstall-pkgconfigDATA \ + uninstall-pluginLTLIBRARIES uninstall-rulesDATA \ + uninstall-sbinPROGRAMS uninstall-stateDATA + +uninstall-man: uninstall-man1 uninstall-man8 + +.MAKE: all install-am install-strip + +.PHONY: CTAGS GTAGS all all-am am--refresh check check-am clean \ + clean-alsaLTLIBRARIES clean-binPROGRAMS clean-cupsPROGRAMS \ + clean-generic clean-gstreamerLTLIBRARIES clean-libLTLIBRARIES \ + clean-libtool clean-local clean-noinstLIBRARIES \ + clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ + clean-pluginLTLIBRARIES clean-sbinPROGRAMS ctags dist dist-all \ + dist-bzip2 dist-gzip dist-lzma dist-shar dist-tarZ dist-xz \ + dist-zip distcheck distclean distclean-compile \ + distclean-generic distclean-hdr distclean-libtool \ + distclean-tags distcleancheck distdir distuninstallcheck dvi \ + dvi-am html html-am info info-am install \ + install-alsaLTLIBRARIES install-alsaconfDATA install-am \ + install-binPROGRAMS install-confDATA install-cupsPROGRAMS \ + install-data install-data-am install-dbusDATA \ + install-dist_udevSCRIPTS install-dvi install-dvi-am \ + install-exec install-exec-am install-gstreamerLTLIBRARIES \ + install-html install-html-am install-includeHEADERS \ + install-info install-info-am install-libLTLIBRARIES \ + install-man install-man1 install-man8 install-pdf \ + install-pdf-am install-pkgconfigDATA install-pluginLTLIBRARIES \ + install-ps install-ps-am install-rulesDATA \ + install-sbinPROGRAMS install-stateDATA install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags uninstall uninstall-alsaLTLIBRARIES \ + uninstall-alsaconfDATA uninstall-am uninstall-binPROGRAMS \ + uninstall-confDATA uninstall-cupsPROGRAMS uninstall-dbusDATA \ + uninstall-dist_udevSCRIPTS uninstall-gstreamerLTLIBRARIES \ + uninstall-includeHEADERS uninstall-libLTLIBRARIES \ + uninstall-man uninstall-man1 uninstall-man8 \ + uninstall-pkgconfigDATA uninstall-pluginLTLIBRARIES \ + uninstall-rulesDATA uninstall-sbinPROGRAMS uninstall-stateDATA + + +@TOOLS_TRUE@tools/kword.c: tools/parser.h + +src/plugin.$(OBJEXT): src/builtin.h + +src/builtin.h: src/genbuiltin $(builtin_sources) + $(AM_V_GEN)$(srcdir)/src/genbuiltin $(builtin_modules) > $@ + +audio/telephony.c: audio/@TELEPHONY_DRIVER@ + $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@ + +sap/sap.c: sap/@SAP_DRIVER@ + $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@ + +scripts/%.rules: + $(AM_V_GEN)cp $(subst 97-,,$@) $@ + +$(lib_libbluetooth_la_OBJECTS): $(local_headers) + +lib/bluetooth/%.h: lib/%.h + $(AM_V_at)$(MKDIR_P) lib/bluetooth + $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@ + +clean-local: + $(RM) -r lib/bluetooth + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/Makefile.tools b/Makefile.tools new file mode 100644 index 0000000..7c5ff55 --- /dev/null +++ b/Makefile.tools @@ -0,0 +1,239 @@ + +if TOOLS +if CONFIGFILES +conf_DATA += tools/rfcomm.conf +endif + +bin_PROGRAMS += tools/rfcomm tools/l2ping \ + tools/hcitool tools/sdptool tools/ciptool + +sbin_PROGRAMS += tools/hciattach tools/hciconfig + +noinst_PROGRAMS += tools/avinfo tools/ppporc \ + tools/hcieventmask tools/hcisecfilter + +tools/kword.c: tools/parser.h + +tools_rfcomm_SOURCES = tools/rfcomm.c tools/parser.y tools/lexer.l \ + tools/kword.h tools/kword.c +EXTRA_tools_rfcomm_SOURCES = tools/parser.h tools/parser.c \ + tools/lexer.c +tools_rfcomm_LDADD = lib/libbluetooth.la + +tools_l2ping_LDADD = lib/libbluetooth.la + +tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \ + tools/hciattach_st.c \ + tools/hciattach_ti.c \ + tools/hciattach_tialt.c \ + tools/hciattach_ath3k.c \ + tools/hciattach_qualcomm.c +tools_hciattach_LDADD = lib/libbluetooth.la + +tools_hciconfig_SOURCES = tools/hciconfig.c tools/csr.h tools/csr.c \ + src/textfile.h src/textfile.c +tools_hciconfig_LDADD = lib/libbluetooth.la + +tools_hcitool_SOURCES = tools/hcitool.c src/oui.h src/oui.c \ + src/textfile.h src/textfile.c +tools_hcitool_LDADD = lib/libbluetooth.la + +tools_sdptool_SOURCES = tools/sdptool.c src/sdp-xml.h src/sdp-xml.c +tools_sdptool_LDADD = lib/libbluetooth.la + +tools_ciptool_LDADD = lib/libbluetooth.la + +tools_avinfo_LDADD = lib/libbluetooth.la + +tools_ppporc_LDADD = lib/libbluetooth.la + +tools_hcieventmask_LDADD = lib/libbluetooth.la + +dist_man_MANS += tools/rfcomm.1 tools/l2ping.8 \ + tools/hciattach.8 tools/hciconfig.8 \ + tools/hcitool.1 tools/sdptool.1 tools/ciptool.1 +else +EXTRA_DIST += tools/rfcomm.1 tools/l2ping.8 \ + tools/hciattach.8 tools/hciconfig.8 \ + tools/hcitool.1 tools/sdptool.1 tools/ciptool.1 +endif + +CLEANFILES += tools/lexer.c tools/parser.c tools/parser.h + +EXTRA_DIST += tools/rfcomm.conf + +if TRACER +sbin_PROGRAMS += tracer/hcitrace + +tracer_hcitrace_SOURCES = tracer/main.c +tracer_hcitrace_LDADD = lib/libbluetooth.la \ + @GLIB_LIBS@ @DBUS_LIBS@ @CAPNG_LIBS@ +tracer_hcitrace_DEPENDENCIES = lib/libbluetooth.la +endif + +if BCCMD +sbin_PROGRAMS += tools/bccmd + +tools_bccmd_SOURCES = tools/bccmd.c tools/csr.h tools/csr.c \ + tools/csr_hci.c tools/csr_h4.c tools/csr_3wire.c \ + tools/csr_bcsp.c tools/ubcsp.h tools/ubcsp.c +tools_bccmd_LDADD = lib/libbluetooth.la + +if USB +tools_bccmd_SOURCES += tools/csr_usb.c +tools_bccmd_LDADD += @USB_LIBS@ +endif + +dist_man_MANS += tools/bccmd.8 +else +EXTRA_DIST += tools/bccmd.8 +endif + +if HID2HCI +sbin_PROGRAMS += tools/hid2hci + +tools_hid2hci_LDADD = @USB_LIBS@ + +dist_man_MANS += tools/hid2hci.8 +else +EXTRA_DIST += tools/hid2hci.8 +endif + +if DFUTOOL +bin_PROGRAMS += tools/dfutool + +tools_dfutool_SOURCES = tools/dfutool.c tools/dfu.h tools/dfu.c +tools_dfutool_LDADD = @USB_LIBS@ + +dist_man_MANS += tools/dfutool.1 +else +EXTRA_DIST += tools/dfutool.1 +endif + + +if USB +noinst_PROGRAMS += tools/dfubabel tools/avctrl + +tools_dfubabel_LDADD = @USB_LIBS@ + +tools_avctrl_LDADD = @USB_LIBS@ +endif + +EXTRA_DIST += tools/dfubabel.1 tools/avctrl.8 + + +if CUPS +cupsdir = $(libdir)/cups/backend + +cups_PROGRAMS = cups/bluetooth + +cups_bluetooth_SOURCES = $(gdbus_sources) cups/main.c cups/cups.h \ + cups/sdp.c cups/spp.c cups/hcrp.c + +cups_bluetooth_LDADD = @GLIB_LIBS@ @DBUS_LIBS@ lib/libbluetooth.la +endif + + +if TEST +sbin_PROGRAMS += test/hciemu + +bin_PROGRAMS += test/l2test test/rctest + +noinst_PROGRAMS += test/gaptest test/sdptest test/scotest \ + test/attest test/hstest test/avtest test/ipctest \ + test/lmptest test/bdaddr test/agent \ + test/btiotest test/test-textfile \ + test/uuidtest + +test_hciemu_LDADD = @GLIB_LIBS@ lib/libbluetooth.la + +test_l2test_LDADD = lib/libbluetooth.la + +test_rctest_LDADD = lib/libbluetooth.la + +test_gaptest_LDADD = @DBUS_LIBS@ + +test_sdptest_LDADD = lib/libbluetooth.la + +test_scotest_LDADD = lib/libbluetooth.la + +test_attest_LDADD = lib/libbluetooth.la + +test_hstest_LDADD = lib/libbluetooth.la + +test_avtest_LDADD = lib/libbluetooth.la + +test_lmptest_LDADD = lib/libbluetooth.la + +test_ipctest_SOURCES = test/ipctest.c audio/ipc.h audio/ipc.c +test_ipctest_LDADD= @GLIB_LIBS@ sbc/libsbc.la + +test_bdaddr_SOURCES = test/bdaddr.c src/oui.h src/oui.c +test_bdaddr_LDADD = lib/libbluetooth.la + +test_agent_LDADD = @DBUS_LIBS@ + +test_btiotest_SOURCES = test/btiotest.c btio/btio.h btio/btio.c +test_btiotest_LDADD = @GLIB_LIBS@ lib/libbluetooth.la + +test_uuidtest_SOURCES = test/uuidtest.c +test_uuidtest_LDADD = lib/libbluetooth.la + +test_test_textfile_SOURCES = test/test-textfile.c src/textfile.h src/textfile.c + +dist_man_MANS += test/rctest.1 test/hciemu.1 + +EXTRA_DIST += test/bdaddr.8 +else +EXTRA_DIST += test/rctest.1 test/hciemu.1 test/bdaddr.8 +endif + +EXTRA_DIST += test/apitest test/sap-client test/hsplay test/hsmicro \ + test/dbusdef.py test/monitor-bluetooth test/list-devices \ + test/test-discovery test/test-manager test/test-adapter \ + test/test-device test/test-service test/test-serial \ + test/test-telephony test/test-network test/simple-agent \ + test/simple-service test/simple-endpoint test/test-audio \ + test/test-input test/test-attrib test/test-sap-server \ + test/service-record.dtd test/service-did.xml test/service-spp.xml \ + test/service-opp.xml test/service-ftp.xml + + +if HIDD +bin_PROGRAMS += compat/hidd + +compat_hidd_SOURCES = compat/hidd.c compat/hidd.h src/uinput.h \ + compat/sdp.h compat/sdp.c compat/fakehid.c \ + src/textfile.h src/textfile.c +compat_hidd_LDADD = -lm lib/libbluetooth.la + +dist_man_MANS += compat/hidd.1 +else +EXTRA_DIST += compat/hidd.1 +endif + +if PAND +bin_PROGRAMS += compat/pand + +compat_pand_SOURCES = compat/pand.c compat/pand.h \ + compat/bnep.c compat/sdp.h compat/sdp.c \ + src/textfile.h src/textfile.c +compat_pand_LDADD = lib/libbluetooth.la + +dist_man_MANS += compat/pand.1 +else +EXTRA_DIST += compat/pand.1 +endif + +if DUND +bin_PROGRAMS += compat/dund + +compat_dund_SOURCES = compat/dund.c compat/dund.h compat/lib.h \ + compat/sdp.h compat/sdp.c compat/dun.c compat/msdun.c \ + src/textfile.h src/textfile.c +compat_dund_LDADD = lib/libbluetooth.la + +dist_man_MANS += compat/dund.1 +else +EXTRA_DIST += compat/dund.1 +endif diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..77778e0 --- /dev/null +++ b/README @@ -0,0 +1,38 @@ +BlueZ - Bluetooth protocol stack for Linux +****************************************** + +Copyright (C) 2000-2001 Qualcomm Incorporated +Copyright (C) 2002-2003 Maxim Krasnyansky +Copyright (C) 2002-2010 Marcel Holtmann + + +Compilation and installation +============================ + +In order to compile Bluetooth utilities you need following software packages: + - Linux Bluetooth protocol stack (BlueZ) + - GCC compiler + - D-Bus library + - GLib library + - USB library (optional) + - Lexical Analyzer (flex, lex) + - YACC (yacc, bison, byacc) + +To configure run: + ./configure --prefix=/usr --mandir=/usr/share/man \ + --sysconfdir=/etc --localstatedir=/var --libexecdir=/lib + +Configure automatically searches for all required components and packages. + +To compile and install run: + make && make install + + +Information +=========== + +Mailing lists: + linux-bluetooth@vger.kernel.org + +For additional information about the project visit BlueZ web site: + http://www.bluez.org diff --git a/TODO b/TODO new file mode 100644 index 0000000..0e06f99 --- /dev/null +++ b/TODO @@ -0,0 +1,254 @@ +Background +========== + +- Priority scale: High, Medium and Low + +- Complexity scale: C1, C2, C4 and C8. The complexity scale is exponential, + with complexity 1 being the lowest complexity. Complexity is a function + of both task 'complexity' and task 'scope'. + + The general rule of thumb is that a complexity 1 task should take 1-2 weeks + for a person very familiar with BlueZ codebase. Higher complexity tasks + require more time and have higher uncertainty. + + Higher complexity tasks should be refined into several lower complexity tasks + once the task is better understood. + +General +========== + +- UUID handling: Use the new functions created for UUID handling in all parts + of BlueZ code. Currently, the new bt_uuid_* functions are being used by + GATT-related code only. + + Priority: high + Complexity: C4 + +- Rename glib-helper file to a more convenient name. The ideia is try to keep + only sdp helpers functions. bt_* prefix shall be also changed. + + Priority: Low + Complexity: C1 + +Low Energy +========== + +- Advertising management. Adapter interface needs to be changed to manage + connection modes, adapter type and advertising policy. See Volume 3, + Part C, section 9.3. If Attribute Server is enabled the LE capable + adapter shall to start advertising. Further investigation is necessary + to define which connectable mode needs to be supported: Non-connectable, + directed connectable and undirected connectable. Basically, two connectable + scenarios shall be addressed: + 1. GATT client is disconnected, but intends to become a Peripheral to + receive indications/notifications. + 2. GATT server intends to accept connections. + + Priority: Medium + Complexity: C2 + +- Define Auto Connection Establishment Procedure. Some profiles such as + Proximity requires an active link to identify path lost situation. It is + necessary to define how to manage connections, it seems that White List + is appropriated to address auto connections, however is not clear if the + this procedure shall be a profile specific detail or if the remote device + object can expose a property "WhiteList", maybe "Trusted" property can be + also used for this purpose. Another alternative is to define a method to + allow application to request/register the wanted scanning/connection + parameters. Before start this task, a RFC/PATCH shall be sent to the ML. + See Volume 3, Part C, section 9.3.5 for more information. + + Priority: Medium + Complexity: C2 + +- Implement a tool(or extend hciconfig) to setup the advertising parameters + and data. Extend hciconfig passing extra arguments when enabling the + advertises is not the right approach, it will be almost impossible to + address all arguments needed in an acceptable way. For testing, we need + a tool to change easily the AD Flags, the UUIDs and other data that can be + exported through the advertising data field. Suggestions: 1) extend hciconfig + passing a config file when enabling advertises; 2) write a ncurses based tool + + Priority: Medium + Complexity: C2 + +- Add new property in the DeviceFound signal to report the device type: + BR/EDR, single mode or dual-mode. + + Priority: Medium + Complexity: C1 + +- Privacy: When privacy is enabled in the adapter, LE scanning/connection + should use a private address. StartDiscovery method shall be changed and + new adapter property shall be added. + + Priority: Medium + Complexity: C1 + +- Static random address setup and storage. Once this address is written + in the a given remote, the address can not be changed anymore. + + Priority: Low + Complexity: C1 + +- Reconnection address: Reconnection address is a non resolvable private + address that the central writes in the peripheral. BlueZ will support + multiple profiles, it is not clear how it needs to be implemented. + Further discussion is necessary. + + Priority: Low + Complexity: C2 + +- Device Name Characteristic is a GAP characteristic for Low Energy. This + characteristic shall be integrated/used in the discovery procedure. The + ideia is to report the value of this characteristic using DeviceFound signals. + Discussion with the community is needed before to start this task. Other GAP + characteristics for LE needs to follow a similar approach. It is not clear + if all GAP characteristics can be exposed using properties instead of a primary + service characteristics. + See Volume 3, Part C, section 12.1 for more information. + + Priority: Low + Complexity: C2 + +ATT/GATT +======== + +- For BR/EDR, primary services can be registered based on the information + extracted from the service records. UUIDs, start and end handles information + are available in the record, Discover All Primary Services procedure is not + necessary. If a GATT service doesn't export a service record means that + it should not be used over BR/EDR. Don't start this task before to move the + attribute client code to the bluetoothd core. + + Priority: Medium + Complexity: C1 + +- At the moment authentication and authorization is not supported at the + same time, read/write requirements in the attribute server needs to + be extended. According to Bluetooth Specification a server shall check + authentication and authorization requirements before any other check is + performed. + + Priority: Medium + Complexity: C1 + +- ATT/GATT parsing to hcidump. Partially implemented, missing to fix + multiple advertises in the same event and RSSI. + + Priority: Medium + Complexity: C2 + +- Implement ATT PDU validation. Malformed PDUs can cause division by zero + when decoding PDUs. A proper error PDU should be returned for this case. + See decoding function in att.c file. + + Priority: Medium + Complexity: C1 + +- Refactor read_by_group() and read_by_type() in src/attrib-server.c + (they've grown simply too big). First step could be to move out the + long for-loops to new functions called e.g. get_groups() and get_types(). + + Priority: Low + Complexity: C1 + +- Agent for characteristics: Agent interface should be extended to support + authorization per characteristic if the remote is not in the trusted list. + + Priority: Low + Complexity: C1 + +- gatttool should have the ability to wait for req responses before + quitting (some servers require a small sleep even with cmd's). Maybe a + --delay-exit or --timeout command line switch. + + Priority: Low + Complexity: C1 + +- Refactoring of gatt.c functions. Currently, the callbacks of the services + and characteristics discovery functions return the ATT PDU and the caller + needs to call again the same function to fetch the remaining data when + necessary. Investigate if all results can be returned in the callback + result to avoid repeated code. Before change the code, please analyze + if this change will not break the GATT/ATT qualification tests. Maybe + an interactive fetch/query is necessary to pass the tests. + + Priority: Low + Complexity: C1 + +- Client needs to export a property in the Device Characteristic hierarchy + to manage characteristic value changes reports in the remote device. + Currently, Client Characteristic Configuration attribute is not exposed + as an object. The user needs to use gatttool to change the value of the + this attribute to receive notification/indications. Export this attribute + as a property is a proposal that needs further discussion. + + Priority: Low + Complexity: C1 + +- Attribute server should process queued GATT/ATT commands if the + client disconnects. The client can simply send a command and quit, + without wait for a response(ex: Write Command). For this scenario + that the client disconnects the link quickly the queued received + command is ignored. + + Priority: Low + Complecity: C1 + +- Add sdp discovery support to gattool with BR (--sdp, default is 0x1f) + + Priority: Low + Complexity: C1 + +- Implement Server characteristic Configuration support in the attribute + server to manage characteristic value broadcasting. There is a single + instance of the Server Characteristic Configuration for all clients. + See Volume 3, Part G, section 3.3.3.4 for more information. + + Priority: Low + Complexity: C1 + +- Long write is not implemented. Attribute server, client and command line + tool shall be changed to support this feature. + + Priority: Low + Complexity: C2 + +- Define attribute server API. External applications needs to register, + change attributes and to be notified about changes. Example: Proximity, + Time and Alert Profiles. "Local Service hierarchy" in the attribute-api + needs to be proposed and a RFC shall be sent to the ML. + + Priority: Low + Complexity: C2 + Owner: Anderson Lizardo + +Management Interface +==================== + +- Device discovery support (both for BR/EDR & LE) + + Priority: High + Complexity: C3 + +- EIR generation support + + Priority: High + Complexity: C2 + +- Blacklist support + + Priority: Medium + Complexity: C1 + +- mgmt_set_fast_connectable + + Priority: Medium + Complexity: C1 + +- Whitelist support (initially only for LE) + + Priority: Medium + Complexity: C2 + Owner: Andre Guedes diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..69e0740 --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,405 @@ +AC_DEFUN([AC_PROG_CC_PIE], [ + AC_CACHE_CHECK([whether ${CC-cc} accepts -fPIE], ac_cv_prog_cc_pie, [ + echo 'void f(){}' > conftest.c + if test -z "`${CC-cc} -fPIE -pie -c conftest.c 2>&1`"; then + ac_cv_prog_cc_pie=yes + else + ac_cv_prog_cc_pie=no + fi + rm -rf conftest* + ]) +]) + +AC_DEFUN([COMPILER_FLAGS], [ + if (test "${CFLAGS}" = ""); then + CFLAGS="-Wall -O2" + fi + if (test "$USE_MAINTAINER_MODE" = "yes"); then + CFLAGS="$CFLAGS -Werror -Wextra" + CFLAGS="$CFLAGS -Wno-unused-parameter" + CFLAGS="$CFLAGS -Wno-missing-field-initializers" + CFLAGS="$CFLAGS -Wdeclaration-after-statement" + CFLAGS="$CFLAGS -Wmissing-declarations" + CFLAGS="$CFLAGS -Wredundant-decls" + CFLAGS="$CFLAGS -Wcast-align" + CFLAGS="$CFLAGS -DG_DISABLE_DEPRECATED" + fi +]) + +AC_DEFUN([AC_FUNC_PPOLL], [ + AC_CHECK_FUNC(ppoll, dummy=yes, AC_DEFINE(NEED_PPOLL, 1, + [Define to 1 if you need the ppoll() function.])) +]) + +AC_DEFUN([AC_INIT_BLUEZ], [ + AC_PREFIX_DEFAULT(/usr/local) + + if (test "${prefix}" = "NONE"); then + dnl no prefix and no sysconfdir, so default to /etc + if (test "$sysconfdir" = '${prefix}/etc'); then + AC_SUBST([sysconfdir], ['/etc']) + fi + + dnl no prefix and no localstatedir, so default to /var + if (test "$localstatedir" = '${prefix}/var'); then + AC_SUBST([localstatedir], ['/var']) + fi + + dnl no prefix and no libexecdir, so default to /lib + if (test "$libexecdir" = '${exec_prefix}/libexec'); then + AC_SUBST([libexecdir], ['/lib']) + fi + + dnl no prefix and no mandir, so use ${prefix}/share/man as default + if (test "$mandir" = '${prefix}/man'); then + AC_SUBST([mandir], ['${prefix}/share/man']) + fi + + prefix="${ac_default_prefix}" + fi + + if (test "${libdir}" = '${exec_prefix}/lib'); then + libdir="${prefix}/lib" + fi + + plugindir="${libdir}/bluetooth/plugins" + + if (test "$sysconfdir" = '${prefix}/etc'); then + configdir="${prefix}/etc/bluetooth" + else + configdir="${sysconfdir}/bluetooth" + fi + + if (test "$localstatedir" = '${prefix}/var'); then + storagedir="${prefix}/var/lib/bluetooth" + else + storagedir="${localstatedir}/lib/bluetooth" + fi + + AC_DEFINE_UNQUOTED(CONFIGDIR, "${configdir}", + [Directory for the configuration files]) + AC_DEFINE_UNQUOTED(STORAGEDIR, "${storagedir}", + [Directory for the storage files]) + + AC_SUBST(CONFIGDIR, "${configdir}") + AC_SUBST(STORAGEDIR, "${storagedir}") + + UDEV_DATADIR="`$PKG_CONFIG --variable=udevdir udev`" + if (test -z "${UDEV_DATADIR}"); then + UDEV_DATADIR="${sysconfdir}/udev/rules.d" + else + UDEV_DATADIR="${UDEV_DATADIR}/rules.d" + fi + AC_SUBST(UDEV_DATADIR) +]) + +AC_DEFUN([AC_PATH_DBUS], [ + PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.0, dummy=yes, + AC_MSG_ERROR(D-Bus library is required)) + AC_CHECK_LIB(dbus-1, dbus_watch_get_unix_fd, dummy=yes, + AC_DEFINE(NEED_DBUS_WATCH_GET_UNIX_FD, 1, + [Define to 1 if you need the dbus_watch_get_unix_fd() function.])) + AC_CHECK_LIB(dbus-1, dbus_connection_can_send_type, dummy=yes, + AC_DEFINE(NEED_DBUS_CONNECTION_CAN_SEND_TYPE, 1, + [Define to 1 if you need the dbus_connection_can_send_type() function.] +)) + AC_SUBST(DBUS_CFLAGS) + AC_SUBST(DBUS_LIBS) +]) + +AC_DEFUN([AC_PATH_GLIB], [ + PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.16, dummy=yes, + AC_MSG_ERROR(GLib library version 2.16 or later is required)) + AC_SUBST(GLIB_CFLAGS) + AC_SUBST(GLIB_LIBS) +]) + +AC_DEFUN([AC_PATH_GSTREAMER], [ + PKG_CHECK_MODULES(GSTREAMER, gstreamer-0.10 >= 0.10.30 gstreamer-plugins-base-0.10, gstreamer_found=yes, + AC_MSG_WARN(GStreamer library version 0.10.30 or later is required);gstreamer_found=no) + AC_SUBST(GSTREAMER_CFLAGS) + AC_SUBST(GSTREAMER_LIBS) + GSTREAMER_PLUGINSDIR=`$PKG_CONFIG --variable=pluginsdir gstreamer-0.10` + AC_SUBST(GSTREAMER_PLUGINSDIR) +]) + +AC_DEFUN([AC_PATH_PULSE], [ + PKG_CHECK_MODULES(PULSE, libpulse, pulse_found=yes, pulse_found=no) + AC_SUBST(PULSE_CFLAGS) + AC_SUBST(PULSE_LIBS) +]) + +AC_DEFUN([AC_PATH_ALSA], [ + PKG_CHECK_MODULES(ALSA, alsa, alsa_found=yes, alsa_found=no) + AC_CHECK_LIB(rt, clock_gettime, ALSA_LIBS="$ALSA_LIBS -lrt", alsa_found=no) + AC_SUBST(ALSA_CFLAGS) + AC_SUBST(ALSA_LIBS) +]) + +AC_DEFUN([AC_PATH_USB], [ + PKG_CHECK_MODULES(USB, libusb, usb_found=yes, usb_found=no) + AC_SUBST(USB_CFLAGS) + AC_SUBST(USB_LIBS) + AC_CHECK_LIB(usb, usb_get_busses, dummy=yes, + AC_DEFINE(NEED_USB_GET_BUSSES, 1, + [Define to 1 if you need the usb_get_busses() function.])) + AC_CHECK_LIB(usb, usb_interrupt_read, dummy=yes, + AC_DEFINE(NEED_USB_INTERRUPT_READ, 1, + [Define to 1 if you need the usb_interrupt_read() function.])) +]) + +AC_DEFUN([AC_PATH_SNDFILE], [ + PKG_CHECK_MODULES(SNDFILE, sndfile, sndfile_found=yes, sndfile_found=no) + AC_SUBST(SNDFILE_CFLAGS) + AC_SUBST(SNDFILE_LIBS) +]) + +AC_DEFUN([AC_PATH_READLINE], [ + AC_CHECK_HEADER(readline/readline.h, + AC_CHECK_LIB(readline, main, + [ readline_found=yes + AC_SUBST(READLINE_LIBS, "-lreadline") + ], readline_found=no), + []) +]) + +AC_DEFUN([AC_PATH_OUI], [ + AC_ARG_WITH(ouifile, + AS_HELP_STRING([--with-ouifile=PATH],[Path to the oui.txt file @<:@auto@:>@]), + [ac_with_ouifile=$withval], + [ac_with_ouifile="/var/lib/misc/oui.txt"]) + AC_DEFINE_UNQUOTED(OUIFILE, ["$ac_with_ouifile"], [Define the OUI file path]) +]) + +AC_DEFUN([AC_ARG_BLUEZ], [ + debug_enable=no + optimization_enable=yes + fortify_enable=yes + pie_enable=yes + sndfile_enable=${sndfile_found} + hal_enable=no + usb_enable=${usb_found} + alsa_enable=${alsa_found} + gstreamer_enable=${gstreamer_found} + audio_enable=yes + input_enable=yes + serial_enable=yes + network_enable=yes + sap_enable=no + service_enable=yes + health_enable=no + pnat_enable=no + attrib_enable=no + tracer_enable=no + tools_enable=yes + hidd_enable=no + pand_enable=no + dund_enable=no + cups_enable=no + test_enable=no + bccmd_enable=no + pcmcia_enable=no + hid2hci_enable=no + dfutool_enable=no + udevrules_enable=yes + configfiles_enable=yes + telephony_driver=dummy + maemo6_enable=no + sap_driver=dummy + dbusoob_enable=no + + AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], [disable code optimization]), [ + optimization_enable=${enableval} + ]) + + AC_ARG_ENABLE(fortify, AC_HELP_STRING([--disable-fortify], [disable compile time buffer checks]), [ + fortify_enable=${enableval} + ]) + + AC_ARG_ENABLE(pie, AC_HELP_STRING([--disable-pie], [disable position independent executables flag]), [ + pie_enable=${enableval} + ]) + + AC_ARG_ENABLE(network, AC_HELP_STRING([--disable-network], [disable network plugin]), [ + network_enable=${enableval} + ]) + + AC_ARG_ENABLE(sap, AC_HELP_STRING([--enable-sap], [enable sap plugin]), [ + sap_enable=${enableval} + ]) + + AC_ARG_WITH(sap, AC_HELP_STRING([--with-sap=DRIVER], [select SAP driver]), [ + sap_driver=${withval} + ]) + AC_SUBST([SAP_DRIVER], [sap-${sap_driver}.c]) + + AC_ARG_ENABLE(serial, AC_HELP_STRING([--disable-serial], [disable serial plugin]), [ + serial_enable=${enableval} + ]) + + AC_ARG_ENABLE(input, AC_HELP_STRING([--disable-input], [disable input plugin]), [ + input_enable=${enableval} + ]) + + AC_ARG_ENABLE(audio, AC_HELP_STRING([--disable-audio], [disable audio plugin]), [ + audio_enable=${enableval} + ]) + + AC_ARG_ENABLE(service, AC_HELP_STRING([--disable-service], [disable service plugin]), [ + service_enable=${enableval} + ]) + + AC_ARG_ENABLE(health, AC_HELP_STRING([--enable-health], [enable health plugin]), [ + health_enable=${enableval} + ]) + + AC_ARG_ENABLE(pnat, AC_HELP_STRING([--enable-pnat], [enable pnat plugin]), [ + pnat_enable=${enableval} + ]) + + AC_ARG_ENABLE(attrib, AC_HELP_STRING([--enable-attrib], [enable attrib plugin]), [ + attrib_enable=${enableval} + ]) + + AC_ARG_ENABLE(gstreamer, AC_HELP_STRING([--enable-gstreamer], [enable GStreamer support]), [ + gstreamer_enable=${enableval} + ]) + + AC_ARG_ENABLE(alsa, AC_HELP_STRING([--enable-alsa], [enable ALSA support]), [ + alsa_enable=${enableval} + ]) + + AC_ARG_ENABLE(usb, AC_HELP_STRING([--enable-usb], [enable USB support]), [ + usb_enable=${enableval} + ]) + + AC_ARG_ENABLE(tracer, AC_HELP_STRING([--enable-tracer], [install Tracing daemon]), [ + tracer_enable=${enableval} + ]) + + AC_ARG_ENABLE(tools, AC_HELP_STRING([--enable-tools], [install Bluetooth utilities]), [ + tools_enable=${enableval} + ]) + + AC_ARG_ENABLE(bccmd, AC_HELP_STRING([--enable-bccmd], [install BCCMD interface utility]), [ + bccmd_enable=${enableval} + ]) + + AC_ARG_ENABLE(pcmcia, AC_HELP_STRING([--enable-pcmcia], [install PCMCIA serial script]), [ + pcmcia_enable=${enableval} + ]) + + AC_ARG_ENABLE(hid2hci, AC_HELP_STRING([--enable-hid2hci], [install HID mode switching utility]), [ + hid2hci_enable=${enableval} + ]) + + AC_ARG_ENABLE(dfutool, AC_HELP_STRING([--enable-dfutool], [install DFU firmware upgrade utility]), [ + dfutool_enable=${enableval} + ]) + + AC_ARG_ENABLE(hidd, AC_HELP_STRING([--enable-hidd], [install HID daemon]), [ + hidd_enable=${enableval} + ]) + + AC_ARG_ENABLE(pand, AC_HELP_STRING([--enable-pand], [install PAN daemon]), [ + pand_enable=${enableval} + ]) + + AC_ARG_ENABLE(dund, AC_HELP_STRING([--enable-dund], [install DUN daemon]), [ + dund_enable=${enableval} + ]) + + AC_ARG_ENABLE(cups, AC_HELP_STRING([--enable-cups], [install CUPS backend support]), [ + cups_enable=${enableval} + ]) + + AC_ARG_ENABLE(test, AC_HELP_STRING([--enable-test], [install test programs]), [ + test_enable=${enableval} + ]) + + AC_ARG_ENABLE(udevrules, AC_HELP_STRING([--enable-udevrules], [install Bluetooth udev rules]), [ + udevrules_enable=${enableval} + ]) + + AC_ARG_ENABLE(configfiles, AC_HELP_STRING([--enable-configfiles], [install Bluetooth configuration files]), [ + configfiles_enable=${enableval} + ]) + + AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], [enable compiling with debugging information]), [ + debug_enable=${enableval} + ]) + + AC_ARG_WITH(telephony, AC_HELP_STRING([--with-telephony=DRIVER], [select telephony driver]), [ + telephony_driver=${withval} + ]) + + AC_SUBST([TELEPHONY_DRIVER], [telephony-${telephony_driver}.c]) + + AC_ARG_ENABLE(maemo6, AC_HELP_STRING([--enable-maemo6], [compile with maemo6 plugin]), [ + maemo6_enable=${enableval} + ]) + + AC_ARG_ENABLE(dbusoob, AC_HELP_STRING([--enable-dbusoob], [compile with D-Bus OOB plugin]), [ + dbusoob_enable=${enableval} + ]) + + AC_ARG_ENABLE(hal, AC_HELP_STRING([--enable-hal], [Use HAL to determine adapter class]), [ + hal_enable=${enableval} + ]) + + if (test "${fortify_enable}" = "yes"); then + CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2" + fi + + if (test "${pie_enable}" = "yes" && test "${ac_cv_prog_cc_pie}" = "yes"); then + CFLAGS="$CFLAGS -fPIC" + LDFLAGS="$LDFLAGS -pie" + fi + + if (test "${debug_enable}" = "yes" && test "${ac_cv_prog_cc_g}" = "yes"); then + CFLAGS="$CFLAGS -g" + fi + + if (test "${optimization_enable}" = "no"); then + CFLAGS="$CFLAGS -O0" + fi + + if (test "${usb_enable}" = "yes" && test "${usb_found}" = "yes"); then + AC_DEFINE(HAVE_LIBUSB, 1, [Define to 1 if you have USB library.]) + fi + + AM_CONDITIONAL(SNDFILE, test "${sndfile_enable}" = "yes" && test "${sndfile_found}" = "yes") + AM_CONDITIONAL(USB, test "${usb_enable}" = "yes" && test "${usb_found}" = "yes") + AM_CONDITIONAL(SBC, test "${alsa_enable}" = "yes" || test "${gstreamer_enable}" = "yes" || + test "${test_enable}" = "yes") + AM_CONDITIONAL(ALSA, test "${alsa_enable}" = "yes" && test "${alsa_found}" = "yes") + AM_CONDITIONAL(GSTREAMER, test "${gstreamer_enable}" = "yes" && test "${gstreamer_found}" = "yes") + AM_CONDITIONAL(AUDIOPLUGIN, test "${audio_enable}" = "yes") + AM_CONDITIONAL(INPUTPLUGIN, test "${input_enable}" = "yes") + AM_CONDITIONAL(SERIALPLUGIN, test "${serial_enable}" = "yes") + AM_CONDITIONAL(NETWORKPLUGIN, test "${network_enable}" = "yes") + AM_CONDITIONAL(SAPPLUGIN, test "${sap_enable}" = "yes") + AM_CONDITIONAL(SERVICEPLUGIN, test "${service_enable}" = "yes") + AM_CONDITIONAL(HEALTHPLUGIN, test "${health_enable}" = "yes") + AM_CONDITIONAL(MCAP, test "${health_enable}" = "yes") + AM_CONDITIONAL(HAL, test "${hal_enable}" = "yes") + AM_CONDITIONAL(READLINE, test "${readline_found}" = "yes") + AM_CONDITIONAL(ATTRIBPLUGIN, test "${attrib_enable}" = "yes") + AM_CONDITIONAL(ECHOPLUGIN, test "no" = "yes") + AM_CONDITIONAL(PNATPLUGIN, test "${pnat_enable}" = "yes") + AM_CONDITIONAL(TRACER, test "${tracer_enable}" = "yes") + AM_CONDITIONAL(HIDD, test "${hidd_enable}" = "yes") + AM_CONDITIONAL(PAND, test "${pand_enable}" = "yes") + AM_CONDITIONAL(DUND, test "${dund_enable}" = "yes") + AM_CONDITIONAL(CUPS, test "${cups_enable}" = "yes") + AM_CONDITIONAL(TEST, test "${test_enable}" = "yes") + AM_CONDITIONAL(TOOLS, test "${tools_enable}" = "yes") + AM_CONDITIONAL(BCCMD, test "${bccmd_enable}" = "yes") + AM_CONDITIONAL(PCMCIA, test "${pcmcia_enable}" = "yes") + AM_CONDITIONAL(HID2HCI, test "${hid2hci_enable}" = "yes" && test "${usb_found}" = "yes") + AM_CONDITIONAL(DFUTOOL, test "${dfutool_enable}" = "yes" && test "${usb_found}" = "yes") + AM_CONDITIONAL(UDEVRULES, test "${udevrules_enable}" = "yes") + AM_CONDITIONAL(CONFIGFILES, test "${configfiles_enable}" = "yes") + AM_CONDITIONAL(MAEMO6PLUGIN, test "${maemo6_enable}" = "yes") + AM_CONDITIONAL(DBUSOOBPLUGIN, test "${dbusoob_enable}" = "yes") +]) diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 0000000..342d0bd --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,9192 @@ +# generated automatically by aclocal 1.11.1 -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.63],, +[m4_warning([this file was generated for autoconf 2.63. +You have another version of autoconf. It may work, but is not guaranteed to. +If you have problems, you may need to regenerate the build system entirely. +To do so, use the procedure documented by the package, typically `autoreconf'.])]) + +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- +# +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, +# 2006, 2007, 2008 Free Software Foundation, Inc. +# Written by Gordon Matzigkeit, 1996 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +m4_define([_LT_COPYING], [dnl +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, +# 2006, 2007, 2008 Free Software Foundation, Inc. +# Written by Gordon Matzigkeit, 1996 +# +# This file is part of GNU Libtool. +# +# GNU Libtool is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# As a special exception to the GNU General Public License, +# if you distribute this file as part of a program or library that +# is built using GNU Libtool, you may include this file under the +# same distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Libtool; see the file COPYING. If not, a copy +# can be downloaded from http://www.gnu.org/licenses/gpl.html, or +# obtained by writing to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +]) + +# serial 56 LT_INIT + + +# LT_PREREQ(VERSION) +# ------------------ +# Complain and exit if this libtool version is less that VERSION. +m4_defun([LT_PREREQ], +[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1, + [m4_default([$3], + [m4_fatal([Libtool version $1 or higher is required], + 63)])], + [$2])]) + + +# _LT_CHECK_BUILDDIR +# ------------------ +# Complain if the absolute build directory name contains unusual characters +m4_defun([_LT_CHECK_BUILDDIR], +[case `pwd` in + *\ * | *\ *) + AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;; +esac +]) + + +# LT_INIT([OPTIONS]) +# ------------------ +AC_DEFUN([LT_INIT], +[AC_PREREQ([2.58])dnl We use AC_INCLUDES_DEFAULT +AC_BEFORE([$0], [LT_LANG])dnl +AC_BEFORE([$0], [LT_OUTPUT])dnl +AC_BEFORE([$0], [LTDL_INIT])dnl +m4_require([_LT_CHECK_BUILDDIR])dnl + +dnl Autoconf doesn't catch unexpanded LT_ macros by default: +m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl +m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl +dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4 +dnl unless we require an AC_DEFUNed macro: +AC_REQUIRE([LTOPTIONS_VERSION])dnl +AC_REQUIRE([LTSUGAR_VERSION])dnl +AC_REQUIRE([LTVERSION_VERSION])dnl +AC_REQUIRE([LTOBSOLETE_VERSION])dnl +m4_require([_LT_PROG_LTMAIN])dnl + +dnl Parse OPTIONS +_LT_SET_OPTIONS([$0], [$1]) + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS="$ltmain" + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' +AC_SUBST(LIBTOOL)dnl + +_LT_SETUP + +# Only expand once: +m4_define([LT_INIT]) +])# LT_INIT + +# Old names: +AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT]) +AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PROG_LIBTOOL], []) +dnl AC_DEFUN([AM_PROG_LIBTOOL], []) + + +# _LT_CC_BASENAME(CC) +# ------------------- +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +m4_defun([_LT_CC_BASENAME], +[for cc_temp in $1""; do + case $cc_temp in + compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; + distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$ECHO "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` +]) + + +# _LT_FILEUTILS_DEFAULTS +# ---------------------- +# It is okay to use these file commands and assume they have been set +# sensibly after `m4_require([_LT_FILEUTILS_DEFAULTS])'. +m4_defun([_LT_FILEUTILS_DEFAULTS], +[: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} +])# _LT_FILEUTILS_DEFAULTS + + +# _LT_SETUP +# --------- +m4_defun([_LT_SETUP], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +_LT_DECL([], [host_alias], [0], [The host system])dnl +_LT_DECL([], [host], [0])dnl +_LT_DECL([], [host_os], [0])dnl +dnl +_LT_DECL([], [build_alias], [0], [The build system])dnl +_LT_DECL([], [build], [0])dnl +_LT_DECL([], [build_os], [0])dnl +dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +dnl +AC_REQUIRE([AC_PROG_LN_S])dnl +test -z "$LN_S" && LN_S="ln -s" +_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl +dnl +AC_REQUIRE([LT_CMD_MAX_LEN])dnl +_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl +_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl +dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_CMD_RELOAD])dnl +m4_require([_LT_CHECK_MAGIC_METHOD])dnl +m4_require([_LT_CMD_OLD_ARCHIVE])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl + +_LT_CONFIG_LIBTOOL_INIT([ +# See if we are running on zsh, and set the options which allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST +fi +]) +if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST +fi + +_LT_CHECK_OBJDIR + +m4_require([_LT_TAG_COMPILER])dnl +_LT_PROG_ECHO_BACKSLASH + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='s/\([["`$\\]]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\([["`\\]]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a `.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld="$lt_cv_prog_gnu_ld" + +old_CC="$CC" +old_CFLAGS="$CFLAGS" + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +_LT_CC_BASENAME([$compiler]) + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + _LT_PATH_MAGIC + fi + ;; +esac + +# Use C for the default configuration in the libtool script +LT_SUPPORTED_TAG([CC]) +_LT_LANG_C_CONFIG +_LT_LANG_DEFAULT_CONFIG +_LT_CONFIG_COMMANDS +])# _LT_SETUP + + +# _LT_PROG_LTMAIN +# --------------- +# Note that this code is called both from `configure', and `config.status' +# now that we use AC_CONFIG_COMMANDS to generate libtool. Notably, +# `config.status' has no value for ac_aux_dir unless we are using Automake, +# so we pass a copy along to make sure it has a sensible value anyway. +m4_defun([_LT_PROG_LTMAIN], +[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl +_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir']) +ltmain="$ac_aux_dir/ltmain.sh" +])# _LT_PROG_LTMAIN + + + +# So that we can recreate a full libtool script including additional +# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS +# in macros and then make a single call at the end using the `libtool' +# label. + + +# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS]) +# ---------------------------------------- +# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL_INIT], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_INIT], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_INIT]) + + +# _LT_CONFIG_LIBTOOL([COMMANDS]) +# ------------------------------ +# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS]) + + +# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS]) +# ----------------------------------------------------- +m4_defun([_LT_CONFIG_SAVE_COMMANDS], +[_LT_CONFIG_LIBTOOL([$1]) +_LT_CONFIG_LIBTOOL_INIT([$2]) +]) + + +# _LT_FORMAT_COMMENT([COMMENT]) +# ----------------------------- +# Add leading comment marks to the start of each line, and a trailing +# full-stop to the whole comment if one is not present already. +m4_define([_LT_FORMAT_COMMENT], +[m4_ifval([$1], [ +m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])], + [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.]) +)]) + + + + + +# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?]) +# ------------------------------------------------------------------- +# CONFIGNAME is the name given to the value in the libtool script. +# VARNAME is the (base) name used in the configure script. +# VALUE may be 0, 1 or 2 for a computed quote escaped value based on +# VARNAME. Any other value will be used directly. +m4_define([_LT_DECL], +[lt_if_append_uniq([lt_decl_varnames], [$2], [, ], + [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name], + [m4_ifval([$1], [$1], [$2])]) + lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3]) + m4_ifval([$4], + [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])]) + lt_dict_add_subkey([lt_decl_dict], [$2], + [tagged?], [m4_ifval([$5], [yes], [no])])]) +]) + + +# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION]) +# -------------------------------------------------------- +m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])]) + + +# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_tag_varnames], +[_lt_decl_filter([tagged?], [yes], $@)]) + + +# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..]) +# --------------------------------------------------------- +m4_define([_lt_decl_filter], +[m4_case([$#], + [0], [m4_fatal([$0: too few arguments: $#])], + [1], [m4_fatal([$0: too few arguments: $#: $1])], + [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)], + [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)], + [lt_dict_filter([lt_decl_dict], $@)])[]dnl +]) + + +# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...]) +# -------------------------------------------------- +m4_define([lt_decl_quote_varnames], +[_lt_decl_filter([value], [1], $@)]) + + +# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_dquote_varnames], +[_lt_decl_filter([value], [2], $@)]) + + +# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_varnames_tagged], +[m4_assert([$# <= 2])dnl +_$0(m4_quote(m4_default([$1], [[, ]])), + m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]), + m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))]) +m4_define([_lt_decl_varnames_tagged], +[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])]) + + +# lt_decl_all_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_all_varnames], +[_$0(m4_quote(m4_default([$1], [[, ]])), + m4_if([$2], [], + m4_quote(lt_decl_varnames), + m4_quote(m4_shift($@))))[]dnl +]) +m4_define([_lt_decl_all_varnames], +[lt_join($@, lt_decl_varnames_tagged([$1], + lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl +]) + + +# _LT_CONFIG_STATUS_DECLARE([VARNAME]) +# ------------------------------------ +# Quote a variable value, and forward it to `config.status' so that its +# declaration there will have the same value as in `configure'. VARNAME +# must have a single quote delimited value for this to work. +m4_define([_LT_CONFIG_STATUS_DECLARE], +[$1='`$ECHO "X$][$1" | $Xsed -e "$delay_single_quote_subst"`']) + + +# _LT_CONFIG_STATUS_DECLARATIONS +# ------------------------------ +# We delimit libtool config variables with single quotes, so when +# we write them to config.status, we have to be sure to quote all +# embedded single quotes properly. In configure, this macro expands +# each variable declared with _LT_DECL (and _LT_TAGDECL) into: +# +# ='`$ECHO "X$" | $Xsed -e "$delay_single_quote_subst"`' +m4_defun([_LT_CONFIG_STATUS_DECLARATIONS], +[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames), + [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAGS +# ---------------- +# Output comment and list of tags supported by the script +m4_defun([_LT_LIBTOOL_TAGS], +[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl +available_tags="_LT_TAGS"dnl +]) + + +# _LT_LIBTOOL_DECLARE(VARNAME, [TAG]) +# ----------------------------------- +# Extract the dictionary values for VARNAME (optionally with TAG) and +# expand to a commented shell variable setting: +# +# # Some comment about what VAR is for. +# visible_name=$lt_internal_name +m4_define([_LT_LIBTOOL_DECLARE], +[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], + [description])))[]dnl +m4_pushdef([_libtool_name], + m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl +m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])), + [0], [_libtool_name=[$]$1], + [1], [_libtool_name=$lt_[]$1], + [2], [_libtool_name=$lt_[]$1], + [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl +m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl +]) + + +# _LT_LIBTOOL_CONFIG_VARS +# ----------------------- +# Produce commented declarations of non-tagged libtool config variables +# suitable for insertion in the LIBTOOL CONFIG section of the `libtool' +# script. Tagged libtool config variables (even for the LIBTOOL CONFIG +# section) are produced by _LT_LIBTOOL_TAG_VARS. +m4_defun([_LT_LIBTOOL_CONFIG_VARS], +[m4_foreach([_lt_var], + m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAG_VARS(TAG) +# ------------------------- +m4_define([_LT_LIBTOOL_TAG_VARS], +[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])]) + + +# _LT_TAGVAR(VARNAME, [TAGNAME]) +# ------------------------------ +m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])]) + + +# _LT_CONFIG_COMMANDS +# ------------------- +# Send accumulated output to $CONFIG_STATUS. Thanks to the lists of +# variables for single and double quote escaping we saved from calls +# to _LT_DECL, we can put quote escaped variables declarations +# into `config.status', and then the shell code to quote escape them in +# for loops in `config.status'. Finally, any additional code accumulated +# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded. +m4_defun([_LT_CONFIG_COMMANDS], +[AC_PROVIDE_IFELSE([LT_OUTPUT], + dnl If the libtool generation code has been placed in $CONFIG_LT, + dnl instead of duplicating it all over again into config.status, + dnl then we will have config.status run $CONFIG_LT later, so it + dnl needs to know what name is stored there: + [AC_CONFIG_COMMANDS([libtool], + [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])], + dnl If the libtool generation code is destined for config.status, + dnl expand the accumulated commands and init code now: + [AC_CONFIG_COMMANDS([libtool], + [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])]) +])#_LT_CONFIG_COMMANDS + + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT], +[ + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +_LT_CONFIG_STATUS_DECLARATIONS +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# Quote evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_quote_varnames); do + case \`eval \\\\\$ECHO "X\\\\\$\$var"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"X\\\$\$var\\" | \\\$Xsed -e \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_dquote_varnames); do + case \`eval \\\\\$ECHO "X\\\\\$\$var"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"X\\\$\$var\\" | \\\$Xsed -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Fix-up fallback echo if it was mangled by the above quoting rules. +case \$lt_ECHO in +*'\\\[$]0 --fallback-echo"')dnl " + lt_ECHO=\`\$ECHO "X\$lt_ECHO" | \$Xsed -e 's/\\\\\\\\\\\\\\\[$]0 --fallback-echo"\[$]/\[$]0 --fallback-echo"/'\` + ;; +esac + +_LT_OUTPUT_LIBTOOL_INIT +]) + + +# LT_OUTPUT +# --------- +# This macro allows early generation of the libtool script (before +# AC_OUTPUT is called), incase it is used in configure for compilation +# tests. +AC_DEFUN([LT_OUTPUT], +[: ${CONFIG_LT=./config.lt} +AC_MSG_NOTICE([creating $CONFIG_LT]) +cat >"$CONFIG_LT" <<_LTEOF +#! $SHELL +# Generated by $as_me. +# Run this file to recreate a libtool stub with the current configuration. + +lt_cl_silent=false +SHELL=\${CONFIG_SHELL-$SHELL} +_LTEOF + +cat >>"$CONFIG_LT" <<\_LTEOF +AS_SHELL_SANITIZE +_AS_PREPARE + +exec AS_MESSAGE_FD>&1 +exec AS_MESSAGE_LOG_FD>>config.log +{ + echo + AS_BOX([Running $as_me.]) +} >&AS_MESSAGE_LOG_FD + +lt_cl_help="\ +\`$as_me' creates a local libtool stub from the current configuration, +for use in further configure time tests before the real libtool is +generated. + +Usage: $[0] [[OPTIONS]] + + -h, --help print this help, then exit + -V, --version print version number, then exit + -q, --quiet do not print progress messages + -d, --debug don't remove temporary files + +Report bugs to ." + +lt_cl_version="\ +m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl +m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION]) +configured by $[0], generated by m4_PACKAGE_STRING. + +Copyright (C) 2008 Free Software Foundation, Inc. +This config.lt script is free software; the Free Software Foundation +gives unlimited permision to copy, distribute and modify it." + +while test $[#] != 0 +do + case $[1] in + --version | --v* | -V ) + echo "$lt_cl_version"; exit 0 ;; + --help | --h* | -h ) + echo "$lt_cl_help"; exit 0 ;; + --debug | --d* | -d ) + debug=: ;; + --quiet | --q* | --silent | --s* | -q ) + lt_cl_silent=: ;; + + -*) AC_MSG_ERROR([unrecognized option: $[1] +Try \`$[0] --help' for more information.]) ;; + + *) AC_MSG_ERROR([unrecognized argument: $[1] +Try \`$[0] --help' for more information.]) ;; + esac + shift +done + +if $lt_cl_silent; then + exec AS_MESSAGE_FD>/dev/null +fi +_LTEOF + +cat >>"$CONFIG_LT" <<_LTEOF +_LT_OUTPUT_LIBTOOL_COMMANDS_INIT +_LTEOF + +cat >>"$CONFIG_LT" <<\_LTEOF +AC_MSG_NOTICE([creating $ofile]) +_LT_OUTPUT_LIBTOOL_COMMANDS +AS_EXIT(0) +_LTEOF +chmod +x "$CONFIG_LT" + +# configure is writing to config.log, but config.lt does its own redirection, +# appending to config.log, which fails on DOS, as config.log is still kept +# open by configure. Here we exec the FD to /dev/null, effectively closing +# config.log, so it can be properly (re)opened and appended to by config.lt. +if test "$no_create" != yes; then + lt_cl_success=: + test "$silent" = yes && + lt_config_lt_args="$lt_config_lt_args --quiet" + exec AS_MESSAGE_LOG_FD>/dev/null + $SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false + exec AS_MESSAGE_LOG_FD>>config.log + $lt_cl_success || AS_EXIT(1) +fi +])# LT_OUTPUT + + +# _LT_CONFIG(TAG) +# --------------- +# If TAG is the built-in tag, create an initial libtool script with a +# default configuration from the untagged config vars. Otherwise add code +# to config.status for appending the configuration named by TAG from the +# matching tagged config vars. +m4_defun([_LT_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_CONFIG_SAVE_COMMANDS([ + m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl + m4_if(_LT_TAG, [C], [ + # See if we are running on zsh, and set the options which allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST + fi + + cfgfile="${ofile}T" + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL + +# `$ECHO "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services. +# Generated automatically by $as_me ($PACKAGE$TIMESTAMP) $VERSION +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: +# NOTE: Changes made to this file will be lost: look at ltmain.sh. +# +_LT_COPYING +_LT_LIBTOOL_TAGS + +# ### BEGIN LIBTOOL CONFIG +_LT_LIBTOOL_CONFIG_VARS +_LT_LIBTOOL_TAG_VARS +# ### END LIBTOOL CONFIG + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + _LT_PROG_LTMAIN + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '/^# Generated shell functions inserted here/q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + _LT_PROG_XSI_SHELLFNS + + sed -n '/^# Generated shell functions inserted here/,$p' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +], +[cat <<_LT_EOF >> "$ofile" + +dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded +dnl in a comment (ie after a #). +# ### BEGIN LIBTOOL TAG CONFIG: $1 +_LT_LIBTOOL_TAG_VARS(_LT_TAG) +# ### END LIBTOOL TAG CONFIG: $1 +_LT_EOF +])dnl /m4_if +], +[m4_if([$1], [], [ + PACKAGE='$PACKAGE' + VERSION='$VERSION' + TIMESTAMP='$TIMESTAMP' + RM='$RM' + ofile='$ofile'], []) +])dnl /_LT_CONFIG_SAVE_COMMANDS +])# _LT_CONFIG + + +# LT_SUPPORTED_TAG(TAG) +# --------------------- +# Trace this macro to discover what tags are supported by the libtool +# --tag option, using: +# autoconf --trace 'LT_SUPPORTED_TAG:$1' +AC_DEFUN([LT_SUPPORTED_TAG], []) + + +# C support is built-in for now +m4_define([_LT_LANG_C_enabled], []) +m4_define([_LT_TAGS], []) + + +# LT_LANG(LANG) +# ------------- +# Enable libtool support for the given language if not already enabled. +AC_DEFUN([LT_LANG], +[AC_BEFORE([$0], [LT_OUTPUT])dnl +m4_case([$1], + [C], [_LT_LANG(C)], + [C++], [_LT_LANG(CXX)], + [Java], [_LT_LANG(GCJ)], + [Fortran 77], [_LT_LANG(F77)], + [Fortran], [_LT_LANG(FC)], + [Windows Resource], [_LT_LANG(RC)], + [m4_ifdef([_LT_LANG_]$1[_CONFIG], + [_LT_LANG($1)], + [m4_fatal([$0: unsupported language: "$1"])])])dnl +])# LT_LANG + + +# _LT_LANG(LANGNAME) +# ------------------ +m4_defun([_LT_LANG], +[m4_ifdef([_LT_LANG_]$1[_enabled], [], + [LT_SUPPORTED_TAG([$1])dnl + m4_append([_LT_TAGS], [$1 ])dnl + m4_define([_LT_LANG_]$1[_enabled], [])dnl + _LT_LANG_$1_CONFIG($1)])dnl +])# _LT_LANG + + +# _LT_LANG_DEFAULT_CONFIG +# ----------------------- +m4_defun([_LT_LANG_DEFAULT_CONFIG], +[AC_PROVIDE_IFELSE([AC_PROG_CXX], + [LT_LANG(CXX)], + [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])]) + +AC_PROVIDE_IFELSE([AC_PROG_F77], + [LT_LANG(F77)], + [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])]) + +AC_PROVIDE_IFELSE([AC_PROG_FC], + [LT_LANG(FC)], + [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])]) + +dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal +dnl pulling things in needlessly. +AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([LT_PROG_GCJ], + [LT_LANG(GCJ)], + [m4_ifdef([AC_PROG_GCJ], + [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([A][M_PROG_GCJ], + [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([LT_PROG_GCJ], + [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])]) + +AC_PROVIDE_IFELSE([LT_PROG_RC], + [LT_LANG(RC)], + [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])]) +])# _LT_LANG_DEFAULT_CONFIG + +# Obsolete macros: +AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)]) +AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)]) +AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)]) +AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_CXX], []) +dnl AC_DEFUN([AC_LIBTOOL_F77], []) +dnl AC_DEFUN([AC_LIBTOOL_FC], []) +dnl AC_DEFUN([AC_LIBTOOL_GCJ], []) + + +# _LT_TAG_COMPILER +# ---------------- +m4_defun([_LT_TAG_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl +_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl +_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl +_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_TAG_COMPILER + + +# _LT_COMPILER_BOILERPLATE +# ------------------------ +# Check for compiler boilerplate output or warnings with +# the simple compiler test code. +m4_defun([_LT_COMPILER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* +])# _LT_COMPILER_BOILERPLATE + + +# _LT_LINKER_BOILERPLATE +# ---------------------- +# Check for linker boilerplate output or warnings with +# the simple link test code. +m4_defun([_LT_LINKER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* +])# _LT_LINKER_BOILERPLATE + +# _LT_REQUIRED_DARWIN_CHECKS +# ------------------------- +m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[ + case $host_os in + rhapsody* | darwin*) + AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) + AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) + AC_CHECK_TOOL([LIPO], [lipo], [:]) + AC_CHECK_TOOL([OTOOL], [otool], [:]) + AC_CHECK_TOOL([OTOOL64], [otool64], [:]) + _LT_DECL([], [DSYMUTIL], [1], + [Tool to manipulate archived DWARF debug symbol files on Mac OS X]) + _LT_DECL([], [NMEDIT], [1], + [Tool to change global to local symbols on Mac OS X]) + _LT_DECL([], [LIPO], [1], + [Tool to manipulate fat objects and archives on Mac OS X]) + _LT_DECL([], [OTOOL], [1], + [ldd/readelf like tool for Mach-O binaries on Mac OS X]) + _LT_DECL([], [OTOOL64], [1], + [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4]) + + AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], + [lt_cv_apple_cc_single_mod=no + if test -z "${LT_MULTI_MODULE}"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + if test -f libconftest.dylib && test ! -s conftest.err && test $_lt_result = 0; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi]) + AC_CACHE_CHECK([for -exported_symbols_list linker flag], + [lt_cv_ld_exported_symbols_list], + [lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [lt_cv_ld_exported_symbols_list=yes], + [lt_cv_ld_exported_symbols_list=no]) + LDFLAGS="$save_LDFLAGS" + ]) + case $host_os in + rhapsody* | darwin1.[[012]]) + _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) + _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;; + 10.[[012]]*) + _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test "$lt_cv_apple_cc_single_mod" = "yes"; then + _lt_dar_single_mod='$single_module' + fi + if test "$lt_cv_ld_exported_symbols_list" = "yes"; then + _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/${libname}-symbols.expsym ${lib}' + fi + if test "$DSYMUTIL" != ":"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac +]) + + +# _LT_DARWIN_LINKER_FEATURES +# -------------------------- +# Checks for linker and compiler features on darwin +m4_defun([_LT_DARWIN_LINKER_FEATURES], +[ + m4_require([_LT_REQUIRED_DARWIN_CHECKS]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_automatic, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_TAGVAR(whole_archive_flag_spec, $1)='' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)="$_lt_dar_allow_undefined" + case $cc_basename in + ifort*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test "$_lt_dar_can_shared" = "yes"; then + output_verbose_link_cmd=echo + _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}" + _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}" + _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}" + m4_if([$1], [CXX], +[ if test "$lt_cv_apple_cc_single_mod" != "yes"; then + _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dsymutil}" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dar_export_syms}${_lt_dsymutil}" + fi +],[]) + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi +]) + +# _LT_SYS_MODULE_PATH_AIX +# ----------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +m4_defun([_LT_SYS_MODULE_PATH_AIX], +[m4_require([_LT_DECL_SED])dnl +AC_LINK_IFELSE(AC_LANG_PROGRAM,[ +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi],[]) +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi +])# _LT_SYS_MODULE_PATH_AIX + + +# _LT_SHELL_INIT(ARG) +# ------------------- +m4_define([_LT_SHELL_INIT], +[ifdef([AC_DIVERSION_NOTICE], + [AC_DIVERT_PUSH(AC_DIVERSION_NOTICE)], + [AC_DIVERT_PUSH(NOTICE)]) +$1 +AC_DIVERT_POP +])# _LT_SHELL_INIT + + +# _LT_PROG_ECHO_BACKSLASH +# ----------------------- +# Add some code to the start of the generated configure script which +# will find an echo command which doesn't interpret backslashes. +m4_defun([_LT_PROG_ECHO_BACKSLASH], +[_LT_SHELL_INIT([ +# Check that we are running under the correct shell. +SHELL=${CONFIG_SHELL-/bin/sh} + +case X$lt_ECHO in +X*--fallback-echo) + # Remove one level of quotation (which was required for Make). + ECHO=`echo "$lt_ECHO" | sed 's,\\\\\[$]\\[$]0,'[$]0','` + ;; +esac + +ECHO=${lt_ECHO-echo} +if test "X[$]1" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift +elif test "X[$]1" = X--fallback-echo; then + # Avoid inline document here, it may be left over + : +elif test "X`{ $ECHO '\t'; } 2>/dev/null`" = 'X\t' ; then + # Yippee, $ECHO works! + : +else + # Restart under the correct shell. + exec $SHELL "[$]0" --no-reexec ${1+"[$]@"} +fi + +if test "X[$]1" = X--fallback-echo; then + # used as fallback echo + shift + cat <<_LT_EOF +[$]* +_LT_EOF + exit 0 +fi + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +if test -z "$lt_ECHO"; then + if test "X${echo_test_string+set}" != Xset; then + # find a string as large as possible, as long as the shell can cope with it + for cmd in 'sed 50q "[$]0"' 'sed 20q "[$]0"' 'sed 10q "[$]0"' 'sed 2q "[$]0"' 'echo test'; do + # expected sizes: less than 2Kb, 1Kb, 512 bytes, 16 bytes, ... + if { echo_test_string=`eval $cmd`; } 2>/dev/null && + { test "X$echo_test_string" = "X$echo_test_string"; } 2>/dev/null + then + break + fi + done + fi + + if test "X`{ $ECHO '\t'; } 2>/dev/null`" = 'X\t' && + echo_testing_string=`{ $ECHO "$echo_test_string"; } 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + : + else + # The Solaris, AIX, and Digital Unix default echo programs unquote + # backslashes. This makes it impossible to quote backslashes using + # echo "$something" | sed 's/\\/\\\\/g' + # + # So, first we look for a working echo in the user's PATH. + + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for dir in $PATH /usr/ucb; do + IFS="$lt_save_ifs" + if (test -f $dir/echo || test -f $dir/echo$ac_exeext) && + test "X`($dir/echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($dir/echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + ECHO="$dir/echo" + break + fi + done + IFS="$lt_save_ifs" + + if test "X$ECHO" = Xecho; then + # We didn't find a better echo, so look for alternatives. + if test "X`{ print -r '\t'; } 2>/dev/null`" = 'X\t' && + echo_testing_string=`{ print -r "$echo_test_string"; } 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # This shell has a builtin print -r that does the trick. + ECHO='print -r' + elif { test -f /bin/ksh || test -f /bin/ksh$ac_exeext; } && + test "X$CONFIG_SHELL" != X/bin/ksh; then + # If we have ksh, try running configure again with it. + ORIGINAL_CONFIG_SHELL=${CONFIG_SHELL-/bin/sh} + export ORIGINAL_CONFIG_SHELL + CONFIG_SHELL=/bin/ksh + export CONFIG_SHELL + exec $CONFIG_SHELL "[$]0" --no-reexec ${1+"[$]@"} + else + # Try using printf. + ECHO='printf %s\n' + if test "X`{ $ECHO '\t'; } 2>/dev/null`" = 'X\t' && + echo_testing_string=`{ $ECHO "$echo_test_string"; } 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # Cool, printf works + : + elif echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + CONFIG_SHELL=$ORIGINAL_CONFIG_SHELL + export CONFIG_SHELL + SHELL="$CONFIG_SHELL" + export SHELL + ECHO="$CONFIG_SHELL [$]0 --fallback-echo" + elif echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + ECHO="$CONFIG_SHELL [$]0 --fallback-echo" + else + # maybe with a smaller string... + prev=: + + for cmd in 'echo test' 'sed 2q "[$]0"' 'sed 10q "[$]0"' 'sed 20q "[$]0"' 'sed 50q "[$]0"'; do + if { test "X$echo_test_string" = "X`eval $cmd`"; } 2>/dev/null + then + break + fi + prev="$cmd" + done + + if test "$prev" != 'sed 50q "[$]0"'; then + echo_test_string=`eval $prev` + export echo_test_string + exec ${ORIGINAL_CONFIG_SHELL-${CONFIG_SHELL-/bin/sh}} "[$]0" ${1+"[$]@"} + else + # Oops. We lost completely, so just stick with echo. + ECHO=echo + fi + fi + fi + fi + fi +fi + +# Copy echo and quote the copy suitably for passing to libtool from +# the Makefile, instead of quoting the original, which is used later. +lt_ECHO=$ECHO +if test "X$lt_ECHO" = "X$CONFIG_SHELL [$]0 --fallback-echo"; then + lt_ECHO="$CONFIG_SHELL \\\$\[$]0 --fallback-echo" +fi + +AC_SUBST(lt_ECHO) +]) +_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts]) +_LT_DECL([], [ECHO], [1], + [An echo program that does not interpret backslashes]) +])# _LT_PROG_ECHO_BACKSLASH + + +# _LT_ENABLE_LOCK +# --------------- +m4_defun([_LT_ENABLE_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AS_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE="32" + ;; + *ELF-64*) + HPUX_IA64_MODE="64" + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out which ABI we are using. + echo '[#]line __oline__ "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test "$lt_cv_prog_gnu_ld" = yes; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_i386" + ;; + ppc64-*linux*|powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + ppc*-*linux*|powerpc*-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test x"$lt_cv_cc_needs_belf" != x"yes"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS="$SAVE_CFLAGS" + fi + ;; +sparc*-*solaris*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) LD="${LD-ld} -m elf64_sparc" ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks="$enable_libtool_lock" +])# _LT_ENABLE_LOCK + + +# _LT_CMD_OLD_ARCHIVE +# ------------------- +m4_defun([_LT_CMD_OLD_ARCHIVE], +[AC_CHECK_TOOL(AR, ar, false) +test -z "$AR" && AR=ar +test -z "$AR_FLAGS" && AR_FLAGS=cru +_LT_DECL([], [AR], [1], [The archiver]) +_LT_DECL([], [AR_FLAGS], [1]) + +AC_CHECK_TOOL(STRIP, strip, :) +test -z "$STRIP" && STRIP=: +_LT_DECL([], [STRIP], [1], [A symbol stripping program]) + +AC_CHECK_TOOL(RANLIB, ranlib, :) +test -z "$RANLIB" && RANLIB=: +_LT_DECL([], [RANLIB], [1], + [Commands used to install an old-style archive]) + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$oldlib" +fi +_LT_DECL([], [old_postinstall_cmds], [2]) +_LT_DECL([], [old_postuninstall_cmds], [2]) +_LT_TAGDECL([], [old_archive_cmds], [2], + [Commands used to build an old-style archive]) +])# _LT_CMD_OLD_ARCHIVE + + +# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([_LT_COMPILER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:__oline__: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:__oline__: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + fi + $RM conftest* +]) + +if test x"[$]$2" = xyes; then + m4_if([$5], , :, [$5]) +else + m4_if([$6], , :, [$6]) +fi +])# _LT_COMPILER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], []) + + +# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------- +# Check whether the given linker option works +AC_DEFUN([_LT_LINKER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $3" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + $ECHO "X$_lt_linker_boilerplate" | $Xsed -e '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + else + $2=yes + fi + fi + $RM -r conftest* + LDFLAGS="$save_LDFLAGS" +]) + +if test x"[$]$2" = xyes; then + m4_if([$4], , :, [$4]) +else + m4_if([$5], , :, [$5]) +fi +])# _LT_LINKER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], []) + + +# LT_CMD_MAX_LEN +#--------------- +AC_DEFUN([LT_CMD_MAX_LEN], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + teststring="ABCD" + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + netbsd* | freebsd* | openbsd* | darwin* | dragonfly*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[ ]]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8 ; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test "X"`$SHELL [$]0 --fallback-echo "X$teststring$teststring" 2>/dev/null` \ + = "XX$teststring$teststring"; } >/dev/null 2>&1 && + test $i != 17 # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac +]) +if test -n $lt_cv_sys_max_cmd_len ; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +max_cmd_len=$lt_cv_sys_max_cmd_len +_LT_DECL([], [max_cmd_len], [0], + [What is the maximum length of a command?]) +])# LT_CMD_MAX_LEN + +# Old name: +AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], []) + + +# _LT_HEADER_DLFCN +# ---------------- +m4_defun([_LT_HEADER_DLFCN], +[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl +])# _LT_HEADER_DLFCN + + +# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# ---------------------------------------------------------------- +m4_defun([_LT_TRY_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test "$cross_compiling" = yes; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +[#line __oline__ "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +void fnord() { int i=42;} +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +}] +_LT_EOF + if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext} 2>/dev/null; then + (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_dlunknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_TRY_DLOPEN_SELF + + +# LT_SYS_DLOPEN_SELF +# ------------------ +AC_DEFUN([LT_SYS_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test "x$enable_dlopen" != xyes; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen="load_add_on" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen="LoadLibrary" + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen="dlopen" + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],[ + lt_cv_dlopen="dyld" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen="shl_load"], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld"], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen="dlopen"], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld"]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test "x$lt_cv_dlopen" != xno; then + enable_dlopen=yes + else + enable_dlopen=no + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS="$CPPFLAGS" + test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS="$LDFLAGS" + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS="$LIBS" + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test "x$lt_cv_dlopen_self" = xyes; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +_LT_DECL([dlopen_support], [enable_dlopen], [0], + [Whether dlopen is supported]) +_LT_DECL([dlopen_self], [enable_dlopen_self], [0], + [Whether dlopen of programs is supported]) +_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0], + [Whether dlopen of statically linked programs is supported]) +])# LT_SYS_DLOPEN_SELF + +# Old name: +AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], []) + + +# _LT_COMPILER_C_O([TAGNAME]) +# --------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler. +# This macro does not hard code the compiler like AC_PROG_CC_C_O. +m4_defun([_LT_COMPILER_C_O], +[m4_require([_LT_DECL_SED])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:__oline__: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:__oline__: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . 2>&AS_MESSAGE_LOG_FD + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* +]) +_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1], + [Does compiler simultaneously support -c and -o options?]) +])# _LT_COMPILER_C_O + + +# _LT_COMPILER_FILE_LOCKS([TAGNAME]) +# ---------------------------------- +# Check to see if we can do hard links to lock some files if needed +m4_defun([_LT_COMPILER_FILE_LOCKS], +[m4_require([_LT_ENABLE_LOCK])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_COMPILER_C_O([$1]) + +hard_links="nottested" +if test "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test "$hard_links" = no; then + AC_MSG_WARN([`$CC' does not support `-c -o', so `make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?]) +])# _LT_COMPILER_FILE_LOCKS + + +# _LT_CHECK_OBJDIR +# ---------------- +m4_defun([_LT_CHECK_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +_LT_DECL([], [objdir], [0], + [The name of the directory that contains temporary libtool files])dnl +m4_pattern_allow([LT_OBJDIR])dnl +AC_DEFINE_UNQUOTED(LT_OBJDIR, "$lt_cv_objdir/", + [Define to the sub-directory in which libtool stores uninstalled libraries.]) +])# _LT_CHECK_OBJDIR + + +# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME]) +# -------------------------------------- +# Check hardcoding attributes. +m4_defun([_LT_LINKER_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" || + test -n "$_LT_TAGVAR(runpath_var, $1)" || + test "X$_LT_TAGVAR(hardcode_automatic, $1)" = "Xyes" ; then + + # We can hardcode non-existent directories. + if test "$_LT_TAGVAR(hardcode_direct, $1)" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" != no && + test "$_LT_TAGVAR(hardcode_minus_L, $1)" != no; then + # Linking always hardcodes the temporary library directory. + _LT_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)]) + +if test "$_LT_TAGVAR(hardcode_action, $1)" = relink || + test "$_LT_TAGVAR(inherit_rpath, $1)" = yes; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi +_LT_TAGDECL([], [hardcode_action], [0], + [How to hardcode a shared library path into an executable]) +])# _LT_LINKER_HARDCODE_LIBPATH + + +# _LT_CMD_STRIPLIB +# ---------------- +m4_defun([_LT_CMD_STRIPLIB], +[m4_require([_LT_DECL_EGREP]) +striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP" ; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +_LT_DECL([], [old_striplib], [1], [Commands to strip libraries]) +_LT_DECL([], [striplib], [1]) +])# _LT_CMD_STRIPLIB + + +# _LT_SYS_DYNAMIC_LINKER([TAG]) +# ----------------------------- +# PORTME Fill in your ld.so characteristics +m4_defun([_LT_SYS_DYNAMIC_LINKER], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_OBJDUMP])dnl +m4_require([_LT_DECL_SED])dnl +AC_MSG_CHECKING([dynamic linker characteristics]) +m4_if([$1], + [], [ +if test "$GCC" = yes; then + case $host_os in + darwin*) lt_awk_arg="/^libraries:/,/LR/" ;; + *) lt_awk_arg="/^libraries:/" ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if $ECHO "$lt_search_path_spec" | $GREP ';' >/dev/null ; then + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED -e 's/;/ /g'` + else + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary. + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path/$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir" + else + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO $lt_tmp_lt_search_path_spec | awk ' +BEGIN {RS=" "; FS="/|\n";} { + lt_foo=""; + lt_count=0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo="/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[[lt_foo]]++; } + if (lt_freq[[lt_foo]] == 1) { print lt_foo; } +}'` + sys_lib_search_path_spec=`$ECHO $lt_search_path_spec` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix[[4-9]]*) + version_type=linux + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$ECHO "X$lib" | $Xsed -e '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[[45]]*) + version_type=linux + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$host_os in + yes,cygwin* | yes,mingw* | yes,pw32* | yes,cegcc*) + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec="/usr/lib /lib/w32api /lib /usr/local/lib" + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec=`$CC -print-search-dirs | $GREP "^libraries:" | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH printed by + # mingw gcc, but we are running on Cygwin. Gcc prints its search + # path with ; separators, and with drive letters. We can handle the + # drive letters (cygwin fileutils understands them), so leave them, + # especially as we might pass files found there to a mingw objdump, + # which wouldn't understand a cygwinified path. Ahh. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + ;; + esac + ;; + + *) + library_names_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext} $libname.lib' + ;; + esac + dynamic_linker='Win32 ld.exe' + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd1*) + dynamic_linker=no + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[[123]]*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[[01]]* | freebsdelf3.[[01]]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ + freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555. + postinstall_cmds='chmod 555 $lib' + ;; + +interix[[3-9]]*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be Linux ELF. +linux* | k*bsd*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + # Some binutils ld are patched to set DT_RUNPATH + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \ + LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\"" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null], + [shlibpath_overrides_runpath=yes])]) + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Add ABI-specific directories to the system library path. + sys_lib_dlsearch_path_spec="/lib64 /usr/lib64 /lib /usr/lib" + + # Append ld.so.conf contents to the search path + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="$sys_lib_dlsearch_path_spec $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd*) + version_type=sunos + sys_lib_dlsearch_path_spec="/usr/lib" + need_lib_prefix=no + # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs. + case $host_os in + openbsd3.3 | openbsd3.3.*) need_version=yes ;; + *) need_version=no ;; + esac + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[[89]] | openbsd2.[[89]].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext_cmds=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=freebsd-elf + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test "$with_gnu_ld" = yes; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test "$dynamic_linker" = no && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then + sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec" +fi +if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then + sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec" +fi + +_LT_DECL([], [variables_saved_for_relink], [1], + [Variables whose values should be saved in libtool wrapper scripts and + restored at link time]) +_LT_DECL([], [need_lib_prefix], [0], + [Do we need the "lib" prefix for modules?]) +_LT_DECL([], [need_version], [0], [Do we need a version for libraries?]) +_LT_DECL([], [version_type], [0], [Library versioning type]) +_LT_DECL([], [runpath_var], [0], [Shared library runtime path variable]) +_LT_DECL([], [shlibpath_var], [0],[Shared library path variable]) +_LT_DECL([], [shlibpath_overrides_runpath], [0], + [Is shlibpath searched before the hard-coded library search path?]) +_LT_DECL([], [libname_spec], [1], [Format of library name prefix]) +_LT_DECL([], [library_names_spec], [1], + [[List of archive names. First name is the real one, the rest are links. + The last name is the one that the linker finds with -lNAME]]) +_LT_DECL([], [soname_spec], [1], + [[The coded name of the library, if different from the real name]]) +_LT_DECL([], [postinstall_cmds], [2], + [Command to use after installation of a shared archive]) +_LT_DECL([], [postuninstall_cmds], [2], + [Command to use after uninstallation of a shared archive]) +_LT_DECL([], [finish_cmds], [2], + [Commands used to finish a libtool library installation in a directory]) +_LT_DECL([], [finish_eval], [1], + [[As "finish_cmds", except a single script fragment to be evaled but + not shown]]) +_LT_DECL([], [hardcode_into_libs], [0], + [Whether we should hardcode library paths into libraries]) +_LT_DECL([], [sys_lib_search_path_spec], [2], + [Compile-time system search path for libraries]) +_LT_DECL([], [sys_lib_dlsearch_path_spec], [2], + [Run-time system search path for libraries]) +])# _LT_SYS_DYNAMIC_LINKER + + +# _LT_PATH_TOOL_PREFIX(TOOL) +# -------------------------- +# find a file program which can recognize shared library +AC_DEFUN([_LT_PATH_TOOL_PREFIX], +[m4_require([_LT_DECL_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD="$MAGIC_CMD" + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="m4_if([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$1; then + lt_cv_path_MAGIC_CMD="$ac_dir/$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD="$lt_cv_path_MAGIC_CMD" + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS="$lt_save_ifs" + MAGIC_CMD="$lt_save_MAGIC_CMD" + ;; +esac]) +MAGIC_CMD="$lt_cv_path_MAGIC_CMD" +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +_LT_DECL([], [MAGIC_CMD], [0], + [Used to examine libraries when file_magic_cmd begins with "file"])dnl +])# _LT_PATH_TOOL_PREFIX + +# Old name: +AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], []) + + +# _LT_PATH_MAGIC +# -------------- +# find a file program which can recognize a shared library +m4_defun([_LT_PATH_MAGIC], +[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# _LT_PATH_MAGIC + + +# LT_PATH_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([LT_PATH_LD], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl + +AC_ARG_WITH([gnu-ld], + [AS_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test "$withval" = no || with_gnu_ld=yes], + [with_gnu_ld=no])dnl + +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 /dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + lt_cv_deplibs_check_method='file_magic file format pei*-i386(.*architecture: i386)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - PA-RISC [0-9].[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]].[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[[3-9]]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be Linux ELF. +linux* | k*bsd*-gnu) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + +_LT_DECL([], [deplibs_check_method], [1], + [Method to check whether dependent libraries are shared objects]) +_LT_DECL([], [file_magic_cmd], [1], + [Command to use when deplibs_check_method == "file_magic"]) +])# _LT_CHECK_MAGIC_METHOD + + +# LT_PATH_NM +# ---------- +# find the pathname to a BSD- or MS-compatible name lister +AC_DEFUN([LT_PATH_NM], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM="$NM" +else + lt_nm_to_check="${ac_tool_prefix}nm" + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + tmp_nm="$ac_dir/$lt_tmp_nm" + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the `sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in + */dev/null* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS="$lt_save_ifs" + done + : ${lt_cv_path_NM=no} +fi]) +if test "$lt_cv_path_NM" != "no"; then + NM="$lt_cv_path_NM" +else + # Didn't find any BSD compatible name lister, look for dumpbin. + AC_CHECK_TOOLS(DUMPBIN, ["dumpbin -symbols" "link -dump -symbols"], :) + AC_SUBST([DUMPBIN]) + if test "$DUMPBIN" != ":"; then + NM="$DUMPBIN" + fi +fi +test -z "$NM" && NM=nm +AC_SUBST([NM]) +_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl + +AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface], + [lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:__oline__: $ac_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:__oline__: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:__oline__: output\"" >&AS_MESSAGE_LOG_FD) + cat conftest.out >&AS_MESSAGE_LOG_FD + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest*]) +])# LT_PATH_NM + +# Old names: +AU_ALIAS([AM_PROG_NM], [LT_PATH_NM]) +AU_ALIAS([AC_PROG_NM], [LT_PATH_NM]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_PROG_NM], []) +dnl AC_DEFUN([AC_PROG_NM], []) + + +# LT_LIB_M +# -------- +# check for math library +AC_DEFUN([LT_LIB_M], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cygwin* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM="-lmw") + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM="-lm") + ;; +esac +AC_SUBST([LIBM]) +])# LT_LIB_M + +# Old name: +AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_CHECK_LIBM], []) + + +# _LT_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------- +m4_defun([_LT_COMPILER_NO_RTTI], +[m4_require([_LT_TAG_COMPILER])dnl + +_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test "$GCC" = yes; then + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + + _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1], + [Compiler flag to turn off builtin functions]) +])# _LT_COMPILER_NO_RTTI + + +# _LT_CMD_GLOBAL_SYMBOLS +# ---------------------- +m4_defun([_LT_CMD_GLOBAL_SYMBOLS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([LT_PATH_NM])dnl +AC_REQUIRE([LT_PATH_LD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_TAG_COMPILER])dnl + +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) + if test "$host_cpu" = ia64; then + symcode='[[ABCDEGRST]]' + fi + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris*) + symcode='[[BDRT]]' + ;; +sco3.2v5*) + symcode='[[DT]]' + ;; +sysv4.2uw2*) + symcode='[[DT]]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[[ABDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGIRSTW]]' ;; +esac + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (void *) \&\2},/p'" +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \(lib[[^ ]]*\)$/ {\"\2\", (void *) \&\2},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/ {\"lib\2\", (void *) \&\2},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function + # and D for any global variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK ['"\ +" {last_section=section; section=\$ 3};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=0}; \$ 0~/\(\).*\|/{f=1}; {printf f ? \"T \" : \"D \"};"\ +" {split(\$ 0, a, /\||\r/); split(a[2], s)};"\ +" s[1]~/^[@?]/{print s[1], s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print t[1], substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx]" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if AC_TRY_EVAL(ac_compile); then + # Now try to grab the symbols. + nlist=conftest.nm + if AC_TRY_EVAL(NM conftest.$ac_objext \| $lt_cv_sys_global_symbol_pipe \> $nlist) && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +const struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[[]] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/ {\"\2\", (void *) \&\2},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_save_LIBS="$LIBS" + lt_save_CFLAGS="$CFLAGS" + LIBS="conftstm.$ac_objext" + CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext}; then + pipe_works=yes + fi + LIBS="$lt_save_LIBS" + CFLAGS="$lt_save_CFLAGS" + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test "$pipe_works" = yes; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi + +_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1], + [Take the output of nm and produce a listing of raw symbols and C names]) +_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1], + [Transform the output of nm in a proper C declaration]) +_LT_DECL([global_symbol_to_c_name_address], + [lt_cv_sys_global_symbol_to_c_name_address], [1], + [Transform the output of nm in a C name address pair]) +_LT_DECL([global_symbol_to_c_name_address_lib_prefix], + [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1], + [Transform the output of nm in a C name address pair when lib prefix is needed]) +]) # _LT_CMD_GLOBAL_SYMBOLS + + +# _LT_COMPILER_PIC([TAGNAME]) +# --------------------------- +m4_defun([_LT_COMPILER_PIC], +[m4_require([_LT_TAG_COMPILER])dnl +_LT_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_TAGVAR(lt_prog_compiler_static, $1)= + +AC_MSG_CHECKING([for $compiler option to produce PIC]) +m4_if([$1], [CXX], [ + # C++ specific cases for pic, static, wl, etc. + if test "$GXX" = yes; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix[[4-9]]*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + dgux*) + case $cc_basename in + ec++*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive' + if test "$host_cpu" != ia64; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu) + case $cc_basename in + KCC*) + # KAI C++ Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64 which still supported -KPIC. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xlc* | xlC*) + # IBM XL 8.0 on PPC + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd*) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx*) + # Digital/Compaq C++ + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc*) + # Lucid + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test "$GCC" = yes; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + + hpux9* | hpux10* | hpux11*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + linux* | k*bsd*-gnu) + case $cc_basename in + # old Intel for x86_64 which still supported -KPIC. + ecc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared' + _LT_TAGVAR(lt_prog_compiler_static, $1)='--static' + ;; + pgcc* | pgf77* | pgf90* | pgf95*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + ccc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xl*) + # IBM XL C 8.0/Fortran 10.1 on PPC + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + *Sun\ F*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='' + ;; + esac + ;; + esac + ;; + + newsos6) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + rdos*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + solaris*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + case $cc_basename in + f77* | f90* | f95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; + *) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; + esac + ;; + + sunos4*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec ;then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + unicos*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + + uts4*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +case $host_os in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" + ;; +esac +AC_MSG_RESULT([$_LT_TAGVAR(lt_prog_compiler_pic, $1)]) +_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1], + [How to pass a linker flag through the compiler]) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], + [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], + [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1], + [Additional compiler flags for building library objects]) + +# +# Check to make sure the static flag actually works. +# +wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\" +_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], + _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1), + $lt_tmp_static_flag, + [], + [_LT_TAGVAR(lt_prog_compiler_static, $1)=]) +_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1], + [Compiler flag to prevent dynamic linking]) +])# _LT_COMPILER_PIC + + +# _LT_LINKER_SHLIBS([TAGNAME]) +# ---------------------------- +# See if the linker supports building shared libraries. +m4_defun([_LT_LINKER_SHLIBS], +[AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +m4_if([$1], [CXX], [ + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + case $host_os in + aix[[4-9]]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_TAGVAR(export_symbols_cmds, $1)="$ltdll_cmds" + ;; + cygwin* | mingw* | cegcc*) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;/^.*[[ ]]__nm__/s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] +], [ + runpath_var= + _LT_TAGVAR(allow_undefined_flag, $1)= + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(archive_cmds, $1)= + _LT_TAGVAR(archive_expsym_cmds, $1)= + _LT_TAGVAR(compiler_needs_object, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(hardcode_automatic, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= + _LT_TAGVAR(hardcode_libdir_separator, $1)= + _LT_TAGVAR(hardcode_minus_L, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_TAGVAR(inherit_rpath, $1)=no + _LT_TAGVAR(link_all_deplibs, $1)=unknown + _LT_TAGVAR(module_cmds, $1)= + _LT_TAGVAR(module_expsym_cmds, $1)= + _LT_TAGVAR(old_archive_from_new_cmds, $1)= + _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_TAGVAR(thread_safe_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ` (' and `)$', so one must not match beginning or + # end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc', + # as well as any symbol that contains `d'. + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. +dnl Note also adjust exclude_expsyms for C++ above. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd*) + with_gnu_ld=no + ;; + esac + + _LT_TAGVAR(ld_shlibs, $1)=yes + if test "$with_gnu_ld" = yes; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='${wl}' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + supports_anon_versioning=no + case `$LD -v 2>&1` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[[3-9]]*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.9.1, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to modify your PATH +*** so that a non-GNU linker is found, and then restart. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu) + tmp_diet=no + if test "$host_os" = linux-dietlibc; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test "$tmp_diet" = no + then + tmp_addflag= + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $ECHO \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95*) # Portland Group f77 and f90 compilers + _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $ECHO \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + _LT_TAGVAR(whole_archive_flag_spec, $1)= + tmp_sharedflag='--shared' ;; + xl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; $ECHO \"$new_convenience\"` ${wl}--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + + if test "x$supports_anon_versioning" = xyes; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + xlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='-rpath $libdir' + _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $compiler_flags -soname $soname -o $lib' + if test "x$supports_anon_versioning" = xyes; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $compiler_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + sunos4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test "$_LT_TAGVAR(ld_shlibs, $1)" = no; then + runpath_var= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix[[4-9]]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='${wl}-f,' + + if test "$GCC" = yes; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + ;; + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then $ECHO "X${wl}${allow_undefined_flag}" | $Xsed; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok' + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + # This is similar to how AIX traditionally builds its shared libraries. + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + bsdi[[45]]*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=".dll" + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `$ECHO "X$deplibs" | $Xsed -e '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' + _LT_TAGVAR(fix_srcfile_path, $1)='`cygpath -w "$srcfile"`' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + dgux*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + freebsd1*) + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test "$GCC" = yes; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + ;; + + hpux10*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test "$with_gnu_ld" = no; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='+b $libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + fi + ;; + + hpux11*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + fi + if test "$with_gnu_ld" = no; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test "$GCC" = yes; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && $ECHO "X${wl}-set_version ${wl}$verstring" | $Xsed` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -shared ${wl}-exported_symbol ${wl}foo ${wl}-update_registry ${wl}/dev/null" + AC_LINK_IFELSE(int foo(void) {}, + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && $ECHO "X${wl}-set_version ${wl}$verstring" | $Xsed` ${wl}-update_registry ${wl}${output_objdir}/so_locations ${wl}-exports_file ${wl}$export_symbols -o $lib' + ) + LDFLAGS="$save_LDFLAGS" + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && $ECHO "X-set_version $verstring" | $Xsed` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && $ECHO "X-set_version $verstring" | $Xsed` -update_registry ${output_objdir}/so_locations -exports_file $export_symbols -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *nto* | *qnx*) + ;; + + openbsd*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + else + case $host_os in + openbsd[[01]].* | openbsd2.[[0-7]] | openbsd2.[[0-7]].*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + ;; + esac + fi + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~$ECHO DATA >> $output_objdir/$libname.def~$ECHO " SINGLE NONSHARED" >> $output_objdir/$libname.def~$ECHO EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_from_new_cmds, $1)='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def' + ;; + + osf3*) + if test "$GCC" = yes; then + _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && $ECHO "X${wl}-set_version ${wl}$verstring" | $Xsed` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && $ECHO "X-set_version $verstring" | $Xsed` -update_registry ${output_objdir}/so_locations -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test "$GCC" = yes; then + _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && $ECHO "X${wl}-set_version ${wl}$verstring" | $Xsed` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && $ECHO "X-set_version $verstring" | $Xsed` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared${allow_undefined_flag} ${wl}-input ${wl}$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "X-set_version $verstring" | $Xsed` -update_registry ${output_objdir}/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + solaris*) + _LT_TAGVAR(no_undefined_flag, $1)=' -z defs' + if test "$GCC" = yes; then + wlarc='${wl}' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-z ${wl}text ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared ${wl}-z ${wl}text ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + _LT_TAGVAR(archive_cmds, $1)='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='${wl}' + _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. GCC discards it without `$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test "$GCC" = yes; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + fi + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test "x$host_vendor" = xsequent; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + if test x$host_vendor = xsni; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Blargedynsym' + ;; + esac + fi + fi +]) +AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) +test "$_LT_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no + +_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld + +_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl +_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl +_LT_DECL([], [extract_expsyms_cmds], [2], + [The commands to extract the exported symbol list from a shared archive]) + +# +# Do we need to explicitly link libc? +# +case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $_LT_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_MSG_CHECKING([whether -lc should be explicitly linked in]) + $RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) + pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1) + _LT_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) + then + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + else + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + AC_MSG_RESULT([$_LT_TAGVAR(archive_cmds_need_lc, $1)]) + ;; + esac + fi + ;; +esac + +_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0], + [Whether or not to add -lc for building shared libraries]) +_LT_TAGDECL([allow_libtool_libs_with_static_runtimes], + [enable_shared_with_static_runtimes], [0], + [Whether or not to disallow shared libs when runtime libs are static]) +_LT_TAGDECL([], [export_dynamic_flag_spec], [1], + [Compiler flag to allow reflexive dlopens]) +_LT_TAGDECL([], [whole_archive_flag_spec], [1], + [Compiler flag to generate shared objects directly from archives]) +_LT_TAGDECL([], [compiler_needs_object], [1], + [Whether the compiler copes with passing no objects directly]) +_LT_TAGDECL([], [old_archive_from_new_cmds], [2], + [Create an old-style archive from a shared archive]) +_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2], + [Create a temporary old-style archive to link instead of a shared archive]) +_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive]) +_LT_TAGDECL([], [archive_expsym_cmds], [2]) +_LT_TAGDECL([], [module_cmds], [2], + [Commands used to build a loadable module if different from building + a shared archive.]) +_LT_TAGDECL([], [module_expsym_cmds], [2]) +_LT_TAGDECL([], [with_gnu_ld], [1], + [Whether we are building with GNU ld or not]) +_LT_TAGDECL([], [allow_undefined_flag], [1], + [Flag that allows shared libraries with undefined symbols to be built]) +_LT_TAGDECL([], [no_undefined_flag], [1], + [Flag that enforces no undefined symbols]) +_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1], + [Flag to hardcode $libdir into a binary during linking. + This must work even if $libdir does not exist]) +_LT_TAGDECL([], [hardcode_libdir_flag_spec_ld], [1], + [[If ld is used when linking, flag to hardcode $libdir into a binary + during linking. This must work even if $libdir does not exist]]) +_LT_TAGDECL([], [hardcode_libdir_separator], [1], + [Whether we need a single "-rpath" flag with a separated argument]) +_LT_TAGDECL([], [hardcode_direct], [0], + [Set to "yes" if using DIR/libNAME${shared_ext} during linking hardcodes + DIR into the resulting binary]) +_LT_TAGDECL([], [hardcode_direct_absolute], [0], + [Set to "yes" if using DIR/libNAME${shared_ext} during linking hardcodes + DIR into the resulting binary and the resulting library dependency is + "absolute", i.e impossible to change by setting ${shlibpath_var} if the + library is relocated]) +_LT_TAGDECL([], [hardcode_minus_L], [0], + [Set to "yes" if using the -LDIR flag during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_shlibpath_var], [0], + [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_automatic], [0], + [Set to "yes" if building a shared library automatically hardcodes DIR + into the library and all subsequent libraries and executables linked + against it]) +_LT_TAGDECL([], [inherit_rpath], [0], + [Set to yes if linker adds runtime paths of dependent libraries + to runtime path list]) +_LT_TAGDECL([], [link_all_deplibs], [0], + [Whether libtool must link a program against all its dependency libraries]) +_LT_TAGDECL([], [fix_srcfile_path], [1], + [Fix the shell variable $srcfile for the compiler]) +_LT_TAGDECL([], [always_export_symbols], [0], + [Set to "yes" if exported symbols are required]) +_LT_TAGDECL([], [export_symbols_cmds], [2], + [The commands to list exported symbols]) +_LT_TAGDECL([], [exclude_expsyms], [1], + [Symbols that should not be listed in the preloaded symbols]) +_LT_TAGDECL([], [include_expsyms], [1], + [Symbols that must always be exported]) +_LT_TAGDECL([], [prelink_cmds], [2], + [Commands necessary for linking programs (against libraries) with templates]) +_LT_TAGDECL([], [file_list_spec], [1], + [Specify filename containing input files]) +dnl FIXME: Not yet implemented +dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1], +dnl [Compiler flag to generate thread safe objects]) +])# _LT_LINKER_SHLIBS + + +# _LT_LANG_C_CONFIG([TAG]) +# ------------------------ +# Ensure that the configuration variables for a C compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to `libtool'. +m4_defun([_LT_LANG_C_CONFIG], +[m4_require([_LT_DECL_EGREP])dnl +lt_save_CC="$CC" +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + +_LT_TAG_COMPILER +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + LT_SYS_DLOPEN_SELF + _LT_CMD_STRIPLIB + + # Report which library types will actually be built + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test "$can_build_shared" = "no" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[[4-9]]*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test "$enable_shared" = yes || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_CONFIG($1) +fi +AC_LANG_POP +CC="$lt_save_CC" +])# _LT_LANG_C_CONFIG + + +# _LT_PROG_CXX +# ------------ +# Since AC_PROG_CXX is broken, in that it returns g++ if there is no c++ +# compiler, we have our own version here. +m4_defun([_LT_PROG_CXX], +[ +pushdef([AC_MSG_ERROR], [_lt_caught_CXX_error=yes]) +AC_PROG_CXX +if test -n "$CXX" && ( test "X$CXX" != "Xno" && + ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) || + (test "X$CXX" != "Xg++"))) ; then + AC_PROG_CXXCPP +else + _lt_caught_CXX_error=yes +fi +popdef([AC_MSG_ERROR]) +])# _LT_PROG_CXX + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([_LT_PROG_CXX], []) + + +# _LT_LANG_CXX_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a C++ compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to `libtool'. +m4_defun([_LT_LANG_CXX_CONFIG], +[AC_REQUIRE([_LT_PROG_CXX])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl + +AC_LANG_PUSH(C++) +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(compiler_needs_object, $1)=no +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test "$_lt_caught_CXX_error" != yes; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test "$GXX" = yes; then + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + else + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + fi + + if test "$GXX" = yes; then + # Set up default GNU C++ configuration + + LT_PATH_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test "$with_gnu_ld" = yes; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='${wl}' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "\-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) + _LT_TAGVAR(ld_shlibs, $1)=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aix[[4-9]]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='${wl}-f,' + + if test "$GXX" = yes; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an empty + # executable. + _LT_SYS_MODULE_PATH_AIX + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then $ECHO "X${wl}${allow_undefined_flag}" | $Xsed; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok' + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + # This is similar to how AIX traditionally builds its shared + # libraries. + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + freebsd[[12]]*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + freebsd-elf*) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + gnu*) + ;; + + hpux9*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; $ECHO "X$list" | $Xsed' + ;; + *) + if test "$GXX" = yes; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test $with_gnu_ld = no; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; $ECHO "X$list" | $Xsed' + ;; + *) + if test "$GXX" = yes; then + if test $with_gnu_ld = no; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && $ECHO "X-set_version $verstring" | $Xsed` -update_registry ${output_objdir}/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test "$GXX" = yes; then + if test "$with_gnu_ld" = no; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && $ECHO "X${wl}-set_version ${wl}$verstring" | $Xsed` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && $ECHO "X${wl}-set_version ${wl}$verstring" | $Xsed` -o $lib' + fi + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + ;; + + linux* | k*bsd*-gnu) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; $ECHO "X$list" | $Xsed' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [[1-5]]* | *pgcpp\ [[1-5]]*) + _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | $NL2SP`"' + _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | $NL2SP`~ + $RANLIB $oldlib' + _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib' + ;; + *) # Version 6 will use weak symbols + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $ECHO \"$new_convenience\"` ${wl}--no-whole-archive' + ;; + cxx*) + # Compaq C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib ${wl}-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`$ECHO "X$templist" | $Xsed -e "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; $ECHO "X$list" | $Xsed' + ;; + xl*) + # IBM XL 8.0 on PPC, with GNU ld + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + if test "x$supports_anon_versioning" = xyes; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file ${wl}$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; $ECHO \"$new_convenience\"` ${wl}--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='echo' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + openbsd2*) + # C++ shared libraries are fairly broken + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + openbsd*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file,$export_symbols -o $lib' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + fi + output_verbose_link_cmd=echo + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + cxx*) + case $host in + osf3*) + _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $soname `test -n "$verstring" && $ECHO "X${wl}-set_version $verstring" | $Xsed` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + ;; + *) + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && $ECHO "X-set_version $verstring" | $Xsed` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname ${wl}-input ${wl}$lib.exp `test -n "$verstring" && $ECHO "X-set_version $verstring" | $Xsed` -update_registry ${output_objdir}/so_locations -o $lib~ + $RM $lib.exp' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`$ECHO "X$templist" | $Xsed -e "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; $ECHO "X$list" | $Xsed' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + case $host in + osf3*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && $ECHO "X${wl}-set_version ${wl}$verstring" | $Xsed` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && $ECHO "${wl}-set_version ${wl}$verstring" | $Xsed` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(archive_cmds_need_lc,$1)=yes + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G${allow_undefined_flag} ${wl}-M ${wl}$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + + output_verbose_link_cmd='echo' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "\-L"' + else + # g++ 2.7 appears to require `-G' NOT `-shared' on this + # platform. + _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP "\-L"' + fi + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $wl$libdir' + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) + test "$_LT_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no + + _LT_TAGVAR(GCC, $1)="$GXX" + _LT_TAGVAR(LD, $1)="$LD" + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + CC=$lt_save_CC + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test "$_lt_caught_CXX_error" != yes + +AC_LANG_POP +])# _LT_LANG_CXX_CONFIG + + +# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME]) +# --------------------------------- +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +m4_defun([_LT_SYS_HIDDEN_LIBDEPS], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +# Dependencies to place before and after the object being linked: +_LT_TAGVAR(predep_objects, $1)= +_LT_TAGVAR(postdep_objects, $1)= +_LT_TAGVAR(predeps, $1)= +_LT_TAGVAR(postdeps, $1)= +_LT_TAGVAR(compiler_lib_search_path, $1)= + +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF +int a; +void foo (void) { a = 0; } +_LT_EOF +], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF +], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer*4 a + a=0 + return + end +_LT_EOF +], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer a + a=0 + return + end +_LT_EOF +], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF +public class foo { + private int a; + public void bar (void) { + a = 0; + } +}; +_LT_EOF +]) +dnl Parse the compiler output and extract the necessary +dnl objects, libraries and library flags. +if AC_TRY_EVAL(ac_compile); then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case $p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test $p = "-L" || + test $p = "-R"; then + prev=$p + continue + else + prev= + fi + + if test "$pre_test_object_deps_done" = no; then + case $p in + -L* | -R*) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then + _LT_TAGVAR(compiler_lib_search_path, $1)="${prev}${p}" + else + _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} ${prev}${p}" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$_LT_TAGVAR(postdeps, $1)"; then + _LT_TAGVAR(postdeps, $1)="${prev}${p}" + else + _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} ${prev}${p}" + fi + fi + ;; + + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test "$pre_test_object_deps_done" = no; then + if test -z "$_LT_TAGVAR(predep_objects, $1)"; then + _LT_TAGVAR(predep_objects, $1)="$p" + else + _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p" + fi + else + if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then + _LT_TAGVAR(postdep_objects, $1)="$p" + else + _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling $1 test program" +fi + +$RM -f confest.$objext + +# PORTME: override above test on systems where it is broken +m4_if([$1], [CXX], +[case $host_os in +interix[[3-9]]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + _LT_TAGVAR(predep_objects,$1)= + _LT_TAGVAR(postdep_objects,$1)= + _LT_TAGVAR(postdeps,$1)= + ;; + +linux*) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + + # The more standards-conforming stlport4 library is + # incompatible with the Cstd library. Avoid specifying + # it if it's in CXXFLAGS. Ignore libCrun as + # -library=stlport4 depends on it. + case " $CXX $CXXFLAGS " in + *" -library=stlport4 "*) + solaris_use_stlport4=yes + ;; + esac + + if test "$solaris_use_stlport4" != yes; then + _LT_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun' + fi + ;; + esac + ;; + +solaris*) + case $cc_basename in + CC*) + # The more standards-conforming stlport4 library is + # incompatible with the Cstd library. Avoid specifying + # it if it's in CXXFLAGS. Ignore libCrun as + # -library=stlport4 depends on it. + case " $CXX $CXXFLAGS " in + *" -library=stlport4 "*) + solaris_use_stlport4=yes + ;; + esac + + # Adding this requires a known-good setup of shared libraries for + # Sun compiler versions before 5.6, else PIC objects from an old + # archive will be linked into the output, leading to subtle bugs. + if test "$solaris_use_stlport4" != yes; then + _LT_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun' + fi + ;; + esac + ;; +esac +]) + +case " $_LT_TAGVAR(postdeps, $1) " in +*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; +esac + _LT_TAGVAR(compiler_lib_search_dirs, $1)= +if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then + _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | ${SED} -e 's! -L! !g' -e 's!^ !!'` +fi +_LT_TAGDECL([], [compiler_lib_search_dirs], [1], + [The directories searched by this compiler when creating a shared library]) +_LT_TAGDECL([], [predep_objects], [1], + [Dependencies to place before and after the objects being linked to + create a shared library]) +_LT_TAGDECL([], [postdep_objects], [1]) +_LT_TAGDECL([], [predeps], [1]) +_LT_TAGDECL([], [postdeps], [1]) +_LT_TAGDECL([], [compiler_lib_search_path], [1], + [The library search path used internally by the compiler when linking + a shared library]) +])# _LT_SYS_HIDDEN_LIBDEPS + + +# _LT_PROG_F77 +# ------------ +# Since AC_PROG_F77 is broken, in that it returns the empty string +# if there is no fortran compiler, we have our own version here. +m4_defun([_LT_PROG_F77], +[ +pushdef([AC_MSG_ERROR], [_lt_disable_F77=yes]) +AC_PROG_F77 +if test -z "$F77" || test "X$F77" = "Xno"; then + _lt_disable_F77=yes +fi +popdef([AC_MSG_ERROR]) +])# _LT_PROG_F77 + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([_LT_PROG_F77], []) + + +# _LT_LANG_F77_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a Fortran 77 compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to `libtool'. +m4_defun([_LT_LANG_F77_CONFIG], +[AC_REQUIRE([_LT_PROG_F77])dnl +AC_LANG_PUSH(Fortran 77) + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for f77 test sources. +ac_ext=f + +# Object file extension for compiled f77 test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the F77 compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test "$_lt_disable_F77" != yes; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC="$CC" + lt_save_GCC=$GCC + CC=${F77-"f77"} + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + GCC=$G77 + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test "$can_build_shared" = "no" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test "$enable_shared" = yes || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)="$G77" + _LT_TAGVAR(LD, $1)="$LD" + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC="$lt_save_CC" +fi # test "$_lt_disable_F77" != yes + +AC_LANG_POP +])# _LT_LANG_F77_CONFIG + + +# _LT_PROG_FC +# ----------- +# Since AC_PROG_FC is broken, in that it returns the empty string +# if there is no fortran compiler, we have our own version here. +m4_defun([_LT_PROG_FC], +[ +pushdef([AC_MSG_ERROR], [_lt_disable_FC=yes]) +AC_PROG_FC +if test -z "$FC" || test "X$FC" = "Xno"; then + _lt_disable_FC=yes +fi +popdef([AC_MSG_ERROR]) +])# _LT_PROG_FC + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([_LT_PROG_FC], []) + + +# _LT_LANG_FC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for a Fortran compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to `libtool'. +m4_defun([_LT_LANG_FC_CONFIG], +[AC_REQUIRE([_LT_PROG_FC])dnl +AC_LANG_PUSH(Fortran) + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for fc test sources. +ac_ext=${ac_fc_srcext-f} + +# Object file extension for compiled fc test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the FC compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test "$_lt_disable_FC" != yes; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC="$CC" + lt_save_GCC=$GCC + CC=${FC-"f95"} + compiler=$CC + GCC=$ac_cv_fc_compiler_gnu + + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test "$can_build_shared" = "no" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test "$enable_shared" = yes || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)="$ac_cv_fc_compiler_gnu" + _LT_TAGVAR(LD, $1)="$LD" + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC="$lt_save_CC" +fi # test "$_lt_disable_FC" != yes + +AC_LANG_POP +])# _LT_LANG_FC_CONFIG + + +# _LT_LANG_GCJ_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Java Compiler compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to `libtool'. +m4_defun([_LT_LANG_GCJ_CONFIG], +[AC_REQUIRE([LT_PROG_GCJ])dnl +AC_LANG_SAVE + +# Source file extension for Java test sources. +ac_ext=java + +# Object file extension for compiled Java test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="class foo {}" + +# Code to be used in simple link tests +lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC="$CC" +lt_save_GCC=$GCC +GCC=yes +CC=${GCJ-"gcj"} +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)="$LD" +_LT_CC_BASENAME([$compiler]) + +# GCJ did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds + +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC="$lt_save_CC" +])# _LT_LANG_GCJ_CONFIG + + +# _LT_LANG_RC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for the Windows resource compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to `libtool'. +m4_defun([_LT_LANG_RC_CONFIG], +[AC_REQUIRE([LT_PROG_RC])dnl +AC_LANG_SAVE + +# Source file extension for RC test sources. +ac_ext=rc + +# Object file extension for compiled RC test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' + +# Code to be used in simple link tests +lt_simple_link_test_code="$lt_simple_compile_test_code" + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC="$CC" +lt_save_GCC=$GCC +GCC= +CC=${RC-"windres"} +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) +_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + +if test -n "$compiler"; then + : + _LT_CONFIG($1) +fi + +GCC=$lt_save_GCC +AC_LANG_RESTORE +CC="$lt_save_CC" +])# _LT_LANG_RC_CONFIG + + +# LT_PROG_GCJ +# ----------- +AC_DEFUN([LT_PROG_GCJ], +[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ], + [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ], + [AC_CHECK_TOOL(GCJ, gcj,) + test "x${GCJFLAGS+set}" = xset || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS)])])[]dnl +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_GCJ], []) + + +# LT_PROG_RC +# ---------- +AC_DEFUN([LT_PROG_RC], +[AC_CHECK_TOOL(RC, windres,) +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_RC], []) + + +# _LT_DECL_EGREP +# -------------- +# If we don't have a new enough Autoconf to choose the best grep +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_EGREP], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_REQUIRE([AC_PROG_FGREP])dnl +test -z "$GREP" && GREP=grep +_LT_DECL([], [GREP], [1], [A grep program that handles long lines]) +_LT_DECL([], [EGREP], [1], [An ERE matcher]) +_LT_DECL([], [FGREP], [1], [A literal string matcher]) +dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too +AC_SUBST([GREP]) +]) + + +# _LT_DECL_OBJDUMP +# -------------- +# If we don't have a new enough Autoconf to choose the best objdump +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_OBJDUMP], +[AC_CHECK_TOOL(OBJDUMP, objdump, false) +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper]) +AC_SUBST([OBJDUMP]) +]) + + +# _LT_DECL_SED +# ------------ +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +m4_defun([_LT_DECL_SED], +[AC_PROG_SED +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" +_LT_DECL([], [SED], [1], [A sed program that does not truncate output]) +_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"], + [Sed that helps us avoid accidentally triggering echo(1) options like -n]) +])# _LT_DECL_SED + +m4_ifndef([AC_PROG_SED], [ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # + +m4_defun([AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +IFS=$as_save_IFS +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f $lt_ac_sed && continue + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test $lt_ac_count -gt 10 && break + lt_ac_count=`expr $lt_ac_count + 1` + if test $lt_ac_count -gt $lt_ac_max; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_SUBST([SED]) +AC_MSG_RESULT([$SED]) +])#AC_PROG_SED +])#m4_ifndef + +# Old name: +AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_SED], []) + + +# _LT_CHECK_SHELL_FEATURES +# ------------------------ +# Find out whether the shell is Bourne or XSI compatible, +# or has some other useful features. +m4_defun([_LT_CHECK_SHELL_FEATURES], +[AC_MSG_CHECKING([whether the shell understands some XSI constructs]) +# Try some XSI features +xsi_shell=no +( _lt_dummy="a/b/c" + test "${_lt_dummy##*/},${_lt_dummy%/*},"${_lt_dummy%"$_lt_dummy"}, \ + = c,a/b,, \ + && eval 'test $(( 1 + 1 )) -eq 2 \ + && test "${#_lt_dummy}" -eq 5' ) >/dev/null 2>&1 \ + && xsi_shell=yes +AC_MSG_RESULT([$xsi_shell]) +_LT_CONFIG_LIBTOOL_INIT([xsi_shell='$xsi_shell']) + +AC_MSG_CHECKING([whether the shell understands "+="]) +lt_shell_append=no +( foo=bar; set foo baz; eval "$[1]+=\$[2]" && test "$foo" = barbaz ) \ + >/dev/null 2>&1 \ + && lt_shell_append=yes +AC_MSG_RESULT([$lt_shell_append]) +_LT_CONFIG_LIBTOOL_INIT([lt_shell_append='$lt_shell_append']) + +if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi +_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac +_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl +_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl +])# _LT_CHECK_SHELL_FEATURES + + +# _LT_PROG_XSI_SHELLFNS +# --------------------- +# Bourne and XSI compatible variants of some useful shell functions. +m4_defun([_LT_PROG_XSI_SHELLFNS], +[case $xsi_shell in + yes) + cat << \_LT_EOF >> "$cfgfile" + +# func_dirname file append nondir_replacement +# Compute the dirname of FILE. If nonempty, add APPEND to the result, +# otherwise set result to NONDIR_REPLACEMENT. +func_dirname () +{ + case ${1} in + */*) func_dirname_result="${1%/*}${2}" ;; + * ) func_dirname_result="${3}" ;; + esac +} + +# func_basename file +func_basename () +{ + func_basename_result="${1##*/}" +} + +# func_dirname_and_basename file append nondir_replacement +# perform func_basename and func_dirname in a single function +# call: +# dirname: Compute the dirname of FILE. If nonempty, +# add APPEND to the result, otherwise set result +# to NONDIR_REPLACEMENT. +# value returned in "$func_dirname_result" +# basename: Compute filename of FILE. +# value retuned in "$func_basename_result" +# Implementation must be kept synchronized with func_dirname +# and func_basename. For efficiency, we do not delegate to +# those functions but instead duplicate the functionality here. +func_dirname_and_basename () +{ + case ${1} in + */*) func_dirname_result="${1%/*}${2}" ;; + * ) func_dirname_result="${3}" ;; + esac + func_basename_result="${1##*/}" +} + +# func_stripname prefix suffix name +# strip PREFIX and SUFFIX off of NAME. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +func_stripname () +{ + # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are + # positional parameters, so assign one to ordinary parameter first. + func_stripname_result=${3} + func_stripname_result=${func_stripname_result#"${1}"} + func_stripname_result=${func_stripname_result%"${2}"} +} + +# func_opt_split +func_opt_split () +{ + func_opt_split_opt=${1%%=*} + func_opt_split_arg=${1#*=} +} + +# func_lo2o object +func_lo2o () +{ + case ${1} in + *.lo) func_lo2o_result=${1%.lo}.${objext} ;; + *) func_lo2o_result=${1} ;; + esac +} + +# func_xform libobj-or-source +func_xform () +{ + func_xform_result=${1%.*}.lo +} + +# func_arith arithmetic-term... +func_arith () +{ + func_arith_result=$(( $[*] )) +} + +# func_len string +# STRING may not start with a hyphen. +func_len () +{ + func_len_result=${#1} +} + +_LT_EOF + ;; + *) # Bourne compatible functions. + cat << \_LT_EOF >> "$cfgfile" + +# func_dirname file append nondir_replacement +# Compute the dirname of FILE. If nonempty, add APPEND to the result, +# otherwise set result to NONDIR_REPLACEMENT. +func_dirname () +{ + # Extract subdirectory from the argument. + func_dirname_result=`$ECHO "X${1}" | $Xsed -e "$dirname"` + if test "X$func_dirname_result" = "X${1}"; then + func_dirname_result="${3}" + else + func_dirname_result="$func_dirname_result${2}" + fi +} + +# func_basename file +func_basename () +{ + func_basename_result=`$ECHO "X${1}" | $Xsed -e "$basename"` +} + +dnl func_dirname_and_basename +dnl A portable version of this function is already defined in general.m4sh +dnl so there is no need for it here. + +# func_stripname prefix suffix name +# strip PREFIX and SUFFIX off of NAME. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +# func_strip_suffix prefix name +func_stripname () +{ + case ${2} in + .*) func_stripname_result=`$ECHO "X${3}" \ + | $Xsed -e "s%^${1}%%" -e "s%\\\\${2}\$%%"`;; + *) func_stripname_result=`$ECHO "X${3}" \ + | $Xsed -e "s%^${1}%%" -e "s%${2}\$%%"`;; + esac +} + +# sed scripts: +my_sed_long_opt='1s/^\(-[[^=]]*\)=.*/\1/;q' +my_sed_long_arg='1s/^-[[^=]]*=//' + +# func_opt_split +func_opt_split () +{ + func_opt_split_opt=`$ECHO "X${1}" | $Xsed -e "$my_sed_long_opt"` + func_opt_split_arg=`$ECHO "X${1}" | $Xsed -e "$my_sed_long_arg"` +} + +# func_lo2o object +func_lo2o () +{ + func_lo2o_result=`$ECHO "X${1}" | $Xsed -e "$lo2o"` +} + +# func_xform libobj-or-source +func_xform () +{ + func_xform_result=`$ECHO "X${1}" | $Xsed -e 's/\.[[^.]]*$/.lo/'` +} + +# func_arith arithmetic-term... +func_arith () +{ + func_arith_result=`expr "$[@]"` +} + +# func_len string +# STRING may not start with a hyphen. +func_len () +{ + func_len_result=`expr "$[1]" : ".*" 2>/dev/null || echo $max_cmd_len` +} + +_LT_EOF +esac + +case $lt_shell_append in + yes) + cat << \_LT_EOF >> "$cfgfile" + +# func_append var value +# Append VALUE to the end of shell variable VAR. +func_append () +{ + eval "$[1]+=\$[2]" +} +_LT_EOF + ;; + *) + cat << \_LT_EOF >> "$cfgfile" + +# func_append var value +# Append VALUE to the end of shell variable VAR. +func_append () +{ + eval "$[1]=\$$[1]\$[2]" +} + +_LT_EOF + ;; + esac +]) + +# Helper functions for option handling. -*- Autoconf -*- +# +# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 6 ltoptions.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) + + +# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) +# ------------------------------------------ +m4_define([_LT_MANGLE_OPTION], +[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) + + +# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) +# --------------------------------------- +# Set option OPTION-NAME for macro MACRO-NAME, and if there is a +# matching handler defined, dispatch to it. Other OPTION-NAMEs are +# saved as a flag. +m4_define([_LT_SET_OPTION], +[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl +m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), + _LT_MANGLE_DEFUN([$1], [$2]), + [m4_warning([Unknown $1 option `$2'])])[]dnl +]) + + +# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) +# ------------------------------------------------------------ +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +m4_define([_LT_IF_OPTION], +[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) + + +# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) +# ------------------------------------------------------- +# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME +# are set. +m4_define([_LT_UNLESS_OPTIONS], +[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), + [m4_define([$0_found])])])[]dnl +m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 +])[]dnl +]) + + +# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) +# ---------------------------------------- +# OPTION-LIST is a space-separated list of Libtool options associated +# with MACRO-NAME. If any OPTION has a matching handler declared with +# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about +# the unknown option and exit. +m4_defun([_LT_SET_OPTIONS], +[# Set options +m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [_LT_SET_OPTION([$1], _LT_Option)]) + +m4_if([$1],[LT_INIT],[ + dnl + dnl Simply set some default values (i.e off) if boolean options were not + dnl specified: + _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no + ]) + _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no + ]) + dnl + dnl If no reference was made to various pairs of opposing options, then + dnl we run the default mode handler for the pair. For example, if neither + dnl `shared' nor `disable-shared' was passed, we enable building of shared + dnl archives by default: + _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) + _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], + [_LT_ENABLE_FAST_INSTALL]) + ]) +])# _LT_SET_OPTIONS + + + +# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) +# ----------------------------------------- +m4_define([_LT_MANGLE_DEFUN], +[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) + + +# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) +# ----------------------------------------------- +m4_define([LT_OPTION_DEFINE], +[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl +])# LT_OPTION_DEFINE + + +# dlopen +# ------ +LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes +]) + +AU_DEFUN([AC_LIBTOOL_DLOPEN], +[_LT_SET_OPTION([LT_INIT], [dlopen]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the `dlopen' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) + + +# win32-dll +# --------- +# Declare package support for building win32 dll's. +LT_OPTION_DEFINE([LT_INIT], [win32-dll], +[enable_win32_dll=yes + +case $host in +*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-cegcc*) + AC_CHECK_TOOL(AS, as, false) + AC_CHECK_TOOL(DLLTOOL, dlltool, false) + AC_CHECK_TOOL(OBJDUMP, objdump, false) + ;; +esac + +test -z "$AS" && AS=as +_LT_DECL([], [AS], [0], [Assembler program])dnl + +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [0], [DLL creation program])dnl + +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [0], [Object dumper program])dnl +])# win32-dll + +AU_DEFUN([AC_LIBTOOL_WIN32_DLL], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +_LT_SET_OPTION([LT_INIT], [win32-dll]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the `win32-dll' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) + + +# _LT_ENABLE_SHARED([DEFAULT]) +# ---------------------------- +# implement the --enable-shared flag, and supports the `shared' and +# `disable-shared' LT_INIT options. +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +m4_define([_LT_ENABLE_SHARED], +[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([shared], + [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], + [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_shared=]_LT_ENABLE_SHARED_DEFAULT) + + _LT_DECL([build_libtool_libs], [enable_shared], [0], + [Whether or not to build shared libraries]) +])# _LT_ENABLE_SHARED + +LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) +]) + +AC_DEFUN([AC_DISABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], [disable-shared]) +]) + +AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) +AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_SHARED], []) +dnl AC_DEFUN([AM_DISABLE_SHARED], []) + + + +# _LT_ENABLE_STATIC([DEFAULT]) +# ---------------------------- +# implement the --enable-static flag, and support the `static' and +# `disable-static' LT_INIT options. +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +m4_define([_LT_ENABLE_STATIC], +[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([static], + [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], + [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_static=]_LT_ENABLE_STATIC_DEFAULT) + + _LT_DECL([build_old_libs], [enable_static], [0], + [Whether or not to build static libraries]) +])# _LT_ENABLE_STATIC + +LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) +]) + +AC_DEFUN([AC_DISABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], [disable-static]) +]) + +AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) +AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_STATIC], []) +dnl AC_DEFUN([AM_DISABLE_STATIC], []) + + + +# _LT_ENABLE_FAST_INSTALL([DEFAULT]) +# ---------------------------------- +# implement the --enable-fast-install flag, and support the `fast-install' +# and `disable-fast-install' LT_INIT options. +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +m4_define([_LT_ENABLE_FAST_INSTALL], +[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([fast-install], + [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], + [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) + +_LT_DECL([fast_install], [enable_fast_install], [0], + [Whether or not to optimize for fast installation])dnl +])# _LT_ENABLE_FAST_INSTALL + +LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) + +# Old names: +AU_DEFUN([AC_ENABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the `fast-install' option into LT_INIT's first parameter.]) +]) + +AU_DEFUN([AC_DISABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], [disable-fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the `disable-fast-install' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) +dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) + + +# _LT_WITH_PIC([MODE]) +# -------------------- +# implement the --with-pic flag, and support the `pic-only' and `no-pic' +# LT_INIT options. +# MODE is either `yes' or `no'. If omitted, it defaults to `both'. +m4_define([_LT_WITH_PIC], +[AC_ARG_WITH([pic], + [AS_HELP_STRING([--with-pic], + [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], + [pic_mode="$withval"], + [pic_mode=default]) + +test -z "$pic_mode" && pic_mode=m4_default([$1], [default]) + +_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl +])# _LT_WITH_PIC + +LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) + +# Old name: +AU_DEFUN([AC_LIBTOOL_PICMODE], +[_LT_SET_OPTION([LT_INIT], [pic-only]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the `pic-only' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) + + +m4_define([_LTDL_MODE], []) +LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], + [m4_define([_LTDL_MODE], [nonrecursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [recursive], + [m4_define([_LTDL_MODE], [recursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [subproject], + [m4_define([_LTDL_MODE], [subproject])]) + +m4_define([_LTDL_TYPE], []) +LT_OPTION_DEFINE([LTDL_INIT], [installable], + [m4_define([_LTDL_TYPE], [installable])]) +LT_OPTION_DEFINE([LTDL_INIT], [convenience], + [m4_define([_LTDL_TYPE], [convenience])]) + +# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- +# +# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 6 ltsugar.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) + + +# lt_join(SEP, ARG1, [ARG2...]) +# ----------------------------- +# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their +# associated separator. +# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier +# versions in m4sugar had bugs. +m4_define([lt_join], +[m4_if([$#], [1], [], + [$#], [2], [[$2]], + [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) +m4_define([_lt_join], +[m4_if([$#$2], [2], [], + [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) + + +# lt_car(LIST) +# lt_cdr(LIST) +# ------------ +# Manipulate m4 lists. +# These macros are necessary as long as will still need to support +# Autoconf-2.59 which quotes differently. +m4_define([lt_car], [[$1]]) +m4_define([lt_cdr], +[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], + [$#], 1, [], + [m4_dquote(m4_shift($@))])]) +m4_define([lt_unquote], $1) + + +# lt_append(MACRO-NAME, STRING, [SEPARATOR]) +# ------------------------------------------ +# Redefine MACRO-NAME to hold its former content plus `SEPARATOR'`STRING'. +# Note that neither SEPARATOR nor STRING are expanded; they are appended +# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). +# No SEPARATOR is output if MACRO-NAME was previously undefined (different +# than defined and empty). +# +# This macro is needed until we can rely on Autoconf 2.62, since earlier +# versions of m4sugar mistakenly expanded SEPARATOR but not STRING. +m4_define([lt_append], +[m4_define([$1], + m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) + + + +# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) +# ---------------------------------------------------------- +# Produce a SEP delimited list of all paired combinations of elements of +# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list +# has the form PREFIXmINFIXSUFFIXn. +# Needed until we can rely on m4_combine added in Autoconf 2.62. +m4_define([lt_combine], +[m4_if(m4_eval([$# > 3]), [1], + [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl +[[m4_foreach([_Lt_prefix], [$2], + [m4_foreach([_Lt_suffix], + ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, + [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) + + +# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) +# ----------------------------------------------------------------------- +# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited +# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. +m4_define([lt_if_append_uniq], +[m4_ifdef([$1], + [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], + [lt_append([$1], [$2], [$3])$4], + [$5])], + [lt_append([$1], [$2], [$3])$4])]) + + +# lt_dict_add(DICT, KEY, VALUE) +# ----------------------------- +m4_define([lt_dict_add], +[m4_define([$1($2)], [$3])]) + + +# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) +# -------------------------------------------- +m4_define([lt_dict_add_subkey], +[m4_define([$1($2:$3)], [$4])]) + + +# lt_dict_fetch(DICT, KEY, [SUBKEY]) +# ---------------------------------- +m4_define([lt_dict_fetch], +[m4_ifval([$3], + m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), + m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) + + +# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) +# ----------------------------------------------------------------- +m4_define([lt_if_dict_fetch], +[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], + [$5], + [$6])]) + + +# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) +# -------------------------------------------------------------- +m4_define([lt_dict_filter], +[m4_if([$5], [], [], + [lt_join(m4_quote(m4_default([$4], [[, ]])), + lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), + [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl +]) + +# ltversion.m4 -- version numbers -*- Autoconf -*- +# +# Copyright (C) 2004 Free Software Foundation, Inc. +# Written by Scott James Remnant, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# Generated from ltversion.in. + +# serial 3017 ltversion.m4 +# This file is part of GNU Libtool + +m4_define([LT_PACKAGE_VERSION], [2.2.6b]) +m4_define([LT_PACKAGE_REVISION], [1.3017]) + +AC_DEFUN([LTVERSION_VERSION], +[macro_version='2.2.6b' +macro_revision='1.3017' +_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) +_LT_DECL(, macro_revision, 0) +]) + +# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- +# +# Copyright (C) 2004, 2005, 2007 Free Software Foundation, Inc. +# Written by Scott James Remnant, 2004. +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 4 lt~obsolete.m4 + +# These exist entirely to fool aclocal when bootstrapping libtool. +# +# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN) +# which have later been changed to m4_define as they aren't part of the +# exported API, or moved to Autoconf or Automake where they belong. +# +# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN +# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us +# using a macro with the same name in our local m4/libtool.m4 it'll +# pull the old libtool.m4 in (it doesn't see our shiny new m4_define +# and doesn't know about Autoconf macros at all.) +# +# So we provide this file, which has a silly filename so it's always +# included after everything else. This provides aclocal with the +# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything +# because those macros already exist, or will be overwritten later. +# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. +# +# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. +# Yes, that means every name once taken will need to remain here until +# we give up compatibility with versions before 1.7, at which point +# we need to keep only those names which we still refer to. + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) + +m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) +m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) +m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) +m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) +m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) +m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) +m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) +m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) +m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) +m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) +m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) +m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) +m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) +m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) +m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) +m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) +m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) +m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) +m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) +m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) +m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) +m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) +m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) +m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) +m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) +m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) +m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) +m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) +m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) +m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) +m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) +m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) +m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) +m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) +m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) +m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) +m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) +m4_ifndef([AC_LIBTOOL_RC], [AC_DEFUN([AC_LIBTOOL_RC])]) +m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) +m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) +m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) +m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) +m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) +m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) +m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) +m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) + +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# +# Copyright © 2004 Scott James Remnant . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# PKG_PROG_PKG_CONFIG([MIN-VERSION]) +# ---------------------------------- +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi + +fi[]dnl +])# PKG_PROG_PKG_CONFIG + +# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# Check to see whether a particular set of modules exists. Similar +# to PKG_CHECK_MODULES(), but does not set variables or print errors. +# +# +# Similar to PKG_CHECK_MODULES, make sure that the first instance of +# this or PKG_CHECK_MODULES is called, or make sure to call +# PKG_CHECK_EXISTS manually +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_ifval([$2], [$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + + +# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +# --------------------------------------------- +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])# _PKG_CONFIG + +# _PKG_SHORT_ERRORS_SUPPORTED +# ----------------------------- +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])# _PKG_SHORT_ERRORS_SUPPORTED + + +# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# +# +# Note that if there is a possibility the first call to +# PKG_CHECK_MODULES might not happen, you should be sure to include an +# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +# +# +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + ifelse([$4], , [AC_MSG_ERROR(dnl +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT +])], + [AC_MSG_RESULT([no]) + $4]) +elif test $pkg_failed = untried; then + ifelse([$4], , [AC_MSG_FAILURE(dnl +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])], + [$4]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + ifelse([$3], , :, [$3]) +fi[]dnl +])# PKG_CHECK_MODULES + +# Copyright (C) 2002, 2003, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_AUTOMAKE_VERSION(VERSION) +# ---------------------------- +# Automake X.Y traces this macro to ensure aclocal.m4 has been +# generated from the m4 files accompanying Automake X.Y. +# (This private macro should not be called outside this file.) +AC_DEFUN([AM_AUTOMAKE_VERSION], +[am__api_version='1.11' +dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to +dnl require some minimum version. Point them to the right macro. +m4_if([$1], [1.11.1], [], + [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl +]) + +# _AM_AUTOCONF_VERSION(VERSION) +# ----------------------------- +# aclocal traces this macro to find the Autoconf version. +# This is a private macro too. Using m4_define simplifies +# the logic in aclocal, which can simply ignore this definition. +m4_define([_AM_AUTOCONF_VERSION], []) + +# AM_SET_CURRENT_AUTOMAKE_VERSION +# ------------------------------- +# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. +# This function is AC_REQUIREd by AM_INIT_AUTOMAKE. +AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], +[AM_AUTOMAKE_VERSION([1.11.1])dnl +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) + +# AM_AUX_DIR_EXPAND -*- Autoconf -*- + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets +# $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to +# `$srcdir', `$srcdir/..', or `$srcdir/../..'. +# +# Of course, Automake must honor this variable whenever it calls a +# tool from the auxiliary directory. The problem is that $srcdir (and +# therefore $ac_aux_dir as well) can be either absolute or relative, +# depending on how configure is run. This is pretty annoying, since +# it makes $ac_aux_dir quite unusable in subdirectories: in the top +# source directory, any form will work fine, but in subdirectories a +# relative path needs to be adjusted first. +# +# $ac_aux_dir/missing +# fails when called from a subdirectory if $ac_aux_dir is relative +# $top_srcdir/$ac_aux_dir/missing +# fails if $ac_aux_dir is absolute, +# fails when called from a subdirectory in a VPATH build with +# a relative $ac_aux_dir +# +# The reason of the latter failure is that $top_srcdir and $ac_aux_dir +# are both prefixed by $srcdir. In an in-source build this is usually +# harmless because $srcdir is `.', but things will broke when you +# start a VPATH build or use an absolute $srcdir. +# +# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, +# iff we strip the leading $srcdir from $ac_aux_dir. That would be: +# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` +# and then we would define $MISSING as +# MISSING="\${SHELL} $am_aux_dir/missing" +# This will work as long as MISSING is not called from configure, because +# unfortunately $(top_srcdir) has no meaning in configure. +# However there are other variables, like CC, which are often used in +# configure, and could therefore not use this "fixed" $ac_aux_dir. +# +# Another solution, used here, is to always expand $ac_aux_dir to an +# absolute PATH. The drawback is that using absolute paths prevent a +# configured tree to be moved without reconfiguration. + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[dnl Rely on autoconf to set up CDPATH properly. +AC_PREREQ([2.50])dnl +# expand $ac_aux_dir to an absolute path +am_aux_dir=`cd $ac_aux_dir && pwd` +]) + +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 9 + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ(2.52)dnl + ifelse([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +m4_define([_AM_COND_VALUE_$1], [$2])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 10 + +# There are a few dirty hacks below to avoid letting `AC_PROG_CC' be +# written in clear, in which case automake, when reading aclocal.m4, +# will think it sees a *use*, and therefore will trigger all it's +# C support machinery. Also note that it means that autoscan, seeing +# CC etc. in the Makefile, will ask for an AC_PROG_CC use... + + +# _AM_DEPENDENCIES(NAME) +# ---------------------- +# See how the compiler implements dependency checking. +# NAME is "CC", "CXX", "GCJ", or "OBJC". +# We try a few techniques and use that to set a single cache variable. +# +# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was +# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular +# dependency, and given that the user is not expected to run this macro, +# just rely on AC_PROG_CC. +AC_DEFUN([_AM_DEPENDENCIES], +[AC_REQUIRE([AM_SET_DEPDIR])dnl +AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl +AC_REQUIRE([AM_MAKE_INCLUDE])dnl +AC_REQUIRE([AM_DEP_TRACK])dnl + +ifelse([$1], CC, [depcc="$CC" am_compiler_list=], + [$1], CXX, [depcc="$CXX" am_compiler_list=], + [$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'], + [$1], UPC, [depcc="$UPC" am_compiler_list=], + [$1], GCJ, [depcc="$GCJ" am_compiler_list='gcc3 gcc'], + [depcc="$$1" am_compiler_list=]) + +AC_CACHE_CHECK([dependency style of $depcc], + [am_cv_$1_dependencies_compiler_type], +[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named `D' -- because `-MD' means `put the output + # in D'. + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_$1_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` + fi + am__universal=false + m4_case([$1], [CC], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac], + [CXX], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac]) + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with + # Solaris 8's {/usr,}/bin/sh. + touch sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with `-c' and `-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle `-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # after this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvisualcpp | msvcmsys) + # This compiler won't grok `-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_$1_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_$1_dependencies_compiler_type=none +fi +]) +AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) +AM_CONDITIONAL([am__fastdep$1], [ + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) +]) + + +# AM_SET_DEPDIR +# ------------- +# Choose a directory name for dependency files. +# This macro is AC_REQUIREd in _AM_DEPENDENCIES +AC_DEFUN([AM_SET_DEPDIR], +[AC_REQUIRE([AM_SET_LEADING_DOT])dnl +AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl +]) + + +# AM_DEP_TRACK +# ------------ +AC_DEFUN([AM_DEP_TRACK], +[AC_ARG_ENABLE(dependency-tracking, +[ --disable-dependency-tracking speeds up one-time build + --enable-dependency-tracking do not reject slow dependency extractors]) +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' +fi +AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) +AC_SUBST([AMDEPBACKSLASH])dnl +_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +]) + +# Generate code to set up dependency tracking. -*- Autoconf -*- + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +#serial 5 + +# _AM_OUTPUT_DEPENDENCY_COMMANDS +# ------------------------------ +AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], +[{ + # Autoconf 2.62 quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + case $CONFIG_FILES in + *\'*) eval set x "$CONFIG_FILES" ;; + *) set x $CONFIG_FILES ;; + esac + shift + for mf + do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named `Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`AS_DIRNAME("$mf")` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running `make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # When using ansi2knr, U may be empty or an underscore; expand it + U=`sed -n 's/^U = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`AS_DIRNAME(["$file"])` + AS_MKDIR_P([$dirpart/$fdir]) + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done + done +} +])# _AM_OUTPUT_DEPENDENCY_COMMANDS + + +# AM_OUTPUT_DEPENDENCY_COMMANDS +# ----------------------------- +# This macro should only be invoked once -- use via AC_REQUIRE. +# +# This code is only required when automatic dependency tracking +# is enabled. FIXME. This creates each `.P' file that we will +# need in order to bootstrap the dependency handling code. +AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], +[AC_CONFIG_COMMANDS([depfiles], + [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], + [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) +]) + +# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 8 + +# AM_CONFIG_HEADER is obsolete. It has been replaced by AC_CONFIG_HEADERS. +AU_DEFUN([AM_CONFIG_HEADER], [AC_CONFIG_HEADERS($@)]) + +# Do all the work for Automake. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2008, 2009 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 16 + +# This macro actually does too much. Some checks are only needed if +# your package does certain things. But this isn't really a big deal. + +# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) +# AM_INIT_AUTOMAKE([OPTIONS]) +# ----------------------------------------------- +# The call with PACKAGE and VERSION arguments is the old style +# call (pre autoconf-2.50), which is being phased out. PACKAGE +# and VERSION should now be passed to AC_INIT and removed from +# the call to AM_INIT_AUTOMAKE. +# We support both call styles for the transition. After +# the next Automake release, Autoconf can make the AC_INIT +# arguments mandatory, and then we can depend on a new Autoconf +# release and drop the old call support. +AC_DEFUN([AM_INIT_AUTOMAKE], +[AC_PREREQ([2.62])dnl +dnl Autoconf wants to disallow AM_ names. We explicitly allow +dnl the ones we care about. +m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl +AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl +AC_REQUIRE([AC_PROG_INSTALL])dnl +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi +AC_SUBST([CYGPATH_W]) + +# Define the identity of the package. +dnl Distinguish between old-style and new-style calls. +m4_ifval([$2], +[m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl + AC_SUBST([PACKAGE], [$1])dnl + AC_SUBST([VERSION], [$2])], +[_AM_SET_OPTIONS([$1])dnl +dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. +m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,, + [m4_fatal([AC_INIT should be called with package and version arguments])])dnl + AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl + AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl + +_AM_IF_OPTION([no-define],, +[AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package]) + AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl + +# Some tools Automake needs. +AC_REQUIRE([AM_SANITY_CHECK])dnl +AC_REQUIRE([AC_ARG_PROGRAM])dnl +AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version}) +AM_MISSING_PROG(AUTOCONF, autoconf) +AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version}) +AM_MISSING_PROG(AUTOHEADER, autoheader) +AM_MISSING_PROG(MAKEINFO, makeinfo) +AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl +AC_REQUIRE([AM_PROG_MKDIR_P])dnl +# We need awk for the "check" target. The system "awk" is bad on +# some platforms. +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([AC_PROG_MAKE_SET])dnl +AC_REQUIRE([AM_SET_LEADING_DOT])dnl +_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], + [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], + [_AM_PROG_TAR([v7])])]) +_AM_IF_OPTION([no-dependencies],, +[AC_PROVIDE_IFELSE([AC_PROG_CC], + [_AM_DEPENDENCIES(CC)], + [define([AC_PROG_CC], + defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_CXX], + [_AM_DEPENDENCIES(CXX)], + [define([AC_PROG_CXX], + defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJC], + [_AM_DEPENDENCIES(OBJC)], + [define([AC_PROG_OBJC], + defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl +]) +_AM_IF_OPTION([silent-rules], [AC_REQUIRE([AM_SILENT_RULES])])dnl +dnl The `parallel-tests' driver may need to know about EXEEXT, so add the +dnl `am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This macro +dnl is hooked onto _AC_COMPILER_EXEEXT early, see below. +AC_CONFIG_COMMANDS_PRE(dnl +[m4_provide_if([_AM_COMPILER_EXEEXT], + [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl +]) + +dnl Hook into `_AC_COMPILER_EXEEXT' early to learn its expansion. Do not +dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further +dnl mangled by Autoconf and run in a shell conditional statement. +m4_define([_AC_COMPILER_EXEEXT], +m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) + + +# When config.status generates a header, we must update the stamp-h file. +# This file resides in the same directory as the config header +# that is generated. The stamp files are numbered to have different names. + +# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the +# loop where config.status creates the headers, so we can generate +# our stamp files there. +AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], +[# Compute $1's index in $config_headers. +_am_arg=$1 +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) + +# Copyright (C) 2001, 2003, 2005, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +if test x"${install_sh}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi +AC_SUBST(install_sh)]) + +# Copyright (C) 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# Check whether the underlying file-system supports filenames +# with a leading dot. For instance MS-DOS doesn't. +AC_DEFUN([AM_SET_LEADING_DOT], +[rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null +AC_SUBST([am__leading_dot])]) + +# Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 5 + +# AM_PROG_LEX +# ----------- +# Autoconf leaves LEX=: if lex or flex can't be found. Change that to a +# "missing" invocation, for better error output. +AC_DEFUN([AM_PROG_LEX], +[AC_PREREQ(2.50)dnl +AC_REQUIRE([AM_MISSING_HAS_RUN])dnl +AC_REQUIRE([AC_PROG_LEX])dnl +if test "$LEX" = :; then + LEX=${am_missing_run}flex +fi]) + +# Add --enable-maintainer-mode option to configure. -*- Autoconf -*- +# From Jim Meyering + +# Copyright (C) 1996, 1998, 2000, 2001, 2002, 2003, 2004, 2005, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 5 + +# AM_MAINTAINER_MODE([DEFAULT-MODE]) +# ---------------------------------- +# Control maintainer-specific portions of Makefiles. +# Default is to disable them, unless `enable' is passed literally. +# For symmetry, `disable' may be passed as well. Anyway, the user +# can override the default with the --enable/--disable switch. +AC_DEFUN([AM_MAINTAINER_MODE], +[m4_case(m4_default([$1], [disable]), + [enable], [m4_define([am_maintainer_other], [disable])], + [disable], [m4_define([am_maintainer_other], [enable])], + [m4_define([am_maintainer_other], [enable]) + m4_warn([syntax], [unexpected argument to AM@&t@_MAINTAINER_MODE: $1])]) +AC_MSG_CHECKING([whether to am_maintainer_other maintainer-specific portions of Makefiles]) + dnl maintainer-mode's default is 'disable' unless 'enable' is passed + AC_ARG_ENABLE([maintainer-mode], +[ --][am_maintainer_other][-maintainer-mode am_maintainer_other make rules and dependencies not useful + (and sometimes confusing) to the casual installer], + [USE_MAINTAINER_MODE=$enableval], + [USE_MAINTAINER_MODE=]m4_if(am_maintainer_other, [enable], [no], [yes])) + AC_MSG_RESULT([$USE_MAINTAINER_MODE]) + AM_CONDITIONAL([MAINTAINER_MODE], [test $USE_MAINTAINER_MODE = yes]) + MAINT=$MAINTAINER_MODE_TRUE + AC_SUBST([MAINT])dnl +] +) + +AU_DEFUN([jm_MAINTAINER_MODE], [AM_MAINTAINER_MODE]) + +# Check to see how 'make' treats includes. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005, 2009 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 4 + +# AM_MAKE_INCLUDE() +# ----------------- +# Check to see how make treats includes. +AC_DEFUN([AM_MAKE_INCLUDE], +[am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo this is the am__doit target +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +AC_MSG_CHECKING([for style of include used by $am_make]) +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# Ignore all kinds of additional output from `make'. +case `$am_make -s -f confmf 2> /dev/null` in #( +*the\ am__doit\ target*) + am__include=include + am__quote= + _am_result=GNU + ;; +esac +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + case `$am_make -s -f confmf 2> /dev/null` in #( + *the\ am__doit\ target*) + am__include=.include + am__quote="\"" + _am_result=BSD + ;; + esac +fi +AC_SUBST([am__include]) +AC_SUBST([am__quote]) +AC_MSG_RESULT([$_am_result]) +rm -f confinc confmf +]) + +# Copyright (C) 1999, 2000, 2001, 2003, 2004, 2005, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 6 + +# AM_PROG_CC_C_O +# -------------- +# Like AC_PROG_CC_C_O, but changed for automake. +AC_DEFUN([AM_PROG_CC_C_O], +[AC_REQUIRE([AC_PROG_CC_C_O])dnl +AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([compile])dnl +# FIXME: we rely on the cache variable name because +# there is no other way. +set dummy $CC +am_cc=`echo $[2] | sed ['s/[^a-zA-Z0-9_]/_/g;s/^[0-9]/_/']` +eval am_t=\$ac_cv_prog_cc_${am_cc}_c_o +if test "$am_t" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +dnl Make sure AC_PROG_CC is never called again, or it will override our +dnl setting of CC. +m4_define([AC_PROG_CC], + [m4_fatal([AC_PROG_CC cannot be called after AM_PROG_CC_C_O])]) +]) + +# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- + +# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 6 + +# AM_MISSING_PROG(NAME, PROGRAM) +# ------------------------------ +AC_DEFUN([AM_MISSING_PROG], +[AC_REQUIRE([AM_MISSING_HAS_RUN]) +$1=${$1-"${am_missing_run}$2"} +AC_SUBST($1)]) + + +# AM_MISSING_HAS_RUN +# ------------------ +# Define MISSING if not defined so far and test if it supports --run. +# If it does, set am_missing_run to use it, otherwise, to nothing. +AC_DEFUN([AM_MISSING_HAS_RUN], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([missing])dnl +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --run true"; then + am_missing_run="$MISSING --run " +else + am_missing_run= + AC_MSG_WARN([`missing' script is too old or missing]) +fi +]) + +# Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_MKDIR_P +# --------------- +# Check for `mkdir -p'. +AC_DEFUN([AM_PROG_MKDIR_P], +[AC_PREREQ([2.60])dnl +AC_REQUIRE([AC_PROG_MKDIR_P])dnl +dnl Automake 1.8 to 1.9.6 used to define mkdir_p. We now use MKDIR_P, +dnl while keeping a definition of mkdir_p for backward compatibility. +dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile. +dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of +dnl Makefile.ins that do not define MKDIR_P, so we do our own +dnl adjustment using top_builddir (which is defined more often than +dnl MKDIR_P). +AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl +case $mkdir_p in + [[\\/$]]* | ?:[[\\/]]*) ;; + */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;; +esac +]) + +# Helper functions for option handling. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 4 + +# _AM_MANGLE_OPTION(NAME) +# ----------------------- +AC_DEFUN([_AM_MANGLE_OPTION], +[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) + +# _AM_SET_OPTION(NAME) +# ------------------------------ +# Set option NAME. Presently that only means defining a flag for this option. +AC_DEFUN([_AM_SET_OPTION], +[m4_define(_AM_MANGLE_OPTION([$1]), 1)]) + +# _AM_SET_OPTIONS(OPTIONS) +# ---------------------------------- +# OPTIONS is a space-separated list of Automake options. +AC_DEFUN([_AM_SET_OPTIONS], +[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) + +# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) +# ------------------------------------------- +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +AC_DEFUN([_AM_IF_OPTION], +[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) + +# Check to make sure that the build environment is sane. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 5 + +# AM_SANITY_CHECK +# --------------- +AC_DEFUN([AM_SANITY_CHECK], +[AC_MSG_CHECKING([whether build environment is sane]) +# Just in case +sleep 1 +echo timestamp > conftest.file +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[[\\\"\#\$\&\'\`$am_lf]]*) + AC_MSG_ERROR([unsafe absolute working directory name]);; +esac +case $srcdir in + *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) + AC_MSG_ERROR([unsafe srcdir value: `$srcdir']);; +esac + +# Do `set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$[*]" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + rm -f conftest.file + if test "$[*]" != "X $srcdir/configure conftest.file" \ + && test "$[*]" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken +alias in your environment]) + fi + + test "$[2]" = conftest.file + ) +then + # Ok. + : +else + AC_MSG_ERROR([newly created file is older than distributed files! +Check your system clock]) +fi +AC_MSG_RESULT(yes)]) + +# Copyright (C) 2009 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 1 + +# AM_SILENT_RULES([DEFAULT]) +# -------------------------- +# Enable less verbose build rules; with the default set to DEFAULT +# (`yes' being less verbose, `no' or empty being verbose). +AC_DEFUN([AM_SILENT_RULES], +[AC_ARG_ENABLE([silent-rules], +[ --enable-silent-rules less verbose build output (undo: `make V=1') + --disable-silent-rules verbose build output (undo: `make V=0')]) +case $enable_silent_rules in +yes) AM_DEFAULT_VERBOSITY=0;; +no) AM_DEFAULT_VERBOSITY=1;; +*) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);; +esac +AC_SUBST([AM_DEFAULT_VERBOSITY])dnl +AM_BACKSLASH='\' +AC_SUBST([AM_BACKSLASH])dnl +_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl +]) + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor `install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in `make install-strip', and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using `strip' when the user +# run `make install-strip'. However `strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the `STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be `maybe'. +if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +# Copyright (C) 2006, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Public sister of _AM_SUBST_NOTMAKE. +AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) + +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of `v7', `ustar', or `pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. +AM_MISSING_PROG([AMTAR], [tar]) +m4_if([$1], [v7], + [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'], + [m4_case([$1], [ustar],, [pax],, + [m4_fatal([Unknown tar format])]) +AC_MSG_CHECKING([how to create a $1 tar archive]) +# Loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' +_am_tools=${am_cv_prog_tar_$1-$_am_tools} +# Do not fold the above two line into one, because Tru64 sh and +# Solaris sh will not grok spaces in the rhs of `-'. +for _am_tool in $_am_tools +do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; + do + AM_RUN_LOG([$_am_tar --version]) && break + done + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar /dev/null 2>&1 && break + fi +done +rm -rf conftest.dir + +AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) +AC_MSG_RESULT([$am_cv_prog_tar_$1])]) +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR + +m4_include([acinclude.m4]) diff --git a/attrib/att.c b/attrib/att.c new file mode 100644 index 0000000..08000e0 --- /dev/null +++ b/attrib/att.c @@ -0,0 +1,968 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include + +#include +#include + +#include + +#include "att.h" + +const char *att_ecode2str(uint8_t status) +{ + switch (status) { + case ATT_ECODE_INVALID_HANDLE: + return "Invalid handle"; + case ATT_ECODE_READ_NOT_PERM: + return "Atribute can't be read"; + case ATT_ECODE_WRITE_NOT_PERM: + return "Attribute can't be written"; + case ATT_ECODE_INVALID_PDU: + return "Attribute PDU was invalid"; + case ATT_ECODE_INSUFF_AUTHEN: + return "Attribute requires authentication before read/write"; + case ATT_ECODE_REQ_NOT_SUPP: + return "Server doesn't support the request received"; + case ATT_ECODE_INVALID_OFFSET: + return "Offset past the end of the attribute"; + case ATT_ECODE_INSUFF_AUTHO: + return "Attribute requires authorization before read/write"; + case ATT_ECODE_PREP_QUEUE_FULL: + return "Too many prepare writes have been queued"; + case ATT_ECODE_ATTR_NOT_FOUND: + return "No attribute found within the given range"; + case ATT_ECODE_ATTR_NOT_LONG: + return "Attribute can't be read/written using Read Blob Req"; + case ATT_ECODE_INSUFF_ENCR_KEY_SIZE: + return "Encryption Key Size is insufficient"; + case ATT_ECODE_INVAL_ATTR_VALUE_LEN: + return "Attribute value length is invalid"; + case ATT_ECODE_UNLIKELY: + return "Request attribute has encountered an unlikely error"; + case ATT_ECODE_INSUFF_ENC: + return "Encryption required before read/write"; + case ATT_ECODE_UNSUPP_GRP_TYPE: + return "Attribute type is not a supported grouping attribute"; + case ATT_ECODE_INSUFF_RESOURCES: + return "Insufficient Resources to complete the request"; + case ATT_ECODE_IO: + return "Internal application error: I/O"; + default: + return "Unexpected error code"; + } +} + +void att_data_list_free(struct att_data_list *list) +{ + if (list == NULL) + return; + + if (list->data) { + int i; + for (i = 0; i < list->num; i++) + g_free(list->data[i]); + } + + g_free(list->data); + g_free(list); +} + +struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len) +{ + struct att_data_list *list; + int i; + + list = g_new0(struct att_data_list, 1); + list->len = len; + list->num = num; + + list->data = g_malloc0(sizeof(uint8_t *) * num); + + for (i = 0; i < num; i++) + list->data[i] = g_malloc0(sizeof(uint8_t) * len); + + return list; +} + +uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end); + uint16_t length; + + if (!uuid) + return 0; + + if (uuid->type == BT_UUID16) + length = 2; + else if (uuid->type == BT_UUID128) + length = 16; + else + return 0; + + if (len < min_len + length) + return 0; + + pdu[0] = ATT_OP_READ_BY_GROUP_REQ; + att_put_u16(start, &pdu[1]); + att_put_u16(end, &pdu[3]); + + att_put_uuid(*uuid, &pdu[5]); + + return min_len + length; +} + +uint16_t dec_read_by_grp_req(const uint8_t *pdu, int len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); + + if (pdu == NULL) + return 0; + + if (start == NULL || end == NULL || uuid == NULL) + return 0; + + if (pdu[0] != ATT_OP_READ_BY_GROUP_REQ) + return 0; + + if (len < min_len + 2) + return 0; + + *start = att_get_u16(&pdu[1]); + *end = att_get_u16(&pdu[3]); + if (len == min_len + 2) + *uuid = att_get_uuid16(&pdu[5]); + else + *uuid = att_get_uuid128(&pdu[5]); + + return len; +} + +uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, + int len) +{ + int i; + uint16_t w; + uint8_t *ptr; + + if (list == NULL) + return 0; + + if (len < list->len + 2) + return 0; + + pdu[0] = ATT_OP_READ_BY_GROUP_RESP; + pdu[1] = list->len; + + ptr = &pdu[2]; + + for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) { + memcpy(ptr, list->data[i], list->len); + ptr += list->len; + w += list->len; + } + + return w; +} + +struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, int len) +{ + struct att_data_list *list; + const uint8_t *ptr; + uint16_t elen, num; + int i; + + if (pdu[0] != ATT_OP_READ_BY_GROUP_RESP) + return NULL; + + elen = pdu[1]; + num = (len - 2) / elen; + list = att_data_list_alloc(num, elen); + + ptr = &pdu[2]; + + for (i = 0; i < num; i++) { + memcpy(list->data[i], ptr, list->len); + ptr += list->len; + } + + return list; +} + +uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + const uint8_t *value, int vlen, uint8_t *pdu, int len) +{ + uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end) + + sizeof(uint16_t); + + if (pdu == NULL) + return 0; + + if (!uuid) + return 0; + + if (uuid->type != BT_UUID16) + return 0; + + if (len < min_len) + return 0; + + if (vlen > len - min_len) + vlen = len - min_len; + + pdu[0] = ATT_OP_FIND_BY_TYPE_REQ; + att_put_u16(start, &pdu[1]); + att_put_u16(end, &pdu[3]); + att_put_uuid16(*uuid, &pdu[5]); + + if (vlen > 0) { + memcpy(&pdu[7], value, vlen); + return min_len + vlen; + } + + return min_len; +} + +uint16_t dec_find_by_type_req(const uint8_t *pdu, int len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid, uint8_t *value, int *vlen) +{ + int valuelen; + uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + + sizeof(*end) + sizeof(uint16_t); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_FIND_BY_TYPE_REQ) + return 0; + + /* First requested handle number */ + if (start) + *start = att_get_u16(&pdu[1]); + + /* Last requested handle number */ + if (end) + *end = att_get_u16(&pdu[3]); + + /* Always UUID16 */ + if (uuid) + *uuid = att_get_uuid16(&pdu[5]); + + valuelen = len - min_len; + + /* Attribute value to find */ + if (valuelen > 0 && value) + memcpy(value, pdu + min_len, valuelen); + + if (vlen) + *vlen = valuelen; + + return len; +} + +uint16_t enc_find_by_type_resp(GSList *matches, uint8_t *pdu, int len) +{ + GSList *l; + uint16_t offset; + + if (pdu == NULL || len < 5) + return 0; + + pdu[0] = ATT_OP_FIND_BY_TYPE_RESP; + + for (l = matches, offset = 1; l && len >= (offset + 4); + l = l->next, offset += 4) { + struct att_range *range = l->data; + + att_put_u16(range->start, &pdu[offset]); + att_put_u16(range->end, &pdu[offset + 2]); + } + + return offset; +} + +GSList *dec_find_by_type_resp(const uint8_t *pdu, int len) +{ + struct att_range *range; + GSList *matches; + int offset; + + if (pdu == NULL || len < 5) + return NULL; + + if (pdu[0] != ATT_OP_FIND_BY_TYPE_RESP) + return NULL; + + for (offset = 1, matches = NULL; len >= (offset + 4); offset += 4) { + range = g_new0(struct att_range, 1); + range->start = att_get_u16(&pdu[offset]); + range->end = att_get_u16(&pdu[offset + 2]); + + matches = g_slist_append(matches, range); + } + + return matches; +} + +uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end); + uint16_t length; + + if (!uuid) + return 0; + + if (uuid->type == BT_UUID16) + length = 2; + else if (uuid->type == BT_UUID128) + length = 16; + else + return 0; + + if (len < min_len + length) + return 0; + + pdu[0] = ATT_OP_READ_BY_TYPE_REQ; + att_put_u16(start, &pdu[1]); + att_put_u16(end, &pdu[3]); + + att_put_uuid(*uuid, &pdu[5]); + + return min_len + length; +} + +uint16_t dec_read_by_type_req(const uint8_t *pdu, int len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); + + if (pdu == NULL) + return 0; + + if (start == NULL || end == NULL || uuid == NULL) + return 0; + + if (len < min_len + 2) + return 0; + + if (pdu[0] != ATT_OP_READ_BY_TYPE_REQ) + return 0; + + *start = att_get_u16(&pdu[1]); + *end = att_get_u16(&pdu[3]); + + if (len == min_len + 2) + *uuid = att_get_uuid16(&pdu[5]); + else + *uuid = att_get_uuid128(&pdu[5]); + + return len; +} + +uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, int len) +{ + uint8_t *ptr; + int i, w, l; + + if (list == NULL) + return 0; + + if (pdu == NULL) + return 0; + + l = MIN(len - 2, list->len); + + pdu[0] = ATT_OP_READ_BY_TYPE_RESP; + pdu[1] = l; + ptr = &pdu[2]; + + for (i = 0, w = 2; i < list->num && w + l <= len; i++) { + memcpy(ptr, list->data[i], l); + ptr += l; + w += l; + } + + return w; +} + +struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, int len) +{ + struct att_data_list *list; + const uint8_t *ptr; + uint16_t elen, num; + int i; + + if (pdu[0] != ATT_OP_READ_BY_TYPE_RESP) + return NULL; + + elen = pdu[1]; + num = (len - 2) / elen; + list = att_data_list_alloc(num, elen); + + ptr = &pdu[2]; + + for (i = 0; i < num; i++) { + memcpy(list->data[i], ptr, list->len); + ptr += list->len; + } + + return list; +} + +uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, int vlen, + uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (vlen > len - min_len) + vlen = len - min_len; + + pdu[0] = ATT_OP_WRITE_CMD; + att_put_u16(handle, &pdu[1]); + + if (vlen > 0) { + memcpy(&pdu[3], value, vlen); + return min_len + vlen; + } + + return min_len; +} + +uint16_t dec_write_cmd(const uint8_t *pdu, int len, uint16_t *handle, + uint8_t *value, int *vlen) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); + + if (pdu == NULL) + return 0; + + if (value == NULL || vlen == NULL || handle == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_WRITE_CMD) + return 0; + + *handle = att_get_u16(&pdu[1]); + memcpy(value, pdu + min_len, len - min_len); + *vlen = len - min_len; + + return len; +} + +uint16_t enc_write_req(uint16_t handle, const uint8_t *value, int vlen, + uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (vlen > len - min_len) + vlen = len - min_len; + + pdu[0] = ATT_OP_WRITE_REQ; + att_put_u16(handle, &pdu[1]); + + if (vlen > 0) { + memcpy(&pdu[3], value, vlen); + return min_len + vlen; + } + + return min_len; +} + +uint16_t dec_write_req(const uint8_t *pdu, int len, uint16_t *handle, + uint8_t *value, int *vlen) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); + + if (pdu == NULL) + return 0; + + if (value == NULL || vlen == NULL || handle == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_WRITE_REQ) + return 0; + + *handle = att_get_u16(&pdu[1]); + *vlen = len - min_len; + if (*vlen > 0) + memcpy(value, pdu + min_len, *vlen); + + return len; +} + +uint16_t enc_write_resp(uint8_t *pdu, int len) +{ + if (pdu == NULL) + return 0; + + pdu[0] = ATT_OP_WRITE_RESP; + + return sizeof(pdu[0]); +} + +uint16_t dec_write_resp(const uint8_t *pdu, int len) +{ + if (pdu == NULL) + return 0; + + if (pdu[0] != ATT_OP_WRITE_RESP) + return 0; + + return len; +} + +uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + pdu[0] = ATT_OP_READ_REQ; + att_put_u16(handle, &pdu[1]); + + return min_len; +} + +uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu, + int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) + + sizeof(offset); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + pdu[0] = ATT_OP_READ_BLOB_REQ; + att_put_u16(handle, &pdu[1]); + att_put_u16(offset, &pdu[3]); + + return min_len; +} + +uint16_t dec_read_req(const uint8_t *pdu, int len, uint16_t *handle) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); + + if (pdu == NULL) + return 0; + + if (handle == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_READ_REQ) + return 0; + + *handle = att_get_u16(&pdu[1]); + + return min_len; +} + +uint16_t dec_read_blob_req(const uint8_t *pdu, int len, uint16_t *handle, + uint16_t *offset) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + + sizeof(*offset); + + if (pdu == NULL) + return 0; + + if (handle == NULL) + return 0; + + if (offset == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_READ_BLOB_REQ) + return 0; + + *handle = att_get_u16(&pdu[1]); + *offset = att_get_u16(&pdu[3]); + + return min_len; +} + +uint16_t enc_read_resp(uint8_t *value, int vlen, uint8_t *pdu, int len) +{ + if (pdu == NULL) + return 0; + + /* If the attribute value length is longer than the allowed PDU size, + * send only the octets that fit on the PDU. The remaining octets can + * be requested using the Read Blob Request. */ + if (vlen > len - 1) + vlen = len - 1; + + pdu[0] = ATT_OP_READ_RESP; + + memcpy(pdu + 1, value, vlen); + + return vlen + 1; +} + +uint16_t enc_read_blob_resp(uint8_t *value, int vlen, uint16_t offset, + uint8_t *pdu, int len) +{ + if (pdu == NULL) + return 0; + + vlen -= offset; + if (vlen > len - 1) + vlen = len - 1; + + pdu[0] = ATT_OP_READ_BLOB_RESP; + + memcpy(pdu + 1, &value[offset], vlen); + + return vlen + 1; +} + +uint16_t dec_read_resp(const uint8_t *pdu, int len, uint8_t *value, int *vlen) +{ + if (pdu == NULL) + return 0; + + if (value == NULL || vlen == NULL) + return 0; + + if (pdu[0] != ATT_OP_READ_RESP) + return 0; + + memcpy(value, pdu + 1, len - 1); + + *vlen = len - 1; + + return len; +} + +uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status, + uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(opcode) + + sizeof(handle) + sizeof(status); + uint16_t u16; + + if (len < min_len) + return 0; + + u16 = htobs(handle); + pdu[0] = ATT_OP_ERROR; + pdu[1] = opcode; + memcpy(&pdu[2], &u16, sizeof(u16)); + pdu[4] = status; + + return min_len; +} + +uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + pdu[0] = ATT_OP_FIND_INFO_REQ; + att_put_u16(start, &pdu[1]); + att_put_u16(end, &pdu[3]); + + return min_len; +} + +uint16_t dec_find_info_req(const uint8_t *pdu, int len, uint16_t *start, + uint16_t *end) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (start == NULL || end == NULL) + return 0; + + if (pdu[0] != ATT_OP_FIND_INFO_REQ) + return 0; + + *start = att_get_u16(&pdu[1]); + *end = att_get_u16(&pdu[3]); + + return min_len; +} + +uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list, + uint8_t *pdu, int len) +{ + uint8_t *ptr; + int i, w; + + if (pdu == NULL) + return 0; + + if (list == NULL) + return 0; + + if (len < list->len + 2) + return 0; + + pdu[0] = ATT_OP_FIND_INFO_RESP; + pdu[1] = format; + ptr = (void *) &pdu[2]; + + for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) { + memcpy(ptr, list->data[i], list->len); + ptr += list->len; + w += list->len; + } + + return w; +} + +struct att_data_list *dec_find_info_resp(const uint8_t *pdu, int len, + uint8_t *format) +{ + struct att_data_list *list; + uint8_t *ptr; + uint16_t elen, num; + int i; + + if (pdu == NULL) + return 0; + + if (format == NULL) + return 0; + + if (pdu[0] != ATT_OP_FIND_INFO_RESP) + return 0; + + *format = pdu[1]; + elen = sizeof(pdu[0]) + sizeof(*format); + if (*format == 0x01) + elen += 2; + else if (*format == 0x02) + elen += 16; + + num = (len - 2) / elen; + + ptr = (void *) &pdu[2]; + + list = att_data_list_alloc(num, elen); + + for (i = 0; i < num; i++) { + memcpy(list->data[i], ptr, list->len); + ptr += list->len; + } + + return list; +} + +uint16_t enc_notification(struct attribute *a, uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); + + if (pdu == NULL) + return 0; + + if (len < (a->len + min_len)) + return 0; + + pdu[0] = ATT_OP_HANDLE_NOTIFY; + att_put_u16(a->handle, &pdu[1]); + memcpy(&pdu[3], a->data, a->len); + + return a->len + min_len; +} + +uint16_t enc_indication(struct attribute *a, uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); + + if (pdu == NULL) + return 0; + + if (len < (a->len + min_len)) + return 0; + + pdu[0] = ATT_OP_HANDLE_IND; + att_put_u16(a->handle, &pdu[1]); + memcpy(&pdu[3], a->data, a->len); + + return a->len + min_len; +} + +struct attribute *dec_indication(const uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); + + struct attribute *a; + + if (pdu == NULL) + return NULL; + + if (pdu[0] != ATT_OP_HANDLE_IND) + return NULL; + + if (len < min_len) + return NULL; + + a = g_malloc0(sizeof(struct attribute) + len - min_len); + a->len = len - min_len; + + a->handle = att_get_u16(&pdu[1]); + memcpy(a->data, &pdu[3], a->len); + + return a; +} + +uint16_t enc_confirmation(uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + pdu[0] = ATT_OP_HANDLE_CNF; + + return min_len; +} + +uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(mtu); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + pdu[0] = ATT_OP_MTU_REQ; + att_put_u16(mtu, &pdu[1]); + + return min_len; +} + +uint16_t dec_mtu_req(const uint8_t *pdu, int len, uint16_t *mtu) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu); + + if (pdu == NULL) + return 0; + + if (mtu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_MTU_REQ) + return 0; + + *mtu = att_get_u16(&pdu[1]); + + return min_len; +} + +uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, int len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(mtu); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + pdu[0] = ATT_OP_MTU_RESP; + att_put_u16(mtu, &pdu[1]); + + return min_len; +} + +uint16_t dec_mtu_resp(const uint8_t *pdu, int len, uint16_t *mtu) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu); + + if (pdu == NULL) + return 0; + + if (mtu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_MTU_RESP) + return 0; + + *mtu = att_get_u16(&pdu[1]); + + return min_len; +} diff --git a/attrib/att.h b/attrib/att.h new file mode 100644 index 0000000..7a83bfa --- /dev/null +++ b/attrib/att.h @@ -0,0 +1,306 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* GATT Profile Attribute types */ +#define GATT_PRIM_SVC_UUID 0x2800 +#define GATT_SND_SVC_UUID 0x2801 +#define GATT_INCLUDE_UUID 0x2802 +#define GATT_CHARAC_UUID 0x2803 + +/* GATT Characteristic Types */ +#define GATT_CHARAC_DEVICE_NAME 0x2A00 +#define GATT_CHARAC_APPEARANCE 0x2A01 +#define GATT_CHARAC_PERIPHERAL_PRIV_FLAG 0x2A02 +#define GATT_CHARAC_RECONNECTION_ADDRESS 0x2A03 +#define GATT_CHARAC_PERIPHERAL_PREF_CONN 0x2A04 +#define GATT_CHARAC_SERVICE_CHANGED 0x2A05 + +/* GATT Characteristic Descriptors */ +#define GATT_CHARAC_EXT_PROPER_UUID 0x2900 +#define GATT_CHARAC_USER_DESC_UUID 0x2901 +#define GATT_CLIENT_CHARAC_CFG_UUID 0x2902 +#define GATT_SERVER_CHARAC_CFG_UUID 0x2903 +#define GATT_CHARAC_FMT_UUID 0x2904 +#define GATT_CHARAC_AGREG_FMT_UUID 0x2905 + +/* Attribute Protocol Opcodes */ +#define ATT_OP_ERROR 0x01 +#define ATT_OP_MTU_REQ 0x02 +#define ATT_OP_MTU_RESP 0x03 +#define ATT_OP_FIND_INFO_REQ 0x04 +#define ATT_OP_FIND_INFO_RESP 0x05 +#define ATT_OP_FIND_BY_TYPE_REQ 0x06 +#define ATT_OP_FIND_BY_TYPE_RESP 0x07 +#define ATT_OP_READ_BY_TYPE_REQ 0x08 +#define ATT_OP_READ_BY_TYPE_RESP 0x09 +#define ATT_OP_READ_REQ 0x0A +#define ATT_OP_READ_RESP 0x0B +#define ATT_OP_READ_BLOB_REQ 0x0C +#define ATT_OP_READ_BLOB_RESP 0x0D +#define ATT_OP_READ_MULTI_REQ 0x0E +#define ATT_OP_READ_MULTI_RESP 0x0F +#define ATT_OP_READ_BY_GROUP_REQ 0x10 +#define ATT_OP_READ_BY_GROUP_RESP 0x11 +#define ATT_OP_WRITE_REQ 0x12 +#define ATT_OP_WRITE_RESP 0x13 +#define ATT_OP_WRITE_CMD 0x52 +#define ATT_OP_PREP_WRITE_REQ 0x16 +#define ATT_OP_PREP_WRITE_RESP 0x17 +#define ATT_OP_EXEC_WRITE_REQ 0x18 +#define ATT_OP_EXEC_WRITE_RESP 0x19 +#define ATT_OP_HANDLE_NOTIFY 0x1B +#define ATT_OP_HANDLE_IND 0x1D +#define ATT_OP_HANDLE_CNF 0x1E +#define ATT_OP_SIGNED_WRITE_CMD 0xD2 + +/* Error codes for Error response PDU */ +#define ATT_ECODE_INVALID_HANDLE 0x01 +#define ATT_ECODE_READ_NOT_PERM 0x02 +#define ATT_ECODE_WRITE_NOT_PERM 0x03 +#define ATT_ECODE_INVALID_PDU 0x04 +#define ATT_ECODE_INSUFF_AUTHEN 0x05 +#define ATT_ECODE_REQ_NOT_SUPP 0x06 +#define ATT_ECODE_INVALID_OFFSET 0x07 +#define ATT_ECODE_INSUFF_AUTHO 0x08 +#define ATT_ECODE_PREP_QUEUE_FULL 0x09 +#define ATT_ECODE_ATTR_NOT_FOUND 0x0A +#define ATT_ECODE_ATTR_NOT_LONG 0x0B +#define ATT_ECODE_INSUFF_ENCR_KEY_SIZE 0x0C +#define ATT_ECODE_INVAL_ATTR_VALUE_LEN 0x0D +#define ATT_ECODE_UNLIKELY 0x0E +#define ATT_ECODE_INSUFF_ENC 0x0F +#define ATT_ECODE_UNSUPP_GRP_TYPE 0x10 +#define ATT_ECODE_INSUFF_RESOURCES 0x11 +/* Application error */ +#define ATT_ECODE_IO 0xFF + +/* Characteristic Property bit field */ +#define ATT_CHAR_PROPER_BROADCAST 0x01 +#define ATT_CHAR_PROPER_READ 0x02 +#define ATT_CHAR_PROPER_WRITE_WITHOUT_RESP 0x04 +#define ATT_CHAR_PROPER_WRITE 0x08 +#define ATT_CHAR_PROPER_NOTIFY 0x10 +#define ATT_CHAR_PROPER_INDICATE 0x20 +#define ATT_CHAR_PROPER_AUTH 0x40 +#define ATT_CHAR_PROPER_EXT_PROPER 0x80 + + +#define ATT_MAX_MTU 256 +#define ATT_DEFAULT_L2CAP_MTU 48 +#define ATT_DEFAULT_LE_MTU 23 + +/* Requirements for read/write operations */ +enum { + ATT_NONE, /* No restrictions */ + ATT_AUTHENTICATION, /* Authentication required */ + ATT_AUTHORIZATION, /* Authorization required */ + ATT_NOT_PERMITTED, /* Operation not permitted */ +}; + +struct attribute { + uint16_t handle; + bt_uuid_t uuid; + int read_reqs; + int write_reqs; + uint8_t (*read_cb)(struct attribute *a, gpointer user_data); + uint8_t (*write_cb)(struct attribute *a, gpointer user_data); + gpointer cb_user_data; + int len; + uint8_t data[0]; +}; + +struct att_data_list { + uint16_t num; + uint16_t len; + uint8_t **data; +}; + +struct att_range { + uint16_t start; + uint16_t end; +}; + +struct att_primary { + char uuid[MAX_LEN_UUID_STR + 1]; + uint16_t start; + uint16_t end; +}; + +struct att_char { + char uuid[MAX_LEN_UUID_STR + 1]; + uint16_t handle; + uint8_t properties; + uint16_t value_handle; +}; + +/* These functions do byte conversion */ +static inline uint8_t att_get_u8(const void *ptr) +{ + const uint8_t *u8_ptr = ptr; + return bt_get_unaligned(u8_ptr); +} + +static inline uint16_t att_get_u16(const void *ptr) +{ + const uint16_t *u16_ptr = ptr; + return btohs(bt_get_unaligned(u16_ptr)); +} + +static inline uint32_t att_get_u32(const void *ptr) +{ + const uint32_t *u32_ptr = ptr; + return btohl(bt_get_unaligned(u32_ptr)); +} + +static inline uint128_t att_get_u128(const void *ptr) +{ + const uint128_t *u128_ptr = ptr; + uint128_t dst; + + btoh128(u128_ptr, &dst); + + return dst; +} + +static inline void att_put_u8(uint8_t src, void *dst) +{ + bt_put_unaligned(src, (uint8_t *) dst); +} + +static inline void att_put_u16(uint16_t src, void *dst) +{ + bt_put_unaligned(htobs(src), (uint16_t *) dst); +} + +static inline void att_put_u32(uint32_t src, void *dst) +{ + bt_put_unaligned(htobl(src), (uint32_t *) dst); +} + +static inline void att_put_u128(uint128_t src, void *dst) +{ + uint128_t *d128 = dst; + + htob128(&src, d128); +} + +static inline void att_put_uuid16(bt_uuid_t src, void *dst) +{ + att_put_u16(src.value.u16, dst); +} + +static inline void att_put_uuid128(bt_uuid_t src, void *dst) +{ + att_put_u128(src.value.u128, dst); +} + +static inline void att_put_uuid(bt_uuid_t src, void *dst) +{ + if (src.type == BT_UUID16) + att_put_uuid16(src, dst); + else + att_put_uuid128(src, dst); +} + +static inline bt_uuid_t att_get_uuid16(const void *ptr) +{ + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, att_get_u16(ptr)); + + return uuid; +} + +static inline bt_uuid_t att_get_uuid128(const void *ptr) +{ + bt_uuid_t uuid; + uint128_t value; + + value = att_get_u128(ptr); + bt_uuid128_create(&uuid, value); + + return uuid; +} + +struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len); +void att_data_list_free(struct att_data_list *list); + +const char *att_ecode2str(uint8_t status); +uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, int len); +uint16_t dec_read_by_grp_req(const uint8_t *pdu, int len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid); +uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, int len); +uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + const uint8_t *value, int vlen, uint8_t *pdu, int len); +uint16_t dec_find_by_type_req(const uint8_t *pdu, int len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid, uint8_t *value, int *vlen); +uint16_t enc_find_by_type_resp(GSList *ranges, uint8_t *pdu, int len); +GSList *dec_find_by_type_resp(const uint8_t *pdu, int len); +struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, int len); +uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, int len); +uint16_t dec_read_by_type_req(const uint8_t *pdu, int len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid); +uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, + int len); +uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, int vlen, + uint8_t *pdu, int len); +uint16_t dec_write_cmd(const uint8_t *pdu, int len, uint16_t *handle, + uint8_t *value, int *vlen); +struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, int len); +uint16_t enc_write_req(uint16_t handle, const uint8_t *value, int vlen, + uint8_t *pdu, int len); +uint16_t dec_write_req(const uint8_t *pdu, int len, uint16_t *handle, + uint8_t *value, int *vlen); +uint16_t enc_write_resp(uint8_t *pdu, int len); +uint16_t dec_write_resp(const uint8_t *pdu, int len); +uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, int len); +uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu, + int len); +uint16_t dec_read_req(const uint8_t *pdu, int len, uint16_t *handle); +uint16_t dec_read_blob_req(const uint8_t *pdu, int len, uint16_t *handle, + uint16_t *offset); +uint16_t enc_read_resp(uint8_t *value, int vlen, uint8_t *pdu, int len); +uint16_t enc_read_blob_resp(uint8_t *value, int vlen, uint16_t offset, + uint8_t *pdu, int len); +uint16_t dec_read_resp(const uint8_t *pdu, int len, uint8_t *value, int *vlen); +uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status, + uint8_t *pdu, int len); +uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, int len); +uint16_t dec_find_info_req(const uint8_t *pdu, int len, uint16_t *start, + uint16_t *end); +uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list, + uint8_t *pdu, int len); +struct att_data_list *dec_find_info_resp(const uint8_t *pdu, int len, + uint8_t *format); +uint16_t enc_notification(struct attribute *a, uint8_t *pdu, int len); +uint16_t enc_indication(struct attribute *a, uint8_t *pdu, int len); +struct attribute *dec_indication(const uint8_t *pdu, int len); +uint16_t enc_confirmation(uint8_t *pdu, int len); + +uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, int len); +uint16_t dec_mtu_req(const uint8_t *pdu, int len, uint16_t *mtu); +uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, int len); +uint16_t dec_mtu_resp(const uint8_t *pdu, int len, uint16_t *mtu); diff --git a/attrib/client.c b/attrib/client.c new file mode 100644 index 0000000..54bdc79 --- /dev/null +++ b/attrib/client.c @@ -0,0 +1,1116 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include + +#include "adapter.h" +#include "device.h" +#include "log.h" +#include "gdbus.h" +#include "error.h" +#include "dbus-common.h" +#include "btio.h" +#include "storage.h" + +#include "att.h" +#include "gattrib.h" +#include "gatt.h" +#include "client.h" + +#define CHAR_INTERFACE "org.bluez.Characteristic" + +struct gatt_service { + struct btd_device *dev; + bdaddr_t sba; + bdaddr_t dba; + char *path; + GSList *primary; + GAttrib *attrib; + DBusMessage *msg; + int psm; + gboolean listen; +}; + +struct format { + guint8 format; + guint8 exponent; + guint16 unit; + guint8 namespace; + guint16 desc; +} __attribute__ ((packed)); + +struct primary { + struct gatt_service *gatt; + struct att_primary *att; + char *path; + GSList *chars; + GSList *watchers; +}; + +struct characteristic { + struct primary *prim; + char *path; + uint16_t handle; + uint16_t end; + uint8_t perm; + char type[MAX_LEN_UUID_STR + 1]; + char *name; + char *desc; + struct format *format; + uint8_t *value; + size_t vlen; +}; + +struct query_data { + struct primary *prim; + struct characteristic *chr; + DBusMessage *msg; + uint16_t handle; +}; + +struct watcher { + guint id; + char *name; + char *path; + struct primary *prim; +}; + +static GSList *gatt_services = NULL; + +static DBusConnection *connection; + +static void characteristic_free(void *user_data) +{ + struct characteristic *chr = user_data; + + g_free(chr->path); + g_free(chr->desc); + g_free(chr->format); + g_free(chr->value); + g_free(chr->name); + g_free(chr); +} + +static void watcher_free(void *user_data) +{ + struct watcher *watcher = user_data; + + g_free(watcher->path); + g_free(watcher->name); + g_free(watcher); +} + +static void primary_free(void *user_data) +{ + struct primary *prim = user_data; + GSList *l; + + for (l = prim->watchers; l; l = l->next) { + struct watcher *watcher = l->data; + g_dbus_remove_watch(connection, watcher->id); + } + + g_slist_foreach(prim->chars, (GFunc) characteristic_free, NULL); + g_slist_free(prim->chars); + g_free(prim->path); + g_free(prim); +} + +static void gatt_service_free(void *user_data) +{ + struct gatt_service *gatt = user_data; + + g_slist_foreach(gatt->primary, (GFunc) primary_free, NULL); + g_slist_free(gatt->primary); + g_attrib_unref(gatt->attrib); + g_free(gatt->path); + btd_device_unref(gatt->dev); + g_free(gatt); +} + +static int gatt_dev_cmp(gconstpointer a, gconstpointer b) +{ + const struct gatt_service *gatt = a; + const struct btd_device *dev = b; + + return gatt->dev != dev; +} + +static int characteristic_handle_cmp(gconstpointer a, gconstpointer b) +{ + const struct characteristic *chr = a; + uint16_t handle = GPOINTER_TO_UINT(b); + + return chr->handle - handle; +} + +static int watcher_cmp(gconstpointer a, gconstpointer b) +{ + const struct watcher *watcher = a; + const struct watcher *match = b; + int ret; + + ret = g_strcmp0(watcher->name, match->name); + if (ret != 0) + return ret; + + return g_strcmp0(watcher->path, match->path); +} + +static void append_char_dict(DBusMessageIter *iter, struct characteristic *chr) +{ + DBusMessageIter dict; + const char *name = ""; + char *uuid; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + uuid = g_strdup(chr->type); + dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid); + g_free(uuid); + + /* FIXME: Translate UUID to name. */ + dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &name); + + if (chr->desc) + dict_append_entry(&dict, "Description", DBUS_TYPE_STRING, + &chr->desc); + + if (chr->value) + dict_append_array(&dict, "Value", DBUS_TYPE_BYTE, &chr->value, + chr->vlen); + + /* FIXME: Missing Format, Value and Representation */ + + dbus_message_iter_close_container(iter, &dict); +} + +static void watcher_exit(DBusConnection *conn, void *user_data) +{ + struct watcher *watcher = user_data; + struct primary *prim = watcher->prim; + struct gatt_service *gatt = prim->gatt; + + DBG("%s watcher %s exited", prim->path, watcher->name); + + prim->watchers = g_slist_remove(prim->watchers, watcher); + + g_attrib_unref(gatt->attrib); +} + +static int characteristic_set_value(struct characteristic *chr, + const uint8_t *value, size_t vlen) +{ + chr->value = g_try_realloc(chr->value, vlen); + if (chr->value == NULL) + return -ENOMEM; + + memcpy(chr->value, value, vlen); + chr->vlen = vlen; + + return 0; +} + +static void update_watchers(gpointer data, gpointer user_data) +{ + struct watcher *w = data; + struct characteristic *chr = user_data; + DBusMessage *msg; + + msg = dbus_message_new_method_call(w->name, w->path, + "org.bluez.Watcher", "ValueChanged"); + if (msg == NULL) + return; + + dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &chr->path, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, + &chr->value, chr->vlen, DBUS_TYPE_INVALID); + + dbus_message_set_no_reply(msg, TRUE); + g_dbus_send_message(connection, msg); +} + +static void events_handler(const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + struct gatt_service *gatt = user_data; + struct characteristic *chr; + struct primary *prim; + GSList *lprim, *lchr; + uint8_t opdu[ATT_MAX_MTU]; + guint handle; + uint16_t olen; + + if (len < 3) { + DBG("Malformed notification/indication packet (opcode 0x%02x)", + pdu[0]); + return; + } + + handle = att_get_u16(&pdu[1]); + + for (lprim = gatt->primary, prim = NULL, chr = NULL; lprim; + lprim = lprim->next) { + prim = lprim->data; + + lchr = g_slist_find_custom(prim->chars, + GUINT_TO_POINTER(handle), characteristic_handle_cmp); + if (lchr) { + chr = lchr->data; + break; + } + } + + if (chr == NULL) { + DBG("Attribute handle 0x%02x not found", handle); + return; + } + + switch (pdu[0]) { + case ATT_OP_HANDLE_IND: + olen = enc_confirmation(opdu, sizeof(opdu)); + g_attrib_send(gatt->attrib, 0, opdu[0], opdu, olen, + NULL, NULL, NULL); + case ATT_OP_HANDLE_NOTIFY: + if (characteristic_set_value(chr, &pdu[3], len - 3) < 0) + DBG("Can't change Characteristic 0x%02x", handle); + + g_slist_foreach(prim->watchers, update_watchers, chr); + break; + } +} + +static void attrib_destroy(gpointer user_data) +{ + struct gatt_service *gatt = user_data; + + gatt->attrib = NULL; +} + +static void attrib_disconnect(gpointer user_data) +{ + struct gatt_service *gatt = user_data; + + /* Remote initiated disconnection only */ + g_attrib_unref(gatt->attrib); +} + +static void connect_cb(GIOChannel *chan, GError *gerr, gpointer user_data) +{ + struct gatt_service *gatt = user_data; + + if (gerr) { + if (gatt->msg) { + DBusMessage *reply = btd_error_failed(gatt->msg, + gerr->message); + g_dbus_send_message(connection, reply); + } + + error("%s", gerr->message); + goto fail; + } + + if (gatt->attrib == NULL) + return; + + /* Listen mode: used for notification and indication */ + if (gatt->listen == TRUE) { + g_attrib_register(gatt->attrib, + ATT_OP_HANDLE_NOTIFY, + events_handler, gatt, NULL); + g_attrib_register(gatt->attrib, + ATT_OP_HANDLE_IND, + events_handler, gatt, NULL); + return; + } + + return; +fail: + g_attrib_unref(gatt->attrib); +} + +static int l2cap_connect(struct gatt_service *gatt, GError **gerr, + gboolean listen) +{ + GIOChannel *io; + + if (gatt->attrib != NULL) { + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt->listen = listen; + return 0; + } + + /* + * FIXME: If the service doesn't support Client Characteristic + * Configuration it is necessary to poll the server from time + * to time checking for modifications. + */ + if (gatt->psm < 0) + io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr, + BT_IO_OPT_SOURCE_BDADDR, &gatt->sba, + BT_IO_OPT_DEST_BDADDR, &gatt->dba, + BT_IO_OPT_CID, GATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + else + io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr, + BT_IO_OPT_SOURCE_BDADDR, &gatt->sba, + BT_IO_OPT_DEST_BDADDR, &gatt->dba, + BT_IO_OPT_PSM, gatt->psm, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!io) + return -1; + + gatt->attrib = g_attrib_new(io); + g_io_channel_unref(io); + gatt->listen = listen; + + g_attrib_set_destroy_function(gatt->attrib, attrib_destroy, gatt); + g_attrib_set_disconnect_function(gatt->attrib, attrib_disconnect, + gatt); + + return 0; +} + +static DBusMessage *register_watcher(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *sender = dbus_message_get_sender(msg); + struct primary *prim = data; + struct watcher *watcher; + GError *gerr = NULL; + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (l2cap_connect(prim->gatt, &gerr, TRUE) < 0) { + DBusMessage *reply = btd_error_failed(msg, gerr->message); + g_error_free(gerr); + return reply; + } + + watcher = g_new0(struct watcher, 1); + watcher->name = g_strdup(sender); + watcher->prim = prim; + watcher->path = g_strdup(path); + watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit, + watcher, watcher_free); + + prim->watchers = g_slist_append(prim->watchers, watcher); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_watcher(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *sender = dbus_message_get_sender(msg); + struct primary *prim = data; + struct watcher *watcher, *match; + GSList *l; + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + match = g_new0(struct watcher, 1); + match->name = g_strdup(sender); + match->path = g_strdup(path); + l = g_slist_find_custom(prim->watchers, match, watcher_cmp); + watcher_free(match); + if (!l) + return btd_error_not_authorized(msg); + + watcher = l->data; + g_dbus_remove_watch(conn, watcher->id); + prim->watchers = g_slist_remove(prim->watchers, watcher); + watcher_free(watcher); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_value(DBusConnection *conn, DBusMessage *msg, + DBusMessageIter *iter, struct characteristic *chr) +{ + struct gatt_service *gatt = chr->prim->gatt; + DBusMessageIter sub; + GError *gerr = NULL; + uint8_t *value; + int len; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(iter) != DBUS_TYPE_BYTE) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(iter, &sub); + + dbus_message_iter_get_fixed_array(&sub, &value, &len); + + if (l2cap_connect(gatt, &gerr, FALSE) < 0) { + DBusMessage *reply = btd_error_failed(msg, gerr->message); + g_error_free(gerr); + return reply; + } + + gatt_write_cmd(gatt->attrib, chr->handle, value, len, NULL, NULL); + + characteristic_set_value(chr, value, len); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct characteristic *chr = data; + DBusMessage *reply; + DBusMessageIter iter; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + append_char_dict(&iter, chr); + + return reply; +} + +static DBusMessage *set_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct characteristic *chr = data; + DBusMessageIter iter; + DBusMessageIter sub; + const char *property; + + if (!dbus_message_iter_init(msg, &iter)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &property); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &sub); + + if (g_str_equal("Value", property)) + return set_value(conn, msg, &sub, chr); + + return btd_error_invalid_args(msg); +} + +static GDBusMethodTable char_methods[] = { + { "GetProperties", "", "a{sv}", get_properties }, + { "SetProperty", "sv", "", set_property, + G_DBUS_METHOD_FLAG_ASYNC}, + { } +}; + +static char *characteristic_list_to_string(GSList *chars) +{ + GString *characteristics; + GSList *l; + + characteristics = g_string_new(NULL); + + for (l = chars; l; l = l->next) { + struct characteristic *chr = l->data; + char chr_str[64]; + + memset(chr_str, 0, sizeof(chr_str)); + + snprintf(chr_str, sizeof(chr_str), "%04X#%02X#%04X#%s ", + chr->handle, chr->perm, chr->end, chr->type); + + characteristics = g_string_append(characteristics, chr_str); + } + + return g_string_free(characteristics, FALSE); +} + +static void store_characteristics(struct gatt_service *gatt, + struct primary *prim) +{ + char *characteristics; + struct att_primary *att = prim->att; + + characteristics = characteristic_list_to_string(prim->chars); + + write_device_characteristics(&gatt->sba, &gatt->dba, att->start, + characteristics); + + g_free(characteristics); +} + +static void register_characteristics(struct primary *prim) +{ + GSList *lc; + + for (lc = prim->chars; lc; lc = lc->next) { + struct characteristic *chr = lc->data; + g_dbus_register_interface(connection, chr->path, + CHAR_INTERFACE, char_methods, + NULL, NULL, chr, NULL); + DBG("Registered: %s", chr->path); + } +} + +static GSList *string_to_characteristic_list(struct primary *prim, + const char *str) +{ + GSList *l = NULL; + char **chars; + int i; + + if (str == NULL) + return NULL; + + chars = g_strsplit(str, " ", 0); + if (chars == NULL) + return NULL; + + for (i = 0; chars[i]; i++) { + struct characteristic *chr; + int ret; + + chr = g_new0(struct characteristic, 1); + + ret = sscanf(chars[i], "%04hX#%02hhX#%04hX#%s", &chr->handle, + &chr->perm, &chr->end, chr->type); + if (ret < 4) { + g_free(chr); + continue; + } + + chr->prim = prim; + chr->path = g_strdup_printf("%s/characteristic%04x", + prim->path, chr->handle); + + l = g_slist_append(l, chr); + } + + g_strfreev(chars); + + return l; +} + +static void load_characteristics(gpointer data, gpointer user_data) +{ + struct primary *prim = data; + struct att_primary *att = prim->att; + struct gatt_service *gatt = user_data; + GSList *chrs_list; + char *str; + + if (prim->chars) { + DBG("Characteristics already loaded"); + return; + } + + str = read_device_characteristics(&gatt->sba, &gatt->dba, att->start); + if (str == NULL) + return; + + chrs_list = string_to_characteristic_list(prim, str); + + free(str); + + if (chrs_list == NULL) + return; + + prim->chars = chrs_list; + register_characteristics(prim); + + return; +} + +static void store_attribute(struct gatt_service *gatt, uint16_t handle, + uint16_t type, uint8_t *value, gsize len) +{ + bt_uuid_t uuid; + char *str, *tmp; + guint i; + + str = g_malloc0(MAX_LEN_UUID_STR + len * 2 + 1); + + bt_uuid16_create(&uuid, type); + bt_uuid_to_string(&uuid, str, MAX_LEN_UUID_STR); + + str[MAX_LEN_UUID_STR - 1] = '#'; + + for (i = 0, tmp = str + MAX_LEN_UUID_STR; i < len; i++, tmp += 2) + sprintf(tmp, "%02X", value[i]); + + write_device_attribute(&gatt->sba, &gatt->dba, handle, str); + g_free(str); +} + +static void update_char_desc(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct query_data *current = user_data; + struct gatt_service *gatt = current->prim->gatt; + struct characteristic *chr = current->chr; + + if (status == 0) { + + g_free(chr->desc); + + chr->desc = g_malloc(len); + memcpy(chr->desc, pdu + 1, len - 1); + chr->desc[len - 1] = '\0'; + + store_attribute(gatt, current->handle, + GATT_CHARAC_USER_DESC_UUID, + (void *) chr->desc, len); + } else if (status == ATT_ECODE_INSUFF_ENC) { + GIOChannel *io = g_attrib_get_channel(gatt->attrib); + + if (bt_io_set(io, BT_IO_L2CAP, NULL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH, + BT_IO_OPT_INVALID)) { + gatt_read_char(gatt->attrib, current->handle, 0, + update_char_desc, current); + return; + } + } + + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static void update_char_format(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct query_data *current = user_data; + struct gatt_service *gatt = current->prim->gatt; + struct characteristic *chr = current->chr; + + if (status != 0) + goto done; + + if (len < 8) + goto done; + + g_free(chr->format); + + chr->format = g_new0(struct format, 1); + memcpy(chr->format, pdu + 1, 7); + + store_attribute(gatt, current->handle, GATT_CHARAC_FMT_UUID, + (void *) chr->format, sizeof(*chr->format)); + +done: + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static void update_char_value(guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data) +{ + struct query_data *current = user_data; + struct gatt_service *gatt = current->prim->gatt; + struct characteristic *chr = current->chr; + + if (status == 0) + characteristic_set_value(chr, pdu + 1, len - 1); + else if (status == ATT_ECODE_INSUFF_ENC) { + GIOChannel *io = g_attrib_get_channel(gatt->attrib); + + if (bt_io_set(io, BT_IO_L2CAP, NULL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH, + BT_IO_OPT_INVALID)) { + gatt_read_char(gatt->attrib, chr->handle, 0, + update_char_value, current); + return; + } + } + + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static int uuid_desc16_cmp(bt_uuid_t *uuid, guint16 desc) +{ + bt_uuid_t u16; + + bt_uuid16_create(&u16, desc); + + return bt_uuid_cmp(uuid, &u16); +} + +static void descriptor_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct query_data *current = user_data; + struct gatt_service *gatt = current->prim->gatt; + struct att_data_list *list; + guint8 format; + int i; + + if (status != 0) + goto done; + + DBG("Find Information Response received"); + + list = dec_find_info_resp(pdu, plen, &format); + if (list == NULL) + goto done; + + for (i = 0; i < list->num; i++) { + guint16 handle; + bt_uuid_t uuid; + uint8_t *info = list->data[i]; + struct query_data *qfmt; + + handle = att_get_u16(info); + + if (format == 0x01) { + uuid = att_get_uuid16(&info[2]); + } else { + /* Currently, only "user description" and "presentation + * format" descriptors are used, and both have 16-bit + * UUIDs. Therefore there is no need to support format + * 0x02 yet. */ + continue; + } + qfmt = g_new0(struct query_data, 1); + qfmt->prim = current->prim; + qfmt->chr = current->chr; + qfmt->handle = handle; + + if (uuid_desc16_cmp(&uuid, GATT_CHARAC_USER_DESC_UUID) == 0) { + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt_read_char(gatt->attrib, handle, 0, update_char_desc, + qfmt); + } else if (uuid_desc16_cmp(&uuid, GATT_CHARAC_FMT_UUID) == 0) { + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt_read_char(gatt->attrib, handle, 0, + update_char_format, qfmt); + } else + g_free(qfmt); + } + + att_data_list_free(list); +done: + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static void update_all_chars(gpointer data, gpointer user_data) +{ + struct query_data *qdesc, *qvalue; + struct characteristic *chr = data; + struct primary *prim = user_data; + struct gatt_service *gatt = prim->gatt; + + qdesc = g_new0(struct query_data, 1); + qdesc->prim = prim; + qdesc->chr = chr; + + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt_find_info(gatt->attrib, chr->handle + 1, chr->end, descriptor_cb, + qdesc); + + qvalue = g_new0(struct query_data, 1); + qvalue->prim = prim; + qvalue->chr = chr; + + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt_read_char(gatt->attrib, chr->handle, 0, update_char_value, qvalue); +} + +static void char_discovered_cb(GSList *characteristics, guint8 status, + gpointer user_data) +{ + DBusMessage *reply; + DBusMessageIter iter, array_iter; + struct query_data *current = user_data; + struct primary *prim = current->prim; + struct att_primary *att = prim->att; + struct gatt_service *gatt = prim->gatt; + uint16_t *previous_end = NULL; + GSList *l; + + if (status != 0) { + const char *str = att_ecode2str(status); + + DBG("Discover all characteristics failed: %s", str); + reply = btd_error_failed(current->msg, str); + goto fail; + } + + for (l = characteristics; l; l = l->next) { + struct att_char *current_chr = l->data; + struct characteristic *chr; + guint handle = current_chr->value_handle; + GSList *lchr; + + lchr = g_slist_find_custom(prim->chars, + GUINT_TO_POINTER(handle), characteristic_handle_cmp); + if (lchr) + continue; + + chr = g_new0(struct characteristic, 1); + chr->prim = prim; + chr->perm = current_chr->properties; + chr->handle = current_chr->value_handle; + chr->path = g_strdup_printf("%s/characteristic%04x", + prim->path, chr->handle); + strncpy(chr->type, current_chr->uuid, sizeof(chr->type)); + + if (previous_end) + *previous_end = current_chr->handle; + + previous_end = &chr->end; + + prim->chars = g_slist_append(prim->chars, chr); + } + + if (previous_end) + *previous_end = att->end; + + store_characteristics(gatt, prim); + register_characteristics(prim); + + reply = dbus_message_new_method_return(current->msg); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter); + + for (l = prim->chars; l; l = l->next) { + struct characteristic *chr = l->data; + + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_OBJECT_PATH, &chr->path); + } + + dbus_message_iter_close_container(&iter, &array_iter); + + g_slist_foreach(prim->chars, update_all_chars, prim); + +fail: + g_dbus_send_message(connection, reply); + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static DBusMessage *discover_char(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct primary *prim = data; + struct att_primary *att = prim->att; + struct gatt_service *gatt = prim->gatt; + struct query_data *qchr; + GError *gerr = NULL; + + if (l2cap_connect(prim->gatt, &gerr, FALSE) < 0) { + DBusMessage *reply = btd_error_failed(msg, gerr->message); + g_error_free(gerr); + return reply; + } + + qchr = g_new0(struct query_data, 1); + qchr->prim = prim; + qchr->msg = dbus_message_ref(msg); + + gatt_discover_char(gatt->attrib, att->start, att->end, NULL, + char_discovered_cb, qchr); + + return NULL; +} + +static DBusMessage *prim_get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct primary *prim = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + GSList *l; + char **chars; + const char *uuid; + int i; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + chars = g_new0(char *, g_slist_length(prim->chars) + 1); + + for (i = 0, l = prim->chars; l; l = l->next, i++) { + struct characteristic *chr = l->data; + chars[i] = chr->path; + } + + dict_append_array(&dict, "Characteristics", DBUS_TYPE_OBJECT_PATH, + &chars, i); + uuid = prim->att->uuid; + dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid); + + g_free(chars); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable prim_methods[] = { + { "DiscoverCharacteristics", "", "ao", discover_char, + G_DBUS_METHOD_FLAG_ASYNC }, + { "RegisterCharacteristicsWatcher", "o", "", + register_watcher }, + { "UnregisterCharacteristicsWatcher", "o", "", + unregister_watcher }, + { "GetProperties", "", "a{sv}",prim_get_properties }, + { } +}; + +static void register_primaries(struct gatt_service *gatt, GSList *primaries) +{ + GSList *l; + + for (l = primaries; l; l = l->next) { + struct att_primary *att = l->data; + struct primary *prim; + + prim = g_new0(struct primary, 1); + prim->att = att; + prim->gatt = gatt; + prim->path = g_strdup_printf("%s/service%04x", gatt->path, + att->start); + + g_dbus_register_interface(connection, prim->path, + CHAR_INTERFACE, prim_methods, + NULL, NULL, prim, NULL); + DBG("Registered: %s", prim->path); + + gatt->primary = g_slist_append(gatt->primary, prim); + btd_device_add_service(gatt->dev, prim->path); + load_characteristics(prim, gatt); + } +} + +int attrib_client_register(struct btd_device *device, int psm) +{ + struct btd_adapter *adapter = device_get_adapter(device); + const char *path = device_get_path(device); + struct gatt_service *gatt; + GSList *primaries = btd_device_get_primaries(device); + bdaddr_t sba, dba; + + adapter_get_address(adapter, &sba); + device_get_address(device, &dba); + + gatt = g_new0(struct gatt_service, 1); + gatt->dev = btd_device_ref(device); + gatt->listen = FALSE; + gatt->path = g_strdup(path); + bacpy(&gatt->sba, &sba); + bacpy(&gatt->dba, &dba); + gatt->psm = psm; + + register_primaries(gatt, primaries); + + gatt_services = g_slist_append(gatt_services, gatt); + + return 0; +} + +void attrib_client_unregister(struct btd_device *device) +{ + struct gatt_service *gatt; + GSList *l, *lp, *lc; + + l = g_slist_find_custom(gatt_services, device, gatt_dev_cmp); + if (!l) + return; + + gatt = l->data; + gatt_services = g_slist_remove(gatt_services, gatt); + + for (lp = gatt->primary; lp; lp = lp->next) { + struct primary *prim = lp->data; + for (lc = prim->chars; lc; lc = lc->next) { + struct characteristic *chr = lc->data; + g_dbus_unregister_interface(connection, chr->path, + CHAR_INTERFACE); + } + g_dbus_unregister_interface(connection, prim->path, + CHAR_INTERFACE); + } + + gatt_service_free(gatt); +} + +int attrib_client_init(DBusConnection *conn) +{ + + connection = dbus_connection_ref(conn); + + /* + * FIXME: if the adapter supports BLE start scanning. Temporary + * solution, this approach doesn't allow to control scanning based + * on the discoverable property. + */ + + return 0; +} + +void attrib_client_exit(void) +{ + dbus_connection_unref(connection); +} diff --git a/attrib/client.h b/attrib/client.h new file mode 100644 index 0000000..50e2b5f --- /dev/null +++ b/attrib/client.h @@ -0,0 +1,28 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int attrib_client_init(DBusConnection *conn); +void attrib_client_exit(void); +int attrib_client_register(struct btd_device *device, int psm); +void attrib_client_unregister(struct btd_device *device); diff --git a/attrib/example.c b/attrib/example.c new file mode 100644 index 0000000..fae288c --- /dev/null +++ b/attrib/example.c @@ -0,0 +1,341 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include + +#include "log.h" +#include "attrib-server.h" + +#include "att.h" +#include "example.h" + +/* FIXME: Not defined by SIG? UUID128? */ +#define OPCODES_SUPPORTED_UUID 0xA001 +#define BATTERY_STATE_SVC_UUID 0xA002 +#define BATTERY_STATE_UUID 0xA003 +#define THERM_HUMIDITY_SVC_UUID 0xA004 +#define MANUFACTURER_SVC_UUID 0xA005 +#define TEMPERATURE_UUID 0xA006 +#define FMT_CELSIUS_UUID 0xA007 +#define FMT_OUTSIDE_UUID 0xA008 +#define RELATIVE_HUMIDITY_UUID 0xA009 +#define FMT_PERCENT_UUID 0xA00A +#define BLUETOOTH_SIG_UUID 0xA00B +#define MANUFACTURER_NAME_UUID 0xA00C +#define MANUFACTURER_SERIAL_UUID 0xA00D +#define VENDOR_SPECIFIC_SVC_UUID 0xA00E +#define VENDOR_SPECIFIC_TYPE_UUID 0xA00F +#define FMT_KILOGRAM_UUID 0xA010 +#define FMT_HANGING_UUID 0xA011 + +static GSList *sdp_handles = NULL; + +static int register_attributes(void) +{ + const char *desc_out_temp = "Outside Temperature"; + const char *desc_out_hum = "Outside Relative Humidity"; + const char *desc_weight = "Rucksack Weight"; + const char *manufacturer_name1 = "ACME Temperature Sensor"; + const char *manufacturer_name2 = "ACME Weighing Scales"; + const char *serial1 = "237495-3282-A"; + const char *serial2 = "11267-2327A00239"; + + const uint128_t char_weight_uuid_btorder = { + .data = { 0x80, 0x88, 0xF2, 0x18, 0x90, 0x2C, 0x45, 0x0B, + 0xB6, 0xC4, 0x62, 0x89, 0x1E, 0x8C, 0x25, 0xE9 } }; + const uint128_t prim_weight_uuid_btorder = { + .data = { 0x4F, 0x0A, 0xC0, 0x96, 0x35, 0xD4, 0x49, 0x11, + 0x96, 0x31, 0xDE, 0xA8, 0xDC, 0x74, 0xEE, 0xFE } }; + + uint128_t char_weight_uuid; + uint8_t atval[256]; + uint32_t handle; + bt_uuid_t uuid; + int len; + + btoh128(&char_weight_uuid_btorder, &char_weight_uuid); + + /* Battery state service: primary service definition */ + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + att_put_u16(BATTERY_STATE_SVC_UUID, &atval[0]); + attrib_db_add(0x0100, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + /* Battery: battery state characteristic */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(0x0110, &atval[1]); + att_put_u16(BATTERY_STATE_UUID, &atval[3]); + attrib_db_add(0x0106, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Battery: battery state attribute */ + bt_uuid16_create(&uuid, BATTERY_STATE_UUID); + atval[0] = 0x04; + attrib_db_add(0x0110, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1); + + /* Battery: Client Characteristic Configuration */ + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + atval[0] = 0x00; + atval[1] = 0x00; + attrib_db_add(0x0111, &uuid, ATT_NONE, ATT_AUTHENTICATION, atval, 2); + + /* Add an SDP record for the above service */ + handle = attrib_create_sdp(0x0100, "Battery State Service"); + if (handle) + sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle)); + + /* Thermometer: primary service definition */ + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + att_put_u16(THERM_HUMIDITY_SVC_UUID, &atval[0]); + attrib_db_add(0x0200, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + /* Thermometer: Include */ + bt_uuid16_create(&uuid, GATT_INCLUDE_UUID); + att_put_u16(0x0500, &atval[0]); + att_put_u16(0x0504, &atval[2]); + att_put_u16(MANUFACTURER_SVC_UUID, &atval[4]); + attrib_db_add(0x0201, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6); + + /* Thermometer: Include */ + att_put_u16(0x0550, &atval[0]); + att_put_u16(0x0568, &atval[2]); + att_put_u16(VENDOR_SPECIFIC_SVC_UUID, &atval[4]); + attrib_db_add(0x0202, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6); + + /* Thermometer: temperature characteristic */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(0x0204, &atval[1]); + att_put_u16(TEMPERATURE_UUID, &atval[3]); + attrib_db_add(0x0203, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Thermometer: temperature characteristic value */ + bt_uuid16_create(&uuid, TEMPERATURE_UUID); + atval[0] = 0x8A; + atval[1] = 0x02; + attrib_db_add(0x0204, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + /* Thermometer: temperature characteristic format */ + bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID); + atval[0] = 0x0E; + atval[1] = 0xFE; + att_put_u16(FMT_CELSIUS_UUID, &atval[2]); + atval[4] = 0x01; + att_put_u16(FMT_OUTSIDE_UUID, &atval[5]); + attrib_db_add(0x0205, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 7); + + /* Thermometer: characteristic user description */ + bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID); + len = strlen(desc_out_temp); + strncpy((char *) atval, desc_out_temp, len); + attrib_db_add(0x0206, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len); + + /* Thermometer: relative humidity characteristic */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(0x0212, &atval[1]); + att_put_u16(RELATIVE_HUMIDITY_UUID, &atval[3]); + attrib_db_add(0x0210, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Thermometer: relative humidity value */ + bt_uuid16_create(&uuid, RELATIVE_HUMIDITY_UUID); + atval[0] = 0x27; + attrib_db_add(0x0212, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1); + + /* Thermometer: relative humidity characteristic format */ + bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID); + atval[0] = 0x04; + atval[1] = 0x00; + att_put_u16(FMT_PERCENT_UUID, &atval[2]); + att_put_u16(BLUETOOTH_SIG_UUID, &atval[4]); + att_put_u16(FMT_OUTSIDE_UUID, &atval[6]); + attrib_db_add(0x0213, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 8); + + /* Thermometer: characteristic user description */ + bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID); + len = strlen(desc_out_hum); + strncpy((char *) atval, desc_out_hum, len); + attrib_db_add(0x0214, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len); + + /* Add an SDP record for the above service */ + handle = attrib_create_sdp(0x0200, "Thermometer"); + if (handle) + sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle)); + + /* Secondary Service: Manufacturer Service */ + bt_uuid16_create(&uuid, GATT_SND_SVC_UUID); + att_put_u16(MANUFACTURER_SVC_UUID, &atval[0]); + attrib_db_add(0x0500, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + /* Manufacturer name characteristic definition */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(0x0502, &atval[1]); + att_put_u16(MANUFACTURER_NAME_UUID, &atval[3]); + attrib_db_add(0x0501, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Manufacturer name characteristic value */ + bt_uuid16_create(&uuid, MANUFACTURER_NAME_UUID); + len = strlen(manufacturer_name1); + strncpy((char *) atval, manufacturer_name1, len); + attrib_db_add(0x0502, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len); + + /* Manufacturer serial number characteristic */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(0x0504, &atval[1]); + att_put_u16(MANUFACTURER_SERIAL_UUID, &atval[3]); + attrib_db_add(0x0503, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Manufacturer serial number characteristic value */ + bt_uuid16_create(&uuid, MANUFACTURER_SERIAL_UUID); + len = strlen(serial1); + strncpy((char *) atval, serial1, len); + attrib_db_add(0x0504, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len); + + /* Secondary Service: Manufacturer Service */ + bt_uuid16_create(&uuid, GATT_SND_SVC_UUID); + att_put_u16(MANUFACTURER_SVC_UUID, &atval[0]); + attrib_db_add(0x0505, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + /* Manufacturer name characteristic definition */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(0x0507, &atval[1]); + att_put_u16(MANUFACTURER_NAME_UUID, &atval[3]); + attrib_db_add(0x0506, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Secondary Service: Vendor Specific Service */ + bt_uuid16_create(&uuid, GATT_SND_SVC_UUID); + att_put_u16(VENDOR_SPECIFIC_SVC_UUID, &atval[0]); + attrib_db_add(0x0550, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + /* Vendor Specific Type characteristic definition */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(0x0568, &atval[1]); + att_put_u16(VENDOR_SPECIFIC_TYPE_UUID, &atval[3]); + attrib_db_add(0x0560, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Vendor Specific Type characteristic value */ + bt_uuid16_create(&uuid, VENDOR_SPECIFIC_TYPE_UUID); + atval[0] = 0x56; + atval[1] = 0x65; + atval[2] = 0x6E; + atval[3] = 0x64; + atval[4] = 0x6F; + atval[5] = 0x72; + attrib_db_add(0x0568, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6); + + /* Manufacturer name attribute */ + bt_uuid16_create(&uuid, MANUFACTURER_NAME_UUID); + len = strlen(manufacturer_name2); + strncpy((char *) atval, manufacturer_name2, len); + attrib_db_add(0x0507, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len); + + /* Characteristic: serial number */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(0x0509, &atval[1]); + att_put_u16(MANUFACTURER_SERIAL_UUID, &atval[3]); + attrib_db_add(0x0508, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Serial number characteristic value */ + bt_uuid16_create(&uuid, MANUFACTURER_SERIAL_UUID); + len = strlen(serial2); + strncpy((char *) atval, serial2, len); + attrib_db_add(0x0509, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len); + + /* Weight service: primary service definition */ + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + memcpy(atval, &prim_weight_uuid_btorder, 16); + attrib_db_add(0x0680, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 16); + + /* Weight: include */ + bt_uuid16_create(&uuid, GATT_INCLUDE_UUID); + att_put_u16(0x0505, &atval[0]); + att_put_u16(0x0509, &atval[2]); + att_put_u16(MANUFACTURER_SVC_UUID, &atval[4]); + attrib_db_add(0x0681, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6); + + /* Weight: characteristic */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(0x0683, &atval[1]); + memcpy(&atval[3], &char_weight_uuid_btorder, 16); + attrib_db_add(0x0682, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 19); + + /* Weight: characteristic value */ + bt_uuid128_create(&uuid, char_weight_uuid); + atval[0] = 0x82; + atval[1] = 0x55; + atval[2] = 0x00; + atval[3] = 0x00; + attrib_db_add(0x0683, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 4); + + /* Weight: characteristic format */ + bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID); + atval[0] = 0x08; + atval[1] = 0xFD; + att_put_u16(FMT_KILOGRAM_UUID, &atval[2]); + att_put_u16(BLUETOOTH_SIG_UUID, &atval[4]); + att_put_u16(FMT_HANGING_UUID, &atval[6]); + attrib_db_add(0x0684, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 8); + + /* Weight: characteristic user description */ + bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID); + len = strlen(desc_weight); + strncpy((char *) atval, desc_weight, len); + attrib_db_add(0x0685, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len); + + /* Add an SDP record for the above service */ + handle = attrib_create_sdp(0x0680, "Weight Service"); + if (handle) + sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle)); + + return 0; +} + +int server_example_init(void) +{ + return register_attributes(); +} + +void server_example_exit(void) +{ + while (sdp_handles) { + uint32_t handle = GPOINTER_TO_UINT(sdp_handles->data); + + attrib_free_sdp(handle); + sdp_handles = g_slist_remove(sdp_handles, sdp_handles->data); + } +} diff --git a/attrib/example.h b/attrib/example.h new file mode 100644 index 0000000..a2b07fe --- /dev/null +++ b/attrib/example.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int server_example_init(void); +void server_example_exit(void); diff --git a/attrib/gatt.c b/attrib/gatt.c new file mode 100644 index 0000000..0b69daf --- /dev/null +++ b/attrib/gatt.c @@ -0,0 +1,577 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include + +#include "att.h" +#include "gattrib.h" +#include "gatt.h" + +struct discover_primary { + GAttrib *attrib; + bt_uuid_t uuid; + GSList *primaries; + gatt_cb_t cb; + void *user_data; +}; + +struct discover_char { + GAttrib *attrib; + bt_uuid_t *uuid; + uint16_t end; + GSList *characteristics; + gatt_cb_t cb; + void *user_data; +}; + +static void discover_primary_free(struct discover_primary *dp) +{ + g_slist_free(dp->primaries); + g_attrib_unref(dp->attrib); + g_free(dp); +} + +static void discover_char_free(struct discover_char *dc) +{ + g_slist_foreach(dc->characteristics, (GFunc) g_free, NULL); + g_slist_free(dc->characteristics); + g_attrib_unref(dc->attrib); + g_free(dc->uuid); + g_free(dc); +} + +static guint16 encode_discover_primary(uint16_t start, uint16_t end, + bt_uuid_t *uuid, uint8_t *pdu, size_t len) +{ + bt_uuid_t prim; + guint16 plen; + uint8_t op; + + bt_uuid16_create(&prim, GATT_PRIM_SVC_UUID); + + if (uuid == NULL) { + /* Discover all primary services */ + op = ATT_OP_READ_BY_GROUP_REQ; + plen = enc_read_by_grp_req(start, end, &prim, pdu, len); + } else { + uint16_t u16; + uint128_t u128; + const void *value; + int vlen; + + /* Discover primary service by service UUID */ + op = ATT_OP_FIND_BY_TYPE_REQ; + + if (uuid->type == BT_UUID16) { + u16 = htobs(uuid->value.u16); + value = &u16; + vlen = sizeof(u16); + } else { + htob128(&uuid->value.u128, &u128); + value = &u128; + vlen = sizeof(u128); + } + + plen = enc_find_by_type_req(start, end, &prim, value, vlen, + pdu, len); + } + + return plen; +} + +static void primary_by_uuid_cb(guint8 status, const guint8 *ipdu, + guint16 iplen, gpointer user_data) + +{ + struct discover_primary *dp = user_data; + GSList *ranges, *last; + struct att_range *range; + uint8_t *buf; + guint16 oplen; + int err = 0, buflen; + + if (status) { + err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status; + goto done; + } + + ranges = dec_find_by_type_resp(ipdu, iplen); + if (ranges == NULL) + goto done; + + dp->primaries = g_slist_concat(dp->primaries, ranges); + + last = g_slist_last(ranges); + range = last->data; + + if (range->end == 0xffff) + goto done; + + buf = g_attrib_get_buffer(dp->attrib, &buflen); + oplen = encode_discover_primary(range->end + 1, 0xffff, &dp->uuid, + buf, buflen); + + if (oplen == 0) + goto done; + + g_attrib_send(dp->attrib, 0, buf[0], buf, oplen, primary_by_uuid_cb, + dp, NULL); + return; + +done: + dp->cb(dp->primaries, err, dp->user_data); + discover_primary_free(dp); +} + +static void primary_all_cb(guint8 status, const guint8 *ipdu, guint16 iplen, + gpointer user_data) +{ + struct discover_primary *dp = user_data; + struct att_data_list *list; + unsigned int i, err; + uint16_t start, end; + + if (status) { + err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status; + goto done; + } + + list = dec_read_by_grp_resp(ipdu, iplen); + if (list == NULL) { + err = ATT_ECODE_IO; + goto done; + } + + for (i = 0, end = 0; i < list->num; i++) { + const uint8_t *data = list->data[i]; + struct att_primary *primary; + bt_uuid_t uuid; + + start = att_get_u16(&data[0]); + end = att_get_u16(&data[2]); + + if (list->len == 6) { + bt_uuid_t uuid16 = att_get_uuid16(&data[4]); + bt_uuid_to_uuid128(&uuid16, &uuid); + } else if (list->len == 20) { + uuid = att_get_uuid128(&data[4]); + } else { + /* Skipping invalid data */ + continue; + } + + primary = g_try_new0(struct att_primary, 1); + if (!primary) { + err = ATT_ECODE_INSUFF_RESOURCES; + goto done; + } + primary->start = start; + primary->end = end; + bt_uuid_to_string(&uuid, primary->uuid, sizeof(primary->uuid)); + dp->primaries = g_slist_append(dp->primaries, primary); + } + + att_data_list_free(list); + err = 0; + + if (end != 0xffff) { + int buflen; + uint8_t *buf = g_attrib_get_buffer(dp->attrib, &buflen); + guint16 oplen = encode_discover_primary(end + 1, 0xffff, NULL, + buf, buflen); + + g_attrib_send(dp->attrib, 0, buf[0], buf, oplen, primary_all_cb, + dp, NULL); + + return; + } + +done: + dp->cb(dp->primaries, err, dp->user_data); + discover_primary_free(dp); +} + +guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + struct discover_primary *dp; + int buflen; + uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); + GAttribResultFunc cb; + guint16 plen; + + plen = encode_discover_primary(0x0001, 0xffff, uuid, buf, buflen); + if (plen == 0) + return 0; + + dp = g_try_new0(struct discover_primary, 1); + if (dp == NULL) + return 0; + + dp->attrib = g_attrib_ref(attrib); + dp->cb = func; + dp->user_data = user_data; + + if (uuid) { + memcpy(&dp->uuid, uuid, sizeof(bt_uuid_t)); + cb = primary_by_uuid_cb; + } else + cb = primary_all_cb; + + return g_attrib_send(attrib, 0, buf[0], buf, plen, cb, dp, NULL); +} + +static void char_discovered_cb(guint8 status, const guint8 *ipdu, guint16 iplen, + gpointer user_data) +{ + struct discover_char *dc = user_data; + struct att_data_list *list; + unsigned int i, err; + int buflen; + uint8_t *buf; + guint16 oplen; + bt_uuid_t uuid; + uint16_t last = 0; + + if (status) { + err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status; + goto done; + } + + list = dec_read_by_type_resp(ipdu, iplen); + if (list == NULL) { + err = ATT_ECODE_IO; + goto done; + } + + for (i = 0; i < list->num; i++) { + uint8_t *value = list->data[i]; + struct att_char *chars; + bt_uuid_t uuid; + + last = att_get_u16(value); + + if (list->len == 7) { + bt_uuid_t uuid16 = att_get_uuid16(&value[5]); + bt_uuid_to_uuid128(&uuid16, &uuid); + } else + uuid = att_get_uuid128(&value[5]); + + chars = g_try_new0(struct att_char, 1); + if (!chars) { + err = ATT_ECODE_INSUFF_RESOURCES; + goto done; + } + + if (dc->uuid && bt_uuid_cmp(dc->uuid, &uuid)) + break; + + chars->handle = last; + chars->properties = value[2]; + chars->value_handle = att_get_u16(&value[3]); + bt_uuid_to_string(&uuid, chars->uuid, sizeof(chars->uuid)); + dc->characteristics = g_slist_append(dc->characteristics, + chars); + } + + att_data_list_free(list); + err = 0; + + if (last != 0) { + buf = g_attrib_get_buffer(dc->attrib, &buflen); + + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + + oplen = enc_read_by_type_req(last + 1, dc->end, &uuid, buf, + buflen); + + if (oplen == 0) + return; + + g_attrib_send(dc->attrib, 0, buf[0], buf, oplen, + char_discovered_cb, dc, NULL); + + return; + } + +done: + dc->cb(dc->characteristics, err, dc->user_data); + discover_char_free(dc); +} + +guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + int buflen; + uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); + struct discover_char *dc; + bt_uuid_t type_uuid; + guint16 plen; + + bt_uuid16_create(&type_uuid, GATT_CHARAC_UUID); + + plen = enc_read_by_type_req(start, end, &type_uuid, buf, buflen); + if (plen == 0) + return 0; + + dc = g_try_new0(struct discover_char, 1); + if (dc == NULL) + return 0; + + dc->attrib = g_attrib_ref(attrib); + dc->cb = func; + dc->user_data = user_data; + dc->end = end; + dc->uuid = g_memdup(uuid, sizeof(bt_uuid_t)); + + return g_attrib_send(attrib, 0, buf[0], buf, plen, char_discovered_cb, + dc, NULL); +} + +guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, GAttribResultFunc func, + gpointer user_data) +{ + int buflen; + uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); + guint16 plen; + + plen = enc_read_by_type_req(start, end, uuid, buf, buflen); + if (plen == 0) + return 0; + + return g_attrib_send(attrib, 0, ATT_OP_READ_BY_TYPE_REQ, + buf, plen, func, user_data, NULL); +} + +struct read_long_data { + GAttrib *attrib; + GAttribResultFunc func; + gpointer user_data; + guint8 *buffer; + guint16 size; + guint16 handle; + guint id; + gint ref; +}; + +static void read_long_destroy(gpointer user_data) +{ + struct read_long_data *long_read = user_data; + + if (g_atomic_int_dec_and_test(&long_read->ref) == FALSE) + return; + + if (long_read->buffer != NULL) + g_free(long_read->buffer); + + g_free(long_read); +} + +static void read_blob_helper(guint8 status, const guint8 *rpdu, guint16 rlen, + gpointer user_data) +{ + struct read_long_data *long_read = user_data; + uint8_t *buf; + int buflen; + guint8 *tmp; + guint16 plen; + guint id; + + if (status != 0 || rlen == 1) { + status = 0; + goto done; + } + + tmp = g_try_realloc(long_read->buffer, long_read->size + rlen - 1); + + if (tmp == NULL) { + status = ATT_ECODE_INSUFF_RESOURCES; + goto done; + } + + memcpy(&tmp[long_read->size], &rpdu[1], rlen - 1); + long_read->buffer = tmp; + long_read->size += rlen - 1; + + buf = g_attrib_get_buffer(long_read->attrib, &buflen); + if (rlen < buflen) + goto done; + + plen = enc_read_blob_req(long_read->handle, long_read->size - 1, + buf, buflen); + id = g_attrib_send(long_read->attrib, long_read->id, + ATT_OP_READ_BLOB_REQ, buf, plen, + read_blob_helper, long_read, read_long_destroy); + + if (id != 0) { + g_atomic_int_inc(&long_read->ref); + return; + } + + status = ATT_ECODE_IO; + +done: + long_read->func(status, long_read->buffer, long_read->size, + long_read->user_data); +} + +static void read_char_helper(guint8 status, const guint8 *rpdu, + guint16 rlen, gpointer user_data) +{ + struct read_long_data *long_read = user_data; + int buflen; + uint8_t *buf = g_attrib_get_buffer(long_read->attrib, &buflen); + guint16 plen; + guint id; + + if (status != 0 || rlen < buflen) + goto done; + + long_read->buffer = g_malloc(rlen); + + if (long_read->buffer == NULL) + goto done; + + memcpy(long_read->buffer, rpdu, rlen); + long_read->size = rlen; + + plen = enc_read_blob_req(long_read->handle, rlen - 1, buf, buflen); + id = g_attrib_send(long_read->attrib, long_read->id, + ATT_OP_READ_BLOB_REQ, buf, plen, read_blob_helper, + long_read, read_long_destroy); + + if (id != 0) { + g_atomic_int_inc(&long_read->ref); + return; + } + + status = ATT_ECODE_IO; + +done: + long_read->func(status, rpdu, rlen, long_read->user_data); +} + +guint gatt_read_char(GAttrib *attrib, uint16_t handle, uint16_t offset, + GAttribResultFunc func, gpointer user_data) +{ + uint8_t *buf; + int buflen; + guint16 plen; + guint id; + struct read_long_data *long_read; + + long_read = g_try_new0(struct read_long_data, 1); + + if (long_read == NULL) + return 0; + + long_read->attrib = attrib; + long_read->func = func; + long_read->user_data = user_data; + long_read->handle = handle; + + buf = g_attrib_get_buffer(attrib, &buflen); + if (offset > 0) { + plen = enc_read_blob_req(long_read->handle, offset, buf, + buflen); + id = g_attrib_send(attrib, 0, ATT_OP_READ_BLOB_REQ, buf, plen, + read_blob_helper, long_read, read_long_destroy); + } else { + plen = enc_read_req(handle, buf, buflen); + id = g_attrib_send(attrib, 0, ATT_OP_READ_REQ, buf, plen, + read_char_helper, long_read, read_long_destroy); + } + + if (id == 0) + g_free(long_read); + else { + g_atomic_int_inc(&long_read->ref); + long_read->id = id; + } + + return id; +} + +guint gatt_write_char(GAttrib *attrib, uint16_t handle, uint8_t *value, + int vlen, GAttribResultFunc func, gpointer user_data) +{ + uint8_t *buf; + int buflen; + guint16 plen; + + buf = g_attrib_get_buffer(attrib, &buflen); + if (func) + plen = enc_write_req(handle, value, vlen, buf, buflen); + else + plen = enc_write_cmd(handle, value, vlen, buf, buflen); + + return g_attrib_send(attrib, 0, buf[0], buf, plen, func, + user_data, NULL); +} + +guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func, + gpointer user_data) +{ + uint8_t *buf; + int buflen; + guint16 plen; + + buf = g_attrib_get_buffer(attrib, &buflen); + plen = enc_mtu_req(mtu, buf, buflen); + return g_attrib_send(attrib, 0, ATT_OP_MTU_REQ, buf, plen, func, + user_data, NULL); +} + +guint gatt_find_info(GAttrib *attrib, uint16_t start, uint16_t end, + GAttribResultFunc func, gpointer user_data) +{ + uint8_t *buf; + int buflen; + guint16 plen; + + buf = g_attrib_get_buffer(attrib, &buflen); + plen = enc_find_info_req(start, end, buf, buflen); + if (plen == 0) + return 0; + + return g_attrib_send(attrib, 0, ATT_OP_FIND_INFO_REQ, buf, plen, func, + user_data, NULL); +} + +guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, uint8_t *value, int vlen, + GDestroyNotify notify, gpointer user_data) +{ + uint8_t *buf; + int buflen; + guint16 plen; + + buf = g_attrib_get_buffer(attrib, &buflen); + plen = enc_write_cmd(handle, value, vlen, buf, buflen); + return g_attrib_send(attrib, 0, ATT_OP_WRITE_CMD, buf, plen, NULL, + user_data, notify); +} diff --git a/attrib/gatt.h b/attrib/gatt.h new file mode 100644 index 0000000..221d94d --- /dev/null +++ b/attrib/gatt.h @@ -0,0 +1,53 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define GATT_CID 4 + +typedef void (*gatt_cb_t) (GSList *l, guint8 status, gpointer user_data); + +guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data); + +guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data); + +guint gatt_read_char(GAttrib *attrib, uint16_t handle, uint16_t offset, + GAttribResultFunc func, gpointer user_data); + +guint gatt_write_char(GAttrib *attrib, uint16_t handle, uint8_t *value, + int vlen, GAttribResultFunc func, gpointer user_data); + +guint gatt_find_info(GAttrib *attrib, uint16_t start, uint16_t end, + GAttribResultFunc func, gpointer user_data); + +guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, uint8_t *value, int vlen, + GDestroyNotify notify, gpointer user_data); + +guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, GAttribResultFunc func, + gpointer user_data); + +guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func, + gpointer user_data); diff --git a/attrib/gattrib.c b/attrib/gattrib.c new file mode 100644 index 0000000..290cd96 --- /dev/null +++ b/attrib/gattrib.c @@ -0,0 +1,636 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include + +#include + +#include +#include + +#include "att.h" +#include "btio.h" +#include "gattrib.h" + +#define GATT_TIMEOUT 30 + +struct _GAttrib { + GIOChannel *io; + gint refs; + uint8_t *buf; + int buflen; + guint read_watch; + guint write_watch; + guint timeout_watch; + GQueue *queue; + GSList *events; + guint next_cmd_id; + guint next_evt_id; + GDestroyNotify destroy; + GAttribDisconnectFunc disconnect; + gpointer destroy_user_data; + gpointer disc_user_data; +}; + +struct command { + guint id; + guint8 opcode; + guint8 *pdu; + guint16 len; + guint8 expected; + gboolean sent; + GAttribResultFunc func; + gpointer user_data; + GDestroyNotify notify; +}; + +struct event { + guint id; + guint8 expected; + GAttribNotifyFunc func; + gpointer user_data; + GDestroyNotify notify; +}; + +static guint8 opcode2expected(guint8 opcode) +{ + switch (opcode) { + case ATT_OP_MTU_REQ: + return ATT_OP_MTU_RESP; + + case ATT_OP_FIND_INFO_REQ: + return ATT_OP_FIND_INFO_RESP; + + case ATT_OP_FIND_BY_TYPE_REQ: + return ATT_OP_FIND_BY_TYPE_RESP; + + case ATT_OP_READ_BY_TYPE_REQ: + return ATT_OP_READ_BY_TYPE_RESP; + + case ATT_OP_READ_REQ: + return ATT_OP_READ_RESP; + + case ATT_OP_READ_BLOB_REQ: + return ATT_OP_READ_BLOB_RESP; + + case ATT_OP_READ_MULTI_REQ: + return ATT_OP_READ_MULTI_RESP; + + case ATT_OP_READ_BY_GROUP_REQ: + return ATT_OP_READ_BY_GROUP_RESP; + + case ATT_OP_WRITE_REQ: + return ATT_OP_WRITE_RESP; + + case ATT_OP_PREP_WRITE_REQ: + return ATT_OP_PREP_WRITE_RESP; + + case ATT_OP_EXEC_WRITE_REQ: + return ATT_OP_EXEC_WRITE_RESP; + + case ATT_OP_HANDLE_IND: + return ATT_OP_HANDLE_CNF; + } + + return 0; +} + +static gboolean is_response(guint8 opcode) +{ + switch (opcode) { + case ATT_OP_ERROR: + case ATT_OP_MTU_RESP: + case ATT_OP_FIND_INFO_RESP: + case ATT_OP_FIND_BY_TYPE_RESP: + case ATT_OP_READ_BY_TYPE_RESP: + case ATT_OP_READ_RESP: + case ATT_OP_READ_BLOB_RESP: + case ATT_OP_READ_MULTI_RESP: + case ATT_OP_READ_BY_GROUP_RESP: + case ATT_OP_WRITE_RESP: + case ATT_OP_PREP_WRITE_RESP: + case ATT_OP_EXEC_WRITE_RESP: + case ATT_OP_HANDLE_CNF: + return TRUE; + } + + return FALSE; +} + +GAttrib *g_attrib_ref(GAttrib *attrib) +{ + if (!attrib) + return NULL; + + g_atomic_int_inc(&attrib->refs); + + return attrib; +} + +static void command_destroy(struct command *cmd) +{ + if (cmd->notify) + cmd->notify(cmd->user_data); + + g_free(cmd->pdu); + g_free(cmd); +} + +static void event_destroy(struct event *evt) +{ + if (evt->notify) + evt->notify(evt->user_data); + + g_free(evt); +} + +static void attrib_destroy(GAttrib *attrib) +{ + GSList *l; + struct command *c; + + while ((c = g_queue_pop_head(attrib->queue))) + command_destroy(c); + + g_queue_free(attrib->queue); + attrib->queue = NULL; + + for (l = attrib->events; l; l = l->next) + event_destroy(l->data); + + g_slist_free(attrib->events); + attrib->events = NULL; + + if (attrib->timeout_watch > 0) + g_source_remove(attrib->timeout_watch); + + if (attrib->write_watch > 0) + g_source_remove(attrib->write_watch); + + if (attrib->read_watch > 0) { + g_source_remove(attrib->read_watch); + g_io_channel_unref(attrib->io); + } + + g_free(attrib->buf); + + if (attrib->destroy) + attrib->destroy(attrib->destroy_user_data); + + g_free(attrib); +} + +void g_attrib_unref(GAttrib *attrib) +{ + if (!attrib) + return; + + if (g_atomic_int_dec_and_test(&attrib->refs) == FALSE) + return; + + attrib_destroy(attrib); +} + +GIOChannel *g_attrib_get_channel(GAttrib *attrib) +{ + if (!attrib) + return NULL; + + return attrib->io; +} + +gboolean g_attrib_set_disconnect_function(GAttrib *attrib, + GAttribDisconnectFunc disconnect, gpointer user_data) +{ + if (attrib == NULL) + return FALSE; + + attrib->disconnect = disconnect; + attrib->disc_user_data = user_data; + + return TRUE; +} + +gboolean g_attrib_set_destroy_function(GAttrib *attrib, + GDestroyNotify destroy, gpointer user_data) +{ + if (attrib == NULL) + return FALSE; + + attrib->destroy = destroy; + attrib->destroy_user_data = user_data; + + return TRUE; +} + +static gboolean disconnect_timeout(gpointer data) +{ + struct _GAttrib *attrib = data; + + attrib_destroy(attrib); + + return FALSE; +} + +static gboolean can_write_data(GIOChannel *io, GIOCondition cond, + gpointer data) +{ + struct _GAttrib *attrib = data; + struct command *cmd; + GError *gerr = NULL; + gsize len; + GIOStatus iostat; + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + if (attrib->disconnect) + attrib->disconnect(attrib->disc_user_data); + + return FALSE; + } + + cmd = g_queue_peek_head(attrib->queue); + if (cmd == NULL) + return FALSE; + + iostat = g_io_channel_write_chars(io, (gchar *) cmd->pdu, cmd->len, + &len, &gerr); + if (iostat != G_IO_STATUS_NORMAL) + return FALSE; + + if (cmd->expected == 0) { + g_queue_pop_head(attrib->queue); + command_destroy(cmd); + + return TRUE; + } + + cmd->sent = TRUE; + + if (attrib->timeout_watch == 0) + attrib->timeout_watch = g_timeout_add_seconds(GATT_TIMEOUT, + disconnect_timeout, attrib); + + return FALSE; +} + +static void destroy_sender(gpointer data) +{ + struct _GAttrib *attrib = data; + + attrib->write_watch = 0; +} + +static void wake_up_sender(struct _GAttrib *attrib) +{ + if (attrib->write_watch == 0) + attrib->write_watch = g_io_add_watch_full(attrib->io, + G_PRIORITY_DEFAULT, G_IO_OUT, can_write_data, + attrib, destroy_sender); +} + +static gboolean received_data(GIOChannel *io, GIOCondition cond, gpointer data) +{ + struct _GAttrib *attrib = data; + struct command *cmd = NULL; + GSList *l; + uint8_t buf[512], status; + gsize len; + GIOStatus iostat; + gboolean qempty; + + if (attrib->timeout_watch > 0) { + g_source_remove(attrib->timeout_watch); + attrib->timeout_watch = 0; + } + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + attrib->read_watch = 0; + if (attrib->disconnect) + attrib->disconnect(attrib->disc_user_data); + return FALSE; + } + + memset(buf, 0, sizeof(buf)); + + iostat = g_io_channel_read_chars(io, (gchar *) buf, sizeof(buf), + &len, NULL); + if (iostat != G_IO_STATUS_NORMAL) { + status = ATT_ECODE_IO; + goto done; + } + + for (l = attrib->events; l; l = l->next) { + struct event *evt = l->data; + + if (evt->expected == buf[0] || + evt->expected == GATTRIB_ALL_EVENTS) + evt->func(buf, len, evt->user_data); + } + + if (is_response(buf[0]) == FALSE) + return TRUE; + + cmd = g_queue_pop_head(attrib->queue); + if (cmd == NULL) { + /* Keep the watch if we have events to report */ + return attrib->events != NULL; + } + + if (buf[0] == ATT_OP_ERROR) { + status = buf[4]; + goto done; + } + + if (cmd->expected != buf[0]) { + status = ATT_ECODE_IO; + goto done; + } + + status = 0; + +done: + qempty = attrib->queue == NULL || g_queue_is_empty(attrib->queue); + + if (cmd) { + if (cmd->func) + cmd->func(status, buf, len, cmd->user_data); + + command_destroy(cmd); + } + + if (!qempty) + wake_up_sender(attrib); + + return TRUE; +} + +GAttrib *g_attrib_new(GIOChannel *io) +{ + struct _GAttrib *attrib; + uint16_t omtu; + + g_io_channel_set_encoding(io, NULL, NULL); + g_io_channel_set_buffered(io, FALSE); + + attrib = g_try_new0(struct _GAttrib, 1); + if (attrib == NULL) + return NULL; + + attrib->io = g_io_channel_ref(io); + attrib->queue = g_queue_new(); + + attrib->read_watch = g_io_add_watch(attrib->io, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + received_data, attrib); + + if (bt_io_get(attrib->io, BT_IO_L2CAP, NULL, + BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_INVALID)) { + if (omtu > ATT_MAX_MTU) + omtu = ATT_MAX_MTU; + } else + omtu = ATT_DEFAULT_LE_MTU; + + attrib->buf = g_malloc0(omtu); + attrib->buflen = omtu; + + return g_attrib_ref(attrib); +} + +guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode, + const guint8 *pdu, guint16 len, GAttribResultFunc func, + gpointer user_data, GDestroyNotify notify) +{ + struct command *c; + + c = g_try_new0(struct command, 1); + if (c == NULL) + return 0; + + c->opcode = opcode; + c->expected = opcode2expected(opcode); + c->pdu = g_malloc(len); + memcpy(c->pdu, pdu, len); + c->len = len; + c->func = func; + c->user_data = user_data; + c->notify = notify; + + if (id) { + c->id = id; + g_queue_push_head(attrib->queue, c); + } else { + c->id = ++attrib->next_cmd_id; + g_queue_push_tail(attrib->queue, c); + } + + if (g_queue_get_length(attrib->queue) == 1) + wake_up_sender(attrib); + + return c->id; +} + +static gint command_cmp_by_id(gconstpointer a, gconstpointer b) +{ + const struct command *cmd = a; + guint id = GPOINTER_TO_UINT(b); + + return cmd->id - id; +} + +gboolean g_attrib_cancel(GAttrib *attrib, guint id) +{ + GList *l; + struct command *cmd; + + if (attrib == NULL || attrib->queue == NULL) + return FALSE; + + l = g_queue_find_custom(attrib->queue, GUINT_TO_POINTER(id), + command_cmp_by_id); + if (l == NULL) + return FALSE; + + cmd = l->data; + + if (cmd == g_queue_peek_head(attrib->queue) && cmd->sent) + cmd->func = NULL; + else { + g_queue_remove(attrib->queue, cmd); + command_destroy(cmd); + } + + return TRUE; +} + +gboolean g_attrib_cancel_all(GAttrib *attrib) +{ + struct command *c, *head = NULL; + gboolean first = TRUE; + + if (attrib == NULL || attrib->queue == NULL) + return FALSE; + + while ((c = g_queue_pop_head(attrib->queue))) { + if (first && c->sent) { + /* If the command was sent ignore its callback ... */ + c->func = NULL; + head = c; + continue; + } + + first = FALSE; + command_destroy(c); + } + + if (head) { + /* ... and put it back in the queue */ + g_queue_push_head(attrib->queue, head); + } + + return TRUE; +} + +gboolean g_attrib_set_debug(GAttrib *attrib, + GAttribDebugFunc func, gpointer user_data) +{ + return TRUE; +} + +uint8_t *g_attrib_get_buffer(GAttrib *attrib, int *len) +{ + if (len == NULL) + return NULL; + + *len = attrib->buflen; + + return attrib->buf; +} + +gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu) +{ + if (mtu < ATT_DEFAULT_LE_MTU) + mtu = ATT_DEFAULT_LE_MTU; + + if (mtu > ATT_MAX_MTU) + mtu = ATT_MAX_MTU; + + if (!bt_io_set(attrib->io, BT_IO_L2CAP, NULL, + BT_IO_OPT_OMTU, mtu, + BT_IO_OPT_INVALID)) + return FALSE; + + attrib->buf = g_realloc(attrib->buf, mtu); + + attrib->buflen = mtu; + + return TRUE; +} + +guint g_attrib_register(GAttrib *attrib, guint8 opcode, + GAttribNotifyFunc func, gpointer user_data, + GDestroyNotify notify) +{ + struct event *event; + + event = g_try_new0(struct event, 1); + if (event == NULL) + return 0; + + event->expected = opcode; + event->func = func; + event->user_data = user_data; + event->notify = notify; + event->id = ++attrib->next_evt_id; + + attrib->events = g_slist_append(attrib->events, event); + + return event->id; +} + +static gint event_cmp_by_id(gconstpointer a, gconstpointer b) +{ + const struct event *evt = a; + guint id = GPOINTER_TO_UINT(b); + + return evt->id - id; +} + +gboolean g_attrib_is_encrypted(GAttrib *attrib) +{ + BtIOSecLevel sec_level; + + if (!bt_io_get(attrib->io, BT_IO_L2CAP, NULL, + BT_IO_OPT_SEC_LEVEL, &sec_level, + BT_IO_OPT_INVALID)) + return FALSE; + + return sec_level > BT_IO_SEC_LOW; +} + +gboolean g_attrib_unregister(GAttrib *attrib, guint id) +{ + struct event *evt; + GSList *l; + + l = g_slist_find_custom(attrib->events, GUINT_TO_POINTER(id), + event_cmp_by_id); + if (l == NULL) + return FALSE; + + evt = l->data; + + attrib->events = g_slist_remove(attrib->events, evt); + + if (evt->notify) + evt->notify(evt->user_data); + + g_free(evt); + + return TRUE; +} + +gboolean g_attrib_unregister_all(GAttrib *attrib) +{ + GSList *l; + + if (attrib->events == NULL) + return FALSE; + + for (l = attrib->events; l; l = l->next) { + struct event *evt = l->data; + + if (evt->notify) + evt->notify(evt->user_data); + + g_free(evt); + } + + g_slist_free(attrib->events); + attrib->events = NULL; + + return TRUE; +} diff --git a/attrib/gattrib.h b/attrib/gattrib.h new file mode 100644 index 0000000..4c49879 --- /dev/null +++ b/attrib/gattrib.h @@ -0,0 +1,80 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef __GATTRIB_H +#define __GATTRIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define GATTRIB_ALL_EVENTS 0xFF + +struct _GAttrib; +typedef struct _GAttrib GAttrib; + +typedef void (*GAttribResultFunc) (guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data); +typedef void (*GAttribDisconnectFunc)(gpointer user_data); +typedef void (*GAttribDebugFunc)(const char *str, gpointer user_data); +typedef void (*GAttribNotifyFunc)(const guint8 *pdu, guint16 len, + gpointer user_data); + +GAttrib *g_attrib_new(GIOChannel *io); +GAttrib *g_attrib_ref(GAttrib *attrib); +void g_attrib_unref(GAttrib *attrib); + +GIOChannel *g_attrib_get_channel(GAttrib *attrib); + +gboolean g_attrib_set_disconnect_function(GAttrib *attrib, + GAttribDisconnectFunc disconnect, gpointer user_data); + +gboolean g_attrib_set_destroy_function(GAttrib *attrib, + GDestroyNotify destroy, gpointer user_data); + +guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode, + const guint8 *pdu, guint16 len, GAttribResultFunc func, + gpointer user_data, GDestroyNotify notify); + +gboolean g_attrib_cancel(GAttrib *attrib, guint id); +gboolean g_attrib_cancel_all(GAttrib *attrib); + +gboolean g_attrib_set_debug(GAttrib *attrib, + GAttribDebugFunc func, gpointer user_data); + +guint g_attrib_register(GAttrib *attrib, guint8 opcode, + GAttribNotifyFunc func, gpointer user_data, + GDestroyNotify notify); + +gboolean g_attrib_is_encrypted(GAttrib *attrib); + +uint8_t *g_attrib_get_buffer(GAttrib *attrib, int *len); +gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu); + +gboolean g_attrib_unregister(GAttrib *attrib, guint id); +gboolean g_attrib_unregister_all(GAttrib *attrib); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/attrib/gatttool.c b/attrib/gatttool.c new file mode 100644 index 0000000..0dfbc04 --- /dev/null +++ b/attrib/gatttool.c @@ -0,0 +1,634 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "att.h" +#include "btio.h" +#include "gattrib.h" +#include "gatt.h" +#include "gatttool.h" + +static gchar *opt_src = NULL; +static gchar *opt_dst = NULL; +static gchar *opt_value = NULL; +static gchar *opt_sec_level = NULL; +static bt_uuid_t *opt_uuid = NULL; +static int opt_start = 0x0001; +static int opt_end = 0xffff; +static int opt_handle = -1; +static int opt_mtu = 0; +static int opt_psm = 0; +static int opt_offset = 0; +static gboolean opt_primary = FALSE; +static gboolean opt_characteristics = FALSE; +static gboolean opt_char_read = FALSE; +static gboolean opt_listen = FALSE; +static gboolean opt_char_desc = FALSE; +static gboolean opt_char_write = FALSE; +static gboolean opt_char_write_req = FALSE; +static gboolean opt_interactive = FALSE; +static GMainLoop *event_loop; +static gboolean got_error = FALSE; + +struct characteristic_data { + GAttrib *attrib; + uint16_t start; + uint16_t end; +}; + +static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + if (err) { + g_printerr("%s\n", err->message); + got_error = TRUE; + g_main_loop_quit(event_loop); + } +} + +static void primary_all_cb(GSList *services, guint8 status, gpointer user_data) +{ + GSList *l; + + if (status) { + g_printerr("Discover all primary services failed: %s\n", + att_ecode2str(status)); + goto done; + } + + for (l = services; l; l = l->next) { + struct att_primary *prim = l->data; + g_print("attr handle = 0x%04x, end grp handle = 0x%04x " + "uuid: %s\n", prim->start, prim->end, prim->uuid); + } + +done: + g_main_loop_quit(event_loop); +} + +static void primary_by_uuid_cb(GSList *ranges, guint8 status, + gpointer user_data) +{ + GSList *l; + + if (status != 0) { + g_printerr("Discover primary services by UUID failed: %s\n", + att_ecode2str(status)); + goto done; + } + + for (l = ranges; l; l = l->next) { + struct att_range *range = l->data; + g_print("Starting handle: %04x Ending handle: %04x\n", + range->start, range->end); + } + +done: + g_main_loop_quit(event_loop); +} + +static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) +{ + GAttrib *attrib = user_data; + uint8_t opdu[ATT_MAX_MTU]; + uint16_t handle, i, olen = 0; + + handle = att_get_u16(&pdu[1]); + + switch (pdu[0]) { + case ATT_OP_HANDLE_NOTIFY: + g_print("Notification handle = 0x%04x value: ", handle); + break; + case ATT_OP_HANDLE_IND: + g_print("Indication handle = 0x%04x value: ", handle); + break; + default: + g_print("Invalid opcode\n"); + return; + } + + for (i = 3; i < len; i++) + g_print("%02x ", pdu[i]); + + g_print("\n"); + + if (pdu[0] == ATT_OP_HANDLE_NOTIFY) + return; + + olen = enc_confirmation(opdu, sizeof(opdu)); + + if (olen > 0) + g_attrib_send(attrib, 0, opdu[0], opdu, olen, NULL, NULL, NULL); +} + +static gboolean listen_start(gpointer user_data) +{ + GAttrib *attrib = user_data; + + g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, events_handler, + attrib, NULL); + g_attrib_register(attrib, ATT_OP_HANDLE_IND, events_handler, + attrib, NULL); + + return FALSE; +} + +static gboolean primary(gpointer user_data) +{ + GAttrib *attrib = user_data; + + if (opt_uuid) + gatt_discover_primary(attrib, opt_uuid, primary_by_uuid_cb, + NULL); + else + gatt_discover_primary(attrib, NULL, primary_all_cb, NULL); + + return FALSE; +} + +static void char_discovered_cb(GSList *characteristics, guint8 status, + gpointer user_data) +{ + GSList *l; + + if (status) { + g_printerr("Discover all characteristics failed: %s\n", + att_ecode2str(status)); + goto done; + } + + for (l = characteristics; l; l = l->next) { + struct att_char *chars = l->data; + + g_print("handle = 0x%04x, char properties = 0x%02x, char value " + "handle = 0x%04x, uuid = %s\n", chars->handle, + chars->properties, chars->value_handle, chars->uuid); + } + +done: + g_main_loop_quit(event_loop); +} + +static gboolean characteristics(gpointer user_data) +{ + GAttrib *attrib = user_data; + + gatt_discover_char(attrib, opt_start, opt_end, opt_uuid, + char_discovered_cb, NULL); + + return FALSE; +} + +static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + uint8_t value[ATT_MAX_MTU]; + int i, vlen; + + if (status != 0) { + g_printerr("Characteristic value/descriptor read failed: %s\n", + att_ecode2str(status)); + goto done; + } + if (!dec_read_resp(pdu, plen, value, &vlen)) { + g_printerr("Protocol error\n"); + goto done; + } + g_print("Characteristic value/descriptor: "); + for (i = 0; i < vlen; i++) + g_print("%02x ", value[i]); + g_print("\n"); + +done: + if (opt_listen == FALSE) + g_main_loop_quit(event_loop); +} + +static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct characteristic_data *char_data = user_data; + struct att_data_list *list; + int i; + + if (status == ATT_ECODE_ATTR_NOT_FOUND && + char_data->start != opt_start) + goto done; + + if (status != 0) { + g_printerr("Read characteristics by UUID failed: %s\n", + att_ecode2str(status)); + goto done; + } + + list = dec_read_by_type_resp(pdu, plen); + if (list == NULL) + goto done; + + for (i = 0; i < list->num; i++) { + uint8_t *value = list->data[i]; + int j; + + char_data->start = att_get_u16(value) + 1; + + g_print("handle: 0x%04x \t value: ", att_get_u16(value)); + value += 2; + for (j = 0; j < list->len - 2; j++, value++) + g_print("%02x ", *value); + g_print("\n"); + } + + att_data_list_free(list); + + gatt_read_char_by_uuid(char_data->attrib, char_data->start, + char_data->end, opt_uuid, + char_read_by_uuid_cb, + char_data); + + return; +done: + g_free(char_data); + g_main_loop_quit(event_loop); +} + +static gboolean characteristics_read(gpointer user_data) +{ + GAttrib *attrib = user_data; + + if (opt_uuid != NULL) { + struct characteristic_data *char_data; + + char_data = g_new(struct characteristic_data, 1); + char_data->attrib = attrib; + char_data->start = opt_start; + char_data->end = opt_end; + + gatt_read_char_by_uuid(attrib, opt_start, opt_end, opt_uuid, + char_read_by_uuid_cb, char_data); + + return FALSE; + } + + if (opt_handle <= 0) { + g_printerr("A valid handle is required\n"); + g_main_loop_quit(event_loop); + return FALSE; + } + + gatt_read_char(attrib, opt_handle, opt_offset, char_read_cb, attrib); + + return FALSE; +} + +static void mainloop_quit(gpointer user_data) +{ + uint8_t *value = user_data; + + g_free(value); + g_main_loop_quit(event_loop); +} + +static gboolean characteristics_write(gpointer user_data) +{ + GAttrib *attrib = user_data; + uint8_t *value; + size_t len; + + if (opt_handle <= 0) { + g_printerr("A valid handle is required\n"); + goto error; + } + + if (opt_value == NULL || opt_value[0] == '\0') { + g_printerr("A value is required\n"); + goto error; + } + + len = gatt_attr_data_from_string(opt_value, &value); + if (len == 0) { + g_printerr("Invalid value\n"); + goto error; + } + + gatt_write_cmd(attrib, opt_handle, value, len, mainloop_quit, value); + + return FALSE; + +error: + g_main_loop_quit(event_loop); + return FALSE; +} + +static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + if (status != 0) { + g_printerr("Characteristic Write Request failed: " + "%s\n", att_ecode2str(status)); + goto done; + } + + if (!dec_write_resp(pdu, plen)) { + g_printerr("Protocol error\n"); + goto done; + } + + g_print("Characteristic value was written sucessfully\n"); + +done: + if (opt_listen == FALSE) + g_main_loop_quit(event_loop); +} + +static gboolean characteristics_write_req(gpointer user_data) +{ + GAttrib *attrib = user_data; + uint8_t *value; + size_t len; + + if (opt_handle <= 0) { + g_printerr("A valid handle is required\n"); + goto error; + } + + if (opt_value == NULL || opt_value[0] == '\0') { + g_printerr("A value is required\n"); + goto error; + } + + len = gatt_attr_data_from_string(opt_value, &value); + if (len == 0) { + g_printerr("Invalid value\n"); + goto error; + } + + gatt_write_char(attrib, opt_handle, value, len, char_write_req_cb, + NULL); + + return FALSE; + +error: + g_main_loop_quit(event_loop); + return FALSE; +} + +static void char_desc_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct att_data_list *list; + guint8 format; + int i; + + if (status != 0) { + g_printerr("Discover all characteristic descriptors failed: " + "%s\n", att_ecode2str(status)); + goto done; + } + + list = dec_find_info_resp(pdu, plen, &format); + if (list == NULL) + goto done; + + for (i = 0; i < list->num; i++) { + char uuidstr[MAX_LEN_UUID_STR]; + uint16_t handle; + uint8_t *value; + bt_uuid_t uuid; + + value = list->data[i]; + handle = att_get_u16(value); + + if (format == 0x01) + uuid = att_get_uuid16(&value[2]); + else + uuid = att_get_uuid128(&value[2]); + + bt_uuid_to_string(&uuid, uuidstr, MAX_LEN_UUID_STR); + g_print("handle = 0x%04x, uuid = %s\n", handle, uuidstr); + } + + att_data_list_free(list); + +done: + if (opt_listen == FALSE) + g_main_loop_quit(event_loop); +} + +static gboolean characteristics_desc(gpointer user_data) +{ + GAttrib *attrib = user_data; + + gatt_find_info(attrib, opt_start, opt_end, char_desc_cb, NULL); + + return FALSE; +} + +static gboolean parse_uuid(const char *key, const char *value, + gpointer user_data, GError **error) +{ + if (!value) + return FALSE; + + opt_uuid = g_try_malloc(sizeof(bt_uuid_t)); + if (opt_uuid == NULL) + return FALSE; + + if (bt_string_to_uuid(opt_uuid, value) < 0) + return FALSE; + + return TRUE; +} + +static GOptionEntry primary_char_options[] = { + { "start", 's' , 0, G_OPTION_ARG_INT, &opt_start, + "Starting handle(optional)", "0x0001" }, + { "end", 'e' , 0, G_OPTION_ARG_INT, &opt_end, + "Ending handle(optional)", "0xffff" }, + { "uuid", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, + parse_uuid, "UUID16 or UUID128(optional)", "0x1801"}, + { NULL }, +}; + +static GOptionEntry char_rw_options[] = { + { "handle", 'a' , 0, G_OPTION_ARG_INT, &opt_handle, + "Read/Write characteristic by handle(required)", "0x0001" }, + { "value", 'n' , 0, G_OPTION_ARG_STRING, &opt_value, + "Write characteristic value (required for write operation)", + "0x0001" }, + { "offset", 'o', 0, G_OPTION_ARG_INT, &opt_offset, + "Offset to long read characteristic by handle", "N"}, + {NULL}, +}; + +static GOptionEntry gatt_options[] = { + { "primary", 0, 0, G_OPTION_ARG_NONE, &opt_primary, + "Primary Service Discovery", NULL }, + { "characteristics", 0, 0, G_OPTION_ARG_NONE, &opt_characteristics, + "Characteristics Discovery", NULL }, + { "char-read", 0, 0, G_OPTION_ARG_NONE, &opt_char_read, + "Characteristics Value/Descriptor Read", NULL }, + { "char-write", 0, 0, G_OPTION_ARG_NONE, &opt_char_write, + "Characteristics Value Write Without Response (Write Command)", + NULL }, + { "char-write-req", 0, 0, G_OPTION_ARG_NONE, &opt_char_write_req, + "Characteristics Value Write (Write Request)", NULL }, + { "char-desc", 0, 0, G_OPTION_ARG_NONE, &opt_char_desc, + "Characteristics Descriptor Discovery", NULL }, + { "listen", 0, 0, G_OPTION_ARG_NONE, &opt_listen, + "Listen for notifications and indications", NULL }, + { "interactive", 'I', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, + &opt_interactive, "Use interactive mode", NULL }, + { NULL }, +}; + +static GOptionEntry options[] = { + { "adapter", 'i', 0, G_OPTION_ARG_STRING, &opt_src, + "Specify local adapter interface", "hciX" }, + { "device", 'b', 0, G_OPTION_ARG_STRING, &opt_dst, + "Specify remote Bluetooth address", "MAC" }, + { "mtu", 'm', 0, G_OPTION_ARG_INT, &opt_mtu, + "Specify the MTU size", "MTU" }, + { "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm, + "Specify the PSM for GATT/ATT over BR/EDR", "PSM" }, + { "sec-level", 'l', 0, G_OPTION_ARG_STRING, &opt_sec_level, + "Set security level. Default: low", "[low | medium | high]"}, + { NULL }, +}; + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + GOptionGroup *gatt_group, *params_group, *char_rw_group; + GError *gerr = NULL; + GAttrib *attrib; + GIOChannel *chan; + GSourceFunc callback; + + opt_sec_level = g_strdup("low"); + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + /* GATT commands */ + gatt_group = g_option_group_new("gatt", "GATT commands", + "Show all GATT commands", NULL, NULL); + g_option_context_add_group(context, gatt_group); + g_option_group_add_entries(gatt_group, gatt_options); + + /* Primary Services and Characteristics arguments */ + params_group = g_option_group_new("params", + "Primary Services/Characteristics arguments", + "Show all Primary Services/Characteristics arguments", + NULL, NULL); + g_option_context_add_group(context, params_group); + g_option_group_add_entries(params_group, primary_char_options); + + /* Characteristics value/descriptor read/write arguments */ + char_rw_group = g_option_group_new("char-read-write", + "Characteristics Value/Descriptor Read/Write arguments", + "Show all Characteristics Value/Descriptor Read/Write " + "arguments", + NULL, NULL); + g_option_context_add_group(context, char_rw_group); + g_option_group_add_entries(char_rw_group, char_rw_options); + + if (g_option_context_parse(context, &argc, &argv, &gerr) == FALSE) { + g_printerr("%s\n", gerr->message); + g_error_free(gerr); + } + + if (opt_interactive) { + interactive(opt_src, opt_dst, opt_psm); + goto done; + } + + if (opt_primary) + callback = primary; + else if (opt_characteristics) + callback = characteristics; + else if (opt_char_read) + callback = characteristics_read; + else if (opt_char_write) + callback = characteristics_write; + else if (opt_char_write_req) + callback = characteristics_write_req; + else if (opt_char_desc) + callback = characteristics_desc; + else { + gchar *help = g_option_context_get_help(context, TRUE, NULL); + g_print("%s\n", help); + g_free(help); + got_error = TRUE; + goto done; + } + + chan = gatt_connect(opt_src, opt_dst, opt_sec_level, + opt_psm, opt_mtu, connect_cb); + if (chan == NULL) { + got_error = TRUE; + goto done; + } + + attrib = g_attrib_new(chan); + g_io_channel_unref(chan); + + event_loop = g_main_loop_new(NULL, FALSE); + + if (opt_listen) + g_idle_add(listen_start, attrib); + + g_idle_add(callback, attrib); + + g_main_loop_run(event_loop); + + g_attrib_unregister_all(attrib); + + g_main_loop_unref(event_loop); + + g_attrib_unref(attrib); + +done: + g_option_context_free(context); + g_free(opt_src); + g_free(opt_dst); + g_free(opt_uuid); + g_free(opt_sec_level); + + if (got_error) + exit(EXIT_FAILURE); + else + exit(EXIT_SUCCESS); +} diff --git a/attrib/gatttool.h b/attrib/gatttool.h new file mode 100644 index 0000000..89ac282 --- /dev/null +++ b/attrib/gatttool.h @@ -0,0 +1,28 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int interactive(const gchar *src, const gchar *dst, gboolean le); +GIOChannel *gatt_connect(const gchar *src, const gchar *dst, + const gchar *sec_level, int psm, int mtu, + BtIOConnect connect_cb); +size_t gatt_attr_data_from_string(const char *str, uint8_t **data); diff --git a/attrib/interactive.c b/attrib/interactive.c new file mode 100644 index 0000000..3fafb1e --- /dev/null +++ b/attrib/interactive.c @@ -0,0 +1,842 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "att.h" +#include "btio.h" +#include "gattrib.h" +#include "gatt.h" +#include "gatttool.h" + +static GIOChannel *iochannel = NULL; +static GAttrib *attrib = NULL; +static GMainLoop *event_loop; +static GString *prompt; + +static gchar *opt_src = NULL; +static gchar *opt_dst = NULL; +static gchar *opt_sec_level = NULL; +static int opt_psm = 0; +static int opt_mtu = 0; + +struct characteristic_data { + uint16_t orig_start; + uint16_t start; + uint16_t end; + bt_uuid_t uuid; +}; + +static void cmd_help(int argcp, char **argvp); + +enum state { + STATE_DISCONNECTED, + STATE_CONNECTING, + STATE_CONNECTED +} conn_state; + +static char *get_prompt(void) +{ + if (conn_state == STATE_CONNECTING) { + g_string_assign(prompt, "Connecting... "); + return prompt->str; + } + + if (conn_state == STATE_CONNECTED) + g_string_assign(prompt, "[CON]"); + else + g_string_assign(prompt, "[ ]"); + + if (opt_dst) + g_string_append_printf(prompt, "[%17s]", opt_dst); + else + g_string_append_printf(prompt, "[%17s]", ""); + + if (opt_psm) + g_string_append(prompt, "[BR]"); + else + g_string_append(prompt, "[LE]"); + + g_string_append(prompt, "> "); + + return prompt->str; +} + + +static void set_state(enum state st) +{ + conn_state = st; + rl_set_prompt(get_prompt()); + rl_redisplay(); +} + +static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) +{ + uint8_t opdu[ATT_MAX_MTU]; + uint16_t handle, i, olen; + + handle = att_get_u16(&pdu[1]); + + printf("\n"); + switch (pdu[0]) { + case ATT_OP_HANDLE_NOTIFY: + printf("Notification handle = 0x%04x value: ", handle); + break; + case ATT_OP_HANDLE_IND: + printf("Indication handle = 0x%04x value: ", handle); + break; + default: + printf("Invalid opcode\n"); + return; + } + + for (i = 3; i < len; i++) + printf("%02x ", pdu[i]); + + printf("\n"); + rl_forced_update_display(); + + if (pdu[0] == ATT_OP_HANDLE_NOTIFY) + return; + + olen = enc_confirmation(opdu, sizeof(opdu)); + + if (olen > 0) + g_attrib_send(attrib, 0, opdu[0], opdu, olen, NULL, NULL, NULL); +} + +static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + if (err) { + printf("connect error: %s\n", err->message); + set_state(STATE_DISCONNECTED); + return; + } + + attrib = g_attrib_new(iochannel); + g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, events_handler, + attrib, NULL); + g_attrib_register(attrib, ATT_OP_HANDLE_IND, events_handler, + attrib, NULL); + set_state(STATE_CONNECTED); +} + +static void primary_all_cb(GSList *services, guint8 status, gpointer user_data) +{ + GSList *l; + + if (status) { + printf("Discover all primary services failed: %s\n", + att_ecode2str(status)); + return; + } + + printf("\n"); + for (l = services; l; l = l->next) { + struct att_primary *prim = l->data; + printf("attr handle: 0x%04x, end grp handle: 0x%04x " + "uuid: %s\n", prim->start, prim->end, prim->uuid); + } + + rl_forced_update_display(); +} + +static void primary_by_uuid_cb(GSList *ranges, guint8 status, + gpointer user_data) +{ + GSList *l; + + if (status) { + printf("Discover primary services by UUID failed: %s\n", + att_ecode2str(status)); + return; + } + + printf("\n"); + for (l = ranges; l; l = l->next) { + struct att_range *range = l->data; + g_print("Starting handle: 0x%04x Ending handle: 0x%04x\n", + range->start, range->end); + } + + rl_forced_update_display(); +} + +static void char_cb(GSList *characteristics, guint8 status, gpointer user_data) +{ + GSList *l; + + if (status) { + printf("Discover all characteristics failed: %s\n", + att_ecode2str(status)); + return; + } + + printf("\n"); + for (l = characteristics; l; l = l->next) { + struct att_char *chars = l->data; + + printf("handle: 0x%04x, char properties: 0x%02x, char value " + "handle: 0x%04x, uuid: %s\n", chars->handle, + chars->properties, chars->value_handle, + chars->uuid); + } + + rl_forced_update_display(); +} + +static void char_desc_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct att_data_list *list; + guint8 format; + int i; + + if (status != 0) { + printf("Discover all characteristic descriptors failed: " + "%s\n", att_ecode2str(status)); + return; + } + + list = dec_find_info_resp(pdu, plen, &format); + if (list == NULL) + return; + + printf("\n"); + for (i = 0; i < list->num; i++) { + char uuidstr[MAX_LEN_UUID_STR]; + uint16_t handle; + uint8_t *value; + bt_uuid_t uuid; + + value = list->data[i]; + handle = att_get_u16(value); + + if (format == 0x01) + uuid = att_get_uuid16(&value[2]); + else + uuid = att_get_uuid128(&value[2]); + + bt_uuid_to_string(&uuid, uuidstr, MAX_LEN_UUID_STR); + printf("handle: 0x%04x, uuid: %s\n", handle, uuidstr); + } + + att_data_list_free(list); + + rl_forced_update_display(); +} + +static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + uint8_t value[ATT_MAX_MTU]; + int i, vlen; + + if (status != 0) { + printf("Characteristic value/descriptor read failed: %s\n", + att_ecode2str(status)); + return; + } + + if (!dec_read_resp(pdu, plen, value, &vlen)) { + printf("Protocol error\n"); + return; + } + + printf("\nCharacteristic value/descriptor: "); + for (i = 0; i < vlen; i++) + printf("%02x ", value[i]); + printf("\n"); + + rl_forced_update_display(); +} + +static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct characteristic_data *char_data = user_data; + struct att_data_list *list; + int i; + + if (status == ATT_ECODE_ATTR_NOT_FOUND && + char_data->start != char_data->orig_start) + goto done; + + if (status != 0) { + printf("Read characteristics by UUID failed: %s\n", + att_ecode2str(status)); + goto done; + } + + list = dec_read_by_type_resp(pdu, plen); + if (list == NULL) + goto done; + + for (i = 0; i < list->num; i++) { + uint8_t *value = list->data[i]; + int j; + + char_data->start = att_get_u16(value) + 1; + + printf("\nhandle: 0x%04x \t value: ", att_get_u16(value)); + value += 2; + for (j = 0; j < list->len - 2; j++, value++) + printf("%02x ", *value); + printf("\n"); + } + + att_data_list_free(list); + + gatt_read_char_by_uuid(attrib, char_data->start, char_data->end, + &char_data->uuid, char_read_by_uuid_cb, + char_data); + + rl_forced_update_display(); + + return; + +done: + g_free(char_data); +} + +static void cmd_exit(int argcp, char **argvp) +{ + rl_callback_handler_remove(); + g_main_loop_quit(event_loop); +} + +static void cmd_connect(int argcp, char **argvp) +{ + if (conn_state != STATE_DISCONNECTED) + return; + + if (argcp > 1) { + g_free(opt_dst); + opt_dst = g_strdup(argvp[1]); + } + + if (opt_dst == NULL) { + printf("Remote Bluetooth address required\n"); + return; + } + + set_state(STATE_CONNECTING); + iochannel = gatt_connect(opt_src, opt_dst, opt_sec_level, opt_psm, + opt_mtu, connect_cb); + if (iochannel == NULL) + set_state(STATE_DISCONNECTED); +} + +static void cmd_disconnect(int argcp, char **argvp) +{ + if (conn_state == STATE_DISCONNECTED) + return; + + g_attrib_unref(attrib); + attrib = NULL; + opt_mtu = 0; + + g_io_channel_shutdown(iochannel, FALSE, NULL); + g_io_channel_unref(iochannel); + iochannel = NULL; + + set_state(STATE_DISCONNECTED); +} + +static void cmd_primary(int argcp, char **argvp) +{ + bt_uuid_t uuid; + + if (conn_state != STATE_CONNECTED) { + printf("Command failed: disconnected\n"); + return; + } + + if (argcp == 1) { + gatt_discover_primary(attrib, NULL, primary_all_cb, NULL); + return; + } + + if (bt_string_to_uuid(&uuid, argvp[1]) < 0) { + printf("Invalid UUID\n"); + return; + } + + gatt_discover_primary(attrib, &uuid, primary_by_uuid_cb, NULL); +} + +static int strtohandle(const char *src) +{ + char *e; + int dst; + + errno = 0; + dst = strtoll(src, &e, 16); + if (errno != 0 || *e != '\0') + return -EINVAL; + + return dst; +} + +static void cmd_char(int argcp, char **argvp) +{ + int start = 0x0001; + int end = 0xffff; + + if (conn_state != STATE_CONNECTED) { + printf("Command failed: disconnected\n"); + return; + } + + if (argcp > 1) { + start = strtohandle(argvp[1]); + if (start < 0) { + printf("Invalid start handle: %s\n", argvp[1]); + return; + } + } + + if (argcp > 2) { + end = strtohandle(argvp[2]); + if (end < 0) { + printf("Invalid end handle: %s\n", argvp[2]); + return; + } + } + + if (argcp > 3) { + bt_uuid_t uuid; + + if (bt_string_to_uuid(&uuid, argvp[3]) < 0) { + printf("Invalid UUID\n"); + return; + } + + gatt_discover_char(attrib, start, end, &uuid, char_cb, NULL); + return; + } + + gatt_discover_char(attrib, start, end, NULL, char_cb, NULL); +} + +static void cmd_char_desc(int argcp, char **argvp) +{ + int start = 0x0001; + int end = 0xffff; + + if (conn_state != STATE_CONNECTED) { + printf("Command failed: disconnected\n"); + return; + } + + if (argcp > 1) { + start = strtohandle(argvp[1]); + if (start < 0) { + printf("Invalid start handle: %s\n", argvp[1]); + return; + } + } + + if (argcp > 2) { + end = strtohandle(argvp[2]); + if (end < 0) { + printf("Invalid end handle: %s\n", argvp[2]); + return; + } + } + + gatt_find_info(attrib, start, end, char_desc_cb, NULL); +} + +static void cmd_read_hnd(int argcp, char **argvp) +{ + int handle; + int offset = 0; + + if (conn_state != STATE_CONNECTED) { + printf("Command failed: disconnected\n"); + return; + } + + if (argcp < 2) { + printf("Missing argument: handle\n"); + return; + } + + handle = strtohandle(argvp[1]); + if (handle < 0) { + printf("Invalid handle: %s\n", argvp[1]); + return; + } + + if (argcp > 2) { + char *e; + + errno = 0; + offset = strtol(argvp[2], &e, 0); + if (errno != 0 || *e != '\0') { + printf("Invalid offset: %s\n", argvp[2]); + return; + } + } + + gatt_read_char(attrib, handle, offset, char_read_cb, attrib); +} + +static void cmd_read_uuid(int argcp, char **argvp) +{ + struct characteristic_data *char_data; + int start = 0x0001; + int end = 0xffff; + bt_uuid_t uuid; + + if (conn_state != STATE_CONNECTED) { + printf("Command failed: disconnected\n"); + return; + } + + if (argcp < 2) { + printf("Missing argument: UUID\n"); + return; + } + + if (bt_string_to_uuid(&uuid, argvp[1]) < 0) { + printf("Invalid UUID\n"); + return; + } + + if (argcp > 2) { + start = strtohandle(argvp[2]); + if (start < 0) { + printf("Invalid start handle: %s\n", argvp[1]); + return; + } + } + + if (argcp > 3) { + end = strtohandle(argvp[3]); + if (end < 0) { + printf("Invalid end handle: %s\n", argvp[2]); + return; + } + } + + char_data = g_new(struct characteristic_data, 1); + char_data->orig_start = start; + char_data->start = start; + char_data->end = end; + char_data->uuid = uuid; + + gatt_read_char_by_uuid(attrib, start, end, &char_data->uuid, + char_read_by_uuid_cb, char_data); +} + +static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + if (status != 0) { + printf("Characteristic Write Request failed: " + "%s\n", att_ecode2str(status)); + return; + } + + if (!dec_write_resp(pdu, plen)) { + printf("Protocol error\n"); + return; + } + + printf("Characteristic value was written successfully\n"); +} + +static void cmd_char_write(int argcp, char **argvp) +{ + uint8_t *value; + size_t plen; + int handle; + + if (conn_state != STATE_CONNECTED) { + printf("Command failed: disconnected\n"); + return; + } + + if (argcp < 3) { + printf("Usage: %s \n", argvp[0]); + return; + } + + handle = strtoll(argvp[1], NULL, 16); + if (errno != 0 || handle <= 0) { + printf("A valid handle is required\n"); + return; + } + + plen = gatt_attr_data_from_string(argvp[2], &value); + if (plen == 0) { + g_printerr("Invalid value\n"); + return; + } + + if (g_strcmp0("char-write-req", argvp[0]) == 0) + gatt_write_char(attrib, handle, value, plen, + char_write_req_cb, NULL); + else + gatt_write_char(attrib, handle, value, plen, NULL, NULL); + + g_free(value); +} + +static void cmd_sec_level(int argcp, char **argvp) +{ + GError *gerr = NULL; + BtIOSecLevel sec_level; + + if (argcp < 2) { + printf("sec-level: %s\n", opt_sec_level); + return; + } + + if (strcasecmp(argvp[1], "medium") == 0) + sec_level = BT_IO_SEC_MEDIUM; + else if (strcasecmp(argvp[1], "high") == 0) + sec_level = BT_IO_SEC_HIGH; + else if (strcasecmp(argvp[1], "low") == 0) + sec_level = BT_IO_SEC_LOW; + else { + printf("Allowed values: low | medium | high\n"); + return; + } + + g_free(opt_sec_level); + opt_sec_level = g_strdup(argvp[1]); + + if (conn_state != STATE_CONNECTED) + return; + + if (opt_psm) { + printf("It must be reconnected to this change take effect\n"); + return; + } + + bt_io_set(iochannel, BT_IO_L2CAP, &gerr, + BT_IO_OPT_SEC_LEVEL, sec_level, + BT_IO_OPT_INVALID); + + if (gerr) { + printf("Error: %s\n", gerr->message); + g_error_free(gerr); + } +} + +static void exchange_mtu_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + uint16_t mtu; + + if (status != 0) { + printf("Exchange MTU Request failed: %s\n", + att_ecode2str(status)); + return; + } + + if (!dec_mtu_resp(pdu, plen, &mtu)) { + printf("Protocol error\n"); + return; + } + + mtu = MIN(mtu, opt_mtu); + /* Set new value for MTU in client */ + if (g_attrib_set_mtu(attrib, mtu)) + printf("MTU was exchanged successfully: %d\n", mtu); + else + printf("Error exchanging MTU\n"); +} + +static void cmd_mtu(int argcp, char **argvp) +{ + if (conn_state != STATE_CONNECTED) { + printf("Command failed: not connected.\n"); + return; + } + + if (opt_psm) { + printf("Command failed: operation is only available for LE" + " transport.\n"); + return; + } + + if (argcp < 2) { + printf("Usage: mtu \n"); + return; + } + + if (opt_mtu) { + printf("Command failed: MTU exchange can only occur once per" + " connection.\n"); + return; + } + + errno = 0; + opt_mtu = strtoll(argvp[1], NULL, 0); + if (errno != 0 || opt_mtu < ATT_DEFAULT_LE_MTU) { + printf("Invalid value. Minimum MTU size is %d\n", + ATT_DEFAULT_LE_MTU); + return; + } + + gatt_exchange_mtu(attrib, opt_mtu, exchange_mtu_cb, NULL); +} + +static struct { + const char *cmd; + void (*func)(int argcp, char **argvp); + const char *params; + const char *desc; +} commands[] = { + { "help", cmd_help, "", + "Show this help"}, + { "exit", cmd_exit, "", + "Exit interactive mode" }, + { "connect", cmd_connect, "[address]", + "Connect to a remote device" }, + { "disconnect", cmd_disconnect, "", + "Disconnect from a remote device" }, + { "primary", cmd_primary, "[UUID]", + "Primary Service Discovery" }, + { "characteristics", cmd_char, "[start hnd [end hnd [UUID]]]", + "Characteristics Discovery" }, + { "char-desc", cmd_char_desc, "[start hnd] [end hnd]", + "Characteristics Descriptor Discovery" }, + { "char-read-hnd", cmd_read_hnd, " [offset]", + "Characteristics Value/Descriptor Read by handle" }, + { "char-read-uuid", cmd_read_uuid, " [start hnd] [end hnd]", + "Characteristics Value/Descriptor Read by UUID" }, + { "char-write-req", cmd_char_write, " ", + "Characteristic Value Write (Write Request)" }, + { "char-write-cmd", cmd_char_write, " ", + "Characteristic Value Write (No response)" }, + { "sec-level", cmd_sec_level, "[low | medium | high]", + "Set security level. Default: low" }, + { "mtu", cmd_mtu, "", + "Exchange MTU for GATT/ATT" }, + { NULL, NULL, NULL} +}; + +static void cmd_help(int argcp, char **argvp) +{ + int i; + + for (i = 0; commands[i].cmd; i++) + printf("%-15s %-30s %s\n", commands[i].cmd, + commands[i].params, commands[i].desc); +} + +static void parse_line(char *line_read) +{ + gchar **argvp; + int argcp; + int i; + + if (line_read == NULL) { + printf("\n"); + cmd_exit(0, NULL); + return; + } + + line_read = g_strstrip(line_read); + + if (*line_read == '\0') + return; + + add_history(line_read); + + g_shell_parse_argv(line_read, &argcp, &argvp, NULL); + + for (i = 0; commands[i].cmd; i++) + if (strcasecmp(commands[i].cmd, argvp[0]) == 0) + break; + + if (commands[i].cmd) + commands[i].func(argcp, argvp); + else + printf("%s: command not found\n", argvp[0]); + + g_strfreev(argvp); +} + +static gboolean prompt_read(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + g_io_channel_unref(chan); + return FALSE; + } + + rl_callback_read_char(); + + return TRUE; +} + +int interactive(const gchar *src, const gchar *dst, int psm) +{ + GIOChannel *pchan; + gint events; + + opt_sec_level = g_strdup("low"); + + opt_src = g_strdup(src); + opt_dst = g_strdup(dst); + opt_psm = psm; + + prompt = g_string_new(NULL); + + event_loop = g_main_loop_new(NULL, FALSE); + + pchan = g_io_channel_unix_new(fileno(stdin)); + g_io_channel_set_close_on_unref(pchan, TRUE); + events = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch(pchan, events, prompt_read, NULL); + + rl_callback_handler_install(get_prompt(), parse_line); + + g_main_loop_run(event_loop); + + rl_callback_handler_remove(); + cmd_disconnect(0, NULL); + g_io_channel_unref(pchan); + g_main_loop_unref(event_loop); + g_string_free(prompt, TRUE); + + g_free(opt_src); + g_free(opt_dst); + g_free(opt_sec_level); + + return 0; +} diff --git a/attrib/main.c b/attrib/main.c new file mode 100644 index 0000000..6c946be --- /dev/null +++ b/attrib/main.c @@ -0,0 +1,60 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "plugin.h" +#include "manager.h" + +static DBusConnection *connection; + +static int attrib_init(void) +{ + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (connection == NULL) + return -EIO; + + if (attrib_manager_init(connection) < 0) { + dbus_connection_unref(connection); + return -EIO; + } + + return 0; +} + +static void attrib_exit(void) +{ + attrib_manager_exit(); + + dbus_connection_unref(connection); +} + +BLUETOOTH_PLUGIN_DEFINE(attrib, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, attrib_init, attrib_exit) diff --git a/attrib/manager.c b/attrib/manager.c new file mode 100644 index 0000000..a5a7de4 --- /dev/null +++ b/attrib/manager.c @@ -0,0 +1,105 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "../src/adapter.h" +#include "../src/device.h" +#include "hcid.h" + +#include "manager.h" +#include "client.h" +#include "example.h" + +#define GATT_UUID "00001801-0000-1000-8000-00805f9b34fb" + +static DBusConnection *connection; + +static int client_probe(struct btd_device *device, GSList *uuids) +{ + const sdp_record_t *rec; + int psm = -1; + + rec = btd_device_get_record(device, GATT_UUID); + if (rec) { + sdp_list_t *list; + if (sdp_get_access_protos(rec, &list) < 0) + return -1; + + psm = sdp_get_proto_port(list, L2CAP_UUID); + + sdp_list_foreach(list, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(list, NULL); + + if (psm < 0) + return -1; + } + + return attrib_client_register(device, psm); +} + +static void client_remove(struct btd_device *device) +{ + attrib_client_unregister(device); +} + +static struct btd_device_driver client_driver = { + .name = "gatt-client", + .uuids = BTD_UUIDS(GATT_UUID), + .probe = client_probe, + .remove = client_remove, +}; + +int attrib_manager_init(DBusConnection *conn) +{ + connection = dbus_connection_ref(conn); + + attrib_client_init(connection); + + btd_register_device_driver(&client_driver); + + + if (main_opts.attrib_server) + return server_example_init(); + + return 0; +} + +void attrib_manager_exit(void) +{ + btd_unregister_device_driver(&client_driver); + + if (main_opts.attrib_server) + server_example_exit(); + + attrib_client_exit(); + + dbus_connection_unref(connection); +} diff --git a/attrib/manager.h b/attrib/manager.h new file mode 100644 index 0000000..fabf342 --- /dev/null +++ b/attrib/manager.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int attrib_manager_init(DBusConnection *conn); +void attrib_manager_exit(void); diff --git a/attrib/utils.c b/attrib/utils.c new file mode 100644 index 0000000..5f4444a --- /dev/null +++ b/attrib/utils.c @@ -0,0 +1,126 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "gattrib.h" +#include "gatt.h" +#include "btio.h" +#include "gatttool.h" + +/* Minimum MTU for ATT connections */ +#define ATT_MIN_MTU_LE 23 +#define ATT_MIN_MTU_L2CAP 48 + +GIOChannel *gatt_connect(const gchar *src, const gchar *dst, + const gchar *sec_level, int psm, int mtu, + BtIOConnect connect_cb) +{ + GIOChannel *chan; + bdaddr_t sba, dba; + GError *err = NULL; + BtIOSecLevel sec; + int minimum_mtu; + + /* This check is required because currently setsockopt() returns no + * errors for MTU values smaller than the allowed minimum. */ + minimum_mtu = psm ? ATT_MIN_MTU_L2CAP : ATT_MIN_MTU_LE; + if (mtu != 0 && mtu < minimum_mtu) { + g_printerr("MTU cannot be smaller than %d\n", minimum_mtu); + return NULL; + } + + /* Remote device */ + if (dst == NULL) { + g_printerr("Remote Bluetooth address required\n"); + return NULL; + } + str2ba(dst, &dba); + + /* Local adapter */ + if (src != NULL) { + if (!strncmp(src, "hci", 3)) + hci_devba(atoi(src + 3), &sba); + else + str2ba(src, &sba); + } else + bacpy(&sba, BDADDR_ANY); + + if (strcmp(sec_level, "medium") == 0) + sec = BT_IO_SEC_MEDIUM; + else if (strcmp(sec_level, "high") == 0) + sec = BT_IO_SEC_HIGH; + else + sec = BT_IO_SEC_LOW; + + if (psm == 0) + chan = bt_io_connect(BT_IO_L2CAP, connect_cb, NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &sba, + BT_IO_OPT_DEST_BDADDR, &dba, + BT_IO_OPT_CID, GATT_CID, + BT_IO_OPT_OMTU, mtu, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_INVALID); + else + chan = bt_io_connect(BT_IO_L2CAP, connect_cb, NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &sba, + BT_IO_OPT_DEST_BDADDR, &dba, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_OMTU, mtu, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_INVALID); + + if (err) { + g_printerr("%s\n", err->message); + g_error_free(err); + return NULL; + } + + return chan; +} + +size_t gatt_attr_data_from_string(const char *str, uint8_t **data) +{ + char tmp[3]; + size_t size, i; + + size = strlen(str) / 2; + *data = g_try_malloc0(size); + if (*data == NULL) + return 0; + + tmp[2] = '\0'; + for (i = 0; i < size; i++) { + memcpy(tmp, str + (i * 2), 2); + (*data)[i] = (uint8_t) strtol(tmp, NULL, 16); + } + + return size; +} diff --git a/audio/a2dp-codecs.h b/audio/a2dp-codecs.h new file mode 100644 index 0000000..e44634e --- /dev/null +++ b/audio/a2dp-codecs.h @@ -0,0 +1,116 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x03 + +#define SBC_SAMPLING_FREQ_16000 (1 << 3) +#define SBC_SAMPLING_FREQ_32000 (1 << 2) +#define SBC_SAMPLING_FREQ_44100 (1 << 1) +#define SBC_SAMPLING_FREQ_48000 1 + +#define SBC_CHANNEL_MODE_MONO (1 << 3) +#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define SBC_CHANNEL_MODE_STEREO (1 << 1) +#define SBC_CHANNEL_MODE_JOINT_STEREO 1 + +#define SBC_BLOCK_LENGTH_4 (1 << 3) +#define SBC_BLOCK_LENGTH_8 (1 << 2) +#define SBC_BLOCK_LENGTH_12 (1 << 1) +#define SBC_BLOCK_LENGTH_16 1 + +#define SBC_SUBBANDS_4 (1 << 1) +#define SBC_SUBBANDS_8 1 + +#define SBC_ALLOCATION_SNR (1 << 1) +#define SBC_ALLOCATION_LOUDNESS 1 + +#define MPEG_CHANNEL_MODE_MONO (1 << 3) +#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define MPEG_CHANNEL_MODE_STEREO (1 << 1) +#define MPEG_CHANNEL_MODE_JOINT_STEREO 1 + +#define MPEG_LAYER_MP1 (1 << 2) +#define MPEG_LAYER_MP2 (1 << 1) +#define MPEG_LAYER_MP3 1 + +#define MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define MPEG_SAMPLING_FREQ_48000 1 + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +typedef struct { + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t allocation_method:2; + uint8_t subbands:2; + uint8_t block_length:4; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t channel_mode:4; + uint8_t crc:1; + uint8_t layer:3; + uint8_t frequency:6; + uint8_t mpf:1; + uint8_t rfa:1; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +#elif __BYTE_ORDER == __BIG_ENDIAN + +typedef struct { + uint8_t frequency:4; + uint8_t channel_mode:4; + uint8_t block_length:4; + uint8_t subbands:2; + uint8_t allocation_method:2; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t layer:3; + uint8_t crc:1; + uint8_t channel_mode:4; + uint8_t rfa:1; + uint8_t mpf:1; + uint8_t frequency:6; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +#else +#error "Unknown byte order" +#endif diff --git a/audio/a2dp.c b/audio/a2dp.c new file mode 100644 index 0000000..9d9c61d --- /dev/null +++ b/audio/a2dp.c @@ -0,0 +1,2361 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include + +#include "log.h" +#include "device.h" +#include "manager.h" +#include "avdtp.h" +#include "sink.h" +#include "source.h" +#include "unix.h" +#include "media.h" +#include "transport.h" +#include "a2dp.h" +#include "sdpd.h" + +/* The duration that streams without users are allowed to stay in + * STREAMING state. */ +#define SUSPEND_TIMEOUT 5 +#define RECONFIGURE_TIMEOUT 500 + +#ifndef MIN +# define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef MAX +# define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +struct a2dp_sep { + struct a2dp_server *server; + struct media_endpoint *endpoint; + uint8_t type; + uint8_t codec; + struct avdtp_local_sep *lsep; + struct avdtp *session; + struct avdtp_stream *stream; + guint suspend_timer; + gboolean delay_reporting; + gboolean locked; + gboolean suspending; + gboolean starting; +}; + +struct a2dp_setup_cb { + struct a2dp_setup *setup; + a2dp_select_cb_t select_cb; + a2dp_config_cb_t config_cb; + a2dp_stream_cb_t resume_cb; + a2dp_stream_cb_t suspend_cb; + void *user_data; + unsigned int id; +}; + +struct a2dp_setup { + struct audio_device *dev; + struct avdtp *session; + struct a2dp_sep *sep; + struct avdtp_remote_sep *rsep; + struct avdtp_stream *stream; + struct avdtp_error *err; + avdtp_set_configuration_cb setconf_cb; + GSList *caps; + gboolean reconfigure; + gboolean start; + GSList *cb; + int ref; +}; + +static DBusConnection *connection = NULL; + +struct a2dp_server { + bdaddr_t src; + GSList *sinks; + GSList *sources; + uint32_t source_record_id; + uint32_t sink_record_id; + uint16_t version; + gboolean sink_enabled; + gboolean source_enabled; +}; + +static GSList *servers = NULL; +static GSList *setups = NULL; +static unsigned int cb_id = 0; + +static struct a2dp_setup *setup_ref(struct a2dp_setup *setup) +{ + setup->ref++; + + DBG("%p: ref=%d", setup, setup->ref); + + return setup; +} + +static struct audio_device *a2dp_get_dev(struct avdtp *session) +{ + bdaddr_t src, dst; + + avdtp_get_peers(session, &src, &dst); + + return manager_find_device(NULL, &src, &dst, NULL, FALSE); +} + +static struct a2dp_setup *setup_new(struct avdtp *session) +{ + struct audio_device *dev; + struct a2dp_setup *setup; + + dev = a2dp_get_dev(session); + if (!dev) { + error("Unable to create setup"); + return NULL; + } + + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); + setup->dev = a2dp_get_dev(session); + setups = g_slist_append(setups, setup); + + return setup; +} + +static void setup_free(struct a2dp_setup *s) +{ + DBG("%p", s); + + setups = g_slist_remove(setups, s); + if (s->session) + avdtp_unref(s->session); + g_slist_foreach(s->cb, (GFunc) g_free, NULL); + g_slist_free(s->cb); + g_slist_foreach(s->caps, (GFunc) g_free, NULL); + g_slist_free(s->caps); + g_free(s); +} + +static void setup_unref(struct a2dp_setup *setup) +{ + setup->ref--; + + DBG("%p: ref=%d", setup, setup->ref); + + if (setup->ref > 0) + return; + + setup_free(setup); +} + +static struct a2dp_setup_cb *setup_cb_new(struct a2dp_setup *setup) +{ + struct a2dp_setup_cb *cb; + + cb = g_new0(struct a2dp_setup_cb, 1); + cb->setup = setup; + cb->id = ++cb_id; + + setup->cb = g_slist_append(setup->cb, cb); + return cb; +} + +static void setup_cb_free(struct a2dp_setup_cb *cb) +{ + struct a2dp_setup *setup = cb->setup; + + setup->cb = g_slist_remove(setup->cb, cb); + setup_unref(cb->setup); + g_free(cb); +} + +static gboolean finalize_config(struct a2dp_setup *s) +{ + GSList *l; + struct avdtp_stream *stream = s->err ? NULL : s->stream; + + for (l = s->cb; l != NULL; ) { + struct a2dp_setup_cb *cb = l->data; + + l = l->next; + + if (!cb->config_cb) + continue; + + cb->config_cb(s->session, s->sep, stream, s->err, + cb->user_data); + setup_cb_free(cb); + } + + return FALSE; +} + +static gboolean finalize_config_errno(struct a2dp_setup *s, int err) +{ + struct avdtp_error avdtp_err; + + avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err); + s->err = err ? &avdtp_err : NULL; + + return finalize_config(s); +} + +static gboolean finalize_resume(struct a2dp_setup *s) +{ + GSList *l; + + for (l = s->cb; l != NULL; ) { + struct a2dp_setup_cb *cb = l->data; + + l = l->next; + + if (!cb->resume_cb) + continue; + + cb->resume_cb(s->session, s->err, cb->user_data); + setup_cb_free(cb); + } + + return FALSE; +} + +static gboolean finalize_resume_errno(struct a2dp_setup *s, int err) +{ + struct avdtp_error avdtp_err; + + avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err); + s->err = err ? &avdtp_err : NULL; + + return finalize_resume(s); +} + +static gboolean finalize_suspend(struct a2dp_setup *s) +{ + GSList *l; + + for (l = s->cb; l != NULL; ) { + struct a2dp_setup_cb *cb = l->data; + + l = l->next; + + if (!cb->suspend_cb) + continue; + + cb->suspend_cb(s->session, s->err, cb->user_data); + setup_cb_free(cb); + } + + return FALSE; +} + +static gboolean finalize_suspend_errno(struct a2dp_setup *s, int err) +{ + struct avdtp_error avdtp_err; + + avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err); + s->err = err ? &avdtp_err : NULL; + + return finalize_suspend(s); +} + +static gboolean finalize_select(struct a2dp_setup *s) +{ + GSList *l; + + for (l = s->cb; l != NULL; ) { + struct a2dp_setup_cb *cb = l->data; + + l = l->next; + + if (!cb->select_cb) + continue; + + cb->select_cb(s->session, s->sep, s->caps, cb->user_data); + setup_cb_free(cb); + } + + return FALSE; +} + +static struct a2dp_setup *find_setup_by_session(struct avdtp *session) +{ + GSList *l; + + for (l = setups; l != NULL; l = l->next) { + struct a2dp_setup *setup = l->data; + + if (setup->session == session) + return setup; + } + + return NULL; +} + +static struct a2dp_setup *a2dp_setup_get(struct avdtp *session) +{ + struct a2dp_setup *setup; + + setup = find_setup_by_session(session); + if (!setup) { + setup = setup_new(session); + if (!setup) + return NULL; + } + + return setup_ref(setup); +} + +static struct a2dp_setup *find_setup_by_dev(struct audio_device *dev) +{ + GSList *l; + + for (l = setups; l != NULL; l = l->next) { + struct a2dp_setup *setup = l->data; + + if (setup->dev == dev) + return setup; + } + + return NULL; +} + +static void stream_state_changed(struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *sep = user_data; + + if (new_state != AVDTP_STATE_IDLE) + return; + + if (sep->suspend_timer) { + g_source_remove(sep->suspend_timer); + sep->suspend_timer = 0; + } + + if (sep->session) { + avdtp_unref(sep->session); + sep->session = NULL; + } + + if (sep->endpoint) + media_endpoint_clear_configuration(sep->endpoint); + + sep->stream = NULL; + +} + +static gboolean auto_config(gpointer data) +{ + struct a2dp_setup *setup = data; + struct avdtp_error *err = NULL; + + /* Check if configuration was aborted */ + if (setup->sep->stream == NULL) + return FALSE; + + if (setup->err != NULL) { + err = setup->err; + goto done; + } + + avdtp_stream_add_cb(setup->session, setup->stream, + stream_state_changed, setup->sep); + + if (setup->sep->type == AVDTP_SEP_TYPE_SOURCE) + sink_new_stream(setup->dev, setup->session, setup->stream); + else + source_new_stream(setup->dev, setup->session, setup->stream); + +done: + if (setup->setconf_cb) + setup->setconf_cb(setup->session, setup->stream, setup->err); + + finalize_config(setup); + + if (err) + g_free(err); + + setup_unref(setup); + + return FALSE; +} + +static gboolean sbc_setconf_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + GSList *caps, + avdtp_set_configuration_cb cb, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Set_Configuration_Ind", sep); + else + DBG("Source %p: Set_Configuration_Ind", sep); + + setup = a2dp_setup_get(session); + if (!setup) + return FALSE; + + a2dp_sep->stream = stream; + setup->sep = a2dp_sep; + setup->stream = stream; + setup->setconf_cb = cb; + + /* Check valid settings */ + for (; caps != NULL; caps = g_slist_next(caps)) { + struct avdtp_service_capability *cap = caps->data; + struct avdtp_media_codec_capability *codec_cap; + struct sbc_codec_cap *sbc_cap; + + if (cap->category == AVDTP_DELAY_REPORTING && + !a2dp_sep->delay_reporting) { + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING, + AVDTP_UNSUPPORTED_CONFIGURATION); + goto done; + } + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + if (cap->length < sizeof(struct sbc_codec_cap)) + continue; + + codec_cap = (void *) cap->data; + + if (codec_cap->media_codec_type != A2DP_CODEC_SBC) + continue; + + sbc_cap = (void *) codec_cap; + + if (sbc_cap->min_bitpool < MIN_BITPOOL || + sbc_cap->max_bitpool > MAX_BITPOOL) { + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC, + AVDTP_UNSUPPORTED_CONFIGURATION); + goto done; + } + } + +done: + g_idle_add(auto_config, setup); + return TRUE; +} + +static gboolean sbc_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep, + gboolean get_all, GSList **caps, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Get_Capability_Ind", sep); + else + DBG("Source %p: Get_Capability_Ind", sep); + + *caps = NULL; + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + memset(&sbc_cap, 0, sizeof(struct sbc_codec_cap)); + + sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC; + + sbc_cap.frequency = ( SBC_SAMPLING_FREQ_48000 | + SBC_SAMPLING_FREQ_44100 | + SBC_SAMPLING_FREQ_32000 | + SBC_SAMPLING_FREQ_16000 ); + + sbc_cap.channel_mode = ( SBC_CHANNEL_MODE_JOINT_STEREO | + SBC_CHANNEL_MODE_STEREO | + SBC_CHANNEL_MODE_DUAL_CHANNEL | + SBC_CHANNEL_MODE_MONO ); + + sbc_cap.block_length = ( SBC_BLOCK_LENGTH_16 | + SBC_BLOCK_LENGTH_12 | + SBC_BLOCK_LENGTH_8 | + SBC_BLOCK_LENGTH_4 ); + + sbc_cap.subbands = ( SBC_SUBBANDS_8 | SBC_SUBBANDS_4 ); + + sbc_cap.allocation_method = ( SBC_ALLOCATION_LOUDNESS | + SBC_ALLOCATION_SNR ); + + sbc_cap.min_bitpool = MIN_BITPOOL; + sbc_cap.max_bitpool = MAX_BITPOOL; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + *caps = g_slist_append(*caps, media_codec); + + if (get_all) { + struct avdtp_service_capability *delay_reporting; + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + *caps = g_slist_append(*caps, delay_reporting); + } + + return TRUE; +} + +static gboolean mpeg_setconf_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + GSList *caps, + avdtp_set_configuration_cb cb, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Set_Configuration_Ind", sep); + else + DBG("Source %p: Set_Configuration_Ind", sep); + + setup = a2dp_setup_get(session); + if (!setup) + return FALSE; + + a2dp_sep->stream = stream; + setup->sep = a2dp_sep; + setup->stream = stream; + setup->setconf_cb = cb; + + for (; caps != NULL; caps = g_slist_next(caps)) { + struct avdtp_service_capability *cap = caps->data; + + if (cap->category == AVDTP_DELAY_REPORTING && + !a2dp_sep->delay_reporting) { + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING, + AVDTP_UNSUPPORTED_CONFIGURATION); + goto done; + } + } + +done: + g_idle_add(auto_config, setup); + return TRUE; +} + +static gboolean mpeg_getcap_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + gboolean get_all, + GSList **caps, uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct avdtp_service_capability *media_transport, *media_codec; + struct mpeg_codec_cap mpeg_cap; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Get_Capability_Ind", sep); + else + DBG("Source %p: Get_Capability_Ind", sep); + + *caps = NULL; + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + memset(&mpeg_cap, 0, sizeof(struct mpeg_codec_cap)); + + mpeg_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + mpeg_cap.cap.media_codec_type = A2DP_CODEC_MPEG12; + + mpeg_cap.frequency = ( MPEG_SAMPLING_FREQ_48000 | + MPEG_SAMPLING_FREQ_44100 | + MPEG_SAMPLING_FREQ_32000 | + MPEG_SAMPLING_FREQ_24000 | + MPEG_SAMPLING_FREQ_22050 | + MPEG_SAMPLING_FREQ_16000 ); + + mpeg_cap.channel_mode = ( MPEG_CHANNEL_MODE_JOINT_STEREO | + MPEG_CHANNEL_MODE_STEREO | + MPEG_CHANNEL_MODE_DUAL_CHANNEL | + MPEG_CHANNEL_MODE_MONO ); + + mpeg_cap.layer = ( MPEG_LAYER_MP3 | MPEG_LAYER_MP2 | MPEG_LAYER_MP1 ); + + mpeg_cap.bitrate = 0xFFFF; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap, + sizeof(mpeg_cap)); + + *caps = g_slist_append(*caps, media_codec); + + if (get_all) { + struct avdtp_service_capability *delay_reporting; + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + *caps = g_slist_append(*caps, delay_reporting); + } + + return TRUE; +} + +static void endpoint_setconf_cb(struct media_endpoint *endpoint, void *ret, + int size, void *user_data) +{ + struct a2dp_setup *setup = user_data; + + if (ret == NULL) { + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC, + AVDTP_UNSUPPORTED_CONFIGURATION); + } + + auto_config(setup); +} + +static gboolean endpoint_setconf_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + GSList *caps, + avdtp_set_configuration_cb cb, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Set_Configuration_Ind", sep); + else + DBG("Source %p: Set_Configuration_Ind", sep); + + setup = a2dp_setup_get(session); + if (!session) + return FALSE; + + a2dp_sep->stream = stream; + setup->sep = a2dp_sep; + setup->stream = stream; + setup->setconf_cb = cb; + + for (; caps != NULL; caps = g_slist_next(caps)) { + struct avdtp_service_capability *cap = caps->data; + struct avdtp_media_codec_capability *codec; + gboolean ret; + + if (cap->category == AVDTP_DELAY_REPORTING && + !a2dp_sep->delay_reporting) { + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING, + AVDTP_UNSUPPORTED_CONFIGURATION); + goto done; + } + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + codec = (struct avdtp_media_codec_capability *) cap->data; + + if (codec->media_codec_type != a2dp_sep->codec) { + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC, + AVDTP_UNSUPPORTED_CONFIGURATION); + goto done; + } + + ret = media_endpoint_set_configuration(a2dp_sep->endpoint, + setup->dev, codec->data, + cap->length - sizeof(*codec), + endpoint_setconf_cb, setup); + if (ret) + return TRUE; + + avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC, + AVDTP_UNSUPPORTED_CONFIGURATION); + break; + } + +done: + g_idle_add(auto_config, setup); + return TRUE; +} + +static gboolean endpoint_getcap_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + gboolean get_all, GSList **caps, + uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct avdtp_service_capability *media_transport, *media_codec; + struct avdtp_media_codec_capability *codec_caps; + uint8_t *capabilities; + size_t length; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Get_Capability_Ind", sep); + else + DBG("Source %p: Get_Capability_Ind", sep); + + *caps = NULL; + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + length = media_endpoint_get_capabilities(a2dp_sep->endpoint, + &capabilities); + + codec_caps = g_malloc0(sizeof(*codec_caps) + length); + codec_caps->media_type = AVDTP_MEDIA_TYPE_AUDIO; + codec_caps->media_codec_type = a2dp_sep->codec; + memcpy(codec_caps->data, capabilities, length); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec_caps, + sizeof(*codec_caps) + length); + + *caps = g_slist_append(*caps, media_codec); + g_free(codec_caps); + + if (get_all) { + struct avdtp_service_capability *delay_reporting; + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + *caps = g_slist_append(*caps, delay_reporting); + } + + return TRUE; +} + +static void endpoint_open_cb(struct media_endpoint *endpoint, void *ret, + int size, void *user_data) +{ + struct a2dp_setup *setup = user_data; + int err; + + if (ret == NULL) { + setup->stream = NULL; + finalize_config_errno(setup, -EPERM); + return; + } + + err = avdtp_open(setup->session, setup->stream); + if (err == 0) + return; + + error("Error on avdtp_open %s (%d)", strerror(-err), -err); + setup->stream = NULL; + finalize_config_errno(setup, err); +} + +static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + struct audio_device *dev; + int ret; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Set_Configuration_Cfm", sep); + else + DBG("Source %p: Set_Configuration_Cfm", sep); + + setup = find_setup_by_session(session); + + if (err) { + if (setup) { + setup->err = err; + finalize_config(setup); + } + return; + } + + avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep); + a2dp_sep->stream = stream; + + if (!setup) + return; + + dev = a2dp_get_dev(session); + + /* Notify D-Bus interface of the new stream */ + if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE) + sink_new_stream(dev, session, setup->stream); + else + source_new_stream(dev, session, setup->stream); + + /* Notify Endpoint */ + if (a2dp_sep->endpoint) { + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + + service = avdtp_stream_get_codec(stream); + codec = (struct avdtp_media_codec_capability *) service->data; + + if (media_endpoint_set_configuration(a2dp_sep->endpoint, dev, + codec->data, service->length - + sizeof(*codec), + endpoint_open_cb, setup) == + TRUE) + return; + + setup->stream = NULL; + finalize_config_errno(setup, -EPERM); + return; + } + + ret = avdtp_open(session, stream); + if (ret < 0) { + error("Error on avdtp_open %s (%d)", strerror(-ret), -ret); + setup->stream = NULL; + finalize_config_errno(setup, ret); + } +} + +static gboolean getconf_ind(struct avdtp *session, struct avdtp_local_sep *sep, + uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Get_Configuration_Ind", sep); + else + DBG("Source %p: Get_Configuration_Ind", sep); + return TRUE; +} + +static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Set_Configuration_Cfm", sep); + else + DBG("Source %p: Set_Configuration_Cfm", sep); +} + +static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Open_Ind", sep); + else + DBG("Source %p: Open_Ind", sep); + return TRUE; +} + +static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Open_Cfm", sep); + else + DBG("Source %p: Open_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (setup->reconfigure) + setup->reconfigure = FALSE; + + if (err) { + setup->stream = NULL; + setup->err = err; + } + + finalize_config(setup); +} + +static gboolean suspend_timeout(struct a2dp_sep *sep) +{ + if (avdtp_suspend(sep->session, sep->stream) == 0) + sep->suspending = TRUE; + + sep->suspend_timer = 0; + + avdtp_unref(sep->session); + sep->session = NULL; + + return FALSE; +} + +static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Start_Ind", sep); + else + DBG("Source %p: Start_Ind", sep); + + setup = find_setup_by_session(session); + if (setup) + finalize_resume(setup); + + if (!a2dp_sep->locked) { + a2dp_sep->session = avdtp_ref(session); + a2dp_sep->suspend_timer = g_timeout_add_seconds(SUSPEND_TIMEOUT, + (GSourceFunc) suspend_timeout, + a2dp_sep); + } + + return TRUE; +} + +static void start_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Start_Cfm", sep); + else + DBG("Source %p: Start_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (err) { + setup->stream = NULL; + setup->err = err; + } + + finalize_resume(setup); +} + +static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Suspend_Ind", sep); + else + DBG("Source %p: Suspend_Ind", sep); + + if (a2dp_sep->suspend_timer) { + g_source_remove(a2dp_sep->suspend_timer); + a2dp_sep->suspend_timer = 0; + avdtp_unref(a2dp_sep->session); + a2dp_sep->session = NULL; + } + + return TRUE; +} + +static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + gboolean start; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Suspend_Cfm", sep); + else + DBG("Source %p: Suspend_Cfm", sep); + + a2dp_sep->suspending = FALSE; + + setup = find_setup_by_session(session); + if (!setup) + return; + + start = setup->start; + setup->start = FALSE; + + if (err) { + setup->stream = NULL; + setup->err = err; + finalize_suspend(setup); + } + else + finalize_suspend_errno(setup, 0); + + if (!start) + return; + + if (err) { + setup->err = err; + finalize_suspend(setup); + } else if (avdtp_start(session, a2dp_sep->stream) < 0) { + struct avdtp_error start_err; + error("avdtp_start failed"); + avdtp_error_init(&start_err, AVDTP_ERRNO, EIO); + setup->err = err; + finalize_suspend(setup); + } +} + +static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Close_Ind", sep); + else + DBG("Source %p: Close_Ind", sep); + + setup = find_setup_by_session(session); + if (!setup) + return TRUE; + + finalize_suspend_errno(setup, -ECONNRESET); + finalize_resume_errno(setup, -ECONNRESET); + + return TRUE; +} + +static gboolean a2dp_reconfigure(gpointer data) +{ + struct a2dp_setup *setup = data; + struct a2dp_sep *sep = setup->sep; + int posix_err; + struct avdtp_media_codec_capability *rsep_codec; + struct avdtp_service_capability *cap; + + if (setup->rsep) { + cap = avdtp_get_codec(setup->rsep); + rsep_codec = (struct avdtp_media_codec_capability *) cap->data; + } + + if (!setup->rsep || sep->codec != rsep_codec->media_codec_type) + setup->rsep = avdtp_find_remote_sep(setup->session, sep->lsep); + + posix_err = avdtp_set_configuration(setup->session, setup->rsep, + sep->lsep, + setup->caps, + &setup->stream); + if (posix_err < 0) { + error("avdtp_set_configuration: %s", strerror(-posix_err)); + goto failed; + } + + return FALSE; + +failed: + finalize_config_errno(setup, posix_err); + return FALSE; +} + +static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Close_Cfm", sep); + else + DBG("Source %p: Close_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (err) { + setup->stream = NULL; + setup->err = err; + finalize_config(setup); + return; + } + + if (!setup->rsep) + setup->rsep = avdtp_stream_get_remote_sep(stream); + + if (setup->reconfigure) + g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup); +} + +static gboolean abort_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Abort_Ind", sep); + else + DBG("Source %p: Abort_Ind", sep); + + a2dp_sep->stream = NULL; + + return TRUE; +} + +static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Abort_Cfm", sep); + else + DBG("Source %p: Abort_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + setup_unref(setup); +} + +static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep, + uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: ReConfigure_Ind", sep); + else + DBG("Source %p: ReConfigure_Ind", sep); + + return TRUE; +} + +static gboolean delayreport_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + uint8_t rseid, uint16_t delay, + uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct audio_device *dev = a2dp_get_dev(session); + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: DelayReport_Ind", sep); + else + DBG("Source %p: DelayReport_Ind", sep); + + unix_delay_report(dev, rseid, delay); + + return TRUE; +} + +static gboolean endpoint_delayreport_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + uint8_t rseid, uint16_t delay, + uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct media_transport *transport; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: DelayReport_Ind", sep); + else + DBG("Source %p: DelayReport_Ind", sep); + + transport = media_endpoint_get_transport(a2dp_sep->endpoint); + if (transport == NULL) + return FALSE; + + media_transport_update_delay(transport, delay); + + return TRUE; +} + +static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: ReConfigure_Cfm", sep); + else + DBG("Source %p: ReConfigure_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (err) { + setup->stream = NULL; + setup->err = err; + } + + finalize_config(setup); +} + +static void delay_report_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: DelayReport_Cfm", sep); + else + DBG("Source %p: DelayReport_Cfm", sep); +} + +static struct avdtp_sep_cfm cfm = { + .set_configuration = setconf_cfm, + .get_configuration = getconf_cfm, + .open = open_cfm, + .start = start_cfm, + .suspend = suspend_cfm, + .close = close_cfm, + .abort = abort_cfm, + .reconfigure = reconf_cfm, + .delay_report = delay_report_cfm, +}; + +static struct avdtp_sep_ind sbc_ind = { + .get_capability = sbc_getcap_ind, + .set_configuration = sbc_setconf_ind, + .get_configuration = getconf_ind, + .open = open_ind, + .start = start_ind, + .suspend = suspend_ind, + .close = close_ind, + .abort = abort_ind, + .reconfigure = reconf_ind, + .delayreport = delayreport_ind, +}; + +static struct avdtp_sep_ind mpeg_ind = { + .get_capability = mpeg_getcap_ind, + .set_configuration = mpeg_setconf_ind, + .get_configuration = getconf_ind, + .open = open_ind, + .start = start_ind, + .suspend = suspend_ind, + .close = close_ind, + .abort = abort_ind, + .reconfigure = reconf_ind, + .delayreport = delayreport_ind, +}; + +static struct avdtp_sep_ind endpoint_ind = { + .get_capability = endpoint_getcap_ind, + .set_configuration = endpoint_setconf_ind, + .get_configuration = getconf_ind, + .open = open_ind, + .start = start_ind, + .suspend = suspend_ind, + .close = close_ind, + .abort = abort_ind, + .reconfigure = reconf_ind, + .delayreport = endpoint_delayreport_ind, +}; + +static sdp_record_t *a2dp_record(uint8_t type, uint16_t avdtp_ver) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVDTP_UUID; + uint16_t a2dp_ver = 0x0102, feat = 0x000f; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + if (type == AVDTP_SEP_TYPE_SOURCE) + sdp_uuid16_create(&a2dp_uuid, AUDIO_SOURCE_SVCLASS_ID); + else + sdp_uuid16_create(&a2dp_uuid, AUDIO_SINK_SVCLASS_ID); + svclass_id = sdp_list_append(0, &a2dp_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID); + profile[0].version = a2dp_ver; + pfseq = sdp_list_append(0, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&avdtp_uuid, AVDTP_UUID); + proto[1] = sdp_list_append(0, &avdtp_uuid); + version = sdp_data_alloc(SDP_UINT16, &avdtp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + if (type == AVDTP_SEP_TYPE_SOURCE) + sdp_set_info_attr(record, "Audio Source", 0, 0); + else + sdp_set_info_attr(record, "Audio Sink", 0, 0); + + free(psm); + free(version); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static struct a2dp_server *find_server(GSList *list, const bdaddr_t *src) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct a2dp_server *server = l->data; + + if (bacmp(&server->src, src) == 0) + return server; + } + + return NULL; +} + +int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config) +{ + int sbc_srcs = 1, sbc_sinks = 1; + int mpeg12_srcs = 0, mpeg12_sinks = 0; + gboolean source = TRUE, sink = FALSE, socket = TRUE; + gboolean delay_reporting = FALSE; + char *str; + GError *err = NULL; + int i; + struct a2dp_server *server; + + if (!config) + goto proceed; + + str = g_key_file_get_string(config, "General", "Enable", &err); + + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + if (strstr(str, "Sink")) + source = TRUE; + if (strstr(str, "Source")) + sink = TRUE; + g_free(str); + } + + str = g_key_file_get_string(config, "General", "Disable", &err); + + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + if (strstr(str, "Sink")) + source = FALSE; + if (strstr(str, "Source")) + sink = FALSE; + if (strstr(str, "Socket")) + socket = FALSE; + g_free(str); + } + + /* Don't register any local sep if Socket is disabled */ + if (socket == FALSE) { + sbc_srcs = 0; + sbc_sinks = 0; + mpeg12_srcs = 0; + mpeg12_sinks = 0; + goto proceed; + } + + str = g_key_file_get_string(config, "A2DP", "SBCSources", &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + sbc_srcs = atoi(str); + g_free(str); + } + + str = g_key_file_get_string(config, "A2DP", "MPEG12Sources", &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + mpeg12_srcs = atoi(str); + g_free(str); + } + + str = g_key_file_get_string(config, "A2DP", "SBCSinks", &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + sbc_sinks = atoi(str); + g_free(str); + } + + str = g_key_file_get_string(config, "A2DP", "MPEG12Sinks", &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + mpeg12_sinks = atoi(str); + g_free(str); + } + +proceed: + if (!connection) + connection = dbus_connection_ref(conn); + + server = find_server(servers, src); + if (!server) { + int av_err; + + server = g_new0(struct a2dp_server, 1); + if (!server) + return -ENOMEM; + + av_err = avdtp_init(src, config, &server->version); + if (av_err < 0) { + g_free(server); + return av_err; + } + + bacpy(&server->src, src); + servers = g_slist_append(servers, server); + } + + if (config) + delay_reporting = g_key_file_get_boolean(config, "A2DP", + "DelayReporting", NULL); + + if (delay_reporting) + server->version = 0x0103; + else + server->version = 0x0102; + + server->source_enabled = source; + if (source) { + for (i = 0; i < sbc_srcs; i++) + a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE, + A2DP_CODEC_SBC, delay_reporting, NULL, NULL); + + for (i = 0; i < mpeg12_srcs; i++) + a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE, + A2DP_CODEC_MPEG12, delay_reporting, + NULL, NULL); + } + server->sink_enabled = sink; + if (sink) { + for (i = 0; i < sbc_sinks; i++) + a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK, + A2DP_CODEC_SBC, delay_reporting, NULL, NULL); + + for (i = 0; i < mpeg12_sinks; i++) + a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK, + A2DP_CODEC_MPEG12, delay_reporting, + NULL, NULL); + } + + return 0; +} + +static void a2dp_unregister_sep(struct a2dp_sep *sep) +{ + if (sep->endpoint) { + media_endpoint_release(sep->endpoint); + sep->endpoint = NULL; + } + + avdtp_unregister_sep(sep->lsep); + g_free(sep); +} + +void a2dp_unregister(const bdaddr_t *src) +{ + struct a2dp_server *server; + + server = find_server(servers, src); + if (!server) + return; + + g_slist_foreach(server->sinks, (GFunc) a2dp_remove_sep, NULL); + g_slist_free(server->sinks); + + g_slist_foreach(server->sources, (GFunc) a2dp_remove_sep, NULL); + g_slist_free(server->sources); + + avdtp_exit(src); + + servers = g_slist_remove(servers, server); + g_free(server); + + if (servers) + return; + + dbus_connection_unref(connection); + connection = NULL; +} + +struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type, + uint8_t codec, gboolean delay_reporting, + struct media_endpoint *endpoint, int *err) +{ + struct a2dp_server *server; + struct a2dp_sep *sep; + GSList **l; + uint32_t *record_id; + sdp_record_t *record; + struct avdtp_sep_ind *ind; + + server = find_server(servers, src); + if (server == NULL) { + if (err) + *err = -EINVAL; + return NULL; + } + + if (type == AVDTP_SEP_TYPE_SINK && !server->sink_enabled) { + if (err) + *err = -EPROTONOSUPPORT; + return NULL; + } + + if (type == AVDTP_SEP_TYPE_SOURCE && !server->source_enabled) { + if (err) + *err = -EPROTONOSUPPORT; + return NULL; + } + + sep = g_new0(struct a2dp_sep, 1); + + if (endpoint) { + ind = &endpoint_ind; + goto proceed; + } + + ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind; + +proceed: + sep->lsep = avdtp_register_sep(&server->src, type, + AVDTP_MEDIA_TYPE_AUDIO, codec, + delay_reporting, ind, &cfm, sep); + if (sep->lsep == NULL) { + g_free(sep); + if (err) + *err = -EINVAL; + return NULL; + } + + sep->server = server; + sep->endpoint = endpoint; + sep->codec = codec; + sep->type = type; + sep->delay_reporting = delay_reporting; + + if (type == AVDTP_SEP_TYPE_SOURCE) { + l = &server->sources; + record_id = &server->source_record_id; + } else { + l = &server->sinks; + record_id = &server->sink_record_id; + } + + if (*record_id != 0) + goto add; + + record = a2dp_record(type, server->version); + if (!record) { + error("Unable to allocate new service record"); + avdtp_unregister_sep(sep->lsep); + g_free(sep); + if (err) + *err = -EINVAL; + return NULL; + } + + if (add_record_to_server(&server->src, record) < 0) { + error("Unable to register A2DP service record");\ + sdp_record_free(record); + avdtp_unregister_sep(sep->lsep); + g_free(sep); + if (err) + *err = -EINVAL; + return NULL; + } + *record_id = record->handle; + +add: + *l = g_slist_append(*l, sep); + + if (err) + *err = 0; + return sep; +} + +void a2dp_remove_sep(struct a2dp_sep *sep) +{ + struct a2dp_server *server = sep->server; + + if (sep->type == AVDTP_SEP_TYPE_SOURCE) { + if (g_slist_find(server->sources, sep) == NULL) + return; + server->sources = g_slist_remove(server->sources, sep); + if (server->sources == NULL && server->source_record_id) { + remove_record_from_server(server->source_record_id); + server->source_record_id = 0; + } + } else { + if (g_slist_find(server->sinks, sep) == NULL) + return; + server->sinks = g_slist_remove(server->sinks, sep); + if (server->sinks == NULL && server->sink_record_id) { + remove_record_from_server(server->sink_record_id); + server->sink_record_id = 0; + } + } + + a2dp_unregister_sep(sep); +} + +struct a2dp_sep *a2dp_get(struct avdtp *session, + struct avdtp_remote_sep *rsep) +{ + GSList *l; + struct a2dp_server *server; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap = NULL; + bdaddr_t src; + + avdtp_get_peers(session, &src, NULL); + server = find_server(servers, &src); + if (!server) + return NULL; + + cap = avdtp_get_codec(rsep); + codec_cap = (void *) cap->data; + + if (avdtp_get_type(rsep) == AVDTP_SEP_TYPE_SINK) + l = server->sources; + else + l = server->sinks; + + for (; l != NULL; l = l->next) { + struct a2dp_sep *sep = l->data; + + if (sep->locked) + continue; + + if (sep->codec != codec_cap->media_codec_type) + continue; + + if (!sep->stream || avdtp_has_stream(session, sep->stream)) + return sep; + } + + return NULL; +} + +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case SBC_SAMPLING_FREQ_16000: + case SBC_SAMPLING_FREQ_32000: + return 53; + case SBC_SAMPLING_FREQ_44100: + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + error("Invalid channel mode %u", mode); + return 53; + } + case SBC_SAMPLING_FREQ_48000: + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + error("Invalid channel mode %u", mode); + return 51; + } + default: + error("Invalid sampling freq %u", freq); + return 53; + } +} + +static gboolean select_sbc_params(struct sbc_codec_cap *cap, + struct sbc_codec_cap *supported) +{ + unsigned int max_bitpool, min_bitpool; + + memset(cap, 0, sizeof(struct sbc_codec_cap)); + + cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + cap->cap.media_codec_type = A2DP_CODEC_SBC; + + if (supported->frequency & SBC_SAMPLING_FREQ_44100) + cap->frequency = SBC_SAMPLING_FREQ_44100; + else if (supported->frequency & SBC_SAMPLING_FREQ_48000) + cap->frequency = SBC_SAMPLING_FREQ_48000; + else if (supported->frequency & SBC_SAMPLING_FREQ_32000) + cap->frequency = SBC_SAMPLING_FREQ_32000; + else if (supported->frequency & SBC_SAMPLING_FREQ_16000) + cap->frequency = SBC_SAMPLING_FREQ_16000; + else { + error("No supported frequencies"); + return FALSE; + } + + if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; + else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO) + cap->channel_mode = SBC_CHANNEL_MODE_STEREO; + else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; + else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO) + cap->channel_mode = SBC_CHANNEL_MODE_MONO; + else { + error("No supported channel modes"); + return FALSE; + } + + if (supported->block_length & SBC_BLOCK_LENGTH_16) + cap->block_length = SBC_BLOCK_LENGTH_16; + else if (supported->block_length & SBC_BLOCK_LENGTH_12) + cap->block_length = SBC_BLOCK_LENGTH_12; + else if (supported->block_length & SBC_BLOCK_LENGTH_8) + cap->block_length = SBC_BLOCK_LENGTH_8; + else if (supported->block_length & SBC_BLOCK_LENGTH_4) + cap->block_length = SBC_BLOCK_LENGTH_4; + else { + error("No supported block lengths"); + return FALSE; + } + + if (supported->subbands & SBC_SUBBANDS_8) + cap->subbands = SBC_SUBBANDS_8; + else if (supported->subbands & SBC_SUBBANDS_4) + cap->subbands = SBC_SUBBANDS_4; + else { + error("No supported subbands"); + return FALSE; + } + + if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS) + cap->allocation_method = SBC_ALLOCATION_LOUDNESS; + else if (supported->allocation_method & SBC_ALLOCATION_SNR) + cap->allocation_method = SBC_ALLOCATION_SNR; + + min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool); + max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), + supported->max_bitpool); + + cap->min_bitpool = min_bitpool; + cap->max_bitpool = max_bitpool; + + return TRUE; +} + +static gboolean select_capabilities(struct avdtp *session, + struct avdtp_remote_sep *rsep, + GSList **caps) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + + media_codec = avdtp_get_codec(rsep); + if (!media_codec) + return FALSE; + + select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data); + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + *caps = g_slist_append(*caps, media_codec); + + if (avdtp_get_delay_reporting(rsep)) { + struct avdtp_service_capability *delay_reporting; + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + *caps = g_slist_append(*caps, delay_reporting); + } + + return TRUE; +} + +static void select_cb(struct media_endpoint *endpoint, void *ret, int size, + void *user_data) +{ + struct a2dp_setup *setup = user_data; + struct avdtp_service_capability *media_transport, *media_codec; + struct avdtp_media_codec_capability *cap; + + if (size < 0) { + DBG("Endpoint replied an invalid configuration"); + goto done; + } + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + setup->caps = g_slist_append(setup->caps, media_transport); + + cap = g_malloc0(sizeof(*cap) + size); + cap->media_type = AVDTP_MEDIA_TYPE_AUDIO; + cap->media_codec_type = setup->sep->codec; + memcpy(cap->data, ret, size); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap, + sizeof(*cap) + size); + + setup->caps = g_slist_append(setup->caps, media_codec); + g_free(cap); + +done: + finalize_select(setup); +} + +static gboolean auto_select(gpointer data) +{ + struct a2dp_setup *setup = data; + + finalize_select(setup); + + return FALSE; +} + +static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list, + const char *sender) +{ + for (; list; list = list->next) { + struct a2dp_sep *sep = list->data; + + /* Use sender's endpoint if available */ + if (sender) { + const char *name; + + if (sep->endpoint == NULL) + continue; + + name = media_endpoint_get_sender(sep->endpoint); + if (g_strcmp0(sender, name) != 0) + continue; + } + + if (avdtp_find_remote_sep(session, sep->lsep) == NULL) + continue; + + return sep; + } + + return NULL; +} + +static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type, + const char *sender) +{ + struct a2dp_server *server; + struct a2dp_sep *sep; + GSList *l; + bdaddr_t src; + + avdtp_get_peers(session, &src, NULL); + server = find_server(servers, &src); + if (!server) + return NULL; + + l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks; + + /* Check sender's seps first */ + sep = a2dp_find_sep(session, l, sender); + if (sep != NULL) + return sep; + + return a2dp_find_sep(session, l, NULL); +} + +unsigned int a2dp_select_capabilities(struct avdtp *session, + uint8_t type, const char *sender, + a2dp_select_cb_t cb, + void *user_data) +{ + struct a2dp_setup *setup; + struct a2dp_setup_cb *cb_data; + struct a2dp_sep *sep; + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + + sep = a2dp_select_sep(session, type, sender); + if (!sep) { + error("Unable to select SEP"); + return 0; + } + + setup = a2dp_setup_get(session); + if (!setup) + return 0; + + cb_data = setup_cb_new(setup); + cb_data->select_cb = cb; + cb_data->user_data = user_data; + + setup->sep = sep; + setup->rsep = avdtp_find_remote_sep(session, sep->lsep); + + if (setup->rsep == NULL) { + error("Could not find remote sep"); + goto fail; + } + + /* FIXME: Remove auto select when it is not longer possible to register + endpoint in the configuration file */ + if (sep->endpoint == NULL) { + if (!select_capabilities(session, setup->rsep, + &setup->caps)) { + error("Unable to auto select remote SEP capabilities"); + goto fail; + } + + g_idle_add(auto_select, setup); + + return cb_data->id; + } + + service = avdtp_get_codec(setup->rsep); + codec = (struct avdtp_media_codec_capability *) service->data; + + if (media_endpoint_select_configuration(sep->endpoint, codec->data, + service->length - sizeof(*codec), + select_cb, setup) == + TRUE) + return cb_data->id; + +fail: + setup_cb_free(cb_data); + return 0; + +} + +unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, + a2dp_config_cb_t cb, GSList *caps, + void *user_data) +{ + struct a2dp_setup_cb *cb_data; + GSList *l; + struct a2dp_server *server; + struct a2dp_setup *setup; + struct a2dp_sep *tmp; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap = NULL; + int posix_err; + bdaddr_t src; + uint8_t remote_type; + + avdtp_get_peers(session, &src, NULL); + server = find_server(servers, &src); + if (!server) + return 0; + + for (l = caps; l != NULL; l = l->next) { + cap = l->data; + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + codec_cap = (void *) cap->data; + break; + } + + if (!codec_cap) + return 0; + + if (sep->codec != codec_cap->media_codec_type) + return 0; + + DBG("a2dp_config: selected SEP %p", sep->lsep); + + setup = a2dp_setup_get(session); + if (!setup) + return 0; + + cb_data = setup_cb_new(setup); + cb_data->config_cb = cb; + cb_data->user_data = user_data; + + setup->sep = sep; + setup->stream = sep->stream; + + /* Copy given caps if they are different than current caps */ + if (setup->caps != caps) { + g_slist_foreach(setup->caps, (GFunc) g_free, NULL); + g_slist_free(setup->caps); + setup->caps = g_slist_copy(caps); + } + + switch (avdtp_sep_get_state(sep->lsep)) { + case AVDTP_STATE_IDLE: + if (sep->type == AVDTP_SEP_TYPE_SOURCE) { + l = server->sources; + remote_type = AVDTP_SEP_TYPE_SINK; + } else { + remote_type = AVDTP_SEP_TYPE_SOURCE; + l = server->sinks; + } + + for (; l != NULL; l = l->next) { + tmp = l->data; + if (avdtp_has_stream(session, tmp->stream)) + break; + } + + if (l != NULL) { + if (a2dp_sep_get_lock(tmp)) + goto failed; + setup->reconfigure = TRUE; + if (avdtp_close(session, tmp->stream, FALSE) < 0) { + error("avdtp_close failed"); + goto failed; + } + break; + } + + setup->rsep = avdtp_find_remote_sep(session, sep->lsep); + if (setup->rsep == NULL) { + error("No matching ACP and INT SEPs found"); + goto failed; + } + + posix_err = avdtp_set_configuration(session, setup->rsep, + sep->lsep, caps, + &setup->stream); + if (posix_err < 0) { + error("avdtp_set_configuration: %s", + strerror(-posix_err)); + goto failed; + } + break; + case AVDTP_STATE_OPEN: + case AVDTP_STATE_STREAMING: + if (avdtp_stream_has_capabilities(setup->stream, caps)) { + DBG("Configuration match: resuming"); + g_idle_add((GSourceFunc) finalize_config, setup); + } else if (!setup->reconfigure) { + setup->reconfigure = TRUE; + if (avdtp_close(session, sep->stream, FALSE) < 0) { + error("avdtp_close failed"); + goto failed; + } + } + break; + default: + error("SEP in bad state for requesting a new stream"); + goto failed; + } + + return cb_data->id; + +failed: + setup_cb_free(cb_data); + return 0; +} + +unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data) +{ + struct a2dp_setup_cb *cb_data; + struct a2dp_setup *setup; + + setup = a2dp_setup_get(session); + if (!setup) + return 0; + + cb_data = setup_cb_new(setup); + cb_data->resume_cb = cb; + cb_data->user_data = user_data; + + setup->sep = sep; + setup->stream = sep->stream; + + switch (avdtp_sep_get_state(sep->lsep)) { + case AVDTP_STATE_IDLE: + goto failed; + break; + case AVDTP_STATE_OPEN: + if (avdtp_start(session, sep->stream) < 0) { + error("avdtp_start failed"); + goto failed; + } + break; + case AVDTP_STATE_STREAMING: + if (!sep->suspending && sep->suspend_timer) { + g_source_remove(sep->suspend_timer); + sep->suspend_timer = 0; + avdtp_unref(sep->session); + sep->session = NULL; + } + if (sep->suspending) + setup->start = TRUE; + else + g_idle_add((GSourceFunc) finalize_resume, setup); + break; + default: + error("SEP in bad state for resume"); + goto failed; + } + + return cb_data->id; + +failed: + setup_cb_free(cb_data); + return 0; +} + +unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data) +{ + struct a2dp_setup_cb *cb_data; + struct a2dp_setup *setup; + + setup = a2dp_setup_get(session); + if (!setup) + return 0; + + cb_data = setup_cb_new(setup); + cb_data->suspend_cb = cb; + cb_data->user_data = user_data; + + setup->sep = sep; + setup->stream = sep->stream; + + switch (avdtp_sep_get_state(sep->lsep)) { + case AVDTP_STATE_IDLE: + error("a2dp_suspend: no stream to suspend"); + goto failed; + break; + case AVDTP_STATE_OPEN: + g_idle_add((GSourceFunc) finalize_suspend, setup); + break; + case AVDTP_STATE_STREAMING: + if (avdtp_suspend(session, sep->stream) < 0) { + error("avdtp_suspend failed"); + goto failed; + } + sep->suspending = TRUE; + break; + default: + error("SEP in bad state for suspend"); + goto failed; + } + + return cb_data->id; + +failed: + setup_cb_free(cb_data); + return 0; +} + +gboolean a2dp_cancel(struct audio_device *dev, unsigned int id) +{ + struct a2dp_setup *setup; + GSList *l; + + setup = find_setup_by_dev(dev); + if (!setup) + return FALSE; + + for (l = setup->cb; l != NULL; l = g_slist_next(l)) { + struct a2dp_setup_cb *cb = l->data; + + if (cb->id != id) + continue; + + setup_ref(setup); + setup_cb_free(cb); + + if (!setup->cb) { + DBG("aborting setup %p", setup); + avdtp_abort(setup->session, setup->stream); + return TRUE; + } + + setup_unref(setup); + return TRUE; + } + + return FALSE; +} + +gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session) +{ + if (sep->locked) + return FALSE; + + DBG("SEP %p locked", sep->lsep); + sep->locked = TRUE; + + return TRUE; +} + +gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session) +{ + avdtp_state_t state; + + state = avdtp_sep_get_state(sep->lsep); + + sep->locked = FALSE; + + DBG("SEP %p unlocked", sep->lsep); + + if (!sep->stream || state == AVDTP_STATE_IDLE) + return TRUE; + + switch (state) { + case AVDTP_STATE_OPEN: + /* Set timer here */ + break; + case AVDTP_STATE_STREAMING: + if (avdtp_suspend(session, sep->stream) == 0) + sep->suspending = TRUE; + break; + default: + break; + } + + return TRUE; +} + +gboolean a2dp_sep_get_lock(struct a2dp_sep *sep) +{ + return sep->locked; +} + +static int stream_cmp(gconstpointer data, gconstpointer user_data) +{ + const struct a2dp_sep *sep = data; + const struct avdtp_stream *stream = user_data; + + return (sep->stream != stream); +} + +struct a2dp_sep *a2dp_get_sep(struct avdtp *session, + struct avdtp_stream *stream) +{ + struct a2dp_server *server; + bdaddr_t src, dst; + GSList *l; + + avdtp_get_peers(session, &src, &dst); + + for (l = servers; l; l = l->next) { + server = l->data; + + if (bacmp(&src, &server->src) == 0) + break; + } + + if (!l) + return NULL; + + l = g_slist_find_custom(server->sources, stream, stream_cmp); + if (l) + return l->data; + + l = g_slist_find_custom(server->sinks, stream, stream_cmp); + if (l) + return l->data; + + return NULL; +} + +struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep) +{ + return sep->stream; +} diff --git a/audio/a2dp.h b/audio/a2dp.h new file mode 100644 index 0000000..5c4232d --- /dev/null +++ b/audio/a2dp.h @@ -0,0 +1,164 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x03 + +#define SBC_SAMPLING_FREQ_16000 (1 << 3) +#define SBC_SAMPLING_FREQ_32000 (1 << 2) +#define SBC_SAMPLING_FREQ_44100 (1 << 1) +#define SBC_SAMPLING_FREQ_48000 1 + +#define SBC_CHANNEL_MODE_MONO (1 << 3) +#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define SBC_CHANNEL_MODE_STEREO (1 << 1) +#define SBC_CHANNEL_MODE_JOINT_STEREO 1 + +#define SBC_BLOCK_LENGTH_4 (1 << 3) +#define SBC_BLOCK_LENGTH_8 (1 << 2) +#define SBC_BLOCK_LENGTH_12 (1 << 1) +#define SBC_BLOCK_LENGTH_16 1 + +#define SBC_SUBBANDS_4 (1 << 1) +#define SBC_SUBBANDS_8 1 + +#define SBC_ALLOCATION_SNR (1 << 1) +#define SBC_ALLOCATION_LOUDNESS 1 + +#define MPEG_CHANNEL_MODE_MONO (1 << 3) +#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define MPEG_CHANNEL_MODE_STEREO (1 << 1) +#define MPEG_CHANNEL_MODE_JOINT_STEREO 1 + +#define MPEG_LAYER_MP1 (1 << 2) +#define MPEG_LAYER_MP2 (1 << 1) +#define MPEG_LAYER_MP3 1 + +#define MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define MPEG_SAMPLING_FREQ_48000 1 + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct sbc_codec_cap { + struct avdtp_media_codec_capability cap; + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t allocation_method:2; + uint8_t subbands:2; + uint8_t block_length:4; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)); + +struct mpeg_codec_cap { + struct avdtp_media_codec_capability cap; + uint8_t channel_mode:4; + uint8_t crc:1; + uint8_t layer:3; + uint8_t frequency:6; + uint8_t mpf:1; + uint8_t rfa:1; + uint16_t bitrate; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct sbc_codec_cap { + struct avdtp_media_codec_capability cap; + uint8_t frequency:4; + uint8_t channel_mode:4; + uint8_t block_length:4; + uint8_t subbands:2; + uint8_t allocation_method:2; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)); + +struct mpeg_codec_cap { + struct avdtp_media_codec_capability cap; + uint8_t layer:3; + uint8_t crc:1; + uint8_t channel_mode:4; + uint8_t rfa:1; + uint8_t mpf:1; + uint8_t frequency:6; + uint16_t bitrate; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +struct a2dp_sep; + + +typedef void (*a2dp_select_cb_t) (struct avdtp *session, + struct a2dp_sep *sep, GSList *caps, + void *user_data); +typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data); +typedef void (*a2dp_stream_cb_t) (struct avdtp *session, + struct avdtp_error *err, + void *user_data); + +int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config); +void a2dp_unregister(const bdaddr_t *src); + +struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type, + uint8_t codec, gboolean delay_reporting, + struct media_endpoint *endpoint, int *err); +void a2dp_remove_sep(struct a2dp_sep *sep); + +struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *sep); + +unsigned int a2dp_select_capabilities(struct avdtp *session, + uint8_t type, const char *sender, + a2dp_select_cb_t cb, + void *user_data); +unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, + a2dp_config_cb_t cb, GSList *caps, + void *user_data); +unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data); +unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data); +gboolean a2dp_cancel(struct audio_device *dev, unsigned int id); + +gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session); +gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session); +gboolean a2dp_sep_get_lock(struct a2dp_sep *sep); +struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep); +struct a2dp_sep *a2dp_get_sep(struct avdtp *session, + struct avdtp_stream *stream); diff --git a/audio/audio.conf b/audio/audio.conf new file mode 100644 index 0000000..302e046 --- /dev/null +++ b/audio/audio.conf @@ -0,0 +1,45 @@ +# Configuration file for the audio service + +# This section contains options which are not specific to any +# particular interface +[General] + +# Switch to master role for incoming connections (defaults to true) +#Master=true + +# If we want to disable support for specific services +# Defaults to supporting all implemented services +#Disable=Control,Source + +# SCO routing. Either PCM or HCI (in which case audio is routed to/from ALSA) +# Defaults to HCI +#SCORouting=PCM + +# Automatically connect both A2DP and HFP/HSP profiles for incoming +# connections. Some headsets that support both profiles will only connect the +# other one automatically so the default setting of true is usually a good +# idea. +#AutoConnect=true + +# Headset interface specific options (i.e. options which affect how the audio +# service interacts with remote headset devices) +[Headset] + +# Set to true to support HFP, false means only HSP is supported +# Defaults to true +HFP=true + +# Maximum number of connected HSP/HFP devices per adapter. Defaults to 1 +MaxConnected=1 + +# Set to true to enable use of fast connectable mode (faster page scanning) +# for HFP when incomming call starts. Default settings are restored after +# call is answered or rejected. Page scan interval is much shorter and page +# scan type changed to interlaced. Such allows faster connection initiated +# by a headset. +FastConnectable=false + +# Just an example of potential config options for the other interfaces +#[A2DP] +#SBCSources=1 +#MPEG12Sources=0 diff --git a/audio/avdtp.c b/audio/avdtp.c new file mode 100644 index 0000000..83b1aa2 --- /dev/null +++ b/audio/avdtp.c @@ -0,0 +1,3914 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "log.h" + +#include "../src/adapter.h" +#include "../src/manager.h" +#include "../src/device.h" + +#include "device.h" +#include "manager.h" +#include "control.h" +#include "avdtp.h" +#include "glib-helper.h" +#include "btio.h" +#include "sink.h" +#include "source.h" + +#define AVDTP_PSM 25 + +#define MAX_SEID 0x3E + +#define AVDTP_DISCOVER 0x01 +#define AVDTP_GET_CAPABILITIES 0x02 +#define AVDTP_SET_CONFIGURATION 0x03 +#define AVDTP_GET_CONFIGURATION 0x04 +#define AVDTP_RECONFIGURE 0x05 +#define AVDTP_OPEN 0x06 +#define AVDTP_START 0x07 +#define AVDTP_CLOSE 0x08 +#define AVDTP_SUSPEND 0x09 +#define AVDTP_ABORT 0x0A +#define AVDTP_SECURITY_CONTROL 0x0B +#define AVDTP_GET_ALL_CAPABILITIES 0x0C +#define AVDTP_DELAY_REPORT 0x0D + +#define AVDTP_PKT_TYPE_SINGLE 0x00 +#define AVDTP_PKT_TYPE_START 0x01 +#define AVDTP_PKT_TYPE_CONTINUE 0x02 +#define AVDTP_PKT_TYPE_END 0x03 + +#define AVDTP_MSG_TYPE_COMMAND 0x00 +#define AVDTP_MSG_TYPE_GEN_REJECT 0x01 +#define AVDTP_MSG_TYPE_ACCEPT 0x02 +#define AVDTP_MSG_TYPE_REJECT 0x03 + +#define REQ_TIMEOUT 6 +#define ABORT_TIMEOUT 2 +#define DISCONNECT_TIMEOUT 1 +#define STREAM_TIMEOUT 20 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avdtp_common_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; +} __attribute__ ((packed)); + +struct avdtp_single_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct avdtp_start_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t no_of_packets; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct avdtp_continue_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t rfa0:1; + uint8_t inuse:1; + uint8_t seid:6; + uint8_t rfa2:3; + uint8_t type:1; + uint8_t media_type:4; +} __attribute__ ((packed)); + +struct seid { + uint8_t rfa0:2; + uint8_t seid:6; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avdtp_common_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; +} __attribute__ ((packed)); + +struct avdtp_single_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; + uint8_t rfa0:2; + uint8_t signal_id:6; +} __attribute__ ((packed)); + +struct avdtp_start_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; + uint8_t no_of_packets; + uint8_t rfa0:2; + uint8_t signal_id:6; +} __attribute__ ((packed)); + +struct avdtp_continue_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t seid:6; + uint8_t inuse:1; + uint8_t rfa0:1; + uint8_t media_type:4; + uint8_t type:1; + uint8_t rfa2:3; +} __attribute__ ((packed)); + +struct seid { + uint8_t seid:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +/* packets */ + +struct discover_resp { + struct seid_info seps[0]; +} __attribute__ ((packed)); + +struct getcap_resp { + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct start_req { + struct seid first_seid; + struct seid other_seids[0]; +} __attribute__ ((packed)); + +struct suspend_req { + struct seid first_seid; + struct seid other_seids[0]; +} __attribute__ ((packed)); + +struct seid_rej { + uint8_t error; +} __attribute__ ((packed)); + +struct conf_rej { + uint8_t category; + uint8_t error; +} __attribute__ ((packed)); + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct seid_req { + uint8_t rfa0:2; + uint8_t acp_seid:6; +} __attribute__ ((packed)); + +struct setconf_req { + uint8_t rfa0:2; + uint8_t acp_seid:6; + uint8_t rfa1:2; + uint8_t int_seid:6; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct stream_rej { + uint8_t rfa0:2; + uint8_t acp_seid:6; + uint8_t error; +} __attribute__ ((packed)); + +struct reconf_req { + uint8_t rfa0:2; + uint8_t acp_seid:6; + + uint8_t serv_cap; + uint8_t serv_cap_len; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct delay_req { + uint8_t rfa0:2; + uint8_t acp_seid:6; + uint16_t delay; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct seid_req { + uint8_t acp_seid:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct setconf_req { + uint8_t acp_seid:6; + uint8_t rfa0:2; + uint8_t int_seid:6; + uint8_t rfa1:2; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct stream_rej { + uint8_t acp_seid:6; + uint8_t rfa0:2; + uint8_t error; +} __attribute__ ((packed)); + +struct reconf_req { + uint8_t acp_seid:6; + uint8_t rfa0:2; + + uint8_t serv_cap; + uint8_t serv_cap_len; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct delay_req { + uint8_t acp_seid:6; + uint8_t rfa0:2; + uint16_t delay; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +struct in_buf { + gboolean active; + int no_of_packets; + uint8_t transaction; + uint8_t message_type; + uint8_t signal_id; + uint8_t buf[1024]; + uint8_t data_size; +}; + +struct pending_req { + uint8_t transaction; + uint8_t signal_id; + void *data; + size_t data_size; + struct avdtp_stream *stream; /* Set if the request targeted a stream */ + guint timeout; +}; + +struct avdtp_remote_sep { + uint8_t seid; + uint8_t type; + uint8_t media_type; + struct avdtp_service_capability *codec; + gboolean delay_reporting; + GSList *caps; /* of type struct avdtp_service_capability */ + struct avdtp_stream *stream; +}; + +struct avdtp_server { + bdaddr_t src; + uint16_t version; + GIOChannel *io; + GSList *seps; + GSList *sessions; +}; + +struct avdtp_local_sep { + avdtp_state_t state; + struct avdtp_stream *stream; + struct seid_info info; + uint8_t codec; + gboolean delay_reporting; + GSList *caps; + struct avdtp_sep_ind *ind; + struct avdtp_sep_cfm *cfm; + void *user_data; + struct avdtp_server *server; +}; + +struct stream_callback { + avdtp_stream_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct avdtp_state_callback { + avdtp_session_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct avdtp_stream { + GIOChannel *io; + uint16_t imtu; + uint16_t omtu; + struct avdtp *session; + struct avdtp_local_sep *lsep; + uint8_t rseid; + GSList *caps; + GSList *callbacks; + struct avdtp_service_capability *codec; + guint io_id; /* Transport GSource ID */ + guint timer; /* Waiting for other side to close or open + * the transport channel */ + gboolean open_acp; /* If we are in ACT role for Open */ + gboolean close_int; /* If we are in INT role for Close */ + gboolean abort_int; /* If we are in INT role for Abort */ + guint idle_timer; + gboolean delay_reporting; + uint16_t delay; /* AVDTP 1.3 Delay Reporting feature */ +}; + +/* Structure describing an AVDTP connection between two devices */ + +struct avdtp { + int ref; + int free_lock; + + uint16_t version; + + struct avdtp_server *server; + bdaddr_t dst; + + avdtp_session_state_t state; + + /* True if the session should be automatically disconnected */ + gboolean auto_dc; + + /* True if the entire device is being disconnected */ + gboolean device_disconnect; + + GIOChannel *io; + guint io_id; + + GSList *seps; /* Elements of type struct avdtp_remote_sep * */ + + GSList *streams; /* Elements of type struct avdtp_stream * */ + + GSList *req_queue; /* Elements of type struct pending_req * */ + GSList *prio_queue; /* Same as req_queue but is processed before it */ + + struct avdtp_stream *pending_open; + + uint16_t imtu; + uint16_t omtu; + + struct in_buf in; + + char *buf; + + avdtp_discover_cb_t discov_cb; + void *user_data; + + struct pending_req *req; + + guint dc_timer; + + /* Attempt stream setup instead of disconnecting */ + gboolean stream_setup; + + DBusPendingCall *pending_auth; +}; + +static GSList *servers = NULL; + +static GSList *avdtp_callbacks = NULL; + +static gboolean auto_connect = TRUE; + +static int send_request(struct avdtp *session, gboolean priority, + struct avdtp_stream *stream, uint8_t signal_id, + void *buffer, size_t size); +static gboolean avdtp_parse_resp(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size); +static gboolean avdtp_parse_rej(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size); +static int process_queue(struct avdtp *session); +static void connection_lost(struct avdtp *session, int err); +static void avdtp_sep_set_state(struct avdtp *session, + struct avdtp_local_sep *sep, + avdtp_state_t state); +static void auth_cb(DBusError *derr, void *user_data); + +static struct avdtp_server *find_server(GSList *list, const bdaddr_t *src) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct avdtp_server *server = l->data; + + if (bacmp(&server->src, src) == 0) + return server; + } + + return NULL; +} + +static const char *avdtp_statestr(avdtp_state_t state) +{ + switch (state) { + case AVDTP_STATE_IDLE: + return "IDLE"; + case AVDTP_STATE_CONFIGURED: + return "CONFIGURED"; + case AVDTP_STATE_OPEN: + return "OPEN"; + case AVDTP_STATE_STREAMING: + return "STREAMING"; + case AVDTP_STATE_CLOSING: + return "CLOSING"; + case AVDTP_STATE_ABORTING: + return "ABORTING"; + default: + return ""; + } +} + +static gboolean try_send(int sk, void *data, size_t len) +{ + int err; + + do { + err = send(sk, data, len, 0); + } while (err < 0 && errno == EINTR); + + if (err < 0) { + error("send: %s (%d)", strerror(errno), errno); + return FALSE; + } else if ((size_t) err != len) { + error("try_send: complete buffer not sent (%d/%zu bytes)", + err, len); + return FALSE; + } + + return TRUE; +} + +static gboolean avdtp_send(struct avdtp *session, uint8_t transaction, + uint8_t message_type, uint8_t signal_id, + void *data, size_t len) +{ + unsigned int cont_fragments, sent; + struct avdtp_start_header start; + struct avdtp_continue_header cont; + int sock; + + if (session->io == NULL) { + error("avdtp_send: session is closed"); + return FALSE; + } + + sock = g_io_channel_unix_get_fd(session->io); + + /* Single packet - no fragmentation */ + if (sizeof(struct avdtp_single_header) + len <= session->omtu) { + struct avdtp_single_header single; + + memset(&single, 0, sizeof(single)); + + single.transaction = transaction; + single.packet_type = AVDTP_PKT_TYPE_SINGLE; + single.message_type = message_type; + single.signal_id = signal_id; + + memcpy(session->buf, &single, sizeof(single)); + memcpy(session->buf + sizeof(single), data, len); + + return try_send(sock, session->buf, sizeof(single) + len); + } + + /* Check if there is enough space to start packet */ + if (session->omtu < sizeof(start)) { + error("No enough space to fragment packet"); + return FALSE; + } + + /* Count the number of needed fragments */ + cont_fragments = (len - (session->omtu - sizeof(start))) / + (session->omtu - sizeof(cont)) + 1; + + DBG("%zu bytes split into %d fragments", len, cont_fragments + 1); + + /* Send the start packet */ + memset(&start, 0, sizeof(start)); + start.transaction = transaction; + start.packet_type = AVDTP_PKT_TYPE_START; + start.message_type = message_type; + start.no_of_packets = cont_fragments + 1; + start.signal_id = signal_id; + + memcpy(session->buf, &start, sizeof(start)); + memcpy(session->buf + sizeof(start), data, + session->omtu - sizeof(start)); + + if (!try_send(sock, session->buf, session->omtu)) + return FALSE; + + DBG("first packet with %zu bytes sent", session->omtu - sizeof(start)); + + sent = session->omtu - sizeof(start); + + /* Send the continue fragments and the end packet */ + while (sent < len) { + int left, to_copy; + + left = len - sent; + if (left + sizeof(cont) > session->omtu) { + cont.packet_type = AVDTP_PKT_TYPE_CONTINUE; + to_copy = session->omtu - sizeof(cont); + DBG("sending continue with %d bytes", to_copy); + } else { + cont.packet_type = AVDTP_PKT_TYPE_END; + to_copy = left; + DBG("sending end with %d bytes", to_copy); + } + + cont.transaction = transaction; + cont.message_type = message_type; + + memcpy(session->buf, &cont, sizeof(cont)); + memcpy(session->buf + sizeof(cont), data + sent, to_copy); + + if (!try_send(sock, session->buf, to_copy + sizeof(cont))) + return FALSE; + + sent += to_copy; + } + + return TRUE; +} + +static void pending_req_free(struct pending_req *req) +{ + if (req->timeout) + g_source_remove(req->timeout); + g_free(req->data); + g_free(req); +} + +static void close_stream(struct avdtp_stream *stream) +{ + int sock; + + if (stream->io == NULL) + return; + + sock = g_io_channel_unix_get_fd(stream->io); + + shutdown(sock, SHUT_RDWR); + + g_io_channel_shutdown(stream->io, FALSE, NULL); + + g_io_channel_unref(stream->io); + stream->io = NULL; +} + +static gboolean stream_close_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + + DBG("Timed out waiting for peer to close the transport channel"); + + stream->timer = 0; + + close_stream(stream); + + return FALSE; +} + +static gboolean stream_open_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + + DBG("Timed out waiting for peer to open the transport channel"); + + stream->timer = 0; + + stream->session->pending_open = NULL; + + avdtp_abort(stream->session, stream); + + return FALSE; +} + +static gboolean disconnect_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + struct audio_device *dev; + gboolean stream_setup; + + session->dc_timer = 0; + stream_setup = session->stream_setup; + session->stream_setup = FALSE; + + dev = manager_get_device(&session->server->src, &session->dst, FALSE); + + if (dev && dev->sink && stream_setup) + sink_setup_stream(dev->sink, session); + else if (dev && dev->source && stream_setup) + source_setup_stream(dev->source, session); + else + connection_lost(session, ETIMEDOUT); + + return FALSE; +} + +static void remove_disconnect_timer(struct avdtp *session) +{ + g_source_remove(session->dc_timer); + session->dc_timer = 0; + session->stream_setup = FALSE; +} + +static void set_disconnect_timer(struct avdtp *session) +{ + if (session->dc_timer) + remove_disconnect_timer(session); + + if (session->device_disconnect) { + g_idle_add(disconnect_timeout, session); + return; + } + + session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT, + disconnect_timeout, + session); +} + +void avdtp_error_init(struct avdtp_error *err, uint8_t category, int id) +{ + err->category = category; + + if (category == AVDTP_ERRNO) + err->err.posix_errno = id; + else + err->err.error_code = id; +} + +uint8_t avdtp_error_category(struct avdtp_error *err) +{ + return err->category; +} + +int avdtp_error_error_code(struct avdtp_error *err) +{ + assert(err->category != AVDTP_ERRNO); + return err->err.error_code; +} + +int avdtp_error_posix_errno(struct avdtp_error *err) +{ + assert(err->category == AVDTP_ERRNO); + return err->err.posix_errno; +} + +static struct avdtp_stream *find_stream_by_rseid(struct avdtp *session, + uint8_t rseid) +{ + GSList *l; + + for (l = session->streams; l != NULL; l = g_slist_next(l)) { + struct avdtp_stream *stream = l->data; + + if (stream->rseid == rseid) + return stream; + } + + return NULL; +} + +static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid) +{ + GSList *l; + + for (l = seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_remote_sep *sep = l->data; + + if (sep->seid == seid) + return sep; + } + + return NULL; +} + +static void avdtp_set_state(struct avdtp *session, + avdtp_session_state_t new_state) +{ + GSList *l; + struct audio_device *dev; + bdaddr_t src, dst; + avdtp_session_state_t old_state = session->state; + + session->state = new_state; + + avdtp_get_peers(session, &src, &dst); + dev = manager_get_device(&src, &dst, FALSE); + if (dev == NULL) { + error("avdtp_set_state(): no matching audio device"); + return; + } + + for (l = avdtp_callbacks; l != NULL; l = l->next) { + struct avdtp_state_callback *cb = l->data; + cb->cb(dev, session, old_state, new_state, cb->user_data); + } +} + +static void stream_free(struct avdtp_stream *stream) +{ + struct avdtp_remote_sep *rsep; + + stream->lsep->info.inuse = 0; + stream->lsep->stream = NULL; + + rsep = find_remote_sep(stream->session->seps, stream->rseid); + if (rsep) + rsep->stream = NULL; + + if (stream->timer) + g_source_remove(stream->timer); + + if (stream->io) + close_stream(stream); + + if (stream->io_id) + g_source_remove(stream->io_id); + + g_slist_foreach(stream->callbacks, (GFunc) g_free, NULL); + g_slist_free(stream->callbacks); + + g_slist_foreach(stream->caps, (GFunc) g_free, NULL); + g_slist_free(stream->caps); + + g_free(stream); +} + +static gboolean stream_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + struct avdtp *session = stream->session; + + if (avdtp_close(session, stream, FALSE) < 0) + error("stream_timeout: closing AVDTP stream failed"); + + stream->idle_timer = 0; + + return FALSE; +} + +static gboolean transport_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp_stream *stream = data; + struct avdtp_local_sep *sep = stream->lsep; + + if (stream->close_int && sep->cfm && sep->cfm->close) + sep->cfm->close(stream->session, sep, stream, NULL, + sep->user_data); + + if (!(cond & G_IO_NVAL)) + close_stream(stream); + + stream->io_id = 0; + + if (!stream->abort_int) + avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE); + + return FALSE; +} + +static int get_send_buffer_size(int sk) +{ + int size; + socklen_t optlen = sizeof(size); + + if (getsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, &optlen) < 0) { + int err = -errno; + error("getsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err), + -err); + return err; + } + + /* + * Doubled value is returned by getsockopt since kernel uses that + * space for its own purposes (see man 7 socket, bookkeeping overhead + * for SO_SNDBUF). + */ + return size / 2; +} + +static int set_send_buffer_size(int sk, int size) +{ + socklen_t optlen = sizeof(size); + + if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, optlen) < 0) { + int err = -errno; + error("setsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err), + -err); + return err; + } + + return 0; +} + +static void handle_transport_connect(struct avdtp *session, GIOChannel *io, + uint16_t imtu, uint16_t omtu) +{ + struct avdtp_stream *stream = session->pending_open; + struct avdtp_local_sep *sep = stream->lsep; + + session->pending_open = NULL; + + if (stream->timer) { + g_source_remove(stream->timer); + stream->timer = 0; + } + + if (io == NULL) { + if (!stream->open_acp && sep->cfm && sep->cfm->open) { + struct avdtp_error err; + avdtp_error_init(&err, AVDTP_ERRNO, EIO); + sep->cfm->open(session, sep, NULL, &err, + sep->user_data); + } + return; + } + + if (stream->io == NULL) + stream->io = g_io_channel_ref(io); + + stream->omtu = omtu; + stream->imtu = imtu; + + /* only if local SEP is of type SRC */ + if (sep->info.type == AVDTP_SEP_TYPE_SOURCE) { + int sk, buf_size, min_buf_size; + + sk = g_io_channel_unix_get_fd(stream->io); + buf_size = get_send_buffer_size(sk); + if (buf_size < 0) + goto proceed; + + DBG("sk %d, omtu %d, send buffer size %d", sk, omtu, buf_size); + min_buf_size = omtu * 2; + if (buf_size < min_buf_size) { + DBG("send buffer size to be increassed to %d", + min_buf_size); + set_send_buffer_size(sk, min_buf_size); + } + } + +proceed: + if (!stream->open_acp && sep->cfm && sep->cfm->open) + sep->cfm->open(session, sep, stream, NULL, sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + + stream->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) transport_cb, stream); +} + +static int pending_req_cmp(gconstpointer a, gconstpointer b) +{ + const struct pending_req *req = a; + const struct avdtp_stream *stream = b; + + if (req->stream == stream) + return 0; + + return -1; +} + +static void cleanup_queue(struct avdtp *session, struct avdtp_stream *stream) +{ + GSList *l; + struct pending_req *req; + + while ((l = g_slist_find_custom(session->prio_queue, stream, + pending_req_cmp))) { + req = l->data; + pending_req_free(req); + session->prio_queue = g_slist_remove(session->prio_queue, req); + } + + while ((l = g_slist_find_custom(session->req_queue, stream, + pending_req_cmp))) { + req = l->data; + pending_req_free(req); + session->req_queue = g_slist_remove(session->req_queue, req); + } +} + +static void handle_unanswered_req(struct avdtp *session, + struct avdtp_stream *stream) +{ + struct pending_req *req; + struct avdtp_local_sep *lsep; + struct avdtp_error err; + + if (session->req->signal_id == AVDTP_ABORT) { + /* Avoid freeing the Abort request here */ + DBG("handle_unanswered_req: Abort req, returning"); + session->req->stream = NULL; + return; + } + + req = session->req; + session->req = NULL; + + avdtp_error_init(&err, AVDTP_ERRNO, EIO); + + lsep = stream->lsep; + + switch (req->signal_id) { + case AVDTP_RECONFIGURE: + error("No reply to Reconfigure request"); + if (lsep && lsep->cfm && lsep->cfm->reconfigure) + lsep->cfm->reconfigure(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_OPEN: + error("No reply to Open request"); + if (lsep && lsep->cfm && lsep->cfm->open) + lsep->cfm->open(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_START: + error("No reply to Start request"); + if (lsep && lsep->cfm && lsep->cfm->start) + lsep->cfm->start(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_SUSPEND: + error("No reply to Suspend request"); + if (lsep && lsep->cfm && lsep->cfm->suspend) + lsep->cfm->suspend(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_CLOSE: + error("No reply to Close request"); + if (lsep && lsep->cfm && lsep->cfm->close) + lsep->cfm->close(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_SET_CONFIGURATION: + error("No reply to SetConfiguration request"); + if (lsep && lsep->cfm && lsep->cfm->set_configuration) + lsep->cfm->set_configuration(session, lsep, stream, + &err, lsep->user_data); + } + + pending_req_free(req); +} + +static void avdtp_sep_set_state(struct avdtp *session, + struct avdtp_local_sep *sep, + avdtp_state_t state) +{ + struct avdtp_stream *stream = sep->stream; + avdtp_state_t old_state; + struct avdtp_error err, *err_ptr = NULL; + GSList *l; + + if (!stream) { + error("Error changing sep state: stream not available"); + return; + } + + if (sep->state == state) { + avdtp_error_init(&err, AVDTP_ERRNO, EIO); + DBG("stream state change failed: %s", avdtp_strerror(&err)); + err_ptr = &err; + } else { + err_ptr = NULL; + DBG("stream state changed: %s -> %s", + avdtp_statestr(sep->state), + avdtp_statestr(state)); + } + + old_state = sep->state; + sep->state = state; + + for (l = stream->callbacks; l != NULL; l = g_slist_next(l)) { + struct stream_callback *cb = l->data; + cb->cb(stream, old_state, state, err_ptr, cb->user_data); + } + + switch (state) { + case AVDTP_STATE_CONFIGURED: + if (sep->info.type == AVDTP_SEP_TYPE_SINK) + avdtp_delay_report(session, stream, stream->delay); + break; + case AVDTP_STATE_OPEN: + if (old_state > AVDTP_STATE_OPEN && session->auto_dc) + stream->idle_timer = g_timeout_add_seconds(STREAM_TIMEOUT, + stream_timeout, + stream); + break; + case AVDTP_STATE_STREAMING: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + if (stream->idle_timer) { + g_source_remove(stream->idle_timer); + stream->idle_timer = 0; + } + break; + case AVDTP_STATE_IDLE: + if (stream->idle_timer) { + g_source_remove(stream->idle_timer); + stream->idle_timer = 0; + } + session->streams = g_slist_remove(session->streams, stream); + if (session->pending_open == stream) + handle_transport_connect(session, NULL, 0, 0); + if (session->req && session->req->stream == stream) + handle_unanswered_req(session, stream); + /* Remove pending commands for this stream from the queue */ + cleanup_queue(session, stream); + stream_free(stream); + break; + default: + break; + } +} + +static void finalize_discovery(struct avdtp *session, int err) +{ + struct avdtp_error avdtp_err; + + avdtp_error_init(&avdtp_err, AVDTP_ERRNO, err); + + if (!session->discov_cb) + return; + + session->discov_cb(session, session->seps, + err ? &avdtp_err : NULL, + session->user_data); + + session->discov_cb = NULL; + session->user_data = NULL; +} + +static void release_stream(struct avdtp_stream *stream, struct avdtp *session) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->abort && + (sep->state != AVDTP_STATE_ABORTING || + stream->abort_int)) + sep->cfm->abort(session, sep, stream, NULL, sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); +} + +static void connection_lost(struct avdtp *session, int err) +{ + char address[18]; + struct audio_device *dev; + + ba2str(&session->dst, address); + DBG("Disconnected from %s", address); + + dev = manager_get_device(&session->server->src, &session->dst, FALSE); + + if (dev != NULL && session->state == AVDTP_SESSION_STATE_CONNECTING && + err != EACCES) + audio_device_cancel_authorization(dev, auth_cb, session); + + session->free_lock = 1; + + finalize_discovery(session, err); + + g_slist_foreach(session->streams, (GFunc) release_stream, session); + session->streams = NULL; + + session->free_lock = 0; + + if (session->io) { + g_io_channel_shutdown(session->io, FALSE, NULL); + g_io_channel_unref(session->io); + session->io = NULL; + } + + avdtp_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED); + + if (session->io_id) { + g_source_remove(session->io_id); + session->io_id = 0; + } + + if (session->dc_timer) + remove_disconnect_timer(session); + + session->auto_dc = TRUE; + + if (session->ref != 1) + error("connection_lost: ref count not 1 after all callbacks"); + else + avdtp_unref(session); +} + +void avdtp_unref(struct avdtp *session) +{ + struct avdtp_server *server; + + if (!session) + return; + + session->ref--; + + DBG("%p: ref=%d", session, session->ref); + + if (session->ref == 1) { + if (session->state == AVDTP_SESSION_STATE_CONNECTING && + session->io) { + struct audio_device *dev; + dev = manager_get_device(&session->server->src, + &session->dst, FALSE); + audio_device_cancel_authorization(dev, auth_cb, + session); + g_io_channel_shutdown(session->io, TRUE, NULL); + g_io_channel_unref(session->io); + session->io = NULL; + avdtp_set_state(session, + AVDTP_SESSION_STATE_DISCONNECTED); + } + + if (session->io) + set_disconnect_timer(session); + else if (!session->free_lock) /* Drop the local ref if we + aren't connected */ + session->ref--; + } + + if (session->ref > 0) + return; + + server = session->server; + + DBG("%p: freeing session and removing from list", session); + + if (session->dc_timer) + remove_disconnect_timer(session); + + server->sessions = g_slist_remove(server->sessions, session); + + if (session->req) + pending_req_free(session->req); + + g_slist_foreach(session->seps, (GFunc) g_free, NULL); + g_slist_free(session->seps); + + g_free(session->buf); + + g_free(session); +} + +struct avdtp *avdtp_ref(struct avdtp *session) +{ + session->ref++; + DBG("%p: ref=%d", session, session->ref); + if (session->dc_timer) + remove_disconnect_timer(session); + return session; +} + +static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp_server *server, + uint8_t seid) +{ + GSList *l; + + for (l = server->seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_local_sep *sep = l->data; + + if (sep->info.seid == seid) + return sep; + } + + return NULL; +} + +struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session, + struct avdtp_local_sep *lsep) +{ + GSList *l; + + if (lsep->info.inuse) + return NULL; + + for (l = session->seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_remote_sep *sep = l->data; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_data; + + /* Type must be different: source <-> sink */ + if (sep->type == lsep->info.type) + continue; + + if (sep->media_type != lsep->info.media_type) + continue; + + if (!sep->codec) + continue; + + cap = sep->codec; + codec_data = (void *) cap->data; + + if (codec_data->media_codec_type != lsep->codec) + continue; + + if (sep->stream == NULL) + return sep; + } + + return NULL; +} + +static GSList *caps_to_list(uint8_t *data, int size, + struct avdtp_service_capability **codec, + gboolean *delay_reporting) +{ + GSList *caps; + int processed; + + if (delay_reporting) + *delay_reporting = FALSE; + + for (processed = 0, caps = NULL; processed + 2 <= size;) { + struct avdtp_service_capability *cap; + uint8_t length, category; + + category = data[0]; + length = data[1]; + + if (processed + 2 + length > size) { + error("Invalid capability data in getcap resp"); + break; + } + + cap = g_malloc(sizeof(struct avdtp_service_capability) + + length); + memcpy(cap, data, 2 + length); + + processed += 2 + length; + data += 2 + length; + + caps = g_slist_append(caps, cap); + + if (category == AVDTP_MEDIA_CODEC && + length >= + sizeof(struct avdtp_media_codec_capability)) + *codec = cap; + else if (category == AVDTP_DELAY_REPORTING && delay_reporting) + *delay_reporting = TRUE; + } + + return caps; +} + +static gboolean avdtp_unknown_cmd(struct avdtp *session, uint8_t transaction, + uint8_t signal_id) +{ + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_GEN_REJECT, + signal_id, NULL, 0); +} + +static gboolean avdtp_discover_cmd(struct avdtp *session, uint8_t transaction, + void *buf, int size) +{ + GSList *l; + unsigned int rsp_size, sep_count, i; + struct seid_info *seps; + gboolean ret; + + sep_count = g_slist_length(session->server->seps); + + if (sep_count == 0) { + uint8_t err = AVDTP_NOT_SUPPORTED_COMMAND; + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_DISCOVER, &err, sizeof(err)); + } + + rsp_size = sep_count * sizeof(struct seid_info); + + seps = g_new0(struct seid_info, sep_count); + + for (l = session->server->seps, i = 0; l != NULL; l = l->next, i++) { + struct avdtp_local_sep *sep = l->data; + + memcpy(&seps[i], &sep->info, sizeof(struct seid_info)); + } + + ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_DISCOVER, seps, rsp_size); + g_free(seps); + + return ret; +} + +static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, unsigned int size, + gboolean get_all) +{ + GSList *l, *caps; + struct avdtp_local_sep *sep = NULL; + unsigned int rsp_size; + uint8_t err, buf[1024], *ptr = buf; + uint8_t cmd; + + cmd = get_all ? AVDTP_GET_ALL_CAPABILITIES : AVDTP_GET_CAPABILITIES; + + if (size < sizeof(struct seid_req)) { + err = AVDTP_BAD_LENGTH; + goto failed; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (get_all && session->server->version < 0x0103) + return avdtp_unknown_cmd(session, transaction, cmd); + + if (!sep->ind->get_capability(session, sep, get_all, &caps, + &err, sep->user_data)) + goto failed; + + for (l = caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (rsp_size + cap->length + 2 > sizeof(buf)) + break; + + memcpy(ptr, cap, cap->length + 2); + rsp_size += cap->length + 2; + ptr += cap->length + 2; + + g_free(cap); + } + + g_slist_free(caps); + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, cmd, + buf, rsp_size); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, cmd, + &err, sizeof(err)); +} + +static void setconf_cb(struct avdtp *session, struct avdtp_stream *stream, + struct avdtp_error *err) +{ + struct conf_rej rej; + struct avdtp_local_sep *sep; + + if (err != NULL) { + rej.error = AVDTP_UNSUPPORTED_CONFIGURATION; + rej.category = err->err.error_code; + avdtp_send(session, session->in.transaction, + AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION, + &rej, sizeof(rej)); + return; + } + + if (!avdtp_send(session, session->in.transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_SET_CONFIGURATION, NULL, 0)) { + stream_free(stream); + return; + } + + sep = stream->lsep; + sep->stream = stream; + sep->info.inuse = 1; + session->streams = g_slist_append(session->streams, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); +} + +static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction, + struct setconf_req *req, unsigned int size) +{ + struct conf_rej rej; + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err, category = 0x00; + struct audio_device *dev; + bdaddr_t src, dst; + GSList *l; + + if (size < sizeof(struct setconf_req)) { + error("Too short getcap request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->stream) { + err = AVDTP_SEP_IN_USE; + goto failed; + } + + avdtp_get_peers(session, &src, &dst); + dev = manager_get_device(&src, &dst, FALSE); + if (!dev) { + error("Unable to get a audio device object"); + err = AVDTP_BAD_STATE; + goto failed; + } + + switch (sep->info.type) { + case AVDTP_SEP_TYPE_SOURCE: + if (!dev->sink) { + btd_device_add_uuid(dev->btd_dev, A2DP_SINK_UUID); + if (!dev->sink) { + error("Unable to get a audio sink object"); + err = AVDTP_BAD_STATE; + goto failed; + } + } + break; + case AVDTP_SEP_TYPE_SINK: + if (!dev->source) { + btd_device_add_uuid(dev->btd_dev, A2DP_SOURCE_UUID); + if (!dev->sink) { + error("Unable to get a audio source object"); + err = AVDTP_BAD_STATE; + goto failed; + } + } + break; + } + + stream = g_new0(struct avdtp_stream, 1); + stream->session = session; + stream->lsep = sep; + stream->rseid = req->int_seid; + stream->caps = caps_to_list(req->caps, + size - sizeof(struct setconf_req), + &stream->codec, + &stream->delay_reporting); + + /* Verify that the Media Transport capability's length = 0. Reject otherwise */ + for (l = stream->caps; l != NULL; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (cap->category == AVDTP_MEDIA_TRANSPORT && cap->length != 0) { + err = AVDTP_BAD_MEDIA_TRANSPORT_FORMAT; + goto failed_stream; + } + } + + if (stream->delay_reporting && session->version < 0x0103) + session->version = 0x0103; + + if (sep->ind && sep->ind->set_configuration) { + if (!sep->ind->set_configuration(session, sep, stream, + stream->caps, + setconf_cb, + sep->user_data)) { + err = AVDTP_UNSUPPORTED_CONFIGURATION; + category = 0x00; + goto failed_stream; + } + } else { + if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_SET_CONFIGURATION, NULL, 0)) { + stream_free(stream); + return FALSE; + } + + sep->stream = stream; + sep->info.inuse = 1; + session->streams = g_slist_append(session->streams, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); + } + + return TRUE; + +failed_stream: + stream_free(stream); +failed: + rej.error = err; + rej.category = category; + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_SET_CONFIGURATION, &rej, sizeof(rej)); +} + +static gboolean avdtp_getconf_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, int size) +{ + GSList *l; + struct avdtp_local_sep *sep = NULL; + int rsp_size; + uint8_t err; + uint8_t buf[1024]; + uint8_t *ptr = buf; + + if (size < (int) sizeof(struct seid_req)) { + error("Too short getconf request"); + return FALSE; + } + + memset(buf, 0, sizeof(buf)); + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + if (!sep->stream || !sep->stream->caps) { + err = AVDTP_UNSUPPORTED_CONFIGURATION; + goto failed; + } + + for (l = sep->stream->caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (rsp_size + cap->length + 2 > (int) sizeof(buf)) + break; + + memcpy(ptr, cap, cap->length + 2); + rsp_size += cap->length + 2; + ptr += cap->length + 2; + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_GET_CONFIGURATION, buf, rsp_size); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_GET_CONFIGURATION, &err, sizeof(err)); +} + +static gboolean avdtp_reconf_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, int size) +{ + return avdtp_unknown_cmd(session, transaction, AVDTP_RECONFIGURE); +} + +static gboolean avdtp_open_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, unsigned int size) +{ + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err; + + if (size < sizeof(struct seid_req)) { + error("Too short abort request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->state != AVDTP_STATE_CONFIGURED) { + err = AVDTP_BAD_STATE; + goto failed; + } + + stream = sep->stream; + + if (sep->ind && sep->ind->open) { + if (!sep->ind->open(session, sep, stream, &err, + sep->user_data)) + goto failed; + } + + if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_OPEN, NULL, 0)) + return FALSE; + + stream->open_acp = TRUE; + session->pending_open = stream; + stream->timer = g_timeout_add_seconds(REQ_TIMEOUT, + stream_open_timeout, + stream); + + return TRUE; + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_OPEN, &err, sizeof(err)); +} + +static gboolean avdtp_start_cmd(struct avdtp *session, uint8_t transaction, + struct start_req *req, unsigned int size) +{ + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + struct stream_rej rej; + struct seid *seid; + uint8_t err, failed_seid; + int seid_count, i; + + if (size < sizeof(struct start_req)) { + error("Too short start request"); + return FALSE; + } + + seid_count = 1 + size - sizeof(struct start_req); + + seid = &req->first_seid; + + for (i = 0; i < seid_count; i++, seid++) { + failed_seid = seid->seid; + + sep = find_local_sep_by_seid(session->server, + req->first_seid.seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + stream = sep->stream; + + if (sep->state != AVDTP_STATE_OPEN) { + err = AVDTP_BAD_STATE; + goto failed; + } + + if (sep->ind && sep->ind->start) { + if (!sep->ind->start(session, sep, stream, &err, + sep->user_data)) + goto failed; + } + + avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING); + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_START, NULL, 0); + +failed: + memset(&rej, 0, sizeof(rej)); + rej.acp_seid = failed_seid; + rej.error = err; + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_START, &rej, sizeof(rej)); +} + +static gboolean avdtp_close_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, unsigned int size) +{ + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err; + + if (size < sizeof(struct seid_req)) { + error("Too short close request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->state != AVDTP_STATE_OPEN && + sep->state != AVDTP_STATE_STREAMING) { + err = AVDTP_BAD_STATE; + goto failed; + } + + stream = sep->stream; + + if (sep->ind && sep->ind->close) { + if (!sep->ind->close(session, sep, stream, &err, + sep->user_data)) + goto failed; + } + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING); + + if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_CLOSE, NULL, 0)) + return FALSE; + + stream->timer = g_timeout_add_seconds(REQ_TIMEOUT, + stream_close_timeout, + stream); + + return TRUE; + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_CLOSE, &err, sizeof(err)); +} + +static gboolean avdtp_suspend_cmd(struct avdtp *session, uint8_t transaction, + struct suspend_req *req, unsigned int size) +{ + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + struct stream_rej rej; + struct seid *seid; + uint8_t err, failed_seid; + int seid_count, i; + + if (size < sizeof(struct suspend_req)) { + error("Too short suspend request"); + return FALSE; + } + + seid_count = 1 + size - sizeof(struct suspend_req); + + seid = &req->first_seid; + + for (i = 0; i < seid_count; i++, seid++) { + failed_seid = seid->seid; + + sep = find_local_sep_by_seid(session->server, + req->first_seid.seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + stream = sep->stream; + + if (sep->state != AVDTP_STATE_STREAMING) { + err = AVDTP_BAD_STATE; + goto failed; + } + + if (sep->ind && sep->ind->suspend) { + if (!sep->ind->suspend(session, sep, stream, &err, + sep->user_data)) + goto failed; + } + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_SUSPEND, NULL, 0); + +failed: + memset(&rej, 0, sizeof(rej)); + rej.acp_seid = failed_seid; + rej.error = err; + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_SUSPEND, &rej, sizeof(rej)); +} + +static gboolean avdtp_abort_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, unsigned int size) +{ + struct avdtp_local_sep *sep; + uint8_t err; + gboolean ret; + + if (size < sizeof(struct seid_req)) { + error("Too short abort request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->ind && sep->ind->abort) { + if (!sep->ind->abort(session, sep, sep->stream, &err, + sep->user_data)) + goto failed; + } + + ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_ABORT, NULL, 0); + if (ret) + avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING); + + return ret; + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_ABORT, &err, sizeof(err)); +} + +static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, int size) +{ + return avdtp_unknown_cmd(session, transaction, AVDTP_SECURITY_CONTROL); +} + +static gboolean avdtp_delayreport_cmd(struct avdtp *session, + uint8_t transaction, + struct delay_req *req, + unsigned int size) +{ + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err; + + if (size < sizeof(struct delay_req)) { + error("Too short delay report request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + stream = sep->stream; + + if (sep->state != AVDTP_STATE_CONFIGURED && + sep->state != AVDTP_STATE_STREAMING) { + err = AVDTP_BAD_STATE; + goto failed; + } + + stream->delay = ntohs(req->delay); + + if (sep->ind && sep->ind->delayreport) { + if (!sep->ind->delayreport(session, sep, stream->rseid, + stream->delay, &err, + sep->user_data)) + goto failed; + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_DELAY_REPORT, NULL, 0); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_DELAY_REPORT, &err, sizeof(err)); +} + +static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction, + uint8_t signal_id, void *buf, int size) +{ + switch (signal_id) { + case AVDTP_DISCOVER: + DBG("Received DISCOVER_CMD"); + return avdtp_discover_cmd(session, transaction, buf, size); + case AVDTP_GET_CAPABILITIES: + DBG("Received GET_CAPABILITIES_CMD"); + return avdtp_getcap_cmd(session, transaction, buf, size, + FALSE); + case AVDTP_GET_ALL_CAPABILITIES: + DBG("Received GET_ALL_CAPABILITIES_CMD"); + return avdtp_getcap_cmd(session, transaction, buf, size, TRUE); + case AVDTP_SET_CONFIGURATION: + DBG("Received SET_CONFIGURATION_CMD"); + return avdtp_setconf_cmd(session, transaction, buf, size); + case AVDTP_GET_CONFIGURATION: + DBG("Received GET_CONFIGURATION_CMD"); + return avdtp_getconf_cmd(session, transaction, buf, size); + case AVDTP_RECONFIGURE: + DBG("Received RECONFIGURE_CMD"); + return avdtp_reconf_cmd(session, transaction, buf, size); + case AVDTP_OPEN: + DBG("Received OPEN_CMD"); + return avdtp_open_cmd(session, transaction, buf, size); + case AVDTP_START: + DBG("Received START_CMD"); + return avdtp_start_cmd(session, transaction, buf, size); + case AVDTP_CLOSE: + DBG("Received CLOSE_CMD"); + return avdtp_close_cmd(session, transaction, buf, size); + case AVDTP_SUSPEND: + DBG("Received SUSPEND_CMD"); + return avdtp_suspend_cmd(session, transaction, buf, size); + case AVDTP_ABORT: + DBG("Received ABORT_CMD"); + return avdtp_abort_cmd(session, transaction, buf, size); + case AVDTP_SECURITY_CONTROL: + DBG("Received SECURITY_CONTROL_CMD"); + return avdtp_secctl_cmd(session, transaction, buf, size); + case AVDTP_DELAY_REPORT: + DBG("Received DELAY_REPORT_CMD"); + return avdtp_delayreport_cmd(session, transaction, buf, size); + default: + DBG("Received unknown request id %u", signal_id); + return avdtp_unknown_cmd(session, transaction, signal_id); + } +} + +enum avdtp_parse_result { PARSE_ERROR, PARSE_FRAGMENT, PARSE_SUCCESS }; + +static enum avdtp_parse_result avdtp_parse_data(struct avdtp *session, + void *buf, size_t size) +{ + struct avdtp_common_header *header = buf; + struct avdtp_single_header *single = (void *) session->buf; + struct avdtp_start_header *start = (void *) session->buf; + void *payload; + gsize payload_size; + + switch (header->packet_type) { + case AVDTP_PKT_TYPE_SINGLE: + if (size < sizeof(*single)) { + error("Received too small single packet (%zu bytes)", size); + return PARSE_ERROR; + } + if (session->in.active) { + error("SINGLE: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(*single); + payload_size = size - sizeof(*single); + + session->in.active = TRUE; + session->in.data_size = 0; + session->in.no_of_packets = 1; + session->in.transaction = header->transaction; + session->in.message_type = header->message_type; + session->in.signal_id = single->signal_id; + + break; + case AVDTP_PKT_TYPE_START: + if (size < sizeof(*start)) { + error("Received too small start packet (%zu bytes)", size); + return PARSE_ERROR; + } + if (session->in.active) { + error("START: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + + session->in.active = TRUE; + session->in.data_size = 0; + session->in.transaction = header->transaction; + session->in.message_type = header->message_type; + session->in.no_of_packets = start->no_of_packets; + session->in.signal_id = start->signal_id; + + payload = session->buf + sizeof(*start); + payload_size = size - sizeof(*start); + + break; + case AVDTP_PKT_TYPE_CONTINUE: + if (size < sizeof(struct avdtp_continue_header)) { + error("Received too small continue packet (%zu bytes)", + size); + return PARSE_ERROR; + } + if (!session->in.active) { + error("CONTINUE: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + if (session->in.transaction != header->transaction) { + error("Continue transaction id doesn't match"); + return PARSE_ERROR; + } + if (session->in.no_of_packets <= 1) { + error("Too few continue packets"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(struct avdtp_continue_header); + payload_size = size - sizeof(struct avdtp_continue_header); + + break; + case AVDTP_PKT_TYPE_END: + if (size < sizeof(struct avdtp_continue_header)) { + error("Received too small end packet (%zu bytes)", size); + return PARSE_ERROR; + } + if (!session->in.active) { + error("END: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + if (session->in.transaction != header->transaction) { + error("End transaction id doesn't match"); + return PARSE_ERROR; + } + if (session->in.no_of_packets > 1) { + error("Got an end packet too early"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(struct avdtp_continue_header); + payload_size = size - sizeof(struct avdtp_continue_header); + + break; + default: + error("Invalid AVDTP packet type 0x%02X", header->packet_type); + return PARSE_ERROR; + } + + if (session->in.data_size + payload_size > + sizeof(session->in.buf)) { + error("Not enough incoming buffer space!"); + return PARSE_ERROR; + } + + memcpy(session->in.buf + session->in.data_size, payload, payload_size); + session->in.data_size += payload_size; + + if (session->in.no_of_packets > 1) { + session->in.no_of_packets--; + DBG("Received AVDTP fragment. %d to go", + session->in.no_of_packets); + return PARSE_FRAGMENT; + } + + session->in.active = FALSE; + + return PARSE_SUCCESS; +} + +static gboolean session_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp *session = data; + struct avdtp_common_header *header; + ssize_t size; + int fd; + + DBG(""); + + if (cond & G_IO_NVAL) + return FALSE; + + header = (void *) session->buf; + + if (cond & (G_IO_HUP | G_IO_ERR)) + goto failed; + + fd = g_io_channel_unix_get_fd(chan); + size = read(fd, session->buf, session->imtu); + if (size < 0) { + error("IO Channel read error"); + goto failed; + } + + if ((size_t) size < sizeof(struct avdtp_common_header)) { + error("Received too small packet (%zu bytes)", size); + goto failed; + } + + switch (avdtp_parse_data(session, session->buf, size)) { + case PARSE_ERROR: + goto failed; + case PARSE_FRAGMENT: + return TRUE; + case PARSE_SUCCESS: + break; + } + + if (session->in.message_type == AVDTP_MSG_TYPE_COMMAND) { + if (!avdtp_parse_cmd(session, session->in.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to handle command. Disconnecting"); + goto failed; + } + + if (session->ref == 1 && !session->streams && !session->req) + set_disconnect_timer(session); + + if (session->streams && session->dc_timer) + remove_disconnect_timer(session); + + return TRUE; + } + + if (session->req == NULL) { + error("No pending request, ignoring message"); + return TRUE; + } + + if (header->transaction != session->req->transaction) { + error("Transaction label doesn't match"); + return TRUE; + } + + if (session->in.signal_id != session->req->signal_id) { + error("Reponse signal doesn't match"); + return TRUE; + } + + g_source_remove(session->req->timeout); + session->req->timeout = 0; + + switch (header->message_type) { + case AVDTP_MSG_TYPE_ACCEPT: + if (!avdtp_parse_resp(session, session->req->stream, + session->in.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to parse accept response"); + goto failed; + } + break; + case AVDTP_MSG_TYPE_REJECT: + if (!avdtp_parse_rej(session, session->req->stream, + session->in.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to parse reject response"); + goto failed; + } + break; + case AVDTP_MSG_TYPE_GEN_REJECT: + error("Received a General Reject message"); + break; + default: + error("Unknown message type 0x%02X", header->message_type); + break; + } + + pending_req_free(session->req); + session->req = NULL; + + process_queue(session); + + return TRUE; + +failed: + connection_lost(session, EIO); + + return FALSE; +} + +static struct avdtp *find_session(GSList *list, const bdaddr_t *dst) +{ + GSList *l; + + for (l = list; l != NULL; l = g_slist_next(l)) { + struct avdtp *s = l->data; + + if (bacmp(dst, &s->dst)) + continue; + + return s; + } + + return NULL; +} + +static uint16_t get_version(struct avdtp *session) +{ + struct btd_adapter *adapter; + struct btd_device *device; + const sdp_record_t *rec; + sdp_list_t *protos; + sdp_data_t *proto_desc; + char addr[18]; + uint16_t ver = 0x0100; + + adapter = manager_find_adapter(&session->server->src); + if (!adapter) + goto done; + + ba2str(&session->dst, addr); + device = adapter_find_device(adapter, addr); + if (!device) + goto done; + + rec = btd_device_get_record(device, A2DP_SINK_UUID); + if (!rec) + rec = btd_device_get_record(device, A2DP_SOURCE_UUID); + + if (!rec) + goto done; + + if (sdp_get_access_protos(rec, &protos) < 0) + goto done; + + proto_desc = sdp_get_proto_desc(protos, AVDTP_UUID); + if (proto_desc && proto_desc->dtd == SDP_UINT16) + ver = proto_desc->val.uint16; + + sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(protos, NULL); + +done: + return ver; +} + +static struct avdtp *avdtp_get_internal(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct avdtp_server *server; + struct avdtp *session; + + assert(src != NULL); + assert(dst != NULL); + + server = find_server(servers, src); + if (server == NULL) + return NULL; + + session = find_session(server->sessions, dst); + if (session) { + if (session->pending_auth) + return NULL; + else + return session; + } + + session = g_new0(struct avdtp, 1); + + session->server = server; + bacpy(&session->dst, dst); + session->ref = 1; + /* We don't use avdtp_set_state() here since this isn't a state change + * but just setting of the initial state */ + session->state = AVDTP_SESSION_STATE_DISCONNECTED; + session->auto_dc = TRUE; + + session->version = get_version(session); + + server->sessions = g_slist_append(server->sessions, session); + + return session; +} + +struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst) +{ + struct avdtp *session; + + session = avdtp_get_internal(src, dst); + + if (!session) + return NULL; + + return avdtp_ref(session); +} + +static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct avdtp *session = user_data; + char address[18]; + GError *gerr = NULL; + + if (err) { + error("%s", err->message); + goto failed; + } + + if (!session->io) + session->io = g_io_channel_ref(chan); + + bt_io_get(chan, BT_IO_L2CAP, &gerr, + BT_IO_OPT_OMTU, &session->omtu, + BT_IO_OPT_IMTU, &session->imtu, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + goto failed; + } + + ba2str(&session->dst, address); + DBG("AVDTP: connected %s channel to %s", + session->pending_open ? "transport" : "signaling", + address); + + if (session->state == AVDTP_SESSION_STATE_CONNECTING) { + DBG("AVDTP imtu=%u, omtu=%u", session->imtu, session->omtu); + + session->buf = g_malloc0(session->imtu); + avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTED); + + if (session->io_id) + g_source_remove(session->io_id); + + /* This watch should be low priority since otherwise the + * connect callback might be dispatched before the session + * callback if the kernel wakes us up at the same time for + * them. This could happen if a headset is very quick in + * sending the Start command after connecting the stream + * transport channel. + */ + session->io_id = g_io_add_watch_full(chan, + G_PRIORITY_LOW, + G_IO_IN | G_IO_ERR | G_IO_HUP + | G_IO_NVAL, + (GIOFunc) session_cb, session, + NULL); + + if (session->stream_setup) { + set_disconnect_timer(session); + avdtp_set_auto_disconnect(session, FALSE); + } + } else if (session->pending_open) + handle_transport_connect(session, chan, session->imtu, + session->omtu); + else + goto failed; + + process_queue(session); + + return; + +failed: + if (session->pending_open) { + struct avdtp_stream *stream = session->pending_open; + + handle_transport_connect(session, NULL, 0, 0); + + if (avdtp_abort(session, stream) < 0) + avdtp_sep_set_state(session, stream->lsep, + AVDTP_STATE_IDLE); + } else + connection_lost(session, EIO); +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct avdtp *session = user_data; + GError *err = NULL; + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + connection_lost(session, EACCES); + return; + } + + if (!bt_io_accept(session->io, avdtp_connect_cb, session, NULL, + &err)) { + error("bt_io_accept: %s", err->message); + connection_lost(session, EACCES); + g_error_free(err); + return; + } + + /* This is so that avdtp_connect_cb will know to do the right thing + * with respect to the disconnect timer */ + session->stream_setup = TRUE; +} + +static void avdtp_confirm_cb(GIOChannel *chan, gpointer data) +{ + struct avdtp *session; + struct audio_device *dev; + char address[18]; + bdaddr_t src, dst; + int perr; + GError *err = NULL; + + bt_io_get(chan, BT_IO_L2CAP, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, address, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + goto drop; + } + + DBG("AVDTP: incoming connect from %s", address); + + session = avdtp_get_internal(&src, &dst); + if (!session) + goto drop; + + /* This state (ie, session is already *connecting*) happens when the + * device initiates a connect (really a config'd L2CAP channel) even + * though there is a connect we initiated in progress. In sink.c & + * source.c, this state is referred to as XCASE connect:connect. + * Abort the device's channel in favor of our own. + */ + if (session->state == AVDTP_SESSION_STATE_CONNECTING) { + DBG("connect already in progress (XCASE connect:connect)"); + goto drop; + } + + if (session->pending_open && session->pending_open->open_acp) { + if (!bt_io_accept(chan, avdtp_connect_cb, session, NULL, NULL)) + goto drop; + return; + } + + if (session->io) { + error("Refusing unexpected connect from %s", address); + goto drop; + } + + dev = manager_get_device(&src, &dst, FALSE); + if (!dev) { + dev = manager_get_device(&src, &dst, TRUE); + if (!dev) { + error("Unable to get audio device object for %s", + address); + goto drop; + } + btd_device_add_uuid(dev->btd_dev, ADVANCED_AUDIO_UUID); + } + + session->io = g_io_channel_ref(chan); + avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING); + + session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) session_cb, session); + + perr = audio_device_request_authorization(dev, ADVANCED_AUDIO_UUID, + auth_cb, session); + if (perr < 0) { + avdtp_unref(session); + goto drop; + } + + dev->auto_connect = auto_connect; + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static GIOChannel *l2cap_connect(struct avdtp *session) +{ + GError *err = NULL; + GIOChannel *io; + + io = bt_io_connect(BT_IO_L2CAP, avdtp_connect_cb, session, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &session->server->src, + BT_IO_OPT_DEST_BDADDR, &session->dst, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return NULL; + } + + return io; +} + +static void queue_request(struct avdtp *session, struct pending_req *req, + gboolean priority) +{ + if (priority) + session->prio_queue = g_slist_append(session->prio_queue, req); + else + session->req_queue = g_slist_append(session->req_queue, req); +} + +static uint8_t req_get_seid(struct pending_req *req) +{ + if (req->signal_id == AVDTP_DISCOVER) + return 0; + + return ((struct seid_req *) (req->data))->acp_seid; +} + +static int cancel_request(struct avdtp *session, int err) +{ + struct pending_req *req; + struct seid_req sreq; + struct avdtp_local_sep *lsep; + struct avdtp_stream *stream; + uint8_t seid; + struct avdtp_error averr; + + req = session->req; + session->req = NULL; + + avdtp_error_init(&averr, AVDTP_ERRNO, err); + + seid = req_get_seid(req); + if (seid) + stream = find_stream_by_rseid(session, seid); + else + stream = NULL; + + if (stream) { + stream->abort_int = TRUE; + lsep = stream->lsep; + } else + lsep = NULL; + + switch (req->signal_id) { + case AVDTP_RECONFIGURE: + error("Reconfigure: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->reconfigure) + lsep->cfm->reconfigure(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_OPEN: + error("Open: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->open) + lsep->cfm->open(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_START: + error("Start: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->start) + lsep->cfm->start(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_SUSPEND: + error("Suspend: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->suspend) + lsep->cfm->suspend(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_CLOSE: + error("Close: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->close) { + lsep->cfm->close(session, lsep, stream, &averr, + lsep->user_data); + if (stream) + stream->close_int = FALSE; + } + break; + case AVDTP_SET_CONFIGURATION: + error("SetConfiguration: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->set_configuration) + lsep->cfm->set_configuration(session, lsep, stream, + &averr, lsep->user_data); + goto failed; + case AVDTP_DISCOVER: + error("Discover: %s (%d)", strerror(err), err); + goto failed; + case AVDTP_GET_CAPABILITIES: + error("GetCapabilities: %s (%d)", strerror(err), err); + goto failed; + case AVDTP_ABORT: + error("Abort: %s (%d)", strerror(err), err); + goto failed; + } + + if (!stream) + goto failed; + + memset(&sreq, 0, sizeof(sreq)); + sreq.acp_seid = seid; + + err = send_request(session, TRUE, stream, AVDTP_ABORT, &sreq, + sizeof(sreq)); + if (err < 0) { + error("Unable to send abort request"); + goto failed; + } + + goto done; + +failed: + connection_lost(session, err); +done: + pending_req_free(req); + return err; +} + +static gboolean request_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + + cancel_request(session, ETIMEDOUT); + + return FALSE; +} + +static int send_req(struct avdtp *session, gboolean priority, + struct pending_req *req) +{ + static int transaction = 0; + int err; + + if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) { + session->io = l2cap_connect(session); + if (!session->io) + goto failed; + avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING); + } + + if (session->state < AVDTP_SESSION_STATE_CONNECTED || + session->req != NULL) { + queue_request(session, req, priority); + return 0; + } + + req->transaction = transaction++; + transaction %= 16; + + /* FIXME: Should we retry to send if the buffer + was not totally sent or in case of EINTR? */ + if (!avdtp_send(session, req->transaction, AVDTP_MSG_TYPE_COMMAND, + req->signal_id, req->data, req->data_size)) { + err = -EIO; + goto failed; + } + + session->req = req; + + req->timeout = g_timeout_add_seconds(req->signal_id == AVDTP_ABORT ? + ABORT_TIMEOUT : REQ_TIMEOUT, + request_timeout, + session); + return 0; + +failed: + g_free(req->data); + g_free(req); + return err; +} + +static int send_request(struct avdtp *session, gboolean priority, + struct avdtp_stream *stream, uint8_t signal_id, + void *buffer, size_t size) +{ + struct pending_req *req; + + if (stream && stream->abort_int && signal_id != AVDTP_ABORT) { + DBG("Unable to send requests while aborting"); + return -EINVAL; + } + + req = g_new0(struct pending_req, 1); + req->signal_id = signal_id; + req->data = g_malloc(size); + memcpy(req->data, buffer, size); + req->data_size = size; + req->stream = stream; + + return send_req(session, priority, req); +} + +static gboolean avdtp_discover_resp(struct avdtp *session, + struct discover_resp *resp, int size) +{ + int sep_count, i; + uint8_t getcap_cmd; + + if (session->version >= 0x0103 && session->server->version >= 0x0103) + getcap_cmd = AVDTP_GET_ALL_CAPABILITIES; + else + getcap_cmd = AVDTP_GET_CAPABILITIES; + + sep_count = size / sizeof(struct seid_info); + + for (i = 0; i < sep_count; i++) { + struct avdtp_remote_sep *sep; + struct avdtp_stream *stream; + struct seid_req req; + int ret; + + DBG("seid %d type %d media %d in use %d", + resp->seps[i].seid, resp->seps[i].type, + resp->seps[i].media_type, resp->seps[i].inuse); + + stream = find_stream_by_rseid(session, resp->seps[i].seid); + + sep = find_remote_sep(session->seps, resp->seps[i].seid); + if (!sep) { + if (resp->seps[i].inuse && !stream) + continue; + sep = g_new0(struct avdtp_remote_sep, 1); + session->seps = g_slist_append(session->seps, sep); + } + + sep->stream = stream; + sep->seid = resp->seps[i].seid; + sep->type = resp->seps[i].type; + sep->media_type = resp->seps[i].media_type; + + memset(&req, 0, sizeof(req)); + req.acp_seid = sep->seid; + + ret = send_request(session, TRUE, NULL, getcap_cmd, + &req, sizeof(req)); + if (ret < 0) { + finalize_discovery(session, -ret); + break; + } + } + + return TRUE; +} + +static gboolean avdtp_get_capabilities_resp(struct avdtp *session, + struct getcap_resp *resp, + unsigned int size) +{ + struct avdtp_remote_sep *sep; + uint8_t seid; + + /* Check for minimum required packet size includes: + * 1. getcap resp header + * 2. media transport capability (2 bytes) + * 3. media codec capability type + length (2 bytes) + * 4. the actual media codec elements + * */ + if (size < (sizeof(struct getcap_resp) + 4 + + sizeof(struct avdtp_media_codec_capability))) { + error("Too short getcap resp packet"); + return FALSE; + } + + seid = ((struct seid_req *) session->req->data)->acp_seid; + + sep = find_remote_sep(session->seps, seid); + + DBG("seid %d type %d media %d", sep->seid, + sep->type, sep->media_type); + + if (sep->caps) { + g_slist_foreach(sep->caps, (GFunc) g_free, NULL); + g_slist_free(sep->caps); + sep->caps = NULL; + sep->codec = NULL; + sep->delay_reporting = FALSE; + } + + sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp), + &sep->codec, &sep->delay_reporting); + + return TRUE; +} + +static gboolean avdtp_set_configuration_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_single_header *resp, + int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->set_configuration) + sep->cfm->set_configuration(session, sep, stream, NULL, + sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); + + return TRUE; +} + +static gboolean avdtp_reconfigure_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_single_header *resp, int size) +{ + return TRUE; +} + +static gboolean avdtp_open_resp(struct avdtp *session, struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + stream->io = l2cap_connect(session); + if (!stream->io) { + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); + return FALSE; + } + + session->pending_open = stream; + + return TRUE; +} + +static gboolean avdtp_start_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->start) + sep->cfm->start(session, sep, stream, NULL, sep->user_data); + + /* We might be in STREAMING already if both sides send START_CMD at the + * same time and the one in SNK role doesn't reject it as it should */ + if (sep->state != AVDTP_STATE_STREAMING) + avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING); + + return TRUE; +} + +static gboolean avdtp_close_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING); + + close_stream(stream); + + return TRUE; +} + +static gboolean avdtp_suspend_resp(struct avdtp *session, + struct avdtp_stream *stream, + void *data, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + + if (sep->cfm && sep->cfm->suspend) + sep->cfm->suspend(session, sep, stream, NULL, sep->user_data); + + return TRUE; +} + +static gboolean avdtp_abort_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING); + + if (sep->cfm && sep->cfm->abort) + sep->cfm->abort(session, sep, stream, NULL, sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); + + return TRUE; +} + +static gboolean avdtp_delay_report_resp(struct avdtp *session, + struct avdtp_stream *stream, + void *data, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->delay_report) + sep->cfm->delay_report(session, sep, stream, NULL, sep->user_data); + + return TRUE; +} + +static gboolean avdtp_parse_resp(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size) +{ + struct pending_req *next; + const char *get_all = ""; + + if (session->prio_queue) + next = session->prio_queue->data; + else if (session->req_queue) + next = session->req_queue->data; + else + next = NULL; + + switch (signal_id) { + case AVDTP_DISCOVER: + DBG("DISCOVER request succeeded"); + return avdtp_discover_resp(session, buf, size); + case AVDTP_GET_ALL_CAPABILITIES: + get_all = "ALL_"; + case AVDTP_GET_CAPABILITIES: + DBG("GET_%sCAPABILITIES request succeeded", get_all); + if (!avdtp_get_capabilities_resp(session, buf, size)) + return FALSE; + if (!(next && (next->signal_id == AVDTP_GET_CAPABILITIES || + next->signal_id == AVDTP_GET_ALL_CAPABILITIES))) + finalize_discovery(session, 0); + return TRUE; + } + + /* The remaining commands require an existing stream so bail out + * here if the stream got unexpectedly disconnected */ + if (!stream) { + DBG("AVDTP: stream was closed while waiting for reply"); + return TRUE; + } + + switch (signal_id) { + case AVDTP_SET_CONFIGURATION: + DBG("SET_CONFIGURATION request succeeded"); + return avdtp_set_configuration_resp(session, stream, + buf, size); + case AVDTP_RECONFIGURE: + DBG("RECONFIGURE request succeeded"); + return avdtp_reconfigure_resp(session, stream, buf, size); + case AVDTP_OPEN: + DBG("OPEN request succeeded"); + return avdtp_open_resp(session, stream, buf, size); + case AVDTP_SUSPEND: + DBG("SUSPEND request succeeded"); + return avdtp_suspend_resp(session, stream, buf, size); + case AVDTP_START: + DBG("START request succeeded"); + return avdtp_start_resp(session, stream, buf, size); + case AVDTP_CLOSE: + DBG("CLOSE request succeeded"); + return avdtp_close_resp(session, stream, buf, size); + case AVDTP_ABORT: + DBG("ABORT request succeeded"); + return avdtp_abort_resp(session, stream, buf, size); + case AVDTP_DELAY_REPORT: + DBG("DELAY_REPORT request succeeded"); + return avdtp_delay_report_resp(session, stream, buf, size); + } + + error("Unknown signal id in accept response: %u", signal_id); + return TRUE; +} + +static gboolean seid_rej_to_err(struct seid_rej *rej, unsigned int size, + struct avdtp_error *err) +{ + if (size < sizeof(struct seid_rej)) { + error("Too small packet for seid_rej"); + return FALSE; + } + + avdtp_error_init(err, 0x00, rej->error); + + return TRUE; +} + +static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size, + struct avdtp_error *err) +{ + if (size < sizeof(struct conf_rej)) { + error("Too small packet for conf_rej"); + return FALSE; + } + + avdtp_error_init(err, rej->category, rej->error); + + return TRUE; +} + +static gboolean stream_rej_to_err(struct stream_rej *rej, unsigned int size, + struct avdtp_error *err, + uint8_t *acp_seid) +{ + if (size < sizeof(struct stream_rej)) { + error("Too small packet for stream_rej"); + return FALSE; + } + + avdtp_error_init(err, 0x00, rej->error); + + if (acp_seid) + *acp_seid = rej->acp_seid; + + return TRUE; +} + +static gboolean avdtp_parse_rej(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size) +{ + struct avdtp_error err; + uint8_t acp_seid; + struct avdtp_local_sep *sep = stream ? stream->lsep : NULL; + + switch (signal_id) { + case AVDTP_DISCOVER: + if (!seid_rej_to_err(buf, size, &err)) + return FALSE; + error("DISCOVER request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_GET_CAPABILITIES: + case AVDTP_GET_ALL_CAPABILITIES: + if (!seid_rej_to_err(buf, size, &err)) + return FALSE; + error("GET_CAPABILITIES request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_OPEN: + if (!seid_rej_to_err(buf, size, &err)) + return FALSE; + error("OPEN request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->open) + sep->cfm->open(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_SET_CONFIGURATION: + if (!conf_rej_to_err(buf, size, &err)) + return FALSE; + error("SET_CONFIGURATION request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->set_configuration) + sep->cfm->set_configuration(session, sep, stream, + &err, sep->user_data); + return TRUE; + case AVDTP_RECONFIGURE: + if (!conf_rej_to_err(buf, size, &err)) + return FALSE; + error("RECONFIGURE request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->reconfigure) + sep->cfm->reconfigure(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_START: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("START request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->start) + sep->cfm->start(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_SUSPEND: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("SUSPEND request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->suspend) + sep->cfm->suspend(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_CLOSE: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("CLOSE request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->close) { + sep->cfm->close(session, sep, stream, &err, + sep->user_data); + stream->close_int = FALSE; + } + return TRUE; + case AVDTP_ABORT: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("ABORT request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->abort) + sep->cfm->abort(session, sep, stream, &err, + sep->user_data); + return FALSE; + case AVDTP_DELAY_REPORT: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("DELAY_REPORT request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->delay_report) + sep->cfm->delay_report(session, sep, stream, &err, + sep->user_data); + return TRUE; + default: + error("Unknown reject response signal id: %u", signal_id); + return TRUE; + } +} + +gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct avdtp_server *server; + struct avdtp *session; + + server = find_server(servers, src); + if (!server) + return FALSE; + + session = find_session(server->sessions, dst); + if (!session) + return FALSE; + + if (session->state != AVDTP_SESSION_STATE_DISCONNECTED) + return TRUE; + + return FALSE; +} + +struct avdtp_service_capability *avdtp_stream_get_codec( + struct avdtp_stream *stream) +{ + GSList *l; + + for (l = stream->caps; l; l = l->next) { + struct avdtp_service_capability *cap = l->data; + + if (cap->category == AVDTP_MEDIA_CODEC) + return cap; + } + + return NULL; +} + +gboolean avdtp_stream_has_capability(struct avdtp_stream *stream, + struct avdtp_service_capability *cap) +{ + GSList *l; + struct avdtp_service_capability *stream_cap; + + for (l = stream->caps; l; l = g_slist_next(l)) { + stream_cap = l->data; + + if (stream_cap->category != cap->category || + stream_cap->length != cap->length) + continue; + + if (memcmp(stream_cap->data, cap->data, cap->length) == 0) + return TRUE; + } + + return FALSE; +} + +gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, + GSList *caps) +{ + GSList *l; + + for (l = caps; l; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (!avdtp_stream_has_capability(stream, cap)) + return FALSE; + } + + return TRUE; +} + +struct avdtp_remote_sep *avdtp_stream_get_remote_sep( + struct avdtp_stream *stream) +{ + return avdtp_get_remote_sep(stream->session, stream->rseid); +} + +gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, + uint16_t *imtu, uint16_t *omtu, + GSList **caps) +{ + if (stream->io == NULL) + return FALSE; + + if (sock) + *sock = g_io_channel_unix_get_fd(stream->io); + + if (omtu) + *omtu = stream->omtu; + + if (imtu) + *imtu = stream->imtu; + + if (caps) + *caps = stream->caps; + + return TRUE; +} + +static int process_queue(struct avdtp *session) +{ + GSList **queue, *l; + struct pending_req *req; + + if (session->req) + return 0; + + if (session->prio_queue) + queue = &session->prio_queue; + else + queue = &session->req_queue; + + if (!*queue) + return 0; + + l = *queue; + req = l->data; + + *queue = g_slist_remove(*queue, req); + + return send_req(session, FALSE, req); +} + +struct avdtp_remote_sep *avdtp_get_remote_sep(struct avdtp *session, + uint8_t seid) +{ + GSList *l; + + for (l = session->seps; l; l = l->next) { + struct avdtp_remote_sep *sep = l->data; + + if (sep->seid == seid) + return sep; + } + + return NULL; +} + +uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep) +{ + return sep->seid; +} + +uint8_t avdtp_get_type(struct avdtp_remote_sep *sep) +{ + return sep->type; +} + +struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep) +{ + return sep->codec; +} + +gboolean avdtp_get_delay_reporting(struct avdtp_remote_sep *sep) +{ + return sep->delay_reporting; +} + +struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep) +{ + return sep->stream; +} + +struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + void *data, int length) +{ + struct avdtp_service_capability *cap; + + if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_DELAY_REPORTING) + return NULL; + + cap = g_malloc(sizeof(struct avdtp_service_capability) + length); + cap->category = category; + cap->length = length; + memcpy(cap->data, data, length); + + return cap; +} + +static gboolean process_discover(gpointer data) +{ + struct avdtp *session = data; + + finalize_discovery(session, 0); + + return FALSE; +} + +int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, + void *user_data) +{ + int err; + + if (session->discov_cb) + return -EBUSY; + + if (session->seps) { + session->discov_cb = cb; + session->user_data = user_data; + g_idle_add(process_discover, session); + return 0; + } + + err = send_request(session, FALSE, NULL, AVDTP_DISCOVER, NULL, 0); + if (err == 0) { + session->discov_cb = cb; + session->user_data = user_data; + } + + return err; +} + +gboolean avdtp_stream_remove_cb(struct avdtp *session, + struct avdtp_stream *stream, + unsigned int id) +{ + GSList *l; + struct stream_callback *cb; + + if (!stream) + return FALSE; + + for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) { + struct stream_callback *tmp = l->data; + if (tmp && tmp->id == id) { + cb = tmp; + break; + } + } + + if (!cb) + return FALSE; + + stream->callbacks = g_slist_remove(stream->callbacks, cb); + g_free(cb); + + return TRUE; +} + +unsigned int avdtp_stream_add_cb(struct avdtp *session, + struct avdtp_stream *stream, + avdtp_stream_state_cb cb, void *data) +{ + struct stream_callback *stream_cb; + static unsigned int id = 0; + + stream_cb = g_new(struct stream_callback, 1); + stream_cb->cb = cb; + stream_cb->user_data = data; + stream_cb->id = ++id; + + stream->callbacks = g_slist_append(stream->callbacks, stream_cb);; + + return stream_cb->id; +} + +int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + if (session->state < AVDTP_SESSION_STATE_CONNECTED) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + return send_request(session, FALSE, stream, AVDTP_GET_CONFIGURATION, + &req, sizeof(req)); +} + +static void copy_capabilities(gpointer data, gpointer user_data) +{ + struct avdtp_service_capability *src_cap = data; + struct avdtp_service_capability *dst_cap; + GSList **l = user_data; + + dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data, + src_cap->length); + + *l = g_slist_append(*l, dst_cap); +} + +int avdtp_set_configuration(struct avdtp *session, + struct avdtp_remote_sep *rsep, + struct avdtp_local_sep *lsep, + GSList *caps, + struct avdtp_stream **stream) +{ + struct setconf_req *req; + struct avdtp_stream *new_stream; + unsigned char *ptr; + int err, caps_len; + struct avdtp_service_capability *cap; + GSList *l; + + if (session->state != AVDTP_SESSION_STATE_CONNECTED) + return -ENOTCONN; + + if (!(lsep && rsep)) + return -EINVAL; + + DBG("%p: int_seid=%u, acp_seid=%u", session, + lsep->info.seid, rsep->seid); + + new_stream = g_new0(struct avdtp_stream, 1); + new_stream->session = session; + new_stream->lsep = lsep; + new_stream->rseid = rsep->seid; + + if (rsep->delay_reporting && lsep->delay_reporting) { + struct avdtp_service_capability *delay_reporting; + + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + caps = g_slist_append(caps, delay_reporting); + new_stream->delay_reporting = TRUE; + } + + g_slist_foreach(caps, copy_capabilities, &new_stream->caps); + + /* Calculate total size of request */ + for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) { + cap = l->data; + caps_len += cap->length + 2; + } + + req = g_malloc0(sizeof(struct setconf_req) + caps_len); + + req->int_seid = lsep->info.seid; + req->acp_seid = rsep->seid; + + /* Copy the capabilities into the request */ + for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) { + cap = l->data; + memcpy(ptr, cap, cap->length + 2); + ptr += cap->length + 2; + } + + err = send_request(session, FALSE, new_stream, + AVDTP_SET_CONFIGURATION, req, + sizeof(struct setconf_req) + caps_len); + if (err < 0) + stream_free(new_stream); + else { + lsep->info.inuse = 1; + lsep->stream = new_stream; + rsep->stream = new_stream; + session->streams = g_slist_append(session->streams, new_stream); + if (stream) + *stream = new_stream; + } + + g_free(req); + + return err; +} + +int avdtp_reconfigure(struct avdtp *session, GSList *caps, + struct avdtp_stream *stream) +{ + struct reconf_req *req; + unsigned char *ptr; + int caps_len, err; + GSList *l; + struct avdtp_service_capability *cap; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state != AVDTP_STATE_OPEN) + return -EINVAL; + + /* Calculate total size of request */ + for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) { + cap = l->data; + caps_len += cap->length + 2; + } + + req = g_malloc0(sizeof(struct reconf_req) + caps_len); + + req->acp_seid = stream->rseid; + + /* Copy the capabilities into the request */ + for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) { + cap = l->data; + memcpy(ptr, cap, cap->length + 2); + ptr += cap->length + 2; + } + + err = send_request(session, FALSE, stream, AVDTP_RECONFIGURE, req, + sizeof(*req) + caps_len); + g_free(req); + + return err; +} + +int avdtp_open(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state > AVDTP_STATE_CONFIGURED) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + return send_request(session, FALSE, stream, AVDTP_OPEN, + &req, sizeof(req)); +} + +int avdtp_start(struct avdtp *session, struct avdtp_stream *stream) +{ + struct start_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state != AVDTP_STATE_OPEN) + return -EINVAL; + + if (stream->close_int == TRUE) { + error("avdtp_start: rejecting start since close is initiated"); + return -EINVAL; + } + + memset(&req, 0, sizeof(req)); + req.first_seid.seid = stream->rseid; + + return send_request(session, FALSE, stream, AVDTP_START, + &req, sizeof(req)); +} + +int avdtp_close(struct avdtp *session, struct avdtp_stream *stream, + gboolean immediate) +{ + struct seid_req req; + int ret; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state < AVDTP_STATE_OPEN) + return -EINVAL; + + if (stream->close_int == TRUE) { + error("avdtp_close: rejecting since close is already initiated"); + return -EINVAL; + } + + if (immediate && session->req && stream == session->req->stream) + return avdtp_abort(session, stream); + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + ret = send_request(session, FALSE, stream, AVDTP_CLOSE, + &req, sizeof(req)); + if (ret == 0) + stream->close_int = TRUE; + + return ret; +} + +int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state <= AVDTP_STATE_OPEN || stream->close_int) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + return send_request(session, FALSE, stream, AVDTP_SUSPEND, + &req, sizeof(req)); +} + +int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + int ret; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state == AVDTP_STATE_ABORTING) + return -EINVAL; + + if (session->req && stream == session->req->stream) + return cancel_request(session, ECANCELED); + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + ret = send_request(session, TRUE, stream, AVDTP_ABORT, + &req, sizeof(req)); + if (ret == 0) + stream->abort_int = TRUE; + + return ret; +} + +int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream, + uint16_t delay) +{ + struct delay_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state != AVDTP_STATE_CONFIGURED && + stream->lsep->state != AVDTP_STATE_STREAMING) + return -EINVAL; + + if (!stream->delay_reporting || session->version < 0x0103 || + session->server->version < 0x0103) + return -EINVAL; + + stream->delay = delay; + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + req.delay = htons(delay); + + return send_request(session, TRUE, stream, AVDTP_DELAY_REPORT, + &req, sizeof(req)); +} + +struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type, + uint8_t media_type, + uint8_t codec_type, + gboolean delay_reporting, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm, + void *user_data) +{ + struct avdtp_server *server; + struct avdtp_local_sep *sep; + + server = find_server(servers, src); + if (!server) + return NULL; + + if (g_slist_length(server->seps) > MAX_SEID) + return NULL; + + sep = g_new0(struct avdtp_local_sep, 1); + + sep->state = AVDTP_STATE_IDLE; + sep->info.seid = g_slist_length(server->seps) + 1; + sep->info.type = type; + sep->info.media_type = media_type; + sep->codec = codec_type; + sep->ind = ind; + sep->cfm = cfm; + sep->user_data = user_data; + sep->server = server; + sep->delay_reporting = TRUE; + + DBG("SEP %p registered: type:%d codec:%d seid:%d", sep, + sep->info.type, sep->codec, sep->info.seid); + server->seps = g_slist_append(server->seps, sep); + + return sep; +} + +int avdtp_unregister_sep(struct avdtp_local_sep *sep) +{ + struct avdtp_server *server; + + if (!sep) + return -EINVAL; + + server = sep->server; + server->seps = g_slist_remove(server->seps, sep); + + if (sep->stream) + release_stream(sep->stream, sep->stream->session); + + DBG("SEP %p unregistered: type:%d codec:%d seid:%d", sep, + sep->info.type, sep->codec, sep->info.seid); + + g_free(sep); + + return 0; +} + +static GIOChannel *avdtp_server_socket(const bdaddr_t *src, gboolean master) +{ + GError *err = NULL; + GIOChannel *io; + + io = bt_io_listen(BT_IO_L2CAP, NULL, avdtp_confirm_cb, + NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + } + + return io; +} + +const char *avdtp_strerror(struct avdtp_error *err) +{ + if (err->category == AVDTP_ERRNO) + return strerror(err->err.posix_errno); + + switch(err->err.error_code) { + case AVDTP_BAD_HEADER_FORMAT: + return "Bad Header Format"; + case AVDTP_BAD_LENGTH: + return "Bad Packet Lenght"; + case AVDTP_BAD_ACP_SEID: + return "Bad Acceptor SEID"; + case AVDTP_SEP_IN_USE: + return "Stream End Point in Use"; + case AVDTP_SEP_NOT_IN_USE: + return "Stream End Point Not in Use"; + case AVDTP_BAD_SERV_CATEGORY: + return "Bad Service Category"; + case AVDTP_BAD_PAYLOAD_FORMAT: + return "Bad Payload format"; + case AVDTP_NOT_SUPPORTED_COMMAND: + return "Command Not Supported"; + case AVDTP_INVALID_CAPABILITIES: + return "Invalid Capabilities"; + case AVDTP_BAD_RECOVERY_TYPE: + return "Bad Recovery Type"; + case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT: + return "Bad Media Transport Format"; + case AVDTP_BAD_RECOVERY_FORMAT: + return "Bad Recovery Format"; + case AVDTP_BAD_ROHC_FORMAT: + return "Bad Header Compression Format"; + case AVDTP_BAD_CP_FORMAT: + return "Bad Content Protetion Format"; + case AVDTP_BAD_MULTIPLEXING_FORMAT: + return "Bad Multiplexing Format"; + case AVDTP_UNSUPPORTED_CONFIGURATION: + return "Configuration not supported"; + case AVDTP_BAD_STATE: + return "Bad State"; + default: + return "Unknow error"; + } +} + +avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep) +{ + return sep->state; +} + +void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst) +{ + if (src) + bacpy(src, &session->server->src); + if (dst) + bacpy(dst, &session->dst); +} + +int avdtp_init(const bdaddr_t *src, GKeyFile *config, uint16_t *version) +{ + GError *err = NULL; + gboolean tmp, master = TRUE; + struct avdtp_server *server; + uint16_t ver = 0x0102; + + if (!config) + goto proceed; + + tmp = g_key_file_get_boolean(config, "General", + "Master", &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else + master = tmp; + + tmp = g_key_file_get_boolean(config, "General", "AutoConnect", + &err); + if (err) + g_clear_error(&err); + else + auto_connect = tmp; + + if (g_key_file_get_boolean(config, "A2DP", "DelayReporting", NULL)) + ver = 0x0103; + +proceed: + server = g_new0(struct avdtp_server, 1); + if (!server) + return -ENOMEM; + + server->version = ver; + + if (version) + *version = server->version; + + server->io = avdtp_server_socket(src, master); + if (!server->io) { + g_free(server); + return -1; + } + + bacpy(&server->src, src); + + servers = g_slist_append(servers, server); + + return 0; +} + +void avdtp_exit(const bdaddr_t *src) +{ + struct avdtp_server *server; + GSList *l; + + server = find_server(servers, src); + if (!server) + return; + + for (l = server->sessions; l; l = l->next) { + struct avdtp *session = l->data; + + connection_lost(session, -ECONNABORTED); + } + + servers = g_slist_remove(servers, server); + + g_io_channel_shutdown(server->io, TRUE, NULL); + g_io_channel_unref(server->io); + g_free(server); +} + +gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream) +{ + return g_slist_find(session->streams, stream) ? TRUE : FALSE; +} + +void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc) +{ + session->auto_dc = auto_dc; +} + +gboolean avdtp_stream_setup_active(struct avdtp *session) +{ + return session->stream_setup; +} + +void avdtp_set_device_disconnect(struct avdtp *session, gboolean dev_dc) +{ + session->device_disconnect = dev_dc; +} + +unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data) +{ + struct avdtp_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct avdtp_state_callback, 1); + state_cb->cb = cb; + state_cb->user_data = user_data; + state_cb->id = ++id; + + avdtp_callbacks = g_slist_append(avdtp_callbacks, state_cb);; + + return state_cb->id; +} + +gboolean avdtp_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = avdtp_callbacks; l != NULL; l = l->next) { + struct avdtp_state_callback *cb = l->data; + if (cb && cb->id == id) { + avdtp_callbacks = g_slist_remove(avdtp_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/audio/avdtp.h b/audio/avdtp.h new file mode 100644 index 0000000..5f37dc3 --- /dev/null +++ b/audio/avdtp.h @@ -0,0 +1,316 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +typedef enum { + AVDTP_SESSION_STATE_DISCONNECTED, + AVDTP_SESSION_STATE_CONNECTING, + AVDTP_SESSION_STATE_CONNECTED +} avdtp_session_state_t; + +struct avdtp; +struct avdtp_stream; +struct avdtp_local_sep; +struct avdtp_remote_sep; +struct avdtp_error { + uint8_t category; + union { + uint8_t error_code; + int posix_errno; + } err; +}; + +/* SEP capability categories */ +#define AVDTP_MEDIA_TRANSPORT 0x01 +#define AVDTP_REPORTING 0x02 +#define AVDTP_RECOVERY 0x03 +#define AVDTP_CONTENT_PROTECTION 0x04 +#define AVDTP_HEADER_COMPRESSION 0x05 +#define AVDTP_MULTIPLEXING 0x06 +#define AVDTP_MEDIA_CODEC 0x07 +#define AVDTP_DELAY_REPORTING 0x08 +#define AVDTP_ERRNO 0xff + +/* AVDTP error definitions */ +#define AVDTP_BAD_HEADER_FORMAT 0x01 +#define AVDTP_BAD_LENGTH 0x11 +#define AVDTP_BAD_ACP_SEID 0x12 +#define AVDTP_SEP_IN_USE 0x13 +#define AVDTP_SEP_NOT_IN_USE 0x14 +#define AVDTP_BAD_SERV_CATEGORY 0x17 +#define AVDTP_BAD_PAYLOAD_FORMAT 0x18 +#define AVDTP_NOT_SUPPORTED_COMMAND 0x19 +#define AVDTP_INVALID_CAPABILITIES 0x1A +#define AVDTP_BAD_RECOVERY_TYPE 0x22 +#define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT 0x23 +#define AVDTP_BAD_RECOVERY_FORMAT 0x25 +#define AVDTP_BAD_ROHC_FORMAT 0x26 +#define AVDTP_BAD_CP_FORMAT 0x27 +#define AVDTP_BAD_MULTIPLEXING_FORMAT 0x28 +#define AVDTP_UNSUPPORTED_CONFIGURATION 0x29 +#define AVDTP_BAD_STATE 0x31 + +/* SEP types definitions */ +#define AVDTP_SEP_TYPE_SOURCE 0x00 +#define AVDTP_SEP_TYPE_SINK 0x01 + +/* Media types definitions */ +#define AVDTP_MEDIA_TYPE_AUDIO 0x00 +#define AVDTP_MEDIA_TYPE_VIDEO 0x01 +#define AVDTP_MEDIA_TYPE_MULTIMEDIA 0x02 + +typedef enum { + AVDTP_STATE_IDLE, + AVDTP_STATE_CONFIGURED, + AVDTP_STATE_OPEN, + AVDTP_STATE_STREAMING, + AVDTP_STATE_CLOSING, + AVDTP_STATE_ABORTING, +} avdtp_state_t; + +struct avdtp_service_capability { + uint8_t category; + uint8_t length; + uint8_t data[0]; +} __attribute__ ((packed)); + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avdtp_media_codec_capability { + uint8_t rfa0:4; + uint8_t media_type:4; + uint8_t media_codec_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avdtp_media_codec_capability { + uint8_t media_type:4; + uint8_t rfa0:4; + uint8_t media_codec_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +typedef void (*avdtp_session_state_cb) (struct audio_device *dev, + struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data); + +typedef void (*avdtp_stream_state_cb) (struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data); + +typedef void (*avdtp_set_configuration_cb) (struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_error *err); + +/* Callbacks for when a reply is received to a command that we sent */ +struct avdtp_sep_cfm { + void (*set_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data); + void (*get_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data); + void (*open) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data); + void (*start) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data); + void (*suspend) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*close) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*abort) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*reconfigure) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*delay_report) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); +}; + +/* Callbacks for indicating when we received a new command. The return value + * indicates whether the command should be rejected or accepted */ +struct avdtp_sep_ind { + gboolean (*get_capability) (struct avdtp *session, + struct avdtp_local_sep *sep, + gboolean get_all, + GSList **caps, uint8_t *err, + void *user_data); + gboolean (*set_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + GSList *caps, + avdtp_set_configuration_cb cb, + void *user_data); + gboolean (*get_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t *err, void *user_data); + gboolean (*open) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*start) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*suspend) (struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*close) (struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*abort) (struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*reconfigure) (struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t *err, void *user_data); + gboolean (*delayreport) (struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t rseid, uint16_t delay, + uint8_t *err, void *user_data); +}; + +typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps, + struct avdtp_error *err, void *user_data); + +struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst); + +void avdtp_unref(struct avdtp *session); +struct avdtp *avdtp_ref(struct avdtp *session); + +gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst); + +struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + void *data, int size); + +struct avdtp_remote_sep *avdtp_get_remote_sep(struct avdtp *session, + uint8_t seid); + +uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep); + +uint8_t avdtp_get_type(struct avdtp_remote_sep *sep); + +struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep); + +gboolean avdtp_get_delay_reporting(struct avdtp_remote_sep *sep); + +struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep); + +int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, + void *user_data); + +gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream); + +unsigned int avdtp_stream_add_cb(struct avdtp *session, + struct avdtp_stream *stream, + avdtp_stream_state_cb cb, void *data); +gboolean avdtp_stream_remove_cb(struct avdtp *session, + struct avdtp_stream *stream, + unsigned int id); + +gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, + uint16_t *imtu, uint16_t *omtu, + GSList **caps); +struct avdtp_service_capability *avdtp_stream_get_codec( + struct avdtp_stream *stream); +gboolean avdtp_stream_has_capability(struct avdtp_stream *stream, + struct avdtp_service_capability *cap); +gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, + GSList *caps); +struct avdtp_remote_sep *avdtp_stream_get_remote_sep( + struct avdtp_stream *stream); + +unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data); + +gboolean avdtp_remove_state_cb(unsigned int id); + +int avdtp_set_configuration(struct avdtp *session, + struct avdtp_remote_sep *rsep, + struct avdtp_local_sep *lsep, + GSList *caps, + struct avdtp_stream **stream); + +int avdtp_get_configuration(struct avdtp *session, + struct avdtp_stream *stream); + +int avdtp_open(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_reconfigure(struct avdtp *session, GSList *caps, + struct avdtp_stream *stream); +int avdtp_start(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_close(struct avdtp *session, struct avdtp_stream *stream, + gboolean immediate); +int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream, + uint16_t delay); + +struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type, + uint8_t media_type, + uint8_t codec_type, + gboolean delay_reporting, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm, + void *user_data); + +/* Find a matching pair of local and remote SEP ID's */ +struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session, + struct avdtp_local_sep *lsep); + +int avdtp_unregister_sep(struct avdtp_local_sep *sep); + +avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep); + +void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id); +const char *avdtp_strerror(struct avdtp_error *err); +uint8_t avdtp_error_category(struct avdtp_error *err); +int avdtp_error_error_code(struct avdtp_error *err); +int avdtp_error_posix_errno(struct avdtp_error *err); + +void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst); + +void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc); +gboolean avdtp_stream_setup_active(struct avdtp *session); +void avdtp_set_device_disconnect(struct avdtp *session, gboolean dev_dc); + +int avdtp_init(const bdaddr_t *src, GKeyFile *config, uint16_t *version); +void avdtp_exit(const bdaddr_t *src); diff --git a/audio/bluetooth.conf b/audio/bluetooth.conf new file mode 100644 index 0000000..55b51e4 --- /dev/null +++ b/audio/bluetooth.conf @@ -0,0 +1,36 @@ +# Please note that this ALSA configuration file fragment needs be enabled in +# /etc/asound.conf or a similar configuration file with directives similar to +# the following: +# +#@hooks [ +# { +# func load +# files [ +# "/etc/alsa/bluetooth.conf" +# ] +# errors false +# } +#] + +pcm.rawbluetooth { + @args [ ADDRESS ] + @args.ADDRESS { + type string + } + type bluetooth + device $ADDRESS +} + +pcm.bluetooth { + @args [ ADDRESS ] + @args.ADDRESS { + type string + } + type plug + slave { + pcm { + type bluetooth + device $ADDRESS + } + } +} diff --git a/audio/control.c b/audio/control.c new file mode 100644 index 0000000..95c0406 --- /dev/null +++ b/audio/control.c @@ -0,0 +1,1193 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "log.h" +#include "error.h" +#include "uinput.h" +#include "adapter.h" +#include "../src/device.h" +#include "device.h" +#include "manager.h" +#include "avdtp.h" +#include "control.h" +#include "sdpd.h" +#include "glib-helper.h" +#include "btio.h" +#include "dbus-common.h" + +#define AVCTP_PSM 23 + +/* Message types */ +#define AVCTP_COMMAND 0 +#define AVCTP_RESPONSE 1 + +/* Packet types */ +#define AVCTP_PACKET_SINGLE 0 +#define AVCTP_PACKET_START 1 +#define AVCTP_PACKET_CONTINUE 2 +#define AVCTP_PACKET_END 3 + +/* ctype entries */ +#define CTYPE_CONTROL 0x0 +#define CTYPE_STATUS 0x1 +#define CTYPE_NOT_IMPLEMENTED 0x8 +#define CTYPE_ACCEPTED 0x9 +#define CTYPE_REJECTED 0xA +#define CTYPE_STABLE 0xC + +/* opcodes */ +#define OP_UNITINFO 0x30 +#define OP_SUBUNITINFO 0x31 +#define OP_PASSTHROUGH 0x7c + +/* subunits of interest */ +#define SUBUNIT_PANEL 0x09 + +/* operands in passthrough commands */ +#define VOL_UP_OP 0x41 +#define VOL_DOWN_OP 0x42 +#define MUTE_OP 0x43 +#define PLAY_OP 0x44 +#define STOP_OP 0x45 +#define PAUSE_OP 0x46 +#define RECORD_OP 0x47 +#define REWIND_OP 0x48 +#define FAST_FORWARD_OP 0x49 +#define EJECT_OP 0x4a +#define FORWARD_OP 0x4b +#define BACKWARD_OP 0x4c + +#define QUIRK_NO_RELEASE 1 << 0 + +static DBusConnection *connection = NULL; + +static GSList *servers = NULL; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avctp_header { + uint8_t ipid:1; + uint8_t cr:1; + uint8_t packet_type:2; + uint8_t transaction:4; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_LENGTH 3 + +struct avrcp_header { + uint8_t code:4; + uint8_t _hdr0:4; + uint8_t subunit_id:3; + uint8_t subunit_type:5; + uint8_t opcode; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 3 + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avctp_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t cr:1; + uint8_t ipid:1; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_LENGTH 3 + +struct avrcp_header { + uint8_t _hdr0:4; + uint8_t code:4; + uint8_t subunit_type:5; + uint8_t subunit_id:3; + uint8_t opcode; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 3 + +#else +#error "Unknown byte order" +#endif + +struct avctp_state_callback { + avctp_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct avctp_server { + bdaddr_t src; + GIOChannel *io; + uint32_t tg_record_id; + uint32_t ct_record_id; +}; + +struct control { + struct audio_device *dev; + + avctp_state_t state; + + int uinput; + + GIOChannel *io; + guint io_id; + + uint16_t mtu; + + gboolean target; + + uint8_t key_quirks[256]; +}; + +static struct { + const char *name; + uint8_t avrcp; + uint16_t uinput; +} key_map[] = { + { "PLAY", PLAY_OP, KEY_PLAYCD }, + { "STOP", STOP_OP, KEY_STOPCD }, + { "PAUSE", PAUSE_OP, KEY_PAUSECD }, + { "FORWARD", FORWARD_OP, KEY_NEXTSONG }, + { "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG }, + { "REWIND", REWIND_OP, KEY_REWIND }, + { "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD }, + { NULL } +}; + +static GSList *avctp_callbacks = NULL; + +static void auth_cb(DBusError *derr, void *user_data); + +static sdp_record_t *avrcp_ct_record() +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avctp, avrct; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVCTP_PSM; + uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); + svclass_id = sdp_list_append(0, &avrct); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(0, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = avrcp_ver; + pfseq = sdp_list_append(0, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP CT", 0, 0); + + free(psm); + free(version); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static sdp_record_t *avrcp_tg_record() +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avctp, avrtg; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVCTP_PSM; + uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); + svclass_id = sdp_list_append(0, &avrtg); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(0, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = avrcp_ver; + pfseq = sdp_list_append(0, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP TG", 0, 0); + + free(psm); + free(version); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static int send_event(int fd, uint16_t type, uint16_t code, int32_t value) +{ + struct uinput_event event; + + memset(&event, 0, sizeof(event)); + event.type = type; + event.code = code; + event.value = value; + + return write(fd, &event, sizeof(event)); +} + +static void send_key(int fd, uint16_t key, int pressed) +{ + if (fd < 0) + return; + + send_event(fd, EV_KEY, key, pressed); + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static void handle_panel_passthrough(struct control *control, + const unsigned char *operands, + int operand_count) +{ + const char *status; + int pressed, i; + + if (operand_count == 0) + return; + + if (operands[0] & 0x80) { + status = "released"; + pressed = 0; + } else { + status = "pressed"; + pressed = 1; + } + + for (i = 0; key_map[i].name != NULL; i++) { + uint8_t key_quirks; + + if ((operands[0] & 0x7F) != key_map[i].avrcp) + continue; + + DBG("AVRCP: %s %s", key_map[i].name, status); + + key_quirks = control->key_quirks[key_map[i].avrcp]; + + if (key_quirks & QUIRK_NO_RELEASE) { + if (!pressed) { + DBG("AVRCP: Ignoring release"); + break; + } + + DBG("AVRCP: treating key press as press + release"); + send_key(control->uinput, key_map[i].uinput, 1); + send_key(control->uinput, key_map[i].uinput, 0); + break; + } + + send_key(control->uinput, key_map[i].uinput, pressed); + break; + } + + if (key_map[i].name == NULL) + DBG("AVRCP: unknown button 0x%02X %s", + operands[0] & 0x7F, status); +} + +static void avctp_disconnected(struct audio_device *dev) +{ + struct control *control = dev->control; + + if (!control) + return; + + if (control->io) { + g_io_channel_shutdown(control->io, TRUE, NULL); + g_io_channel_unref(control->io); + control->io = NULL; + } + + if (control->io_id) { + g_source_remove(control->io_id); + control->io_id = 0; + + if (control->state == AVCTP_STATE_CONNECTING) + audio_device_cancel_authorization(dev, auth_cb, + control); + } + + if (control->uinput >= 0) { + char address[18]; + + ba2str(&dev->dst, address); + DBG("AVRCP: closing uinput for %s", address); + + ioctl(control->uinput, UI_DEV_DESTROY); + close(control->uinput); + control->uinput = -1; + } +} + +static void avctp_set_state(struct control *control, avctp_state_t new_state) +{ + GSList *l; + struct audio_device *dev = control->dev; + avctp_state_t old_state = control->state; + gboolean value; + + switch (new_state) { + case AVCTP_STATE_DISCONNECTED: + DBG("AVCTP Disconnected"); + + avctp_disconnected(control->dev); + + if (old_state != AVCTP_STATE_CONNECTED) + break; + + value = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_CONTROL_INTERFACE, + "Disconnected", DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_CONTROL_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); + + if (!audio_device_is_active(dev, NULL)) + audio_device_set_authorized(dev, FALSE); + + break; + case AVCTP_STATE_CONNECTING: + DBG("AVCTP Connecting"); + break; + case AVCTP_STATE_CONNECTED: + DBG("AVCTP Connected"); + value = TRUE; + g_dbus_emit_signal(control->dev->conn, control->dev->path, + AUDIO_CONTROL_INTERFACE, "Connected", + DBUS_TYPE_INVALID); + emit_property_changed(control->dev->conn, control->dev->path, + AUDIO_CONTROL_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); + break; + default: + error("Invalid AVCTP state %d", new_state); + return; + } + + control->state = new_state; + + for (l = avctp_callbacks; l != NULL; l = l->next) { + struct avctp_state_callback *cb = l->data; + cb->cb(control->dev, old_state, new_state, cb->user_data); + } +} + +static gboolean control_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct control *control = data; + unsigned char buf[1024], *operands; + struct avctp_header *avctp; + struct avrcp_header *avrcp; + int ret, packet_size, operand_count, sock; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + goto failed; + + sock = g_io_channel_unix_get_fd(control->io); + + ret = read(sock, buf, sizeof(buf)); + if (ret <= 0) + goto failed; + + DBG("Got %d bytes of data for AVCTP session %p", ret, control); + + if ((unsigned int) ret < sizeof(struct avctp_header)) { + error("Too small AVCTP packet"); + goto failed; + } + + packet_size = ret; + + avctp = (struct avctp_header *) buf; + + DBG("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, " + "PID 0x%04X", + avctp->transaction, avctp->packet_type, + avctp->cr, avctp->ipid, ntohs(avctp->pid)); + + ret -= sizeof(struct avctp_header); + if ((unsigned int) ret < sizeof(struct avrcp_header)) { + error("Too small AVRCP packet"); + goto failed; + } + + avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header)); + + ret -= sizeof(struct avrcp_header); + + operands = buf + sizeof(struct avctp_header) + sizeof(struct avrcp_header); + operand_count = ret; + + DBG("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, " + "opcode 0x%02X, %d operands", + avctp->cr ? "response" : "command", + avrcp->code, avrcp->subunit_type, avrcp->subunit_id, + avrcp->opcode, operand_count); + + if (avctp->packet_type != AVCTP_PACKET_SINGLE) { + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_NOT_IMPLEMENTED; + } else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) { + avctp->ipid = 1; + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_REJECTED; + } else if (avctp->cr == AVCTP_COMMAND && + avrcp->code == CTYPE_CONTROL && + avrcp->subunit_type == SUBUNIT_PANEL && + avrcp->opcode == OP_PASSTHROUGH) { + handle_panel_passthrough(control, operands, operand_count); + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_ACCEPTED; + } else if (avctp->cr == AVCTP_COMMAND && + avrcp->code == CTYPE_STATUS && + (avrcp->opcode == OP_UNITINFO + || avrcp->opcode == OP_SUBUNITINFO)) { + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_STABLE; + /* The first operand should be 0x07 for the UNITINFO response. + * Neither AVRCP (section 22.1, page 117) nor AVC Digital + * Interface Command Set (section 9.2.1, page 45) specs + * explain this value but both use it */ + if (operand_count >= 1 && avrcp->opcode == OP_UNITINFO) + operands[0] = 0x07; + if (operand_count >= 2) + operands[1] = SUBUNIT_PANEL << 3; + DBG("reply to %s", avrcp->opcode == OP_UNITINFO ? + "OP_UNITINFO" : "OP_SUBUNITINFO"); + } else { + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_REJECTED; + } + ret = write(sock, buf, packet_size); + + return TRUE; + +failed: + DBG("AVCTP session %p got disconnected", control); + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + return FALSE; +} + +static int uinput_create(char *name) +{ + struct uinput_dev dev; + int fd, err, i; + + fd = open("/dev/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/input/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/misc/uinput", O_RDWR); + if (fd < 0) { + err = errno; + error("Can't open input device: %s (%d)", + strerror(err), err); + return -err; + } + } + } + + memset(&dev, 0, sizeof(dev)); + if (name) + strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1); + + dev.id.bustype = BUS_BLUETOOTH; + dev.id.vendor = 0x0000; + dev.id.product = 0x0000; + dev.id.version = 0x0000; + + if (write(fd, &dev, sizeof(dev)) < 0) { + err = errno; + error("Can't write device information: %s (%d)", + strerror(err), err); + close(fd); + errno = err; + return -err; + } + + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_EVBIT, EV_REL); + ioctl(fd, UI_SET_EVBIT, EV_REP); + ioctl(fd, UI_SET_EVBIT, EV_SYN); + + for (i = 0; key_map[i].name != NULL; i++) + ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput); + + if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) { + err = errno; + error("Can't create uinput device: %s (%d)", + strerror(err), err); + close(fd); + errno = err; + return -err; + } + + return fd; +} + +static void init_uinput(struct control *control) +{ + struct audio_device *dev = control->dev; + char address[18], name[248 + 1]; + + device_get_name(dev->btd_dev, name, sizeof(name)); + if (g_str_equal(name, "Nokia CK-20W")) { + control->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE; + control->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE; + control->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE; + control->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE; + } + + ba2str(&dev->dst, address); + + control->uinput = uinput_create(address); + if (control->uinput < 0) + error("AVRCP: failed to init uinput for %s", address); + else + DBG("AVRCP: uinput initialized for %s", address); +} + +static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data) +{ + struct control *control = data; + char address[18]; + uint16_t imtu; + GError *gerr = NULL; + + if (err) { + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + error("%s", err->message); + return; + } + + bt_io_get(chan, BT_IO_L2CAP, &gerr, + BT_IO_OPT_DEST, &address, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_INVALID); + if (gerr) { + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + error("%s", gerr->message); + g_error_free(gerr); + return; + } + + DBG("AVCTP: connected to %s", address); + + if (!control->io) + control->io = g_io_channel_ref(chan); + + init_uinput(control); + + avctp_set_state(control, AVCTP_STATE_CONNECTED); + control->mtu = imtu; + control->io_id = g_io_add_watch(chan, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) control_cb, control); +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct control *control = user_data; + GError *err = NULL; + + if (control->io_id) { + g_source_remove(control->io_id); + control->io_id = 0; + } + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + return; + } + + if (!bt_io_accept(control->io, avctp_connect_cb, control, + NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + } +} + +static void avctp_confirm_cb(GIOChannel *chan, gpointer data) +{ + struct control *control = NULL; + struct audio_device *dev; + char address[18]; + bdaddr_t src, dst; + GError *err = NULL; + + bt_io_get(chan, BT_IO_L2CAP, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, address, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + dev = manager_get_device(&src, &dst, TRUE); + if (!dev) { + error("Unable to get audio device object for %s", address); + goto drop; + } + + if (!dev->control) { + btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID); + if (!dev->control) + goto drop; + } + + control = dev->control; + + if (control->io) { + error("Refusing unexpected connect from %s", address); + goto drop; + } + + avctp_set_state(control, AVCTP_STATE_CONNECTING); + control->io = g_io_channel_ref(chan); + + if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID, + auth_cb, dev->control) < 0) + goto drop; + + control->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + control_cb, control); + return; + +drop: + if (!control || !control->io) + g_io_channel_shutdown(chan, TRUE, NULL); + if (control) + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); +} + +static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master) +{ + GError *err = NULL; + GIOChannel *io; + + io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, AVCTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + } + + return io; +} + +gboolean avrcp_connect(struct audio_device *dev) +{ + struct control *control = dev->control; + GError *err = NULL; + GIOChannel *io; + + if (control->state > AVCTP_STATE_DISCONNECTED) + return TRUE; + + avctp_set_state(control, AVCTP_STATE_CONNECTING); + + io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, control, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, AVCTP_PSM, + BT_IO_OPT_INVALID); + if (err) { + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + error("%s", err->message); + g_error_free(err); + return FALSE; + } + + control->io = io; + + return TRUE; +} + +void avrcp_disconnect(struct audio_device *dev) +{ + struct control *control = dev->control; + + if (!(control && control->io)) + return; + + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); +} + +int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config) +{ + sdp_record_t *record; + gboolean tmp, master = TRUE; + GError *err = NULL; + struct avctp_server *server; + + if (config) { + tmp = g_key_file_get_boolean(config, "General", + "Master", &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_error_free(err); + } else + master = tmp; + } + + server = g_new0(struct avctp_server, 1); + if (!server) + return -ENOMEM; + + if (!connection) + connection = dbus_connection_ref(conn); + + record = avrcp_tg_record(); + if (!record) { + error("Unable to allocate new service record"); + g_free(server); + return -1; + } + + if (add_record_to_server(src, record) < 0) { + error("Unable to register AVRCP target service record"); + g_free(server); + sdp_record_free(record); + return -1; + } + server->tg_record_id = record->handle; + + record = avrcp_ct_record(); + if (!record) { + error("Unable to allocate new service record"); + g_free(server); + return -1; + } + + if (add_record_to_server(src, record) < 0) { + error("Unable to register AVRCP controller service record"); + sdp_record_free(record); + g_free(server); + return -1; + } + server->ct_record_id = record->handle; + + server->io = avctp_server_socket(src, master); + if (!server->io) { + remove_record_from_server(server->ct_record_id); + remove_record_from_server(server->tg_record_id); + g_free(server); + return -1; + } + + bacpy(&server->src, src); + + servers = g_slist_append(servers, server); + + return 0; +} + +static struct avctp_server *find_server(GSList *list, const bdaddr_t *src) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct avctp_server *server = l->data; + + if (bacmp(&server->src, src) == 0) + return server; + } + + return NULL; +} + +void avrcp_unregister(const bdaddr_t *src) +{ + struct avctp_server *server; + + server = find_server(servers, src); + if (!server) + return; + + servers = g_slist_remove(servers, server); + + remove_record_from_server(server->ct_record_id); + remove_record_from_server(server->tg_record_id); + + g_io_channel_shutdown(server->io, TRUE, NULL); + g_io_channel_unref(server->io); + g_free(server); + + if (servers) + return; + + dbus_connection_unref(connection); + connection = NULL; +} + +static DBusMessage *control_is_connected(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct control *control = device->control; + DBusMessage *reply; + dbus_bool_t connected; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + connected = (control->state == AVCTP_STATE_CONNECTED); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, + DBUS_TYPE_INVALID); + + return reply; +} + +static int avctp_send_passthrough(struct control *control, uint8_t op) +{ + unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 2]; + struct avctp_header *avctp = (void *) buf; + struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH]; + uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH]; + int sk = g_io_channel_unix_get_fd(control->io); + static uint8_t transaction = 0; + + memset(buf, 0, sizeof(buf)); + + avctp->transaction = transaction++; + avctp->packet_type = AVCTP_PACKET_SINGLE; + avctp->cr = AVCTP_COMMAND; + avctp->pid = htons(AV_REMOTE_SVCLASS_ID); + + avrcp->code = CTYPE_CONTROL; + avrcp->subunit_type = SUBUNIT_PANEL; + avrcp->opcode = OP_PASSTHROUGH; + + operands[0] = op & 0x7f; + operands[1] = 0; + + if (write(sk, buf, sizeof(buf)) < 0) + return -errno; + + /* Button release */ + avctp->transaction = transaction++; + operands[0] |= 0x80; + + if (write(sk, buf, sizeof(buf)) < 0) + return -errno; + + return 0; +} + +static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct control *control = device->control; + DBusMessage *reply; + int err; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (control->state != AVCTP_STATE_CONNECTED) + return btd_error_not_connected(msg); + + if (!control->target) + return btd_error_not_supported(msg); + + err = avctp_send_passthrough(control, VOL_UP_OP); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct control *control = device->control; + DBusMessage *reply; + int err; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (control->state != AVCTP_STATE_CONNECTED) + return btd_error_not_connected(msg); + + if (!control->target) + return btd_error_not_supported(msg); + + err = avctp_send_passthrough(control, VOL_DOWN_OP); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *control_get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + gboolean value; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + /* Connected */ + value = (device->control->state == AVCTP_STATE_CONNECTED); + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable control_methods[] = { + { "IsConnected", "", "b", control_is_connected, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetProperties", "", "a{sv}",control_get_properties }, + { "VolumeUp", "", "", volume_up }, + { "VolumeDown", "", "", volume_down }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable control_signals[] = { + { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED}, + { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED}, + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +static void path_unregister(void *data) +{ + struct audio_device *dev = data; + struct control *control = dev->control; + + DBG("Unregistered interface %s on path %s", + AUDIO_CONTROL_INTERFACE, dev->path); + + if (control->state != AVCTP_STATE_DISCONNECTED) + avctp_disconnected(dev); + + g_free(control); + dev->control = NULL; +} + +void control_unregister(struct audio_device *dev) +{ + g_dbus_unregister_interface(dev->conn, dev->path, + AUDIO_CONTROL_INTERFACE); +} + +void control_update(struct audio_device *dev, uint16_t uuid16) +{ + struct control *control = dev->control; + + if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) + control->target = TRUE; +} + +struct control *control_init(struct audio_device *dev, uint16_t uuid16) +{ + struct control *control; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_CONTROL_INTERFACE, + control_methods, control_signals, NULL, + dev, path_unregister)) + return NULL; + + DBG("Registered interface %s on path %s", + AUDIO_CONTROL_INTERFACE, dev->path); + + control = g_new0(struct control, 1); + control->dev = dev; + control->state = AVCTP_STATE_DISCONNECTED; + control->uinput = -1; + + if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) + control->target = TRUE; + + return control; +} + +gboolean control_is_active(struct audio_device *dev) +{ + struct control *control = dev->control; + + if (control && control->state != AVCTP_STATE_DISCONNECTED) + return TRUE; + + return FALSE; +} + +unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data) +{ + struct avctp_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct avctp_state_callback, 1); + state_cb->cb = cb; + state_cb->user_data = user_data; + state_cb->id = ++id; + + avctp_callbacks = g_slist_append(avctp_callbacks, state_cb); + + return state_cb->id; +} + +gboolean avctp_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = avctp_callbacks; l != NULL; l = l->next) { + struct avctp_state_callback *cb = l->data; + if (cb && cb->id == id) { + avctp_callbacks = g_slist_remove(avctp_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/audio/control.h b/audio/control.h new file mode 100644 index 0000000..49f25c2 --- /dev/null +++ b/audio/control.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define AUDIO_CONTROL_INTERFACE "org.bluez.Control" + +typedef enum { + AVCTP_STATE_DISCONNECTED = 0, + AVCTP_STATE_CONNECTING, + AVCTP_STATE_CONNECTED +} avctp_state_t; + +typedef void (*avctp_state_cb) (struct audio_device *dev, + avctp_state_t old_state, + avctp_state_t new_state, + void *user_data); + +unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data); +gboolean avctp_remove_state_cb(unsigned int id); + +int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config); +void avrcp_unregister(const bdaddr_t *src); + +gboolean avrcp_connect(struct audio_device *dev); +void avrcp_disconnect(struct audio_device *dev); + +struct control *control_init(struct audio_device *dev, uint16_t uuid16); +void control_update(struct audio_device *dev, uint16_t uuid16); +void control_unregister(struct audio_device *dev); +gboolean control_is_active(struct audio_device *dev); diff --git a/audio/ctl_bluetooth.c b/audio/ctl_bluetooth.c new file mode 100644 index 0000000..2981385 --- /dev/null +++ b/audio/ctl_bluetooth.c @@ -0,0 +1,384 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include + +#include "ipc.h" + +#ifdef ENABLE_DEBUG +#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg) +#else +#define DBG(fmt, arg...) +#endif + +#define BLUETOOTH_MINVOL 0 +#define BLUETOOTH_MAXVOL 15 + +struct bluetooth_data { + snd_ctl_ext_t ext; + int sock; +}; + +enum { + BLUETOOTH_PLAYBACK, + BLUETOOTH_CAPTURE, +}; + +static const char *vol_devices[2] = { + [BLUETOOTH_PLAYBACK] = "Playback volume", + [BLUETOOTH_CAPTURE] = "Capture volume", +}; + +static void bluetooth_exit(struct bluetooth_data *data) +{ + if (data == NULL) + return; + + if (data->sock >= 0) + bt_audio_service_close(data->sock); + + free(data); +} + +static void bluetooth_close(snd_ctl_ext_t *ext) +{ + struct bluetooth_data *data = ext->private_data; + + DBG("ext %p", ext); + + bluetooth_exit(data); +} + +static int bluetooth_elem_count(snd_ctl_ext_t *ext) +{ + DBG("ext %p", ext); + + return 2; +} + +static int bluetooth_elem_list(snd_ctl_ext_t *ext, + unsigned int offset, snd_ctl_elem_id_t *id) +{ + DBG("ext %p offset %d id %p", ext, offset, id); + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + + if (offset > 1) + return -EINVAL; + + snd_ctl_elem_id_set_name(id, vol_devices[offset]); + + return 0; +} + +static snd_ctl_ext_key_t bluetooth_find_elem(snd_ctl_ext_t *ext, + const snd_ctl_elem_id_t *id) +{ + const char *name = snd_ctl_elem_id_get_name(id); + int i; + + DBG("ext %p id %p name '%s'", ext, id, name); + + for (i = 0; i < 2; i++) + if (strcmp(name, vol_devices[i]) == 0) + return i; + + return SND_CTL_EXT_KEY_NOT_FOUND; +} + +static int bluetooth_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + int *type, unsigned int *acc, unsigned int *count) +{ + DBG("ext %p key %ld", ext, key); + + *type = SND_CTL_ELEM_TYPE_INTEGER; + *acc = SND_CTL_EXT_ACCESS_READWRITE; + *count = 1; + + return 0; +} + +static int bluetooth_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *imin, long *imax, long *istep) +{ + DBG("ext %p key %ld", ext, key); + + *istep = 1; + *imin = BLUETOOTH_MINVOL; + *imax = BLUETOOTH_MAXVOL; + + return 0; +} + +static int bluetooth_send_ctl(struct bluetooth_data *data, + uint8_t mode, uint8_t key, struct bt_control_rsp *rsp) +{ + int ret; + struct bt_control_req *req = (void *) rsp; + bt_audio_error_t *err = (void *) rsp; + const char *type, *name; + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_CONTROL; + req->h.length = sizeof(*req); + + req->mode = mode; + req->key = key; + + ret = send(data->sock, req, BT_SUGGESTED_BUFFER_SIZE, MSG_NOSIGNAL); + if (ret <= 0) { + SYSERR("Unable to request new volume value to server"); + return -errno; + } + + ret = recv(data->sock, rsp, BT_SUGGESTED_BUFFER_SIZE, 0); + if (ret <= 0) { + SNDERR("Unable to receive new volume value from server"); + return -errno; + } + + if (rsp->h.type == BT_ERROR) { + SNDERR("BT_CONTROL failed : %s (%d)", + strerror(err->posix_errno), + err->posix_errno); + return -err->posix_errno; + } + + type = bt_audio_strtype(rsp->h.type); + if (!type) { + SNDERR("Bogus message type %d " + "received from audio service", + rsp->h.type); + return -EINVAL; + } + + name = bt_audio_strname(rsp->h.name); + if (!name) { + SNDERR("Bogus message name %d " + "received from audio service", + rsp->h.name); + return -EINVAL; + } + + if (rsp->h.name != BT_CONTROL) { + SNDERR("Unexpected message %s received", type); + return -EINVAL; + } + + return 0; +} + +static int bluetooth_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *value) +{ + struct bluetooth_data *data = ext->private_data; + int ret; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_control_rsp *rsp = (void *) buf; + + DBG("ext %p key %ld", ext, key); + + memset(buf, 0, sizeof(buf)); + *value = 0; + + ret = bluetooth_send_ctl(data, key, 0, rsp); + if (ret < 0) + goto done; + + *value = rsp->key; +done: + return ret; +} + +static int bluetooth_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *value) +{ + struct bluetooth_data *data = ext->private_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_control_rsp *rsp = (void *) buf; + long current; + int ret, keyvalue; + + DBG("ext %p key %ld", ext, key); + + ret = bluetooth_read_integer(ext, key, ¤t); + if (ret < 0) + return ret; + + if (*value == current) + return 0; + + while (*value != current) { + keyvalue = (*value > current) ? BT_CONTROL_KEY_VOL_UP : + BT_CONTROL_KEY_VOL_DOWN; + + ret = bluetooth_send_ctl(data, key, keyvalue, rsp); + if (ret < 0) + break; + + current = keyvalue; + } + + return ret; +} + +static int bluetooth_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id, + unsigned int *event_mask) +{ + struct bluetooth_data *data = ext->private_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_control_ind *ind = (void *) buf; + int ret; + const char *type, *name; + + DBG("ext %p id %p", ext, id); + + memset(buf, 0, sizeof(buf)); + + ret = recv(data->sock, ind, BT_SUGGESTED_BUFFER_SIZE, MSG_DONTWAIT); + if (ret < 0) { + SNDERR("Failed while receiving data: %s (%d)", + strerror(errno), errno); + return -errno; + } + + type = bt_audio_strtype(ind->h.type); + if (!type) { + SNDERR("Bogus message type %d " + "received from audio service", + ind->h.type); + return -EAGAIN; + } + + name = bt_audio_strname(ind->h.name); + if (!name) { + SNDERR("Bogus message name %d " + "received from audio service", + ind->h.name); + return -EAGAIN; + } + + if (ind->h.name != BT_CONTROL) { + SNDERR("Unexpected message %s received", name); + return -EAGAIN; + } + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(id, ind->mode == BLUETOOTH_PLAYBACK ? + vol_devices[BLUETOOTH_PLAYBACK] : + vol_devices[BLUETOOTH_CAPTURE]); + *event_mask = SND_CTL_EVENT_MASK_VALUE; + + return 1; +} + +static snd_ctl_ext_callback_t bluetooth_callback = { + .close = bluetooth_close, + .elem_count = bluetooth_elem_count, + .elem_list = bluetooth_elem_list, + .find_elem = bluetooth_find_elem, + .get_attribute = bluetooth_get_attribute, + .get_integer_info = bluetooth_get_integer_info, + .read_integer = bluetooth_read_integer, + .write_integer = bluetooth_write_integer, + .read_event = bluetooth_read_event, +}; + +static int bluetooth_init(struct bluetooth_data *data) +{ + int sk; + + if (!data) + return -EINVAL; + + memset(data, 0, sizeof(struct bluetooth_data)); + + data->sock = -1; + + sk = bt_audio_service_open(); + if (sk < 0) + return -errno; + + data->sock = sk; + + return 0; +} + +SND_CTL_PLUGIN_DEFINE_FUNC(bluetooth); + +SND_CTL_PLUGIN_DEFINE_FUNC(bluetooth) +{ + struct bluetooth_data *data; + int err; + + DBG("Bluetooth Control plugin"); + + data = malloc(sizeof(struct bluetooth_data)); + if (!data) { + err = -ENOMEM; + goto error; + } + + err = bluetooth_init(data); + if (err < 0) + goto error; + + data->ext.version = SND_CTL_EXT_VERSION; + data->ext.card_idx = -1; + + strncpy(data->ext.id, "bluetooth", sizeof(data->ext.id) - 1); + strncpy(data->ext.driver, "Bluetooth-Audio", sizeof(data->ext.driver) - 1); + strncpy(data->ext.name, "Bluetooth Audio", sizeof(data->ext.name) - 1); + strncpy(data->ext.longname, "Bluetooth Audio", sizeof(data->ext.longname) - 1); + strncpy(data->ext.mixername, "Bluetooth Audio", sizeof(data->ext.mixername) - 1); + + data->ext.callback = &bluetooth_callback; + data->ext.poll_fd = data->sock; + data->ext.private_data = data; + + err = snd_ctl_ext_create(&data->ext, name, mode); + if (err < 0) + goto error; + + *handlep = data->ext.handle; + + return 0; + +error: + bluetooth_exit(data); + + return err; +} + +SND_CTL_PLUGIN_SYMBOL(bluetooth); diff --git a/audio/device.c b/audio/device.c new file mode 100644 index 0000000..e38e598 --- /dev/null +++ b/audio/device.c @@ -0,0 +1,862 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "log.h" +#include "textfile.h" +#include "../src/adapter.h" +#include "../src/device.h" + +#include "error.h" +#include "ipc.h" +#include "dbus-common.h" +#include "device.h" +#include "unix.h" +#include "avdtp.h" +#include "control.h" +#include "headset.h" +#include "gateway.h" +#include "sink.h" +#include "source.h" + +#define AUDIO_INTERFACE "org.bluez.Audio" + +#define CONTROL_CONNECT_TIMEOUT 2 +#define AVDTP_CONNECT_TIMEOUT 1 +#define HEADSET_CONNECT_TIMEOUT 1 + +typedef enum { + AUDIO_STATE_DISCONNECTED, + AUDIO_STATE_CONNECTING, + AUDIO_STATE_CONNECTED, +} audio_state_t; + +struct service_auth { + service_auth_cb cb; + void *user_data; +}; + +struct dev_priv { + audio_state_t state; + + headset_state_t hs_state; + sink_state_t sink_state; + avctp_state_t avctp_state; + GSList *auths; + + DBusMessage *conn_req; + DBusMessage *dc_req; + + guint control_timer; + guint avdtp_timer; + guint headset_timer; + guint dc_id; + + gboolean disconnecting; + gboolean authorized; + guint auth_idle_id; +}; + +static unsigned int sink_callback_id = 0; +static unsigned int avctp_callback_id = 0; +static unsigned int avdtp_callback_id = 0; +static unsigned int headset_callback_id = 0; + +static void device_free(struct audio_device *dev) +{ + struct dev_priv *priv = dev->priv; + + if (dev->conn) + dbus_connection_unref(dev->conn); + + btd_device_unref(dev->btd_dev); + + if (priv) { + if (priv->auths) + audio_device_cancel_authorization(dev, NULL, NULL); + if (priv->control_timer) + g_source_remove(priv->control_timer); + if (priv->avdtp_timer) + g_source_remove(priv->avdtp_timer); + if (priv->headset_timer) + g_source_remove(priv->headset_timer); + if (priv->dc_req) + dbus_message_unref(priv->dc_req); + if (priv->conn_req) + dbus_message_unref(priv->conn_req); + if (priv->dc_id) + device_remove_disconnect_watch(dev->btd_dev, + priv->dc_id); + g_free(priv); + } + + g_free(dev->path); + g_free(dev); +} + +static const char *state2str(audio_state_t state) +{ + switch (state) { + case AUDIO_STATE_DISCONNECTED: + return "disconnected"; + case AUDIO_STATE_CONNECTING: + return "connecting"; + case AUDIO_STATE_CONNECTED: + return "connected"; + default: + error("Invalid audio state %d", state); + return NULL; + } +} + +static gboolean control_connect_timeout(gpointer user_data) +{ + struct audio_device *dev = user_data; + + dev->priv->control_timer = 0; + + if (dev->control) + avrcp_connect(dev); + + return FALSE; +} + +static gboolean device_set_control_timer(struct audio_device *dev) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->control) + return FALSE; + + if (priv->control_timer) + return FALSE; + + priv->control_timer = g_timeout_add_seconds(CONTROL_CONNECT_TIMEOUT, + control_connect_timeout, + dev); + + return TRUE; +} + +static void device_remove_control_timer(struct audio_device *dev) +{ + if (dev->priv->control_timer) + g_source_remove(dev->priv->control_timer); + dev->priv->control_timer = 0; +} + +static void device_remove_avdtp_timer(struct audio_device *dev) +{ + if (dev->priv->avdtp_timer) + g_source_remove(dev->priv->avdtp_timer); + dev->priv->avdtp_timer = 0; +} + +static void device_remove_headset_timer(struct audio_device *dev) +{ + if (dev->priv->headset_timer) + g_source_remove(dev->priv->headset_timer); + dev->priv->headset_timer = 0; +} + +static void disconnect_cb(struct btd_device *btd_dev, gboolean removal, + void *user_data) +{ + struct audio_device *dev = user_data; + struct dev_priv *priv = dev->priv; + + if (priv->state == AUDIO_STATE_DISCONNECTED) + return; + + if (priv->disconnecting) + return; + + priv->disconnecting = TRUE; + + device_remove_control_timer(dev); + device_remove_avdtp_timer(dev); + device_remove_headset_timer(dev); + + if (dev->control) + avrcp_disconnect(dev); + + if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED) + sink_shutdown(dev->sink); + else if (priv->hs_state != HEADSET_STATE_DISCONNECTED) + headset_shutdown(dev); + else + priv->disconnecting = FALSE; +} + +static void device_set_state(struct audio_device *dev, audio_state_t new_state) +{ + struct dev_priv *priv = dev->priv; + const char *state_str; + DBusMessage *reply = NULL; + + state_str = state2str(new_state); + if (!state_str) + return; + + if (new_state == AUDIO_STATE_DISCONNECTED) { + priv->authorized = FALSE; + + if (priv->dc_id) { + device_remove_disconnect_watch(dev->btd_dev, + priv->dc_id); + priv->dc_id = 0; + } + } else if (new_state == AUDIO_STATE_CONNECTING) + priv->dc_id = device_add_disconnect_watch(dev->btd_dev, + disconnect_cb, dev, NULL); + + if (dev->priv->state == new_state) { + DBG("state change attempted from %s to %s", + state_str, state_str); + return; + } + + dev->priv->state = new_state; + + if (new_state == AUDIO_STATE_DISCONNECTED) { + if (priv->dc_req) { + reply = dbus_message_new_method_return(priv->dc_req); + dbus_message_unref(priv->dc_req); + priv->dc_req = NULL; + g_dbus_send_message(dev->conn, reply); + } + priv->disconnecting = FALSE; + } + + if (priv->conn_req && new_state != AUDIO_STATE_CONNECTING) { + if (new_state == AUDIO_STATE_CONNECTED) + reply = dbus_message_new_method_return(priv->conn_req); + else + reply = btd_error_failed(priv->conn_req, + "Connect Failed"); + + dbus_message_unref(priv->conn_req); + priv->conn_req = NULL; + g_dbus_send_message(dev->conn, reply); + } + + emit_property_changed(dev->conn, dev->path, + AUDIO_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); +} + +static gboolean avdtp_connect_timeout(gpointer user_data) +{ + struct audio_device *dev = user_data; + + dev->priv->avdtp_timer = 0; + + if (dev->sink) { + struct avdtp *session = avdtp_get(&dev->src, &dev->dst); + + if (!session) + return FALSE; + + sink_setup_stream(dev->sink, session); + avdtp_unref(session); + } + + return FALSE; +} + +static gboolean device_set_avdtp_timer(struct audio_device *dev) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->sink) + return FALSE; + + if (priv->avdtp_timer) + return FALSE; + + priv->avdtp_timer = g_timeout_add_seconds(AVDTP_CONNECT_TIMEOUT, + avdtp_connect_timeout, + dev); + + return TRUE; +} + +static gboolean headset_connect_timeout(gpointer user_data) +{ + struct audio_device *dev = user_data; + struct dev_priv *priv = dev->priv; + + dev->priv->headset_timer = 0; + + if (dev->headset == NULL) + return FALSE; + + if (headset_config_stream(dev, FALSE, NULL, NULL) == 0) { + if (priv->state != AUDIO_STATE_CONNECTED && + (priv->sink_state == SINK_STATE_CONNECTED || + priv->sink_state == SINK_STATE_PLAYING)) + device_set_state(dev, AUDIO_STATE_CONNECTED); + } + + return FALSE; +} + +static gboolean device_set_headset_timer(struct audio_device *dev) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->headset) + return FALSE; + + if (priv->headset_timer) + return FALSE; + + priv->headset_timer = g_timeout_add_seconds(HEADSET_CONNECT_TIMEOUT, + headset_connect_timeout, dev); + + return TRUE; +} + +static void device_avdtp_cb(struct audio_device *dev, struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data) +{ + if (!dev->sink || !dev->control) + return; + + if (new_state == AVDTP_SESSION_STATE_CONNECTED) { + if (avdtp_stream_setup_active(session)) + device_set_control_timer(dev); + else + avrcp_connect(dev); + } +} + +static void device_sink_cb(struct audio_device *dev, + sink_state_t old_state, + sink_state_t new_state, + void *user_data) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->sink) + return; + + priv->sink_state = new_state; + + switch (new_state) { + case SINK_STATE_DISCONNECTED: + if (dev->control) { + device_remove_control_timer(dev); + avrcp_disconnect(dev); + } + if (priv->hs_state != HEADSET_STATE_DISCONNECTED && + (priv->dc_req || priv->disconnecting)) { + headset_shutdown(dev); + break; + } + if (priv->hs_state == HEADSET_STATE_DISCONNECTED) + device_set_state(dev, AUDIO_STATE_DISCONNECTED); + else if (old_state == SINK_STATE_CONNECTING) { + switch (priv->hs_state) { + case HEADSET_STATE_CONNECTED: + case HEADSET_STATE_PLAY_IN_PROGRESS: + case HEADSET_STATE_PLAYING: + device_set_state(dev, AUDIO_STATE_CONNECTED); + default: + break; + } + } + break; + case SINK_STATE_CONNECTING: + device_remove_avdtp_timer(dev); + if (priv->hs_state == HEADSET_STATE_DISCONNECTED) + device_set_state(dev, AUDIO_STATE_CONNECTING); + break; + case SINK_STATE_CONNECTED: + if (old_state == SINK_STATE_PLAYING) + break; + if (dev->auto_connect) { + if (!dev->headset) + device_set_state(dev, AUDIO_STATE_CONNECTED); + else if (priv->hs_state == HEADSET_STATE_DISCONNECTED) + device_set_headset_timer(dev); + else if (priv->hs_state == HEADSET_STATE_CONNECTED || + priv->hs_state == HEADSET_STATE_PLAY_IN_PROGRESS || + priv->hs_state == HEADSET_STATE_PLAYING) + device_set_state(dev, AUDIO_STATE_CONNECTED); + } else if (priv->hs_state == HEADSET_STATE_DISCONNECTED || + priv->hs_state == HEADSET_STATE_CONNECTING) + device_set_state(dev, AUDIO_STATE_CONNECTED); + break; + case SINK_STATE_PLAYING: + break; + } +} + +static void device_avctp_cb(struct audio_device *dev, + avctp_state_t old_state, + avctp_state_t new_state, + void *user_data) +{ + if (!dev->control) + return; + + dev->priv->avctp_state = new_state; + + switch (new_state) { + case AVCTP_STATE_DISCONNECTED: + break; + case AVCTP_STATE_CONNECTING: + device_remove_control_timer(dev); + break; + case AVCTP_STATE_CONNECTED: + break; + } +} + +static void device_headset_cb(struct audio_device *dev, + headset_state_t old_state, + headset_state_t new_state, + void *user_data) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->headset) + return; + + priv->hs_state = new_state; + + switch (new_state) { + case HEADSET_STATE_DISCONNECTED: + device_remove_avdtp_timer(dev); + if (priv->sink_state != SINK_STATE_DISCONNECTED && dev->sink && + (priv->dc_req || priv->disconnecting)) { + sink_shutdown(dev->sink); + break; + } + if (priv->sink_state == SINK_STATE_DISCONNECTED) + device_set_state(dev, AUDIO_STATE_DISCONNECTED); + else if (old_state == HEADSET_STATE_CONNECTING && + (priv->sink_state == SINK_STATE_CONNECTED || + priv->sink_state == SINK_STATE_PLAYING)) + device_set_state(dev, AUDIO_STATE_CONNECTED); + break; + case HEADSET_STATE_CONNECTING: + device_remove_headset_timer(dev); + if (priv->sink_state == SINK_STATE_DISCONNECTED) + device_set_state(dev, AUDIO_STATE_CONNECTING); + break; + case HEADSET_STATE_CONNECTED: + if (old_state == HEADSET_STATE_CONNECTED || + old_state == HEADSET_STATE_PLAY_IN_PROGRESS || + old_state == HEADSET_STATE_PLAYING) + break; + if (dev->auto_connect) { + if (!dev->sink) + device_set_state(dev, AUDIO_STATE_CONNECTED); + else if (priv->sink_state == SINK_STATE_DISCONNECTED) + device_set_avdtp_timer(dev); + else if (priv->sink_state == SINK_STATE_CONNECTED || + priv->sink_state == SINK_STATE_PLAYING) + device_set_state(dev, AUDIO_STATE_CONNECTED); + } else if (priv->sink_state == SINK_STATE_DISCONNECTED || + priv->sink_state == SINK_STATE_CONNECTING) + device_set_state(dev, AUDIO_STATE_CONNECTED); + break; + case HEADSET_STATE_PLAY_IN_PROGRESS: + break; + case HEADSET_STATE_PLAYING: + break; + } +} + +static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *dev = data; + struct dev_priv *priv = dev->priv; + + if (priv->state == AUDIO_STATE_CONNECTING) + return btd_error_in_progress(msg); + else if (priv->state == AUDIO_STATE_CONNECTED) + return btd_error_already_connected(msg); + + dev->auto_connect = TRUE; + + if (dev->headset) + headset_config_stream(dev, FALSE, NULL, NULL); + + if (priv->state != AUDIO_STATE_CONNECTING && dev->sink) { + struct avdtp *session = avdtp_get(&dev->src, &dev->dst); + + if (!session) + return btd_error_failed(msg, + "Failed to get AVDTP session"); + + sink_setup_stream(dev->sink, session); + avdtp_unref(session); + } + + /* The previous calls should cause a call to the state callback to + * indicate AUDIO_STATE_CONNECTING */ + if (priv->state != AUDIO_STATE_CONNECTING) + return btd_error_failed(msg, "Connect Failed"); + + priv->conn_req = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *dev_disconnect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *dev = data; + struct dev_priv *priv = dev->priv; + + if (priv->state == AUDIO_STATE_DISCONNECTED) + return btd_error_not_connected(msg); + + if (priv->dc_req) + return dbus_message_new_method_return(msg); + + priv->dc_req = dbus_message_ref(msg); + + if (dev->control) { + device_remove_control_timer(dev); + avrcp_disconnect(dev); + } + + if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED) + sink_shutdown(dev->sink); + else if (priv->hs_state != HEADSET_STATE_DISCONNECTED) + headset_shutdown(dev); + else { + dbus_message_unref(priv->dc_req); + priv->dc_req = NULL; + return dbus_message_new_method_return(msg); + } + + return NULL; +} + +static DBusMessage *dev_get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + const char *state; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + /* State */ + state = state2str(device->priv->state); + if (state) + dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable dev_methods[] = { + { "Connect", "", "", dev_connect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", dev_disconnect }, + { "GetProperties", "", "a{sv}",dev_get_properties }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable dev_signals[] = { + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +struct audio_device *audio_device_register(DBusConnection *conn, + struct btd_device *device, + const char *path, const bdaddr_t *src, + const bdaddr_t *dst) +{ + struct audio_device *dev; + + if (!conn || !path) + return NULL; + + dev = g_new0(struct audio_device, 1); + + dev->btd_dev = btd_device_ref(device); + dev->path = g_strdup(path); + bacpy(&dev->dst, dst); + bacpy(&dev->src, src); + dev->conn = dbus_connection_ref(conn); + dev->priv = g_new0(struct dev_priv, 1); + dev->priv->state = AUDIO_STATE_DISCONNECTED; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_INTERFACE, + dev_methods, dev_signals, NULL, + dev, NULL)) { + error("Unable to register %s on %s", AUDIO_INTERFACE, + dev->path); + device_free(dev); + return NULL; + } + + DBG("Registered interface %s on path %s", AUDIO_INTERFACE, + dev->path); + + if (sink_callback_id == 0) + sink_callback_id = sink_add_state_cb(device_sink_cb, NULL); + + if (avdtp_callback_id == 0) + avdtp_callback_id = avdtp_add_state_cb(device_avdtp_cb, NULL); + if (avctp_callback_id == 0) + avctp_callback_id = avctp_add_state_cb(device_avctp_cb, NULL); + + if (headset_callback_id == 0) + headset_callback_id = headset_add_state_cb(device_headset_cb, + NULL); + + return dev; +} + +gboolean audio_device_is_active(struct audio_device *dev, + const char *interface) +{ + if (!interface) { + if ((dev->sink || dev->source) && + avdtp_is_connected(&dev->src, &dev->dst)) + return TRUE; + if (dev->headset && headset_is_active(dev)) + return TRUE; + } else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink && + avdtp_is_connected(&dev->src, &dev->dst)) + return TRUE; + else if (!strcmp(interface, AUDIO_SOURCE_INTERFACE) && dev->source && + avdtp_is_connected(&dev->src, &dev->dst)) + return TRUE; + else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset && + headset_is_active(dev)) + return TRUE; + else if (!strcmp(interface, AUDIO_CONTROL_INTERFACE) && dev->control && + control_is_active(dev)) + return TRUE; + else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway && + gateway_is_connected(dev)) + return TRUE; + + return FALSE; +} + +void audio_device_unregister(struct audio_device *device) +{ + unix_device_removed(device); + + if (device->hs_preauth_id) { + g_source_remove(device->hs_preauth_id); + device->hs_preauth_id = 0; + } + + if (device->headset) + headset_unregister(device); + + if (device->gateway) + gateway_unregister(device); + + if (device->sink) + sink_unregister(device); + + if (device->source) + source_unregister(device); + + if (device->control) + control_unregister(device); + + g_dbus_unregister_interface(device->conn, device->path, + AUDIO_INTERFACE); + + device_free(device); +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct audio_device *dev = user_data; + struct dev_priv *priv = dev->priv; + + if (derr == NULL) + priv->authorized = TRUE; + + while (priv->auths) { + struct service_auth *auth = priv->auths->data; + + auth->cb(derr, auth->user_data); + priv->auths = g_slist_remove(priv->auths, auth); + g_free(auth); + } +} + +static gboolean auth_idle_cb(gpointer user_data) +{ + struct audio_device *dev = user_data; + struct dev_priv *priv = dev->priv; + + priv->auth_idle_id = 0; + + auth_cb(NULL, dev); + + return FALSE; +} + +static gboolean audio_device_is_connected(struct audio_device *dev) +{ + if (dev->headset) { + headset_state_t state = headset_get_state(dev); + + if (state == HEADSET_STATE_CONNECTED || + state == HEADSET_STATE_PLAY_IN_PROGRESS || + state == HEADSET_STATE_PLAYING) + return TRUE; + } + + if (dev->sink) { + sink_state_t state = sink_get_state(dev); + + if (state == SINK_STATE_CONNECTED || + state == SINK_STATE_PLAYING) + return TRUE; + } + + if (dev->source) { + source_state_t state = source_get_state(dev); + + if (state == SOURCE_STATE_CONNECTED || + state == SOURCE_STATE_PLAYING) + return TRUE; + } + + return FALSE; +} + +int audio_device_request_authorization(struct audio_device *dev, + const char *uuid, service_auth_cb cb, + void *user_data) +{ + struct dev_priv *priv = dev->priv; + struct service_auth *auth; + int err; + + auth = g_try_new0(struct service_auth, 1); + if (!auth) + return -ENOMEM; + + auth->cb = cb; + auth->user_data = user_data; + + priv->auths = g_slist_append(priv->auths, auth); + if (g_slist_length(priv->auths) > 1) + return 0; + + if (priv->authorized || audio_device_is_connected(dev)) { + priv->auth_idle_id = g_idle_add(auth_idle_cb, dev); + return 0; + } + + err = btd_request_authorization(&dev->src, &dev->dst, uuid, auth_cb, + dev); + if (err < 0) { + priv->auths = g_slist_remove(priv->auths, auth); + g_free(auth); + } + + return err; +} + +int audio_device_cancel_authorization(struct audio_device *dev, + authorization_cb cb, void *user_data) +{ + struct dev_priv *priv = dev->priv; + GSList *l, *next; + + for (l = priv->auths; l != NULL; l = next) { + struct service_auth *auth = l->data; + + next = g_slist_next(l); + + if (cb && auth->cb != cb) + continue; + + if (user_data && auth->user_data != user_data) + continue; + + priv->auths = g_slist_remove(priv->auths, auth); + g_free(auth); + } + + if (g_slist_length(priv->auths) == 0) { + if (priv->auth_idle_id > 0) { + g_source_remove(priv->auth_idle_id); + priv->auth_idle_id = 0; + } else + btd_cancel_authorization(&dev->src, &dev->dst); + } + + return 0; +} + +void audio_device_set_authorized(struct audio_device *dev, gboolean auth) +{ + struct dev_priv *priv = dev->priv; + + priv->authorized = auth; +} diff --git a/audio/device.h b/audio/device.h new file mode 100644 index 0000000..35af788 --- /dev/null +++ b/audio/device.h @@ -0,0 +1,94 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805f9b34fb" + +#define HSP_HS_UUID "00001108-0000-1000-8000-00805f9b34fb" +#define HSP_AG_UUID "00001112-0000-1000-8000-00805f9b34fb" + +#define HFP_HS_UUID "0000111e-0000-1000-8000-00805f9b34fb" +#define HFP_AG_UUID "0000111f-0000-1000-8000-00805f9b34fb" + +#define ADVANCED_AUDIO_UUID "0000110d-0000-1000-8000-00805f9b34fb" + +#define A2DP_SOURCE_UUID "0000110a-0000-1000-8000-00805f9b34fb" +#define A2DP_SINK_UUID "0000110b-0000-1000-8000-00805f9b34fb" + +#define AVRCP_REMOTE_UUID "0000110e-0000-1000-8000-00805f9b34fb" +#define AVRCP_TARGET_UUID "0000110c-0000-1000-8000-00805f9b34fb" + +/* Move these to respective .h files once they exist */ +#define AUDIO_SOURCE_INTERFACE "org.bluez.AudioSource" +#define AUDIO_CONTROL_INTERFACE "org.bluez.Control" + +struct source; +struct control; +struct target; +struct sink; +struct headset; +struct gateway; +struct dev_priv; + +struct audio_device { + struct btd_device *btd_dev; + + DBusConnection *conn; + char *path; + bdaddr_t src; + bdaddr_t dst; + + gboolean auto_connect; + + struct headset *headset; + struct gateway *gateway; + struct sink *sink; + struct source *source; + struct control *control; + struct target *target; + + guint hs_preauth_id; + + struct dev_priv *priv; +}; + +struct audio_device *audio_device_register(DBusConnection *conn, + struct btd_device *device, + const char *path, const bdaddr_t *src, + const bdaddr_t *dst); + +void audio_device_unregister(struct audio_device *device); + +gboolean audio_device_is_active(struct audio_device *dev, + const char *interface); + +typedef void (*authorization_cb) (DBusError *derr, void *user_data); + +int audio_device_cancel_authorization(struct audio_device *dev, + authorization_cb cb, void *user_data); + +int audio_device_request_authorization(struct audio_device *dev, + const char *uuid, authorization_cb cb, + void *user_data); + +void audio_device_set_authorized(struct audio_device *dev, gboolean auth); diff --git a/audio/gateway.c b/audio/gateway.c new file mode 100644 index 0000000..ec0ec5d --- /dev/null +++ b/audio/gateway.c @@ -0,0 +1,711 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2008-2009 Leonid Movshovich + * Copyright (C) 2010 ProFUSION embedded systems + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "glib-helper.h" +#include "device.h" +#include "gateway.h" +#include "log.h" +#include "error.h" +#include "btio.h" +#include "dbus-common.h" + +#ifndef DBUS_TYPE_UNIX_FD +#define DBUS_TYPE_UNIX_FD -1 +#endif + +struct hf_agent { + char *name; /* Bus id */ + char *path; /* D-Bus path */ + guint watch; /* Disconnect watch */ +}; + +struct gateway { + gateway_state_t state; + GIOChannel *rfcomm; + GIOChannel *sco; + gateway_stream_cb_t sco_start_cb; + void *sco_start_cb_data; + struct hf_agent *agent; + DBusMessage *msg; +}; + +int gateway_close(struct audio_device *device); + +static const char *state2str(gateway_state_t state) +{ + switch (state) { + case GATEWAY_STATE_DISCONNECTED: + return "disconnected"; + case GATEWAY_STATE_CONNECTING: + return "connecting"; + case GATEWAY_STATE_CONNECTED: + return "connected"; + case GATEWAY_STATE_PLAYING: + return "playing"; + default: + return ""; + } +} + +static void agent_free(struct hf_agent *agent) +{ + if (!agent) + return; + + g_free(agent->name); + g_free(agent->path); + g_free(agent); +} + +static void change_state(struct audio_device *dev, gateway_state_t new_state) +{ + struct gateway *gw = dev->gateway; + const char *val; + + if (gw->state == new_state) + return; + + val = state2str(new_state); + gw->state = new_state; + + emit_property_changed(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, "State", + DBUS_TYPE_STRING, &val); +} + +static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.HandsfreeAgent", "Release"); + + g_dbus_send_message(dev->conn, msg); +} + +static gboolean agent_sendfd(struct hf_agent *agent, int fd, + DBusPendingCallNotifyFunction notify, void *data) +{ + struct audio_device *dev = data; + DBusMessage *msg; + DBusPendingCall *call; + + msg = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.HandsfreeAgent", "NewConnection"); + + dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE) + return FALSE; + + dbus_pending_call_set_notify(call, notify, dev, NULL); + dbus_pending_call_unref(call); + + return TRUE; +} + +static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond, + struct audio_device *dev) +{ + struct gateway *gw = dev->gateway; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + DBG("sco connection is released"); + g_io_channel_shutdown(gw->sco, TRUE, NULL); + g_io_channel_unref(gw->sco); + gw->sco = NULL; + change_state(dev, GATEWAY_STATE_CONNECTED); + return FALSE; + } + + return TRUE; +} + +static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct audio_device *dev = (struct audio_device *) user_data; + struct gateway *gw = dev->gateway; + + DBG("at the begin of sco_connect_cb() in gateway.c"); + + gw->sco = g_io_channel_ref(chan); + + if (gw->sco_start_cb) + gw->sco_start_cb(dev, err, gw->sco_start_cb_data); + + if (err) { + error("sco_connect_cb(): %s", err->message); + gateway_close(dev); + return; + } + + g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) sco_io_cb, dev); +} + +static void newconnection_reply(DBusPendingCall *call, void *data) +{ + struct audio_device *dev = data; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError derr; + + if (!dev->gateway->rfcomm) { + DBG("RFCOMM disconnected from server before agent reply"); + goto done; + } + + dbus_error_init(&derr); + if (!dbus_set_error_from_message(&derr, reply)) { + DBG("Agent reply: file descriptor passed successfully"); + change_state(dev, GATEWAY_STATE_CONNECTED); + goto done; + } + + DBG("Agent reply: %s", derr.message); + + dbus_error_free(&derr); + gateway_close(dev); + +done: + dbus_message_unref(reply); +} + +static void rfcomm_connect_cb(GIOChannel *chan, GError *err, + gpointer user_data) +{ + struct audio_device *dev = user_data; + struct gateway *gw = dev->gateway; + DBusMessage *reply; + int sk, ret; + + if (err) { + error("connect(): %s", err->message); + if (gw->sco_start_cb) + gw->sco_start_cb(dev, err, gw->sco_start_cb_data); + goto fail; + } + + if (!gw->agent) { + error("Handsfree Agent not registered"); + goto fail; + } + + sk = g_io_channel_unix_get_fd(chan); + + gw->rfcomm = g_io_channel_ref(chan); + + ret = agent_sendfd(gw->agent, sk, newconnection_reply, dev); + + if (!gw->msg) + return; + + if (ret) + reply = dbus_message_new_method_return(gw->msg); + else + reply = btd_error_failed(gw->msg, "Can't pass file descriptor"); + + g_dbus_send_message(dev->conn, reply); + + return; + +fail: + if (gw->msg) { + DBusMessage *reply; + reply = btd_error_failed(gw->msg, "Connect failed"); + g_dbus_send_message(dev->conn, reply); + } + + change_state(dev, GATEWAY_STATE_DISCONNECTED); +} + +static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct audio_device *dev = user_data; + struct gateway *gw = dev->gateway; + int ch; + sdp_list_t *protos, *classes; + uuid_t uuid; + GIOChannel *io; + GError *gerr = NULL; + + if (err < 0) { + error("Unable to get service record: %s (%d)", strerror(-err), + -err); + goto fail; + } + + if (!recs || !recs->data) { + error("No records found"); + err = -EIO; + goto fail; + } + + if (sdp_get_service_classes(recs->data, &classes) < 0) { + error("Unable to get service classes from record"); + err = -EINVAL; + goto fail; + } + + if (sdp_get_access_protos(recs->data, &protos) < 0) { + error("Unable to get access protocols from record"); + err = -ENODATA; + goto fail; + } + + memcpy(&uuid, classes->data, sizeof(uuid)); + sdp_list_free(classes, free); + + if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 || + uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) { + sdp_list_free(protos, NULL); + error("Invalid service record or not HFP"); + err = -EIO; + goto fail; + } + + ch = sdp_get_proto_port(protos, RFCOMM_UUID); + sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(protos, NULL); + if (ch <= 0) { + error("Unable to extract RFCOMM channel from service record"); + err = -EIO; + goto fail; + } + + io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_INVALID); + if (!io) { + error("Unable to connect: %s", gerr->message); + gateway_close(dev); + goto fail; + } + + g_io_channel_unref(io); + + change_state(dev, GATEWAY_STATE_CONNECTING); + return; + +fail: + if (gw->msg) { + DBusMessage *reply = btd_error_failed(gw->msg, + gerr ? gerr->message : strerror(-err)); + g_dbus_send_message(dev->conn, reply); + } + + change_state(dev, GATEWAY_STATE_DISCONNECTED); + + if (!gerr) + g_set_error(&gerr, BT_IO_ERROR, BT_IO_ERROR_FAILED, + "connect: %s (%d)", strerror(-err), -err); + + if (gw->sco_start_cb) + gw->sco_start_cb(dev, gerr, gw->sco_start_cb_data); + + g_error_free(gerr); +} + +static int get_records(struct audio_device *device) +{ + uuid_t uuid; + + sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID); + return bt_search_service(&device->src, &device->dst, &uuid, + get_record_cb, device, NULL); +} + +static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *au_dev = (struct audio_device *) data; + struct gateway *gw = au_dev->gateway; + int err; + + if (!gw->agent) + return btd_error_agent_not_available(msg); + + err = get_records(au_dev); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + gw->msg = dbus_message_ref(msg); + + return NULL; +} + +int gateway_close(struct audio_device *device) +{ + struct gateway *gw = device->gateway; + int sock; + + if (gw->rfcomm) { + sock = g_io_channel_unix_get_fd(gw->rfcomm); + shutdown(sock, SHUT_RDWR); + + g_io_channel_shutdown(gw->rfcomm, TRUE, NULL); + g_io_channel_unref(gw->rfcomm); + gw->rfcomm = NULL; + } + + if (gw->sco) { + g_io_channel_shutdown(gw->sco, TRUE, NULL); + g_io_channel_unref(gw->sco); + gw->sco = NULL; + gw->sco_start_cb = NULL; + gw->sco_start_cb_data = NULL; + } + + change_state(device, GATEWAY_STATE_DISCONNECTED); + + return 0; +} + +static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct gateway *gw = device->gateway; + DBusMessage *reply = NULL; + char gw_addr[18]; + + if (!device->conn) + return NULL; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (!gw->rfcomm) + return btd_error_not_connected(msg); + + gateway_close(device); + ba2str(&device->dst, gw_addr); + DBG("Disconnected from %s, %s", gw_addr, device->path); + + return reply; +} + +static void agent_exited(DBusConnection *conn, void *data) +{ + struct gateway *gateway = data; + struct hf_agent *agent = gateway->agent; + + DBG("Agent %s exited", agent->name); + + agent_free(agent); + gateway->agent = NULL; +} + +static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct gateway *gw = device->gateway; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + const char *value; + + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + value = state2str(gw->state); + dict_append_entry(&dict, "State", + DBUS_TYPE_STRING, &value); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *register_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct gateway *gw = device->gateway; + struct hf_agent *agent; + const char *path, *name; + + if (gw->agent) + return btd_error_already_exists(msg); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + name = dbus_message_get_sender(msg); + agent = g_new0(struct hf_agent, 1); + + agent->name = g_strdup(name); + agent->path = g_strdup(path); + + agent->watch = g_dbus_add_disconnect_watch(conn, name, + agent_exited, gw, NULL); + + gw->agent = agent; + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct gateway *gw = device->gateway; + const char *path; + + if (!gw->agent) + goto done; + + if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0) + return btd_error_not_authorized(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (strcmp(gw->agent->path, path) != 0) + return btd_error_does_not_exist(msg); + + g_dbus_remove_watch(device->conn, gw->agent->watch); + + agent_free(gw->agent); + gw->agent = NULL; + +done: + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable gateway_methods[] = { + { "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC }, + { "GetProperties", "", "a{sv}", ag_get_properties }, + { "RegisterAgent", "o", "", register_agent }, + { "UnregisterAgent", "o", "", unregister_agent }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable gateway_signals[] = { + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +static void path_unregister(void *data) +{ + struct audio_device *dev = data; + + DBG("Unregistered interface %s on path %s", + AUDIO_GATEWAY_INTERFACE, dev->path); + + gateway_close(dev); + + g_free(dev->gateway); + dev->gateway = NULL; +} + +void gateway_unregister(struct audio_device *dev) +{ + if (dev->gateway->agent) + agent_disconnect(dev, dev->gateway->agent); + + g_dbus_unregister_interface(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE); +} + +struct gateway *gateway_init(struct audio_device *dev) +{ + if (DBUS_TYPE_UNIX_FD < 0) + return NULL; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, + gateway_methods, gateway_signals, + NULL, dev, path_unregister)) + return NULL; + + return g_new0(struct gateway, 1); + +} + +gboolean gateway_is_connected(struct audio_device *dev) +{ + return (dev && dev->gateway && + dev->gateway->state == GATEWAY_STATE_CONNECTED); +} + +int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io) +{ + if (!io) + return -EINVAL; + + dev->gateway->rfcomm = g_io_channel_ref(io); + + return 0; +} + +int gateway_connect_sco(struct audio_device *dev, GIOChannel *io) +{ + struct gateway *gw = dev->gateway; + + if (gw->sco) + return -EISCONN; + + gw->sco = g_io_channel_ref(io); + + g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) sco_io_cb, dev); + + change_state(dev, GATEWAY_STATE_PLAYING); + + return 0; +} + +void gateway_start_service(struct audio_device *dev) +{ + struct gateway *gw = dev->gateway; + GError *err = NULL; + + if (gw->rfcomm == NULL) + return; + + if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + } +} + +/* These are functions to be called from unix.c for audio system + * ifaces (alsa, gstreamer, etc.) */ +gboolean gateway_request_stream(struct audio_device *dev, + gateway_stream_cb_t cb, void *user_data) +{ + struct gateway *gw = dev->gateway; + GError *err = NULL; + GIOChannel *io; + + if (!gw->rfcomm) { + gw->sco_start_cb = cb; + gw->sco_start_cb_data = user_data; + get_records(dev); + } else if (!gw->sco) { + gw->sco_start_cb = cb; + gw->sco_start_cb_data = user_data; + io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return FALSE; + } + } else if (cb) + cb(dev, err, user_data); + + return TRUE; +} + +int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t sco_cb, + void *user_data) +{ + struct gateway *gw = dev->gateway; + + if (!gw->rfcomm) { + gw->sco_start_cb = sco_cb; + gw->sco_start_cb_data = user_data; + return get_records(dev); + } + + if (sco_cb) + sco_cb(dev, NULL, user_data); + + return 0; +} + +gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id) +{ + gateway_close(dev); + return TRUE; +} + +int gateway_get_sco_fd(struct audio_device *dev) +{ + struct gateway *gw = dev->gateway; + + if (!gw || !gw->sco) + return -1; + + return g_io_channel_unix_get_fd(gw->sco); +} + +void gateway_suspend_stream(struct audio_device *dev) +{ + struct gateway *gw = dev->gateway; + + if (!gw || !gw->sco) + return; + + g_io_channel_shutdown(gw->sco, TRUE, NULL); + g_io_channel_unref(gw->sco); + gw->sco = NULL; + gw->sco_start_cb = NULL; + gw->sco_start_cb_data = NULL; + change_state(dev, GATEWAY_STATE_CONNECTED); +} diff --git a/audio/gateway.h b/audio/gateway.h new file mode 100644 index 0000000..a45ef82 --- /dev/null +++ b/audio/gateway.h @@ -0,0 +1,51 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway" + +#define DEFAULT_HFP_HS_CHANNEL 7 + +typedef enum { + GATEWAY_STATE_DISCONNECTED, + GATEWAY_STATE_CONNECTING, + GATEWAY_STATE_CONNECTED, + GATEWAY_STATE_PLAYING, +} gateway_state_t; + +typedef void (*gateway_stream_cb_t) (struct audio_device *dev, GError *err, + void *user_data); + +void gateway_unregister(struct audio_device *dev); +struct gateway *gateway_init(struct audio_device *device); +gboolean gateway_is_connected(struct audio_device *dev); +int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io); +int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan); +void gateway_start_service(struct audio_device *device); +gboolean gateway_request_stream(struct audio_device *dev, + gateway_stream_cb_t cb, void *user_data); +int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t cb, + void *user_data); +gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id); +int gateway_get_sco_fd(struct audio_device *dev); +void gateway_suspend_stream(struct audio_device *dev); diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c new file mode 100644 index 0000000..930d14e --- /dev/null +++ b/audio/gsta2dpsink.c @@ -0,0 +1,730 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "gstpragma.h" +#include "gsta2dpsink.h" + +GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug); +#define GST_CAT_DEFAULT gst_a2dp_sink_debug + +#define A2DP_SBC_RTP_PAYLOAD_TYPE 1 +#define TEMPLATE_MAX_BITPOOL_STR "64" + +#define DEFAULT_AUTOCONNECT TRUE + +enum { + PROP_0, + PROP_DEVICE, + PROP_AUTOCONNECT, + PROP_TRANSPORT +}; + +GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN); + +static const GstElementDetails gst_a2dp_sink_details = + GST_ELEMENT_DETAILS("Bluetooth A2DP sink", + "Sink/Audio", + "Plays audio to an A2DP device", + "Marcel Holtmann "); + +static GstStaticPadTemplate gst_a2dp_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, " + "blocks = (int) { 4, 8, 12, 16 }, " + "subbands = (int) { 4, 8 }, " + "allocation = (string) { \"snr\", \"loudness\" }, " + "bitpool = (int) [ 2, " + TEMPLATE_MAX_BITPOOL_STR " ]; " + "audio/mpeg" + )); + +static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event); +static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps); +static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad); +static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self); +static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self); +static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self); + +static void gst_a2dp_sink_finalize(GObject *obj) +{ + GstA2dpSink *self = GST_A2DP_SINK(obj); + + g_mutex_free(self->cb_mutex); + + G_OBJECT_CLASS(parent_class)->finalize(obj); +} + +static GstState gst_a2dp_sink_get_state(GstA2dpSink *self) +{ + GstState current, pending; + + gst_element_get_state(GST_ELEMENT(self), ¤t, &pending, 0); + if (pending == GST_STATE_VOID_PENDING) + return current; + + return pending; +} + +/* + * Helper function to create elements, add to the bin and link it + * to another element. + */ +static GstElement *gst_a2dp_sink_init_element(GstA2dpSink *self, + const gchar *elementname, const gchar *name, + GstElement *link_to) +{ + GstElement *element; + GstState state; + + GST_LOG_OBJECT(self, "Initializing %s", elementname); + + element = gst_element_factory_make(elementname, name); + if (element == NULL) { + GST_DEBUG_OBJECT(self, "Couldn't create %s", elementname); + return NULL; + } + + if (!gst_bin_add(GST_BIN(self), element)) { + GST_DEBUG_OBJECT(self, "failed to add %s to the bin", + elementname); + goto cleanup_and_fail; + } + + state = gst_a2dp_sink_get_state(self); + if (gst_element_set_state(element, state) == + GST_STATE_CHANGE_FAILURE) { + GST_DEBUG_OBJECT(self, "%s failed to go to playing", + elementname); + goto remove_element_and_fail; + } + + if (link_to != NULL) + if (!gst_element_link(link_to, element)) { + GST_DEBUG_OBJECT(self, "couldn't link %s", + elementname); + goto remove_element_and_fail; + } + + return element; + +remove_element_and_fail: + gst_element_set_state(element, GST_STATE_NULL); + gst_bin_remove(GST_BIN(self), element); + return NULL; + +cleanup_and_fail: + g_object_unref(G_OBJECT(element)); + + return NULL; +} + +static void gst_a2dp_sink_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_set_details(element_class, + &gst_a2dp_sink_details); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&gst_a2dp_sink_factory)); +} + +static void gst_a2dp_sink_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstA2dpSink *self = GST_A2DP_SINK(object); + + switch (prop_id) { + case PROP_DEVICE: + if (self->sink != NULL) + gst_avdtp_sink_set_device(self->sink, + g_value_get_string(value)); + + if (self->device != NULL) + g_free(self->device); + self->device = g_value_dup_string(value); + break; + + case PROP_TRANSPORT: + if (self->sink != NULL) + gst_avdtp_sink_set_transport(self->sink, + g_value_get_string(value)); + + if (self->transport != NULL) + g_free(self->transport); + self->transport = g_value_dup_string(value); + break; + + case PROP_AUTOCONNECT: + self->autoconnect = g_value_get_boolean(value); + + if (self->sink != NULL) + g_object_set(G_OBJECT(self->sink), "auto-connect", + self->autoconnect, NULL); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_a2dp_sink_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstA2dpSink *self = GST_A2DP_SINK(object); + gchar *device, *transport; + + switch (prop_id) { + case PROP_DEVICE: + if (self->sink != NULL) { + device = gst_avdtp_sink_get_device(self->sink); + if (device != NULL) + g_value_take_string(value, device); + } + break; + case PROP_AUTOCONNECT: + if (self->sink != NULL) + g_object_get(G_OBJECT(self->sink), "auto-connect", + &self->autoconnect, NULL); + + g_value_set_boolean(value, self->autoconnect); + break; + case PROP_TRANSPORT: + if (self->sink != NULL) { + transport = gst_avdtp_sink_get_transport(self->sink); + if (transport != NULL) + g_value_take_string(value, transport); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static gboolean gst_a2dp_sink_init_ghost_pad(GstA2dpSink *self) +{ + GstPad *capsfilter_pad; + + /* we search for the capsfilter sinkpad */ + capsfilter_pad = gst_element_get_static_pad(self->capsfilter, "sink"); + + /* now we add a ghostpad */ + self->ghostpad = GST_GHOST_PAD(gst_ghost_pad_new("sink", + capsfilter_pad)); + g_object_unref(capsfilter_pad); + + /* the getcaps of our ghostpad must reflect the device caps */ + gst_pad_set_getcaps_function(GST_PAD(self->ghostpad), + gst_a2dp_sink_get_caps); + self->ghostpad_setcapsfunc = GST_PAD_SETCAPSFUNC(self->ghostpad); + gst_pad_set_setcaps_function(GST_PAD(self->ghostpad), + GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps)); + + /* we need to handle events on our own and we also need the eventfunc + * of the ghostpad for forwarding calls */ + self->ghostpad_eventfunc = GST_PAD_EVENTFUNC(GST_PAD(self->ghostpad)); + gst_pad_set_event_function(GST_PAD(self->ghostpad), + gst_a2dp_sink_handle_event); + + if (!gst_element_add_pad(GST_ELEMENT(self), GST_PAD(self->ghostpad))) + GST_ERROR_OBJECT(self, "failed to add ghostpad"); + + return TRUE; +} + +static void gst_a2dp_sink_remove_dynamic_elements(GstA2dpSink *self) +{ + if (self->rtp) { + GST_LOG_OBJECT(self, "removing rtp element from the bin"); + if (!gst_bin_remove(GST_BIN(self), GST_ELEMENT(self->rtp))) + GST_WARNING_OBJECT(self, "failed to remove rtp " + "element from bin"); + else + self->rtp = NULL; + } +} + +static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstA2dpSink *self = GST_A2DP_SINK(element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + self->taglist = gst_tag_list_new(); + + gst_a2dp_sink_init_fakesink(self); + break; + + case GST_STATE_CHANGE_NULL_TO_READY: + self->sink_is_in_bin = FALSE; + self->sink = GST_AVDTP_SINK(gst_element_factory_make( + "avdtpsink", "avdtpsink")); + if (self->sink == NULL) { + GST_WARNING_OBJECT(self, "failed to create avdtpsink"); + return GST_STATE_CHANGE_FAILURE; + } + + if (self->device != NULL) + gst_avdtp_sink_set_device(self->sink, + self->device); + + if (self->transport != NULL) + gst_avdtp_sink_set_transport(self->sink, + self->transport); + + g_object_set(G_OBJECT(self->sink), "auto-connect", + self->autoconnect, NULL); + + ret = gst_element_set_state(GST_ELEMENT(self->sink), + GST_STATE_READY); + break; + default: + break; + } + + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, + transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + if (self->taglist) { + gst_tag_list_free(self->taglist); + self->taglist = NULL; + } + if (self->newseg_event != NULL) { + gst_event_unref(self->newseg_event); + self->newseg_event = NULL; + } + gst_a2dp_sink_remove_fakesink(self); + break; + + case GST_STATE_CHANGE_READY_TO_NULL: + if (self->sink_is_in_bin) { + if (!gst_bin_remove(GST_BIN(self), + GST_ELEMENT(self->sink))) + GST_WARNING_OBJECT(self, "Failed to remove " + "avdtpsink from bin"); + } else if (self->sink != NULL) { + gst_element_set_state(GST_ELEMENT(self->sink), + GST_STATE_NULL); + g_object_unref(G_OBJECT(self->sink)); + } + + self->sink = NULL; + + gst_a2dp_sink_remove_dynamic_elements(self); + break; + default: + break; + } + + return ret; +} + +static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + object_class->set_property = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_set_property); + object_class->get_property = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_get_property); + + object_class->finalize = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_finalize); + + element_class->change_state = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_change_state); + + g_object_class_install_property(object_class, PROP_DEVICE, + g_param_spec_string("device", "Device", + "Bluetooth remote device address", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_AUTOCONNECT, + g_param_spec_boolean("auto-connect", "Auto-connect", + "Automatically attempt to connect to device", + DEFAULT_AUTOCONNECT, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_TRANSPORT, + g_param_spec_string("transport", "Transport", + "Use configured transport", + NULL, G_PARAM_READWRITE)); + + GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0, + "A2DP sink element"); +} + +GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self) +{ + return gst_avdtp_sink_get_device_caps(self->sink); +} + +static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad) +{ + GstCaps *caps; + GstCaps *caps_aux; + GstA2dpSink *self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); + + if (self->sink == NULL) { + GST_DEBUG_OBJECT(self, "a2dpsink isn't initialized " + "returning template caps"); + caps = gst_static_pad_template_get_caps( + &gst_a2dp_sink_factory); + } else { + GST_LOG_OBJECT(self, "Getting device caps"); + caps = gst_a2dp_sink_get_device_caps(self); + if (caps == NULL) + caps = gst_static_pad_template_get_caps( + &gst_a2dp_sink_factory); + } + caps_aux = gst_caps_copy(caps); + g_object_set(self->capsfilter, "caps", caps_aux, NULL); + gst_caps_unref(caps_aux); + return caps; +} + +static gboolean gst_a2dp_sink_init_avdtp_sink(GstA2dpSink *self) +{ + GstElement *sink; + + /* check if we don't need a new sink */ + if (self->sink_is_in_bin) + return TRUE; + + if (self->sink == NULL) + sink = gst_element_factory_make("avdtpsink", "avdtpsink"); + else + sink = GST_ELEMENT(self->sink); + + if (sink == NULL) { + GST_ERROR_OBJECT(self, "Couldn't create avdtpsink"); + return FALSE; + } + + if (!gst_bin_add(GST_BIN(self), sink)) { + GST_ERROR_OBJECT(self, "failed to add avdtpsink " + "to the bin"); + goto cleanup_and_fail; + } + + if (gst_element_set_state(sink, GST_STATE_READY) == + GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT(self, "avdtpsink failed to go to ready"); + goto remove_element_and_fail; + } + + if (!gst_element_link(GST_ELEMENT(self->rtp), sink)) { + GST_ERROR_OBJECT(self, "couldn't link rtpsbcpay " + "to avdtpsink"); + goto remove_element_and_fail; + } + + self->sink = GST_AVDTP_SINK(sink); + self->sink_is_in_bin = TRUE; + g_object_set(G_OBJECT(self->sink), "device", self->device, NULL); + g_object_set(G_OBJECT(self->sink), "transport", self->transport, NULL); + + gst_element_set_state(sink, GST_STATE_PAUSED); + + return TRUE; + +remove_element_and_fail: + gst_element_set_state(sink, GST_STATE_NULL); + gst_bin_remove(GST_BIN(self), sink); + return FALSE; + +cleanup_and_fail: + if (sink != NULL) + g_object_unref(G_OBJECT(sink)); + + return FALSE; +} + +static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self) +{ + GstElement *rtppay; + + /* if we already have a rtp, we don't need a new one */ + if (self->rtp != NULL) + return TRUE; + + rtppay = gst_a2dp_sink_init_element(self, "rtpsbcpay", "rtp", + self->capsfilter); + if (rtppay == NULL) + return FALSE; + + self->rtp = GST_BASE_RTP_PAYLOAD(rtppay); + g_object_set(G_OBJECT(self->rtp), "min-frames", -1, NULL); + + gst_element_set_state(rtppay, GST_STATE_PAUSED); + + return TRUE; +} + +static gboolean gst_a2dp_sink_init_rtp_mpeg_element(GstA2dpSink *self) +{ + GstElement *rtppay; + + /* check if we don't need a new rtp */ + if (self->rtp) + return TRUE; + + GST_LOG_OBJECT(self, "Initializing rtp mpeg element"); + /* if capsfilter is not created then we can't have our rtp element */ + if (self->capsfilter == NULL) + return FALSE; + + rtppay = gst_a2dp_sink_init_element(self, "rtpmpapay", "rtp", + self->capsfilter); + if (rtppay == NULL) + return FALSE; + + self->rtp = GST_BASE_RTP_PAYLOAD(rtppay); + + gst_element_set_state(rtppay, GST_STATE_PAUSED); + + return TRUE; +} + +static gboolean gst_a2dp_sink_init_dynamic_elements(GstA2dpSink *self, + GstCaps *caps) +{ + GstStructure *structure; + GstEvent *event; + GstPad *capsfilterpad; + gboolean crc; + gchar *mode = NULL; + + structure = gst_caps_get_structure(caps, 0); + + /* before everything we need to remove fakesink */ + gst_a2dp_sink_remove_fakesink(self); + + /* first, we need to create our rtp payloader */ + if (gst_structure_has_name(structure, "audio/x-sbc")) { + GST_LOG_OBJECT(self, "sbc media received"); + if (!gst_a2dp_sink_init_rtp_sbc_element(self)) + return FALSE; + } else if (gst_structure_has_name(structure, "audio/mpeg")) { + GST_LOG_OBJECT(self, "mp3 media received"); + if (!gst_a2dp_sink_init_rtp_mpeg_element(self)) + return FALSE; + } else { + GST_ERROR_OBJECT(self, "Unexpected media type"); + return FALSE; + } + + if (!gst_a2dp_sink_init_avdtp_sink(self)) + return FALSE; + + /* check if we should push the taglist FIXME should we push this? + * we can send the tags directly if needed */ + if (self->taglist != NULL && + gst_structure_has_name(structure, "audio/mpeg")) { + + event = gst_event_new_tag(self->taglist); + + /* send directly the crc */ + if (gst_tag_list_get_boolean(self->taglist, "has-crc", &crc)) + gst_avdtp_sink_set_crc(self->sink, crc); + + if (gst_tag_list_get_string(self->taglist, "channel-mode", + &mode)) + gst_avdtp_sink_set_channel_mode(self->sink, mode); + + capsfilterpad = gst_ghost_pad_get_target(self->ghostpad); + gst_pad_send_event(capsfilterpad, event); + self->taglist = NULL; + g_free(mode); + } + + if (!gst_avdtp_sink_set_device_caps(self->sink, caps)) + return FALSE; + + g_object_set(G_OBJECT(self->rtp), "mtu", + gst_avdtp_sink_get_link_mtu(self->sink), NULL); + + /* we forward our new segment here if we have one */ + if (self->newseg_event) { + gst_pad_send_event(GST_BASE_RTP_PAYLOAD_SINKPAD(self->rtp), + self->newseg_event); + self->newseg_event = NULL; + } + + return TRUE; +} + +static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps) +{ + GstA2dpSink *self; + + self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); + GST_INFO_OBJECT(self, "setting caps"); + + /* now we know the caps */ + gst_a2dp_sink_init_dynamic_elements(self, caps); + + return self->ghostpad_setcapsfunc(GST_PAD(self->ghostpad), caps); +} + +/* used for catching newsegment events while we don't have a sink, for + * later forwarding it to the sink */ +static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event) +{ + GstA2dpSink *self; + GstTagList *taglist = NULL; + GstObject *parent; + + self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); + parent = gst_element_get_parent(GST_ELEMENT(self->sink)); + + if (GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT && + parent != GST_OBJECT_CAST(self)) { + if (self->newseg_event != NULL) + gst_event_unref(self->newseg_event); + self->newseg_event = gst_event_ref(event); + + } else if (GST_EVENT_TYPE(event) == GST_EVENT_TAG && + parent != GST_OBJECT_CAST(self)) { + if (self->taglist == NULL) + gst_event_parse_tag(event, &self->taglist); + else { + gst_event_parse_tag(event, &taglist); + gst_tag_list_insert(self->taglist, taglist, + GST_TAG_MERGE_REPLACE); + } + } + + if (parent != NULL) + gst_object_unref(GST_OBJECT(parent)); + + return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event); +} + +static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self) +{ + GstElement *element; + + element = gst_element_factory_make("capsfilter", "filter"); + if (element == NULL) + goto failed; + + if (!gst_bin_add(GST_BIN(self), element)) + goto failed; + + self->capsfilter = element; + return TRUE; + +failed: + GST_ERROR_OBJECT(self, "Failed to initialize caps filter"); + return FALSE; +} + +static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self) +{ + if (self->fakesink != NULL) + return TRUE; + + g_mutex_lock(self->cb_mutex); + self->fakesink = gst_a2dp_sink_init_element(self, "fakesink", + "fakesink", self->capsfilter); + g_mutex_unlock(self->cb_mutex); + + if (!self->fakesink) + return FALSE; + + return TRUE; +} + +static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self) +{ + g_mutex_lock(self->cb_mutex); + + if (self->fakesink != NULL) { + gst_element_set_locked_state(self->fakesink, TRUE); + gst_element_set_state(self->fakesink, GST_STATE_NULL); + + gst_bin_remove(GST_BIN(self), self->fakesink); + self->fakesink = NULL; + } + + g_mutex_unlock(self->cb_mutex); + + return TRUE; +} + +static void gst_a2dp_sink_init(GstA2dpSink *self, + GstA2dpSinkClass *klass) +{ + self->sink = NULL; + self->fakesink = NULL; + self->rtp = NULL; + self->device = NULL; + self->transport = NULL; + self->autoconnect = DEFAULT_AUTOCONNECT; + self->capsfilter = NULL; + self->newseg_event = NULL; + self->taglist = NULL; + self->ghostpad = NULL; + self->sink_is_in_bin = FALSE; + + self->cb_mutex = g_mutex_new(); + + /* we initialize our capsfilter */ + gst_a2dp_sink_init_caps_filter(self); + g_object_set(self->capsfilter, "caps", + gst_static_pad_template_get_caps(&gst_a2dp_sink_factory), + NULL); + + gst_a2dp_sink_init_fakesink(self); + + gst_a2dp_sink_init_ghost_pad(self); + +} + +gboolean gst_a2dp_sink_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "a2dpsink", + GST_RANK_MARGINAL, GST_TYPE_A2DP_SINK); +} + diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h new file mode 100644 index 0000000..5f96a3e --- /dev/null +++ b/audio/gsta2dpsink.h @@ -0,0 +1,85 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __GST_A2DP_SINK_H__ +#define __GST_A2DP_SINK_H__ + +#include +#include +#include "gstavdtpsink.h" + +G_BEGIN_DECLS + +#define GST_TYPE_A2DP_SINK \ + (gst_a2dp_sink_get_type()) +#define GST_A2DP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_A2DP_SINK,GstA2dpSink)) +#define GST_A2DP_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_A2DP_SINK,GstA2dpSinkClass)) +#define GST_IS_A2DP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_A2DP_SINK)) +#define GST_IS_A2DP_SINK_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_A2DP_SINK)) + +typedef struct _GstA2dpSink GstA2dpSink; +typedef struct _GstA2dpSinkClass GstA2dpSinkClass; + +struct _GstA2dpSink { + GstBin bin; + + GstBaseRTPPayload *rtp; + GstAvdtpSink *sink; + GstElement *capsfilter; + GstElement *fakesink; + + gchar *device; + gchar *transport; + gboolean autoconnect; + gboolean sink_is_in_bin; + + GstGhostPad *ghostpad; + GstPadSetCapsFunction ghostpad_setcapsfunc; + GstPadEventFunction ghostpad_eventfunc; + + GstEvent *newseg_event; + /* Store the tags received before the a2dpsender sink is created + * when it is created we forward this to it */ + GstTagList *taglist; + + GMutex *cb_mutex; +}; + +struct _GstA2dpSinkClass { + GstBinClass parent_class; +}; + +GType gst_a2dp_sink_get_type(void); + +gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin); + +GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self); + +G_END_DECLS + +#endif + diff --git a/audio/gstavdtpsink.c b/audio/gstavdtpsink.c new file mode 100644 index 0000000..ce1365a --- /dev/null +++ b/audio/gstavdtpsink.c @@ -0,0 +1,2029 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +#include "ipc.h" +#include "rtp.h" +#include "a2dp-codecs.h" + +#include "gstpragma.h" +#include "gstavdtpsink.h" + +GST_DEBUG_CATEGORY_STATIC(avdtp_sink_debug); +#define GST_CAT_DEFAULT avdtp_sink_debug + +#define BUFFER_SIZE 2048 +#define TEMPLATE_MAX_BITPOOL 64 +#define CRC_PROTECTED 1 +#define CRC_UNPROTECTED 0 + +#define DEFAULT_AUTOCONNECT TRUE + +#define GST_AVDTP_SINK_MUTEX_LOCK(s) G_STMT_START { \ + g_mutex_lock(s->sink_lock); \ + } G_STMT_END + +#define GST_AVDTP_SINK_MUTEX_UNLOCK(s) G_STMT_START { \ + g_mutex_unlock(s->sink_lock); \ + } G_STMT_END + +#ifndef DBUS_TYPE_UNIX_FD +#define DBUS_TYPE_UNIX_FD -1 +#endif + +struct bluetooth_data { + struct bt_get_capabilities_rsp *caps; /* Bluetooth device caps */ + guint link_mtu; + + DBusConnection *conn; + guint8 codec; /* Bluetooth transport configuration */ + gchar *uuid; + guint8 *config; + gint config_size; + + gchar buffer[BUFFER_SIZE]; /* Codec transfer buffer */ +}; + +#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0) +#define IS_MPEG_AUDIO(n) (strcmp((n), "audio/mpeg") == 0) + +enum { + PROP_0, + PROP_DEVICE, + PROP_AUTOCONNECT, + PROP_TRANSPORT +}; + +GST_BOILERPLATE(GstAvdtpSink, gst_avdtp_sink, GstBaseSink, + GST_TYPE_BASE_SINK); + +static const GstElementDetails avdtp_sink_details = + GST_ELEMENT_DETAILS("Bluetooth AVDTP sink", + "Sink/Audio", + "Plays audio to an A2DP device", + "Marcel Holtmann "); + +static GstStaticPadTemplate avdtp_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("application/x-rtp, " + "media = (string) \"audio\"," + "payload = (int) " + GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) { 16000, 32000, " + "44100, 48000 }, " + "encoding-name = (string) \"SBC\"; " + "application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) " + GST_RTP_PAYLOAD_MPA_STRING ", " + "clock-rate = (int) 90000; " + "application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) " + GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) 90000, " + "encoding-name = (string) \"MPA\"" + )); + +static int gst_avdtp_sink_audioservice_send(GstAvdtpSink *self, + const bt_audio_msg_header_t *msg); +static int gst_avdtp_sink_audioservice_expect(GstAvdtpSink *self, + bt_audio_msg_header_t *outmsg, + guint8 expected_name); + + +static void gst_avdtp_sink_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&avdtp_sink_factory)); + + gst_element_class_set_details(element_class, &avdtp_sink_details); +} + +static void gst_avdtp_sink_transport_release(GstAvdtpSink *self) +{ + DBusMessage *msg; + const char *access_type = "w"; + + msg = dbus_message_new_method_call("org.bluez", self->transport, + "org.bluez.MediaTransport", + "Release"); + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type, + DBUS_TYPE_INVALID); + + dbus_connection_send(self->data->conn, msg, NULL); + + dbus_message_unref(msg); +} + +static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + + GST_INFO_OBJECT(self, "stop"); + + if (self->watch_id != 0) { + g_source_remove(self->watch_id); + self->watch_id = 0; + } + + if (self->server) { + bt_audio_service_close(g_io_channel_unix_get_fd(self->server)); + g_io_channel_unref(self->server); + self->server = NULL; + } + + if (self->stream) { + g_io_channel_shutdown(self->stream, TRUE, NULL); + g_io_channel_unref(self->stream); + self->stream = NULL; + } + + if (self->data) { + if (self->transport) + gst_avdtp_sink_transport_release(self); + if (self->data->conn) + dbus_connection_unref(self->data->conn); + g_free(self->data); + self->data = NULL; + } + + if (self->stream_caps) { + gst_caps_unref(self->stream_caps); + self->stream_caps = NULL; + } + + if (self->dev_caps) { + gst_caps_unref(self->dev_caps); + self->dev_caps = NULL; + } + + return TRUE; +} + +static void gst_avdtp_sink_finalize(GObject *object) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(object); + + if (self->data) + gst_avdtp_sink_stop(GST_BASE_SINK(self)); + + if (self->device) + g_free(self->device); + + if (self->transport) + g_free(self->transport); + + g_mutex_free(self->sink_lock); + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void gst_avdtp_sink_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstAvdtpSink *sink = GST_AVDTP_SINK(object); + + switch (prop_id) { + case PROP_DEVICE: + if (sink->device) + g_free(sink->device); + sink->device = g_value_dup_string(value); + break; + + case PROP_AUTOCONNECT: + sink->autoconnect = g_value_get_boolean(value); + break; + + case PROP_TRANSPORT: + if (sink->transport) + g_free(sink->transport); + sink->transport = g_value_dup_string(value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_avdtp_sink_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstAvdtpSink *sink = GST_AVDTP_SINK(object); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_string(value, sink->device); + break; + + case PROP_AUTOCONNECT: + g_value_set_boolean(value, sink->autoconnect); + break; + + case PROP_TRANSPORT: + g_value_set_string(value, sink->transport); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static gint gst_avdtp_sink_bluetooth_recvmsg_fd(GstAvdtpSink *sink) +{ + int err, ret; + + ret = bt_audio_service_get_data_fd( + g_io_channel_unix_get_fd(sink->server)); + + if (ret < 0) { + err = errno; + GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)", + strerror(err), err); + return -err; + } + + sink->stream = g_io_channel_unix_new(ret); + g_io_channel_set_encoding(sink->stream, NULL, NULL); + GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret); + + return 0; +} + +static codec_capabilities_t *gst_avdtp_find_caps(GstAvdtpSink *sink, + uint8_t codec_type) +{ + struct bt_get_capabilities_rsp *rsp = sink->data->caps; + codec_capabilities_t *codec = (void *) rsp->data; + int bytes_left = rsp->h.length - sizeof(*rsp); + + while (bytes_left > 0) { + if ((codec->type == codec_type) && + !(codec->lock & BT_WRITE_LOCK)) + break; + + bytes_left -= codec->length; + codec = (void *) codec + codec->length; + } + + if (bytes_left <= 0) + return NULL; + + return codec; +} + +static gboolean gst_avdtp_sink_init_sbc_pkt_conf(GstAvdtpSink *sink, + GstCaps *caps, + sbc_capabilities_t *pkt) +{ + sbc_capabilities_t *cfg; + const GValue *value = NULL; + const char *pref, *name; + gint rate, subbands, blocks; + GstStructure *structure = gst_caps_get_structure(caps, 0); + + cfg = (void *) gst_avdtp_find_caps(sink, BT_A2DP_SBC_SINK); + name = gst_structure_get_name(structure); + + if (!(IS_SBC(name))) { + GST_ERROR_OBJECT(sink, "Unexpected format %s, " + "was expecting sbc", name); + return FALSE; + } + + value = gst_structure_get_value(structure, "rate"); + rate = g_value_get_int(value); + if (rate == 44100) + cfg->frequency = BT_SBC_SAMPLING_FREQ_44100; + else if (rate == 48000) + cfg->frequency = BT_SBC_SAMPLING_FREQ_48000; + else if (rate == 32000) + cfg->frequency = BT_SBC_SAMPLING_FREQ_32000; + else if (rate == 16000) + cfg->frequency = BT_SBC_SAMPLING_FREQ_16000; + else { + GST_ERROR_OBJECT(sink, "Invalid rate while setting caps"); + return FALSE; + } + + value = gst_structure_get_value(structure, "mode"); + pref = g_value_get_string(value); + if (strcmp(pref, "mono") == 0) + cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + else if (strcmp(pref, "dual") == 0) + cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (strcmp(pref, "stereo") == 0) + cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (strcmp(pref, "joint") == 0) + cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else { + GST_ERROR_OBJECT(sink, "Invalid mode %s", pref); + return FALSE; + } + + value = gst_structure_get_value(structure, "allocation"); + pref = g_value_get_string(value); + if (strcmp(pref, "loudness") == 0) + cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (strcmp(pref, "snr") == 0) + cfg->allocation_method = BT_A2DP_ALLOCATION_SNR; + else { + GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref); + return FALSE; + } + + value = gst_structure_get_value(structure, "subbands"); + subbands = g_value_get_int(value); + if (subbands == 8) + cfg->subbands = BT_A2DP_SUBBANDS_8; + else if (subbands == 4) + cfg->subbands = BT_A2DP_SUBBANDS_4; + else { + GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands); + return FALSE; + } + + value = gst_structure_get_value(structure, "blocks"); + blocks = g_value_get_int(value); + if (blocks == 16) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (blocks == 12) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (blocks == 8) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (blocks == 4) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks); + return FALSE; + } + + value = gst_structure_get_value(structure, "bitpool"); + cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value); + + memcpy(pkt, cfg, sizeof(*pkt)); + + return TRUE; +} + +static gboolean gst_avdtp_sink_conf_recv_stream_fd( + GstAvdtpSink *self) +{ + struct bluetooth_data *data = self->data; + gint ret; + GError *gerr = NULL; + GIOStatus status; + GIOFlags flags; + int fd; + + /* Proceed if stream was already acquired */ + if (self->stream != NULL) + goto proceed; + + ret = gst_avdtp_sink_bluetooth_recvmsg_fd(self); + if (ret < 0) + return FALSE; + + if (!self->stream) { + GST_ERROR_OBJECT(self, "Error while configuring device: " + "could not acquire audio socket"); + return FALSE; + } + +proceed: + /* set stream socket to nonblock */ + GST_LOG_OBJECT(self, "setting stream socket to nonblock"); + flags = g_io_channel_get_flags(self->stream); + flags |= G_IO_FLAG_NONBLOCK; + status = g_io_channel_set_flags(self->stream, flags, &gerr); + if (status != G_IO_STATUS_NORMAL) { + if (gerr) + GST_WARNING_OBJECT(self, "Error while " + "setting server socket to nonblock: " + "%s", gerr->message); + else + GST_WARNING_OBJECT(self, "Error while " + "setting server " + "socket to nonblock"); + } + + fd = g_io_channel_unix_get_fd(self->stream); + + /* It is possible there is some outstanding + data in the pipe - we have to empty it */ + GST_LOG_OBJECT(self, "emptying stream pipe"); + while (1) { + ssize_t bread = read(fd, data->buffer, data->link_mtu); + if (bread <= 0) + break; + } + + /* set stream socket to block */ + GST_LOG_OBJECT(self, "setting stream socket to block"); + flags = g_io_channel_get_flags(self->stream); + flags &= ~G_IO_FLAG_NONBLOCK; + status = g_io_channel_set_flags(self->stream, flags, &gerr); + if (status != G_IO_STATUS_NORMAL) { + if (gerr) + GST_WARNING_OBJECT(self, "Error while " + "setting server socket to block:" + "%s", gerr->message); + else + GST_WARNING_OBJECT(self, "Error while " + "setting server " + "socket to block"); + } + + memset(data->buffer, 0, sizeof(data->buffer)); + + return TRUE; +} + +static gboolean server_callback(GIOChannel *chan, + GIOCondition cond, gpointer data) +{ + if (cond & G_IO_HUP || cond & G_IO_NVAL) + return FALSE; + else if (cond & G_IO_ERR) + GST_WARNING_OBJECT(GST_AVDTP_SINK(data), + "Untreated callback G_IO_ERR"); + + return TRUE; +} + +static GstStructure *gst_avdtp_sink_parse_sbc_caps( + GstAvdtpSink *self, sbc_capabilities_t *sbc) +{ + GstStructure *structure; + GValue *value; + GValue *list; + gboolean mono, stereo; + + if (sbc == NULL) + return NULL; + + structure = gst_structure_empty_new("audio/x-sbc"); + value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING); + + /* mode */ + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { + g_value_set_static_string(value, "mono"); + gst_value_list_prepend_value(list, value); + } + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) { + g_value_set_static_string(value, "stereo"); + gst_value_list_prepend_value(list, value); + } + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) { + g_value_set_static_string(value, "dual"); + gst_value_list_prepend_value(list, value); + } + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) { + g_value_set_static_string(value, "joint"); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "mode", list); + g_free(list); + list = NULL; + } + + /* subbands */ + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + value = g_value_init(value, G_TYPE_INT); + if (sbc->subbands & BT_A2DP_SUBBANDS_4) { + g_value_set_int(value, 4); + gst_value_list_prepend_value(list, value); + } + if (sbc->subbands & BT_A2DP_SUBBANDS_8) { + g_value_set_int(value, 8); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "subbands", list); + g_free(list); + list = NULL; + } + + /* blocks */ + value = g_value_init(value, G_TYPE_INT); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) { + g_value_set_int(value, 16); + gst_value_list_prepend_value(list, value); + } + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) { + g_value_set_int(value, 12); + gst_value_list_prepend_value(list, value); + } + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) { + g_value_set_int(value, 8); + gst_value_list_prepend_value(list, value); + } + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) { + g_value_set_int(value, 4); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "blocks", list); + g_free(list); + list = NULL; + } + + /* allocation */ + g_value_init(value, G_TYPE_STRING); + list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST); + if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) { + g_value_set_static_string(value, "loudness"); + gst_value_list_prepend_value(list, value); + } + if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) { + g_value_set_static_string(value, "snr"); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "allocation", list); + g_free(list); + list = NULL; + } + + /* rate */ + g_value_init(value, G_TYPE_INT); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_48000) { + g_value_set_int(value, 48000); + gst_value_list_prepend_value(list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) { + g_value_set_int(value, 44100); + gst_value_list_prepend_value(list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) { + g_value_set_int(value, 32000); + gst_value_list_prepend_value(list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_16000) { + g_value_set_int(value, 16000); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "rate", list); + g_free(list); + list = NULL; + } + + /* bitpool */ + value = g_value_init(value, GST_TYPE_INT_RANGE); + gst_value_set_int_range(value, + MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL), + MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL)); + gst_structure_set_value(structure, "bitpool", value); + g_value_unset(value); + + /* channels */ + mono = FALSE; + stereo = FALSE; + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + mono = TRUE; + if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) || + (sbc->channel_mode & + BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) || + (sbc->channel_mode & + BT_A2DP_CHANNEL_MODE_JOINT_STEREO)) + stereo = TRUE; + + if (mono && stereo) { + g_value_init(value, GST_TYPE_INT_RANGE); + gst_value_set_int_range(value, 1, 2); + } else { + g_value_init(value, G_TYPE_INT); + if (mono) + g_value_set_int(value, 1); + else if (stereo) + g_value_set_int(value, 2); + else { + GST_ERROR_OBJECT(self, + "Unexpected number of channels"); + g_value_set_int(value, 0); + } + } + + gst_structure_set_value(structure, "channels", value); + g_free(value); + + return structure; +} + +static GstStructure *gst_avdtp_sink_parse_mpeg_caps( + GstAvdtpSink *self, mpeg_capabilities_t *mpeg) +{ + GstStructure *structure; + GValue *value; + GValue *list; + gboolean valid_layer = FALSE; + gboolean mono, stereo; + + if (!mpeg) + return NULL; + + GST_LOG_OBJECT(self, "parsing mpeg caps"); + + structure = gst_structure_empty_new("audio/mpeg"); + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_INT); + + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + g_value_set_int(value, 1); + gst_value_list_prepend_value(list, value); + g_value_set_int(value, 2); + gst_value_list_prepend_value(list, value); + gst_structure_set_value(structure, "mpegversion", list); + g_free(list); + + /* layer */ + GST_LOG_OBJECT(self, "setting mpeg layer"); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (mpeg->layer & BT_MPEG_LAYER_1) { + g_value_set_int(value, 1); + gst_value_list_prepend_value(list, value); + valid_layer = TRUE; + } + if (mpeg->layer & BT_MPEG_LAYER_2) { + g_value_set_int(value, 2); + gst_value_list_prepend_value(list, value); + valid_layer = TRUE; + } + if (mpeg->layer & BT_MPEG_LAYER_3) { + g_value_set_int(value, 3); + gst_value_list_prepend_value(list, value); + valid_layer = TRUE; + } + if (list) { + gst_structure_set_value(structure, "layer", list); + g_free(list); + list = NULL; + } + + if (!valid_layer) { + gst_structure_free(structure); + g_free(value); + return NULL; + } + + /* rate */ + GST_LOG_OBJECT(self, "setting mpeg rate"); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) { + g_value_set_int(value, 48000); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) { + g_value_set_int(value, 44100); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) { + g_value_set_int(value, 32000); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) { + g_value_set_int(value, 24000); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) { + g_value_set_int(value, 22050); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_16000) { + g_value_set_int(value, 16000); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "rate", list); + g_free(list); + list = NULL; + } + + /* channels */ + GST_LOG_OBJECT(self, "setting mpeg channels"); + mono = FALSE; + stereo = FALSE; + if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + mono = TRUE; + if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) || + (mpeg->channel_mode & + BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) || + (mpeg->channel_mode & + BT_A2DP_CHANNEL_MODE_JOINT_STEREO)) + stereo = TRUE; + + if (mono && stereo) { + g_value_init(value, GST_TYPE_INT_RANGE); + gst_value_set_int_range(value, 1, 2); + } else { + g_value_init(value, G_TYPE_INT); + if (mono) + g_value_set_int(value, 1); + else if (stereo) + g_value_set_int(value, 2); + else { + GST_ERROR_OBJECT(self, + "Unexpected number of channels"); + g_value_set_int(value, 0); + } + } + gst_structure_set_value(structure, "channels", value); + g_free(value); + + return structure; +} + +static GstStructure *gst_avdtp_sink_parse_sbc_raw(GstAvdtpSink *self) +{ + a2dp_sbc_t *sbc = (a2dp_sbc_t *) self->data->config; + GstStructure *structure; + GValue *value; + GValue *list; + gboolean mono, stereo; + + structure = gst_structure_empty_new("audio/x-sbc"); + value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING); + + /* mode */ + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { + g_value_set_static_string(value, "mono"); + gst_value_list_prepend_value(list, value); + } + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) { + g_value_set_static_string(value, "stereo"); + gst_value_list_prepend_value(list, value); + } + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) { + g_value_set_static_string(value, "dual"); + gst_value_list_prepend_value(list, value); + } + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) { + g_value_set_static_string(value, "joint"); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "mode", list); + g_free(list); + list = NULL; + } + + /* subbands */ + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + value = g_value_init(value, G_TYPE_INT); + if (sbc->subbands & BT_A2DP_SUBBANDS_4) { + g_value_set_int(value, 4); + gst_value_list_prepend_value(list, value); + } + if (sbc->subbands & BT_A2DP_SUBBANDS_8) { + g_value_set_int(value, 8); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "subbands", list); + g_free(list); + list = NULL; + } + + /* blocks */ + value = g_value_init(value, G_TYPE_INT); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) { + g_value_set_int(value, 16); + gst_value_list_prepend_value(list, value); + } + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) { + g_value_set_int(value, 12); + gst_value_list_prepend_value(list, value); + } + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) { + g_value_set_int(value, 8); + gst_value_list_prepend_value(list, value); + } + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) { + g_value_set_int(value, 4); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "blocks", list); + g_free(list); + list = NULL; + } + + /* allocation */ + g_value_init(value, G_TYPE_STRING); + list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST); + if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) { + g_value_set_static_string(value, "loudness"); + gst_value_list_prepend_value(list, value); + } + if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) { + g_value_set_static_string(value, "snr"); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "allocation", list); + g_free(list); + list = NULL; + } + + /* rate */ + g_value_init(value, G_TYPE_INT); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_48000) { + g_value_set_int(value, 48000); + gst_value_list_prepend_value(list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) { + g_value_set_int(value, 44100); + gst_value_list_prepend_value(list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) { + g_value_set_int(value, 32000); + gst_value_list_prepend_value(list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_16000) { + g_value_set_int(value, 16000); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "rate", list); + g_free(list); + list = NULL; + } + + /* bitpool */ + value = g_value_init(value, GST_TYPE_INT_RANGE); + gst_value_set_int_range(value, + MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL), + MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL)); + gst_structure_set_value(structure, "bitpool", value); + g_value_unset(value); + + /* channels */ + mono = FALSE; + stereo = FALSE; + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + mono = TRUE; + if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) || + (sbc->channel_mode & + BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) || + (sbc->channel_mode & + BT_A2DP_CHANNEL_MODE_JOINT_STEREO)) + stereo = TRUE; + + if (mono && stereo) { + g_value_init(value, GST_TYPE_INT_RANGE); + gst_value_set_int_range(value, 1, 2); + } else { + g_value_init(value, G_TYPE_INT); + if (mono) + g_value_set_int(value, 1); + else if (stereo) + g_value_set_int(value, 2); + else { + GST_ERROR_OBJECT(self, + "Unexpected number of channels"); + g_value_set_int(value, 0); + } + } + + gst_structure_set_value(structure, "channels", value); + g_free(value); + + return structure; +} + +static GstStructure *gst_avdtp_sink_parse_mpeg_raw(GstAvdtpSink *self) +{ + a2dp_mpeg_t *mpeg = (a2dp_mpeg_t *) self->data->config; + GstStructure *structure; + GValue *value; + GValue *list; + gboolean valid_layer = FALSE; + gboolean mono, stereo; + + GST_LOG_OBJECT(self, "parsing mpeg caps"); + + structure = gst_structure_empty_new("audio/mpeg"); + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_INT); + + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + g_value_set_int(value, 1); + gst_value_list_prepend_value(list, value); + g_value_set_int(value, 2); + gst_value_list_prepend_value(list, value); + gst_structure_set_value(structure, "mpegversion", list); + g_free(list); + + /* layer */ + GST_LOG_OBJECT(self, "setting mpeg layer"); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (mpeg->layer & BT_MPEG_LAYER_1) { + g_value_set_int(value, 1); + gst_value_list_prepend_value(list, value); + valid_layer = TRUE; + } + if (mpeg->layer & BT_MPEG_LAYER_2) { + g_value_set_int(value, 2); + gst_value_list_prepend_value(list, value); + valid_layer = TRUE; + } + if (mpeg->layer & BT_MPEG_LAYER_3) { + g_value_set_int(value, 3); + gst_value_list_prepend_value(list, value); + valid_layer = TRUE; + } + if (list) { + gst_structure_set_value(structure, "layer", list); + g_free(list); + list = NULL; + } + + if (!valid_layer) { + gst_structure_free(structure); + g_free(value); + return NULL; + } + + /* rate */ + GST_LOG_OBJECT(self, "setting mpeg rate"); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) { + g_value_set_int(value, 48000); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) { + g_value_set_int(value, 44100); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) { + g_value_set_int(value, 32000); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) { + g_value_set_int(value, 24000); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) { + g_value_set_int(value, 22050); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_16000) { + g_value_set_int(value, 16000); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "rate", list); + g_free(list); + list = NULL; + } + + /* channels */ + GST_LOG_OBJECT(self, "setting mpeg channels"); + mono = FALSE; + stereo = FALSE; + if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + mono = TRUE; + if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) || + (mpeg->channel_mode & + BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) || + (mpeg->channel_mode & + BT_A2DP_CHANNEL_MODE_JOINT_STEREO)) + stereo = TRUE; + + if (mono && stereo) { + g_value_init(value, GST_TYPE_INT_RANGE); + gst_value_set_int_range(value, 1, 2); + } else { + g_value_init(value, G_TYPE_INT); + if (mono) + g_value_set_int(value, 1); + else if (stereo) + g_value_set_int(value, 2); + else { + GST_ERROR_OBJECT(self, + "Unexpected number of channels"); + g_value_set_int(value, 0); + } + } + gst_structure_set_value(structure, "channels", value); + g_free(value); + + return structure; +} + +static gboolean gst_avdtp_sink_update_config(GstAvdtpSink *self) +{ + GstStructure *structure; + gchar *tmp; + + switch (self->data->codec) { + case A2DP_CODEC_SBC: + structure = gst_avdtp_sink_parse_sbc_raw(self); + break; + case A2DP_CODEC_MPEG12: + structure = gst_avdtp_sink_parse_mpeg_raw(self); + break; + default: + GST_ERROR_OBJECT(self, "Unsupported configuration"); + return FALSE; + } + + if (structure == NULL) + return FALSE; + + if (self->dev_caps != NULL) + gst_caps_unref(self->dev_caps); + + self->dev_caps = gst_caps_new_full(structure, NULL); + + tmp = gst_caps_to_string(self->dev_caps); + GST_DEBUG_OBJECT(self, "Transport configuration: %s", tmp); + g_free(tmp); + + return TRUE; +} + +static gboolean gst_avdtp_sink_update_caps(GstAvdtpSink *self) +{ + sbc_capabilities_t *sbc; + mpeg_capabilities_t *mpeg; + GstStructure *sbc_structure; + GstStructure *mpeg_structure; + gchar *tmp; + + GST_LOG_OBJECT(self, "updating device caps"); + + if (self->data->config_size != 0 && self->data->config != NULL) + return gst_avdtp_sink_update_config(self); + + sbc = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK); + mpeg = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK); + + sbc_structure = gst_avdtp_sink_parse_sbc_caps(self, sbc); + mpeg_structure = gst_avdtp_sink_parse_mpeg_caps(self, mpeg); + + if (self->dev_caps != NULL) + gst_caps_unref(self->dev_caps); + self->dev_caps = gst_caps_new_full(sbc_structure, NULL); + if (mpeg_structure != NULL) + gst_caps_append_structure(self->dev_caps, mpeg_structure); + + tmp = gst_caps_to_string(self->dev_caps); + GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp); + g_free(tmp); + + return TRUE; +} + +static gboolean gst_avdtp_sink_get_capabilities(GstAvdtpSink *self) +{ + gchar *buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_get_capabilities_req *req = (void *) buf; + struct bt_get_capabilities_rsp *rsp = (void *) buf; + int err; + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + + req->h.type = BT_REQUEST; + req->h.name = BT_GET_CAPABILITIES; + req->h.length = sizeof(*req); + + if (self->device == NULL) + return FALSE; + strncpy(req->destination, self->device, 18); + if (self->autoconnect) + req->flags |= BT_FLAG_AUTOCONNECT; + + err = gst_avdtp_sink_audioservice_send(self, &req->h); + if (err < 0) { + GST_ERROR_OBJECT(self, "Error while asking device caps"); + return FALSE; + } + + rsp->h.length = 0; + err = gst_avdtp_sink_audioservice_expect(self, + &rsp->h, BT_GET_CAPABILITIES); + if (err < 0) { + GST_ERROR_OBJECT(self, "Error while getting device caps"); + return FALSE; + } + + self->data->caps = g_malloc0(rsp->h.length); + memcpy(self->data->caps, rsp, rsp->h.length); + if (!gst_avdtp_sink_update_caps(self)) { + GST_WARNING_OBJECT(self, "failed to update capabilities"); + return FALSE; + } + + return TRUE; +} + +static gint gst_avdtp_sink_get_channel_mode(const gchar *mode) +{ + if (strcmp(mode, "stereo") == 0) + return BT_A2DP_CHANNEL_MODE_STEREO; + else if (strcmp(mode, "joint-stereo") == 0) + return BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (strcmp(mode, "dual-channel") == 0) + return BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (strcmp(mode, "mono") == 0) + return BT_A2DP_CHANNEL_MODE_MONO; + else + return -1; +} + +static void gst_avdtp_sink_tag(const GstTagList *taglist, + const gchar *tag, gpointer user_data) +{ + gboolean crc; + gchar *channel_mode = NULL; + GstAvdtpSink *self = GST_AVDTP_SINK(user_data); + + if (strcmp(tag, "has-crc") == 0) { + + if (!gst_tag_list_get_boolean(taglist, tag, &crc)) { + GST_WARNING_OBJECT(self, "failed to get crc tag"); + return; + } + + gst_avdtp_sink_set_crc(self, crc); + + } else if (strcmp(tag, "channel-mode") == 0) { + + if (!gst_tag_list_get_string(taglist, tag, &channel_mode)) { + GST_WARNING_OBJECT(self, + "failed to get channel-mode tag"); + return; + } + + self->channel_mode = gst_avdtp_sink_get_channel_mode( + channel_mode); + if (self->channel_mode == -1) + GST_WARNING_OBJECT(self, "Received invalid channel " + "mode: %s", channel_mode); + g_free(channel_mode); + + } else + GST_DEBUG_OBJECT(self, "received unused tag: %s", tag); +} + +static gboolean gst_avdtp_sink_event(GstBaseSink *basesink, + GstEvent *event) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + GstTagList *taglist = NULL; + + if (GST_EVENT_TYPE(event) == GST_EVENT_TAG) { + /* we check the tags, mp3 has tags that are importants and + * are outside caps */ + gst_event_parse_tag(event, &taglist); + gst_tag_list_foreach(taglist, gst_avdtp_sink_tag, self); + } + + return TRUE; +} + +static gboolean gst_avdtp_sink_transport_parse_property(GstAvdtpSink *self, + DBusMessageIter *i) +{ + const char *key; + DBusMessageIter variant_i; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { + GST_ERROR_OBJECT(self, "Property name not a string."); + return FALSE; + } + + dbus_message_iter_get_basic(i, &key); + + if (!dbus_message_iter_next(i)) { + GST_ERROR_OBJECT(self, "Property value missing"); + return FALSE; + } + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) { + GST_ERROR_OBJECT(self, "Property value not a variant."); + return FALSE; + } + + dbus_message_iter_recurse(i, &variant_i); + + switch (dbus_message_iter_get_arg_type(&variant_i)) { + case DBUS_TYPE_BYTE: { + uint8_t value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (g_str_equal(key, "Codec") == TRUE) + self->data->codec = value; + + break; + } + case DBUS_TYPE_STRING: { + const char *value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (g_str_equal(key, "UUID") == TRUE) { + g_free(self->data->uuid); + self->data->uuid = g_strdup(value); + } + + break; + } + case DBUS_TYPE_ARRAY: { + DBusMessageIter array_i; + char *value; + int size; + + dbus_message_iter_recurse(&variant_i, &array_i); + dbus_message_iter_get_fixed_array(&array_i, &value, &size); + + if (g_str_equal(key, "Configuration")) { + g_free(self->data->config); + self->data->config = g_new0(guint8, size); + self->data->config_size = size; + memcpy(self->data->config, value, size); + } + + break; + } + } + + return TRUE; +} + +static gboolean gst_avdtp_sink_transport_acquire(GstAvdtpSink *self) +{ + DBusMessage *msg, *reply; + DBusError err; + const char *access_type = "w"; + int fd; + uint16_t imtu, omtu; + + dbus_error_init(&err); + + if (self->data->conn == NULL) + self->data->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + + msg = dbus_message_new_method_call("org.bluez", self->transport, + "org.bluez.MediaTransport", + "Acquire"); + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type, + DBUS_TYPE_INVALID); + + reply = dbus_connection_send_with_reply_and_block(self->data->conn, + msg, -1, &err); + + if (dbus_error_is_set(&err)) + goto fail; + + if (dbus_message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_UINT16, &imtu, + DBUS_TYPE_UINT16, &omtu, + DBUS_TYPE_INVALID) == FALSE) + goto fail; + + dbus_message_unref(reply); + + self->stream = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(self->stream, NULL, NULL); + g_io_channel_set_close_on_unref(self->stream, TRUE); + self->data->link_mtu = omtu; + GST_DEBUG_OBJECT(self, "stream_fd=%d mtu=%d", fd, omtu); + + return TRUE; + +fail: + GST_ERROR_OBJECT(self, "Failed to acquire transport stream: %s", + err.message); + + dbus_error_free(&err); + + if (reply) + dbus_message_unref(msg); + + return FALSE; +} + +static gboolean gst_avdtp_sink_transport_get_properties(GstAvdtpSink *self) +{ + DBusMessage *msg, *reply; + DBusMessageIter arg_i, ele_i; + DBusError err; + + dbus_error_init(&err); + + /* Transport need to be acquire first to make sure the MTUs are + available */ + if (gst_avdtp_sink_transport_acquire(self) == FALSE) + return FALSE; + + msg = dbus_message_new_method_call("org.bluez", self->transport, + "org.bluez.MediaTransport", + "GetProperties"); + reply = dbus_connection_send_with_reply_and_block(self->data->conn, + msg, -1, &err); + + if (dbus_error_is_set(&err) || reply == NULL) { + GST_ERROR_OBJECT(self, "Failed to get transport properties: %s", + err.message); + goto fail; + } + + if (!dbus_message_iter_init(reply, &arg_i)) { + GST_ERROR_OBJECT(self, "GetProperties reply has no arguments."); + goto fail; + } + + if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) { + GST_ERROR_OBJECT(self, "GetProperties argument is not an array."); + goto fail; + } + + dbus_message_iter_recurse(&arg_i, &ele_i); + while (dbus_message_iter_get_arg_type(&ele_i) != DBUS_TYPE_INVALID) { + + if (dbus_message_iter_get_arg_type(&ele_i) == + DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter dict_i; + + dbus_message_iter_recurse(&ele_i, &dict_i); + + gst_avdtp_sink_transport_parse_property(self, &dict_i); + } + + if (!dbus_message_iter_next(&ele_i)) + break; + } + + return gst_avdtp_sink_update_caps(self); + +fail: + dbus_message_unref(msg); + dbus_message_unref(reply); + return FALSE; + +} + +static gboolean gst_avdtp_sink_start(GstBaseSink *basesink) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + gint sk; + gint err; + + GST_INFO_OBJECT(self, "start"); + + self->data = g_new0(struct bluetooth_data, 1); + + self->stream = NULL; + self->stream_caps = NULL; + self->mp3_using_crc = -1; + self->channel_mode = -1; + + if (self->transport != NULL) + return gst_avdtp_sink_transport_get_properties(self); + + self->watch_id = 0; + + sk = bt_audio_service_open(); + if (sk <= 0) { + err = errno; + GST_ERROR_OBJECT(self, "Cannot open connection to bt " + "audio service: %s %d", strerror(err), err); + goto failed; + } + + self->server = g_io_channel_unix_new(sk); + g_io_channel_set_encoding(self->server, NULL, NULL); + self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR | + G_IO_NVAL, server_callback, self); + + if (!gst_avdtp_sink_get_capabilities(self)) { + GST_ERROR_OBJECT(self, "failed to get capabilities " + "from device"); + goto failed; + } + + return TRUE; + +failed: + bt_audio_service_close(sk); + return FALSE; +} + +static gboolean gst_avdtp_sink_stream_start(GstAvdtpSink *self) +{ + gchar buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_req *req = (void *) buf; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + int err; + + if (self->transport != NULL) + return gst_avdtp_sink_conf_recv_stream_fd(self); + + memset(req, 0, sizeof(buf)); + req->h.type = BT_REQUEST; + req->h.name = BT_START_STREAM; + req->h.length = sizeof(*req); + + err = gst_avdtp_sink_audioservice_send(self, &req->h); + if (err < 0) { + GST_ERROR_OBJECT(self, "Error ocurred while sending " + "start packet"); + return FALSE; + } + + rsp->h.length = sizeof(*rsp); + err = gst_avdtp_sink_audioservice_expect(self, &rsp->h, + BT_START_STREAM); + if (err < 0) { + GST_ERROR_OBJECT(self, "Error while stream " + "start confirmation"); + return FALSE; + } + + ind->h.length = sizeof(*ind); + err = gst_avdtp_sink_audioservice_expect(self, &ind->h, + BT_NEW_STREAM); + if (err < 0) { + GST_ERROR_OBJECT(self, "Error while receiving " + "stream filedescriptor"); + return FALSE; + } + + if (!gst_avdtp_sink_conf_recv_stream_fd(self)) + return FALSE; + + return TRUE; +} + +static gboolean gst_avdtp_sink_init_mp3_pkt_conf( + GstAvdtpSink *self, GstCaps *caps, + mpeg_capabilities_t *pkt) +{ + const GValue *value = NULL; + gint rate, layer; + const gchar *name; + GstStructure *structure = gst_caps_get_structure(caps, 0); + + name = gst_structure_get_name(structure); + + if (!(IS_MPEG_AUDIO(name))) { + GST_ERROR_OBJECT(self, "Unexpected format %s, " + "was expecting mp3", name); + return FALSE; + } + + /* layer */ + value = gst_structure_get_value(structure, "layer"); + layer = g_value_get_int(value); + if (layer == 1) + pkt->layer = BT_MPEG_LAYER_1; + else if (layer == 2) + pkt->layer = BT_MPEG_LAYER_2; + else if (layer == 3) + pkt->layer = BT_MPEG_LAYER_3; + else { + GST_ERROR_OBJECT(self, "Unexpected layer: %d", layer); + return FALSE; + } + + /* crc */ + if (self->mp3_using_crc != -1) + pkt->crc = self->mp3_using_crc; + else { + GST_ERROR_OBJECT(self, "No info about crc was received, " + " can't proceed"); + return FALSE; + } + + /* channel mode */ + if (self->channel_mode != -1) + pkt->channel_mode = self->channel_mode; + else { + GST_ERROR_OBJECT(self, "No info about channel mode " + "received, can't proceed"); + return FALSE; + } + + /* mpf - we will only use the mandatory one */ + pkt->mpf = 0; + + value = gst_structure_get_value(structure, "rate"); + rate = g_value_get_int(value); + if (rate == 44100) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_44100; + else if (rate == 48000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_48000; + else if (rate == 32000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_32000; + else if (rate == 24000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_24000; + else if (rate == 22050) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_22050; + else if (rate == 16000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_16000; + else { + GST_ERROR_OBJECT(self, "Invalid rate while setting caps"); + return FALSE; + } + + /* vbr - we always say its vbr, we don't have how to know it */ + pkt->bitrate = 0x8000; + + return TRUE; +} + +static gboolean gst_avdtp_sink_configure(GstAvdtpSink *self, + GstCaps *caps) +{ + gchar buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_open_req *open_req = (void *) buf; + struct bt_open_rsp *open_rsp = (void *) buf; + struct bt_set_configuration_req *req = (void *) buf; + struct bt_set_configuration_rsp *rsp = (void *) buf; + gboolean ret; + gchar *temp; + GstStructure *structure; + codec_capabilities_t *codec = NULL; + int err; + + temp = gst_caps_to_string(caps); + GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp); + g_free(temp); + + /* Transport already configured */ + if (self->transport != NULL) + return TRUE; + + structure = gst_caps_get_structure(caps, 0); + + if (gst_structure_has_name(structure, "audio/x-sbc")) + codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK); + else if (gst_structure_has_name(structure, "audio/mpeg")) + codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK); + + if (codec == NULL) { + GST_ERROR_OBJECT(self, "Couldn't parse caps " + "to packet configuration"); + return FALSE; + } + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + open_req->h.type = BT_REQUEST; + open_req->h.name = BT_OPEN; + open_req->h.length = sizeof(*open_req); + + strncpy(open_req->destination, self->device, 18); + open_req->seid = codec->seid; + open_req->lock = BT_WRITE_LOCK; + + err = gst_avdtp_sink_audioservice_send(self, &open_req->h); + if (err < 0) { + GST_ERROR_OBJECT(self, "Error ocurred while sending " + "open packet"); + return FALSE; + } + + open_rsp->h.length = sizeof(*open_rsp); + err = gst_avdtp_sink_audioservice_expect(self, &open_rsp->h, + BT_OPEN); + if (err < 0) { + GST_ERROR_OBJECT(self, "Error while receiving device " + "confirmation"); + return FALSE; + } + + memset(req, 0, sizeof(buf)); + req->h.type = BT_REQUEST; + req->h.name = BT_SET_CONFIGURATION; + req->h.length = sizeof(*req); + memcpy(&req->codec, codec, sizeof(req->codec)); + + if (codec->type == BT_A2DP_SBC_SINK) + ret = gst_avdtp_sink_init_sbc_pkt_conf(self, caps, + (void *) &req->codec); + else + ret = gst_avdtp_sink_init_mp3_pkt_conf(self, caps, + (void *) &req->codec); + + if (!ret) { + GST_ERROR_OBJECT(self, "Couldn't parse caps " + "to packet configuration"); + return FALSE; + } + + req->h.length += req->codec.length - sizeof(req->codec); + err = gst_avdtp_sink_audioservice_send(self, &req->h); + if (err < 0) { + GST_ERROR_OBJECT(self, "Error ocurred while sending " + "configurarion packet"); + return FALSE; + } + + rsp->h.length = sizeof(*rsp); + err = gst_avdtp_sink_audioservice_expect(self, &rsp->h, + BT_SET_CONFIGURATION); + if (err < 0) { + GST_ERROR_OBJECT(self, "Error while receiving device " + "confirmation"); + return FALSE; + } + + self->data->link_mtu = rsp->link_mtu; + + return TRUE; +} + +static GstFlowReturn gst_avdtp_sink_preroll(GstBaseSink *basesink, + GstBuffer *buffer) +{ + GstAvdtpSink *sink = GST_AVDTP_SINK(basesink); + gboolean ret; + + GST_AVDTP_SINK_MUTEX_LOCK(sink); + + ret = gst_avdtp_sink_stream_start(sink); + + GST_AVDTP_SINK_MUTEX_UNLOCK(sink); + + if (!ret) + return GST_FLOW_ERROR; + + return GST_FLOW_OK; +} + +static GstFlowReturn gst_avdtp_sink_render(GstBaseSink *basesink, + GstBuffer *buffer) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + ssize_t ret; + int fd; + + fd = g_io_channel_unix_get_fd(self->stream); + + ret = write(fd, GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer)); + if (ret < 0) { + GST_ERROR_OBJECT(self, "Error while writting to socket: %s", + strerror(errno)); + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; +} + +static gboolean gst_avdtp_sink_unlock(GstBaseSink *basesink) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + + if (self->stream != NULL) + g_io_channel_flush(self->stream, NULL); + + return TRUE; +} + +static GstFlowReturn gst_avdtp_sink_buffer_alloc(GstBaseSink *basesink, + guint64 offset, guint size, GstCaps *caps, + GstBuffer **buf) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + + *buf = gst_buffer_new_and_alloc(size); + if (!(*buf)) { + GST_ERROR_OBJECT(self, "buffer allocation failed"); + return GST_FLOW_ERROR; + } + + gst_buffer_set_caps(*buf, caps); + + GST_BUFFER_OFFSET(*buf) = offset; + + return GST_FLOW_OK; +} + +static void gst_avdtp_sink_class_init(GstAvdtpSinkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + object_class->finalize = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_finalize); + object_class->set_property = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_set_property); + object_class->get_property = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_get_property); + + basesink_class->start = GST_DEBUG_FUNCPTR(gst_avdtp_sink_start); + basesink_class->stop = GST_DEBUG_FUNCPTR(gst_avdtp_sink_stop); + basesink_class->render = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_render); + basesink_class->preroll = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_preroll); + basesink_class->unlock = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_unlock); + basesink_class->event = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_event); + + basesink_class->buffer_alloc = + GST_DEBUG_FUNCPTR(gst_avdtp_sink_buffer_alloc); + + g_object_class_install_property(object_class, PROP_DEVICE, + g_param_spec_string("device", "Device", + "Bluetooth remote device address", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_AUTOCONNECT, + g_param_spec_boolean("auto-connect", + "Auto-connect", + "Automatically attempt to connect " + "to device", DEFAULT_AUTOCONNECT, + G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_TRANSPORT, + g_param_spec_string("transport", + "Transport", + "Use configured transport", + NULL, G_PARAM_READWRITE)); + + GST_DEBUG_CATEGORY_INIT(avdtp_sink_debug, "avdtpsink", 0, + "A2DP headset sink element"); +} + +static void gst_avdtp_sink_init(GstAvdtpSink *self, + GstAvdtpSinkClass *klass) +{ + self->device = NULL; + self->transport = NULL; + self->data = NULL; + + self->stream = NULL; + + self->dev_caps = NULL; + + self->autoconnect = DEFAULT_AUTOCONNECT; + + self->sink_lock = g_mutex_new(); + + /* FIXME this is for not synchronizing with clock, should be tested + * with devices to see the behaviour + gst_base_sink_set_sync(GST_BASE_SINK(self), FALSE); + */ +} + +static int gst_avdtp_sink_audioservice_send(GstAvdtpSink *self, + const bt_audio_msg_header_t *msg) +{ + ssize_t written; + const char *type, *name; + uint16_t length; + int fd; + + length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE; + + fd = g_io_channel_unix_get_fd(self->server); + + written = write(fd, msg, length); + if (written < 0) { + GST_ERROR_OBJECT(self, "Error sending data to audio service:" + " %s", strerror(errno)); + return -errno; + } + + type = bt_audio_strtype(msg->type); + name = bt_audio_strname(msg->name); + + GST_DEBUG_OBJECT(self, "sent: %s -> %s", type, name); + + return 0; +} + +static int gst_avdtp_sink_audioservice_recv(GstAvdtpSink *self, + bt_audio_msg_header_t *inmsg) +{ + ssize_t bytes_read; + const char *type, *name; + uint16_t length; + int fd, err; + + length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE; + + fd = g_io_channel_unix_get_fd(self->server); + + bytes_read = read(fd, inmsg, length); + if (bytes_read < 0) { + GST_ERROR_OBJECT(self, "Error receiving data from " + "audio service: %s", strerror(errno)); + return -errno; + } + + type = bt_audio_strtype(inmsg->type); + if (!type) { + err = -EINVAL; + GST_ERROR_OBJECT(self, "Bogus message type %d " + "received from audio service", + inmsg->type); + } + + name = bt_audio_strname(inmsg->name); + if (!name) { + err = -EINVAL; + GST_ERROR_OBJECT(self, "Bogus message name %d " + "received from audio service", + inmsg->name); + } + + if (inmsg->type == BT_ERROR) { + bt_audio_error_t *msg = (void *) inmsg; + err = -EINVAL; + GST_ERROR_OBJECT(self, "%s failed : " + "%s(%d)", + name, + strerror(msg->posix_errno), + msg->posix_errno); + } + + GST_DEBUG_OBJECT(self, "received: %s <- %s", type, name); + + return err; +} + +static int gst_avdtp_sink_audioservice_expect(GstAvdtpSink *self, + bt_audio_msg_header_t *outmsg, + guint8 expected_name) +{ + int err; + + err = gst_avdtp_sink_audioservice_recv(self, outmsg); + if (err < 0) + return err; + + if (outmsg->name != expected_name) + return -EINVAL; + + return 0; +} + +gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "avdtpsink", GST_RANK_NONE, + GST_TYPE_AVDTP_SINK); +} + + +/* public functions */ +GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink) +{ + if (sink->dev_caps == NULL) + return NULL; + + return gst_caps_copy(sink->dev_caps); +} + +gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *self, + GstCaps *caps) +{ + gboolean ret; + + GST_DEBUG_OBJECT(self, "setting device caps"); + GST_AVDTP_SINK_MUTEX_LOCK(self); + ret = gst_avdtp_sink_configure(self, caps); + + if (self->stream_caps) + gst_caps_unref(self->stream_caps); + self->stream_caps = gst_caps_ref(caps); + + GST_AVDTP_SINK_MUTEX_UNLOCK(self); + + return ret; +} + +guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink) +{ + return sink->data->link_mtu; +} + +void gst_avdtp_sink_set_device(GstAvdtpSink *self, const gchar *dev) +{ + if (self->device != NULL) + g_free(self->device); + + GST_LOG_OBJECT(self, "Setting device: %s", dev); + self->device = g_strdup(dev); +} + +void gst_avdtp_sink_set_transport(GstAvdtpSink *self, const gchar *trans) +{ + if (self->transport != NULL) + g_free(self->transport); + + GST_LOG_OBJECT(self, "Setting transport: %s", trans); + self->transport = g_strdup(trans); +} + +gchar *gst_avdtp_sink_get_device(GstAvdtpSink *self) +{ + return g_strdup(self->device); +} + +gchar *gst_avdtp_sink_get_transport(GstAvdtpSink *self) +{ + return g_strdup(self->transport); +} + +void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc) +{ + gint new_crc; + + new_crc = crc ? CRC_PROTECTED : CRC_UNPROTECTED; + + /* test if we already received a different crc */ + if (self->mp3_using_crc != -1 && new_crc != self->mp3_using_crc) { + GST_WARNING_OBJECT(self, "crc changed during stream"); + return; + } + self->mp3_using_crc = new_crc; + +} + +void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self, + const gchar *mode) +{ + gint new_mode; + + new_mode = gst_avdtp_sink_get_channel_mode(mode); + + if (self->channel_mode != -1 && new_mode != self->channel_mode) { + GST_WARNING_OBJECT(self, "channel mode changed during stream"); + return; + } + + self->channel_mode = new_mode; + if (self->channel_mode == -1) + GST_WARNING_OBJECT(self, "Received invalid channel " + "mode: %s", mode); +} diff --git a/audio/gstavdtpsink.h b/audio/gstavdtpsink.h new file mode 100644 index 0000000..c4e5645 --- /dev/null +++ b/audio/gstavdtpsink.h @@ -0,0 +1,107 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __GST_AVDTP_SINK_H +#define __GST_AVDTP_SINK_H + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_AVDTP_SINK \ + (gst_avdtp_sink_get_type()) +#define GST_AVDTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVDTP_SINK,\ + GstAvdtpSink)) +#define GST_AVDTP_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVDTP_SINK,\ + GstAvdtpSinkClass)) +#define GST_IS_AVDTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVDTP_SINK)) +#define GST_IS_AVDTP_SINK_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVDTP_SINK)) + +typedef struct _GstAvdtpSink GstAvdtpSink; +typedef struct _GstAvdtpSinkClass GstAvdtpSinkClass; + +struct bluetooth_data; + +struct _GstAvdtpSink { + GstBaseSink sink; + + gchar *device; + gchar *transport; + GIOChannel *stream; + + struct bluetooth_data *data; + gboolean autoconnect; + GIOChannel *server; + + /* mp3 stream data (outside caps data)*/ + gint mp3_using_crc; + gint channel_mode; + + /* stream connection data */ + GstCaps *stream_caps; + + GstCaps *dev_caps; + + GMutex *sink_lock; + + guint watch_id; +}; + +struct _GstAvdtpSinkClass { + GstBaseSinkClass parent_class; +}; + +GType gst_avdtp_sink_get_type(void); + +GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink); +gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *sink, + GstCaps *caps); + +guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink); + +void gst_avdtp_sink_set_device(GstAvdtpSink *sink, + const gchar* device); + +void gst_avdtp_sink_set_transport(GstAvdtpSink *sink, + const gchar *transport); + +gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink); + +gchar *gst_avdtp_sink_get_transport(GstAvdtpSink *sink); + +gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin); + +void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc); + +void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self, + const gchar *mode); + + +G_END_DECLS + +#endif /* __GST_AVDTP_SINK_H */ diff --git a/audio/gstbluetooth.c b/audio/gstbluetooth.c new file mode 100644 index 0000000..9930820 --- /dev/null +++ b/audio/gstbluetooth.c @@ -0,0 +1,109 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "gstsbcutil.h" +#include + +#include "gstsbcenc.h" +#include "gstsbcdec.h" +#include "gstsbcparse.h" +#include "gstavdtpsink.h" +#include "gsta2dpsink.h" +#include "gstrtpsbcpay.h" + +static GstStaticCaps sbc_caps = GST_STATIC_CAPS("audio/x-sbc"); + +#define SBC_CAPS (gst_static_caps_get(&sbc_caps)) + +static void sbc_typefind(GstTypeFind *tf, gpointer ignore) +{ + GstCaps *caps; + guint8 *aux; + sbc_t sbc; + guint8 *data = gst_type_find_peek(tf, 0, 32); + + if (data == NULL) + return; + + if (sbc_init(&sbc, 0) < 0) + return; + + aux = g_new(guint8, 32); + memcpy(aux, data, 32); + if (sbc_parse(&sbc, aux, 32) < 0) + goto done; + + caps = gst_sbc_parse_caps_from_sbc(&sbc); + gst_type_find_suggest(tf, GST_TYPE_FIND_POSSIBLE, caps); + gst_caps_unref(caps); + +done: + g_free(aux); + sbc_finish(&sbc); +} + +static gchar *sbc_exts[] = { "sbc", NULL }; + +static gboolean plugin_init(GstPlugin *plugin) +{ + GST_INFO("Bluetooth plugin %s", VERSION); + + if (gst_type_find_register(plugin, "sbc", + GST_RANK_PRIMARY, sbc_typefind, sbc_exts, + SBC_CAPS, NULL, NULL) == FALSE) + return FALSE; + + if (!gst_sbc_enc_plugin_init(plugin)) + return FALSE; + + if (!gst_sbc_dec_plugin_init(plugin)) + return FALSE; + + if (!gst_sbc_parse_plugin_init(plugin)) + return FALSE; + + if (!gst_avdtp_sink_plugin_init(plugin)) + return FALSE; + + if (!gst_a2dp_sink_plugin_init(plugin)) + return FALSE; + + if (!gst_rtp_sbc_pay_plugin_init(plugin)) + return FALSE; + + return TRUE; +} + +extern GstPluginDesc gst_plugin_desc __attribute__ ((visibility("default"))); + +GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR, + "bluetooth", "Bluetooth plugin library", + plugin_init, VERSION, "LGPL", "BlueZ", "http://www.bluez.org/") diff --git a/audio/gstpragma.h b/audio/gstpragma.h new file mode 100644 index 0000000..626311c --- /dev/null +++ b/audio/gstpragma.h @@ -0,0 +1,24 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +//#pragma GCC diagnostic warning "-Wmissing-declarations" diff --git a/audio/gstrtpsbcpay.c b/audio/gstrtpsbcpay.c new file mode 100644 index 0000000..1159bfe --- /dev/null +++ b/audio/gstrtpsbcpay.c @@ -0,0 +1,352 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstpragma.h" +#include "gstrtpsbcpay.h" +#include +#include + +#define RTP_SBC_PAYLOAD_HEADER_SIZE 1 +#define DEFAULT_MIN_FRAMES 0 +#define RTP_SBC_HEADER_TOTAL (12 + RTP_SBC_PAYLOAD_HEADER_SIZE) + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct rtp_payload { + guint8 frame_count:4; + guint8 rfa0:1; + guint8 is_last_fragment:1; + guint8 is_first_fragment:1; + guint8 is_fragmented:1; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct rtp_payload { + guint8 is_fragmented:1; + guint8 is_first_fragment:1; + guint8 is_last_fragment:1; + guint8 rfa0:1; + guint8 frame_count:4; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +enum { + PROP_0, + PROP_MIN_FRAMES +}; + +GST_DEBUG_CATEGORY_STATIC(gst_rtp_sbc_pay_debug); +#define GST_CAT_DEFAULT gst_rtp_sbc_pay_debug + +GST_BOILERPLATE(GstRtpSBCPay, gst_rtp_sbc_pay, GstBaseRTPPayload, + GST_TYPE_BASE_RTP_PAYLOAD); + +static const GstElementDetails gst_rtp_sbc_pay_details = + GST_ELEMENT_DETAILS("RTP packet payloader", + "Codec/Payloader/Network", + "Payload SBC audio as RTP packets", + "Thiago Sousa Santos " + ""); + +static GstStaticPadTemplate gst_rtp_sbc_pay_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, " + "blocks = (int) { 4, 8, 12, 16 }, " + "subbands = (int) { 4, 8 }, " + "allocation = (string) { \"snr\", \"loudness\" }, " + "bitpool = (int) [ 2, 64 ]") + ); + +static GstStaticPadTemplate gst_rtp_sbc_pay_src_factory = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "application/x-rtp, " + "media = (string) \"audio\"," + "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) { 16000, 32000, 44100, 48000 }," + "encoding-name = (string) \"SBC\"") + ); + +static void gst_rtp_sbc_pay_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static void gst_rtp_sbc_pay_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); + +static gint gst_rtp_sbc_pay_get_frame_len(gint subbands, gint channels, + gint blocks, gint bitpool, const gchar *channel_mode) +{ + gint len; + gint join; + + len = 4 + (4 * subbands * channels)/8; + + if (strcmp(channel_mode, "mono") == 0 || + strcmp(channel_mode, "dual") == 0) + len += ((blocks * channels * bitpool) + 7) / 8; + else { + join = strcmp(channel_mode, "joint") == 0 ? 1 : 0; + len += ((join * subbands + blocks * bitpool) + 7) / 8; + } + + return len; +} + +static gboolean gst_rtp_sbc_pay_set_caps(GstBaseRTPPayload *payload, + GstCaps *caps) +{ + GstRtpSBCPay *sbcpay; + gint rate, subbands, channels, blocks, bitpool; + gint frame_len; + const gchar *channel_mode; + GstStructure *structure; + + sbcpay = GST_RTP_SBC_PAY(payload); + + structure = gst_caps_get_structure(caps, 0); + if (!gst_structure_get_int(structure, "rate", &rate)) + return FALSE; + if (!gst_structure_get_int(structure, "channels", &channels)) + return FALSE; + if (!gst_structure_get_int(structure, "blocks", &blocks)) + return FALSE; + if (!gst_structure_get_int(structure, "bitpool", &bitpool)) + return FALSE; + if (!gst_structure_get_int(structure, "subbands", &subbands)) + return FALSE; + + channel_mode = gst_structure_get_string(structure, "mode"); + if (!channel_mode) + return FALSE; + + frame_len = gst_rtp_sbc_pay_get_frame_len(subbands, channels, blocks, + bitpool, channel_mode); + + sbcpay->frame_length = frame_len; + + gst_basertppayload_set_options(payload, "audio", TRUE, "SBC", rate); + + GST_DEBUG_OBJECT(payload, "calculated frame length: %d ", frame_len); + + return gst_basertppayload_set_outcaps(payload, NULL); +} + +static GstFlowReturn gst_rtp_sbc_pay_flush_buffers(GstRtpSBCPay *sbcpay) +{ + guint available; + guint max_payload; + GstBuffer *outbuf; + guint8 *payload_data; + guint frame_count; + guint payload_length; + struct rtp_payload *payload; + + if (sbcpay->frame_length == 0) { + GST_ERROR_OBJECT(sbcpay, "Frame length is 0"); + return GST_FLOW_ERROR; + } + + available = gst_adapter_available(sbcpay->adapter); + + max_payload = gst_rtp_buffer_calc_payload_len( + GST_BASE_RTP_PAYLOAD_MTU(sbcpay) - RTP_SBC_PAYLOAD_HEADER_SIZE, + 0, 0); + + max_payload = MIN(max_payload, available); + frame_count = max_payload / sbcpay->frame_length; + payload_length = frame_count * sbcpay->frame_length; + if (payload_length == 0) /* Nothing to send */ + return GST_FLOW_OK; + + outbuf = gst_rtp_buffer_new_allocate(payload_length + + RTP_SBC_PAYLOAD_HEADER_SIZE, 0, 0); + + gst_rtp_buffer_set_payload_type(outbuf, + GST_BASE_RTP_PAYLOAD_PT(sbcpay)); + + payload_data = gst_rtp_buffer_get_payload(outbuf); + payload = (struct rtp_payload *) payload_data; + memset(payload, 0, sizeof(struct rtp_payload)); + payload->frame_count = frame_count; + + gst_adapter_copy(sbcpay->adapter, payload_data + + RTP_SBC_PAYLOAD_HEADER_SIZE, 0, payload_length); + gst_adapter_flush(sbcpay->adapter, payload_length); + + GST_BUFFER_TIMESTAMP(outbuf) = sbcpay->timestamp; + GST_DEBUG_OBJECT(sbcpay, "Pushing %d bytes", payload_length); + + return gst_basertppayload_push(GST_BASE_RTP_PAYLOAD(sbcpay), outbuf); +} + +static GstFlowReturn gst_rtp_sbc_pay_handle_buffer(GstBaseRTPPayload *payload, + GstBuffer *buffer) +{ + GstRtpSBCPay *sbcpay; + guint available; + + /* FIXME check for negotiation */ + + sbcpay = GST_RTP_SBC_PAY(payload); + sbcpay->timestamp = GST_BUFFER_TIMESTAMP(buffer); + + gst_adapter_push(sbcpay->adapter, buffer); + + available = gst_adapter_available(sbcpay->adapter); + if (available + RTP_SBC_HEADER_TOTAL >= + GST_BASE_RTP_PAYLOAD_MTU(sbcpay) || + (available > + (sbcpay->min_frames * sbcpay->frame_length))) + return gst_rtp_sbc_pay_flush_buffers(sbcpay); + + return GST_FLOW_OK; +} + +static gboolean gst_rtp_sbc_pay_handle_event(GstPad *pad, + GstEvent *event) +{ + GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(GST_PAD_PARENT(pad)); + + switch (GST_EVENT_TYPE(event)) { + case GST_EVENT_EOS: + gst_rtp_sbc_pay_flush_buffers(sbcpay); + break; + default: + break; + } + + return FALSE; +} + +static void gst_rtp_sbc_pay_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&gst_rtp_sbc_pay_sink_factory)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&gst_rtp_sbc_pay_src_factory)); + + gst_element_class_set_details(element_class, &gst_rtp_sbc_pay_details); +} + +static void gst_rtp_sbc_pay_finalize(GObject *object) +{ + GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(object); + g_object_unref(sbcpay->adapter); + + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} + +static void gst_rtp_sbc_pay_class_init(GstRtpSBCPayClass *klass) +{ + GObjectClass *gobject_class; + GstBaseRTPPayloadClass *payload_class = + GST_BASE_RTP_PAYLOAD_CLASS(klass); + + gobject_class = G_OBJECT_CLASS(klass); + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_finalize); + gobject_class->set_property = GST_DEBUG_FUNCPTR( + gst_rtp_sbc_pay_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR( + gst_rtp_sbc_pay_get_property); + + payload_class->set_caps = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_set_caps); + payload_class->handle_buffer = GST_DEBUG_FUNCPTR( + gst_rtp_sbc_pay_handle_buffer); + payload_class->handle_event = GST_DEBUG_FUNCPTR( + gst_rtp_sbc_pay_handle_event); + + /* properties */ + g_object_class_install_property(G_OBJECT_CLASS(klass), + PROP_MIN_FRAMES, + g_param_spec_int("min-frames", "minimum frame number", + "Minimum quantity of frames to send in one packet " + "(-1 for maximum allowed by the mtu)", + -1, G_MAXINT, DEFAULT_MIN_FRAMES, G_PARAM_READWRITE)); + + GST_DEBUG_CATEGORY_INIT(gst_rtp_sbc_pay_debug, "rtpsbcpay", 0, + "RTP SBC payloader"); +} + +static void gst_rtp_sbc_pay_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstRtpSBCPay *sbcpay; + + sbcpay = GST_RTP_SBC_PAY(object); + + switch (prop_id) { + case PROP_MIN_FRAMES: + sbcpay->min_frames = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_rtp_sbc_pay_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstRtpSBCPay *sbcpay; + + sbcpay = GST_RTP_SBC_PAY(object); + + switch (prop_id) { + case PROP_MIN_FRAMES: + g_value_set_int(value, sbcpay->min_frames); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_rtp_sbc_pay_init(GstRtpSBCPay *self, GstRtpSBCPayClass *klass) +{ + self->adapter = gst_adapter_new(); + self->frame_length = 0; + self->timestamp = 0; + + self->min_frames = DEFAULT_MIN_FRAMES; +} + +gboolean gst_rtp_sbc_pay_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "rtpsbcpay", GST_RANK_NONE, + GST_TYPE_RTP_SBC_PAY); +} + diff --git a/audio/gstrtpsbcpay.h b/audio/gstrtpsbcpay.h new file mode 100644 index 0000000..398de82 --- /dev/null +++ b/audio/gstrtpsbcpay.h @@ -0,0 +1,66 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_RTP_SBC_PAY \ + (gst_rtp_sbc_pay_get_type()) +#define GST_RTP_SBC_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_SBC_PAY,\ + GstRtpSBCPay)) +#define GST_RTP_SBC_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_SBC_PAY,\ + GstRtpSBCPayClass)) +#define GST_IS_RTP_SBC_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_SBC_PAY)) +#define GST_IS_RTP_SBC_PAY_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_SBC_PAY)) + +typedef struct _GstRtpSBCPay GstRtpSBCPay; +typedef struct _GstRtpSBCPayClass GstRtpSBCPayClass; + +struct _GstRtpSBCPay { + GstBaseRTPPayload base; + + GstAdapter *adapter; + GstClockTime timestamp; + + guint frame_length; + + guint min_frames; +}; + +struct _GstRtpSBCPayClass { + GstBaseRTPPayloadClass parent_class; +}; + +GType gst_rtp_sbc_pay_get_type(void); + +gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin); + +G_END_DECLS diff --git a/audio/gstsbcdec.c b/audio/gstsbcdec.c new file mode 100644 index 0000000..2e5cb0a --- /dev/null +++ b/audio/gstsbcdec.c @@ -0,0 +1,223 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gstpragma.h" +#include "gstsbcutil.h" +#include "gstsbcdec.h" + +GST_DEBUG_CATEGORY_STATIC(sbc_dec_debug); +#define GST_CAT_DEFAULT sbc_dec_debug + +GST_BOILERPLATE(GstSbcDec, gst_sbc_dec, GstElement, GST_TYPE_ELEMENT); + +static const GstElementDetails sbc_dec_details = + GST_ELEMENT_DETAILS("Bluetooth SBC decoder", + "Codec/Decoder/Audio", + "Decode a SBC audio stream", + "Marcel Holtmann "); + +static GstStaticPadTemplate sbc_dec_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc")); + +static GstStaticPadTemplate sbc_dec_src_factory = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-raw-int, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "endianness = (int) BYTE_ORDER, " + "signed = (boolean) true, " + "width = (int) 16, " + "depth = (int) 16")); + +static GstFlowReturn sbc_dec_chain(GstPad *pad, GstBuffer *buffer) +{ + GstSbcDec *dec = GST_SBC_DEC(gst_pad_get_parent(pad)); + GstFlowReturn res = GST_FLOW_OK; + guint size, codesize, offset = 0; + guint8 *data; + + codesize = sbc_get_codesize(&dec->sbc); + + if (dec->buffer) { + GstBuffer *temp = buffer; + buffer = gst_buffer_span(dec->buffer, 0, buffer, + GST_BUFFER_SIZE(dec->buffer) + GST_BUFFER_SIZE(buffer)); + gst_buffer_unref(temp); + gst_buffer_unref(dec->buffer); + dec->buffer = NULL; + } + + data = GST_BUFFER_DATA(buffer); + size = GST_BUFFER_SIZE(buffer); + + while (offset < size) { + GstBuffer *output; + GstPadTemplate *template; + GstCaps *caps; + int consumed; + + res = gst_pad_alloc_buffer_and_set_caps(dec->srcpad, + GST_BUFFER_OFFSET_NONE, + codesize, NULL, &output); + + if (res != GST_FLOW_OK) + goto done; + + consumed = sbc_decode(&dec->sbc, data + offset, size - offset, + GST_BUFFER_DATA(output), codesize, + NULL); + if (consumed <= 0) + break; + + /* we will reuse the same caps object */ + if (dec->outcaps == NULL) { + caps = gst_caps_new_simple("audio/x-raw-int", + "rate", G_TYPE_INT, + gst_sbc_parse_rate_from_sbc( + dec->sbc.frequency), + "channels", G_TYPE_INT, + gst_sbc_get_channel_number( + dec->sbc.mode), + NULL); + + template = gst_static_pad_template_get(&sbc_dec_src_factory); + + dec->outcaps = gst_caps_intersect(caps, + gst_pad_template_get_caps(template)); + + gst_caps_unref(caps); + } + + gst_buffer_set_caps(output, dec->outcaps); + + /* FIXME get a real timestamp */ + GST_BUFFER_TIMESTAMP(output) = GST_CLOCK_TIME_NONE; + + res = gst_pad_push(dec->srcpad, output); + if (res != GST_FLOW_OK) + goto done; + + offset += consumed; + } + + if (offset < size) + dec->buffer = gst_buffer_create_sub(buffer, + offset, size - offset); + +done: + gst_buffer_unref(buffer); + gst_object_unref(dec); + + return res; +} + +static GstStateChangeReturn sbc_dec_change_state(GstElement *element, + GstStateChange transition) +{ + GstSbcDec *dec = GST_SBC_DEC(element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG("Setup subband codec"); + if (dec->buffer) { + gst_buffer_unref(dec->buffer); + dec->buffer = NULL; + } + sbc_init(&dec->sbc, 0); + dec->outcaps = NULL; + break; + + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG("Finish subband codec"); + if (dec->buffer) { + gst_buffer_unref(dec->buffer); + dec->buffer = NULL; + } + sbc_finish(&dec->sbc); + if (dec->outcaps) { + gst_caps_unref(dec->outcaps); + dec->outcaps = NULL; + } + break; + + default: + break; + } + + return parent_class->change_state(element, transition); +} + +static void gst_sbc_dec_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_dec_sink_factory)); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_dec_src_factory)); + + gst_element_class_set_details(element_class, &sbc_dec_details); +} + +static void gst_sbc_dec_class_init(GstSbcDecClass *klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + element_class->change_state = GST_DEBUG_FUNCPTR(sbc_dec_change_state); + + GST_DEBUG_CATEGORY_INIT(sbc_dec_debug, "sbcdec", 0, + "SBC decoding element"); +} + +static void gst_sbc_dec_init(GstSbcDec *self, GstSbcDecClass *klass) +{ + self->sinkpad = gst_pad_new_from_static_template( + &sbc_dec_sink_factory, "sink"); + gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR( + sbc_dec_chain)); + gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); + + self->srcpad = gst_pad_new_from_static_template( + &sbc_dec_src_factory, "src"); + gst_element_add_pad(GST_ELEMENT(self), self->srcpad); + + self->outcaps = NULL; +} + +gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "sbcdec", GST_RANK_PRIMARY, + GST_TYPE_SBC_DEC); +} + + diff --git a/audio/gstsbcdec.h b/audio/gstsbcdec.h new file mode 100644 index 0000000..c77feae --- /dev/null +++ b/audio/gstsbcdec.h @@ -0,0 +1,66 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include "sbc.h" + +G_BEGIN_DECLS + +#define GST_TYPE_SBC_DEC \ + (gst_sbc_dec_get_type()) +#define GST_SBC_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_DEC,GstSbcDec)) +#define GST_SBC_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_DEC,GstSbcDecClass)) +#define GST_IS_SBC_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_DEC)) +#define GST_IS_SBC_DEC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_DEC)) + +typedef struct _GstSbcDec GstSbcDec; +typedef struct _GstSbcDecClass GstSbcDecClass; + +struct _GstSbcDec { + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + + GstBuffer *buffer; + + /* caps for outgoing buffers */ + GstCaps *outcaps; + + sbc_t sbc; +}; + +struct _GstSbcDecClass { + GstElementClass parent_class; +}; + +GType gst_sbc_dec_get_type(void); + +gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin); + +G_END_DECLS diff --git a/audio/gstsbcenc.c b/audio/gstsbcenc.c new file mode 100644 index 0000000..d2de4ee --- /dev/null +++ b/audio/gstsbcenc.c @@ -0,0 +1,603 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gstpragma.h" +#include "gstsbcutil.h" +#include "gstsbcenc.h" + +#define SBC_ENC_DEFAULT_MODE SBC_MODE_AUTO +#define SBC_ENC_DEFAULT_BLOCKS 0 +#define SBC_ENC_DEFAULT_SUB_BANDS 0 +#define SBC_ENC_DEFAULT_ALLOCATION SBC_AM_AUTO +#define SBC_ENC_DEFAULT_RATE 0 +#define SBC_ENC_DEFAULT_CHANNELS 0 + +#define SBC_ENC_BITPOOL_AUTO 1 +#define SBC_ENC_BITPOOL_MIN 2 +#define SBC_ENC_BITPOOL_MIN_STR "2" +#define SBC_ENC_BITPOOL_MAX 64 +#define SBC_ENC_BITPOOL_MAX_STR "64" + +GST_DEBUG_CATEGORY_STATIC(sbc_enc_debug); +#define GST_CAT_DEFAULT sbc_enc_debug + +#define GST_TYPE_SBC_MODE (gst_sbc_mode_get_type()) + +static GType gst_sbc_mode_get_type(void) +{ + static GType sbc_mode_type = 0; + static GEnumValue sbc_modes[] = { + { SBC_MODE_MONO, "Mono", "mono" }, + { SBC_MODE_DUAL_CHANNEL, "Dual Channel", "dual" }, + { SBC_MODE_STEREO, "Stereo", "stereo"}, + { SBC_MODE_JOINT_STEREO, "Joint Stereo", "joint" }, + { SBC_MODE_AUTO, "Auto", "auto" }, + { -1, NULL, NULL} + }; + + if (!sbc_mode_type) + sbc_mode_type = g_enum_register_static("GstSbcMode", sbc_modes); + + return sbc_mode_type; +} + +#define GST_TYPE_SBC_ALLOCATION (gst_sbc_allocation_get_type()) + +static GType gst_sbc_allocation_get_type(void) +{ + static GType sbc_allocation_type = 0; + static GEnumValue sbc_allocations[] = { + { SBC_AM_LOUDNESS, "Loudness", "loudness" }, + { SBC_AM_SNR, "SNR", "snr" }, + { SBC_AM_AUTO, "Auto", "auto" }, + { -1, NULL, NULL} + }; + + if (!sbc_allocation_type) + sbc_allocation_type = g_enum_register_static( + "GstSbcAllocation", sbc_allocations); + + return sbc_allocation_type; +} + +#define GST_TYPE_SBC_BLOCKS (gst_sbc_blocks_get_type()) + +static GType gst_sbc_blocks_get_type(void) +{ + static GType sbc_blocks_type = 0; + static GEnumValue sbc_blocks[] = { + { 0, "Auto", "auto" }, + { 4, "4", "4" }, + { 8, "8", "8" }, + { 12, "12", "12" }, + { 16, "16", "16" }, + { -1, NULL, NULL} + }; + + if (!sbc_blocks_type) + sbc_blocks_type = g_enum_register_static( + "GstSbcBlocks", sbc_blocks); + + return sbc_blocks_type; +} + +#define GST_TYPE_SBC_SUBBANDS (gst_sbc_subbands_get_type()) + +static GType gst_sbc_subbands_get_type(void) +{ + static GType sbc_subbands_type = 0; + static GEnumValue sbc_subbands[] = { + { 0, "Auto", "auto" }, + { 4, "4 subbands", "4" }, + { 8, "8 subbands", "8" }, + { -1, NULL, NULL} + }; + + if (!sbc_subbands_type) + sbc_subbands_type = g_enum_register_static( + "GstSbcSubbands", sbc_subbands); + + return sbc_subbands_type; +} + +enum { + PROP_0, + PROP_MODE, + PROP_ALLOCATION, + PROP_BLOCKS, + PROP_SUBBANDS, + PROP_BITPOOL +}; + +GST_BOILERPLATE(GstSbcEnc, gst_sbc_enc, GstElement, GST_TYPE_ELEMENT); + +static const GstElementDetails sbc_enc_details = + GST_ELEMENT_DETAILS("Bluetooth SBC encoder", + "Codec/Encoder/Audio", + "Encode a SBC audio stream", + "Marcel Holtmann "); + +static GstStaticPadTemplate sbc_enc_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-raw-int, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "endianness = (int) BYTE_ORDER, " + "signed = (boolean) true, " + "width = (int) 16, " + "depth = (int) 16")); + +static GstStaticPadTemplate sbc_enc_src_factory = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, " + "blocks = (int) { 4, 8, 12, 16 }, " + "subbands = (int) { 4, 8 }, " + "allocation = (string) { \"snr\", \"loudness\" }, " + "bitpool = (int) [ " SBC_ENC_BITPOOL_MIN_STR + ", " SBC_ENC_BITPOOL_MAX_STR " ]")); + +gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps); + +static GstCaps *sbc_enc_generate_srcpad_caps(GstSbcEnc *enc) +{ + GstCaps *src_caps; + GstStructure *structure; + GEnumValue *enum_value; + GEnumClass *enum_class; + GValue *value; + + src_caps = gst_caps_copy(gst_pad_get_pad_template_caps(enc->srcpad)); + structure = gst_caps_get_structure(src_caps, 0); + + value = g_new0(GValue, 1); + + if (enc->rate != 0) + gst_sbc_util_set_structure_int_param(structure, "rate", + enc->rate, value); + + if (enc->channels != 0) + gst_sbc_util_set_structure_int_param(structure, "channels", + enc->channels, value); + + if (enc->subbands != 0) + gst_sbc_util_set_structure_int_param(structure, "subbands", + enc->subbands, value); + + if (enc->blocks != 0) + gst_sbc_util_set_structure_int_param(structure, "blocks", + enc->blocks, value); + + if (enc->bitpool != SBC_ENC_BITPOOL_AUTO) + gst_sbc_util_set_structure_int_param(structure, "bitpool", + enc->bitpool, value); + + if (enc->mode != SBC_ENC_DEFAULT_MODE) { + enum_class = g_type_class_ref(GST_TYPE_SBC_MODE); + enum_value = g_enum_get_value(enum_class, enc->mode); + gst_sbc_util_set_structure_string_param(structure, "mode", + enum_value->value_nick, value); + g_type_class_unref(enum_class); + } + + if (enc->allocation != SBC_AM_AUTO) { + enum_class = g_type_class_ref(GST_TYPE_SBC_ALLOCATION); + enum_value = g_enum_get_value(enum_class, enc->allocation); + gst_sbc_util_set_structure_string_param(structure, "allocation", + enum_value->value_nick, value); + g_type_class_unref(enum_class); + } + + g_free(value); + + return src_caps; +} + +static GstCaps *sbc_enc_src_getcaps(GstPad *pad) +{ + GstSbcEnc *enc; + + enc = GST_SBC_ENC(GST_PAD_PARENT(pad)); + + return sbc_enc_generate_srcpad_caps(enc); +} + +static gboolean sbc_enc_src_setcaps(GstPad *pad, GstCaps *caps) +{ + GstSbcEnc *enc = GST_SBC_ENC(GST_PAD_PARENT(pad)); + + GST_LOG_OBJECT(enc, "setting srcpad caps"); + + return gst_sbc_enc_fill_sbc_params(enc, caps); +} + +static GstCaps *sbc_enc_src_caps_fixate(GstSbcEnc *enc, GstCaps *caps) +{ + gchar *error_message = NULL; + GstCaps *result; + + result = gst_sbc_util_caps_fixate(caps, &error_message); + + if (!result) { + GST_WARNING_OBJECT(enc, "Invalid input caps caused parsing " + "error: %s", error_message); + g_free(error_message); + return NULL; + } + + return result; +} + +static GstCaps *sbc_enc_get_fixed_srcpad_caps(GstSbcEnc *enc) +{ + GstCaps *caps; + gboolean res = TRUE; + GstCaps *result_caps = NULL; + + caps = gst_pad_get_allowed_caps(enc->srcpad); + if (caps == NULL) + caps = sbc_enc_src_getcaps(enc->srcpad); + + if (caps == GST_CAPS_NONE || gst_caps_is_empty(caps)) { + res = FALSE; + goto done; + } + + result_caps = sbc_enc_src_caps_fixate(enc, caps); + +done: + gst_caps_unref(caps); + + if (!res) + return NULL; + + return result_caps; +} + +static gboolean sbc_enc_sink_setcaps(GstPad *pad, GstCaps *caps) +{ + GstSbcEnc *enc; + GstStructure *structure; + GstCaps *src_caps; + gint rate, channels; + gboolean res; + + enc = GST_SBC_ENC(GST_PAD_PARENT(pad)); + structure = gst_caps_get_structure(caps, 0); + + if (!gst_structure_get_int(structure, "rate", &rate)) + return FALSE; + if (!gst_structure_get_int(structure, "channels", &channels)) + return FALSE; + + enc->rate = rate; + enc->channels = channels; + + src_caps = sbc_enc_get_fixed_srcpad_caps(enc); + if (!src_caps) + return FALSE; + res = gst_pad_set_caps(enc->srcpad, src_caps); + gst_caps_unref(src_caps); + + return res; +} + +gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps) +{ + if (!gst_caps_is_fixed(caps)) { + GST_DEBUG_OBJECT(enc, "didn't receive fixed caps, " + "returning false"); + return FALSE; + } + + if (!gst_sbc_util_fill_sbc_params(&enc->sbc, caps)) + return FALSE; + + if (enc->rate != 0 && gst_sbc_parse_rate_from_sbc(enc->sbc.frequency) + != enc->rate) + goto fail; + + if (enc->channels != 0 && gst_sbc_get_channel_number(enc->sbc.mode) + != enc->channels) + goto fail; + + if (enc->blocks != 0 && gst_sbc_parse_blocks_from_sbc(enc->sbc.blocks) + != enc->blocks) + goto fail; + + if (enc->subbands != 0 && gst_sbc_parse_subbands_from_sbc( + enc->sbc.subbands) != enc->subbands) + goto fail; + + if (enc->mode != SBC_ENC_DEFAULT_MODE && enc->sbc.mode != enc->mode) + goto fail; + + if (enc->allocation != SBC_AM_AUTO && + enc->sbc.allocation != enc->allocation) + goto fail; + + if (enc->bitpool != SBC_ENC_BITPOOL_AUTO && + enc->sbc.bitpool != enc->bitpool) + goto fail; + + enc->codesize = sbc_get_codesize(&enc->sbc); + enc->frame_length = sbc_get_frame_length(&enc->sbc); + enc->frame_duration = sbc_get_frame_duration(&enc->sbc); + + GST_DEBUG_OBJECT(enc, "codesize: %d, frame_length: %d, frame_duration:" + " %d", enc->codesize, enc->frame_length, + enc->frame_duration); + + return TRUE; + +fail: + memset(&enc->sbc, 0, sizeof(sbc_t)); + return FALSE; +} + +static GstFlowReturn sbc_enc_chain(GstPad *pad, GstBuffer *buffer) +{ + GstSbcEnc *enc = GST_SBC_ENC(gst_pad_get_parent(pad)); + GstAdapter *adapter = enc->adapter; + GstFlowReturn res = GST_FLOW_OK; + + gst_adapter_push(adapter, buffer); + + while (gst_adapter_available(adapter) >= enc->codesize && + res == GST_FLOW_OK) { + GstBuffer *output; + GstCaps *caps; + const guint8 *data; + gint consumed; + + caps = GST_PAD_CAPS(enc->srcpad); + res = gst_pad_alloc_buffer_and_set_caps(enc->srcpad, + GST_BUFFER_OFFSET_NONE, + enc->frame_length, caps, + &output); + if (res != GST_FLOW_OK) + goto done; + + data = gst_adapter_peek(adapter, enc->codesize); + + consumed = sbc_encode(&enc->sbc, (gpointer) data, + enc->codesize, + GST_BUFFER_DATA(output), + GST_BUFFER_SIZE(output), NULL); + if (consumed <= 0) { + GST_DEBUG_OBJECT(enc, "comsumed < 0, codesize: %d", + enc->codesize); + break; + } + gst_adapter_flush(adapter, consumed); + + GST_BUFFER_TIMESTAMP(output) = GST_BUFFER_TIMESTAMP(buffer); + /* we have only 1 frame */ + GST_BUFFER_DURATION(output) = enc->frame_duration; + + res = gst_pad_push(enc->srcpad, output); + + if (res != GST_FLOW_OK) + goto done; + } + +done: + gst_object_unref(enc); + + return res; +} + +static GstStateChangeReturn sbc_enc_change_state(GstElement *element, + GstStateChange transition) +{ + GstSbcEnc *enc = GST_SBC_ENC(element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG("Setup subband codec"); + sbc_init(&enc->sbc, 0); + break; + + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG("Finish subband codec"); + sbc_finish(&enc->sbc); + break; + + default: + break; + } + + return parent_class->change_state(element, transition); +} + +static void gst_sbc_enc_dispose(GObject *object) +{ + GstSbcEnc *enc = GST_SBC_ENC(object); + + if (enc->adapter != NULL) + g_object_unref(G_OBJECT(enc->adapter)); + + enc->adapter = NULL; +} + +static void gst_sbc_enc_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_enc_sink_factory)); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_enc_src_factory)); + + gst_element_class_set_details(element_class, &sbc_enc_details); +} + +static void gst_sbc_enc_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstSbcEnc *enc = GST_SBC_ENC(object); + + /* changes to those properties will only happen on the next caps + * negotiation */ + + switch (prop_id) { + case PROP_MODE: + enc->mode = g_value_get_enum(value); + break; + case PROP_ALLOCATION: + enc->allocation = g_value_get_enum(value); + break; + case PROP_BLOCKS: + enc->blocks = g_value_get_enum(value); + break; + case PROP_SUBBANDS: + enc->subbands = g_value_get_enum(value); + break; + case PROP_BITPOOL: + enc->bitpool = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_sbc_enc_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstSbcEnc *enc = GST_SBC_ENC(object); + + switch (prop_id) { + case PROP_MODE: + g_value_set_enum(value, enc->mode); + break; + case PROP_ALLOCATION: + g_value_set_enum(value, enc->allocation); + break; + case PROP_BLOCKS: + g_value_set_enum(value, enc->blocks); + break; + case PROP_SUBBANDS: + g_value_set_enum(value, enc->subbands); + break; + case PROP_BITPOOL: + g_value_set_int(value, enc->bitpool); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_sbc_enc_class_init(GstSbcEncClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + object_class->set_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_set_property); + object_class->get_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_get_property); + object_class->dispose = GST_DEBUG_FUNCPTR(gst_sbc_enc_dispose); + + element_class->change_state = GST_DEBUG_FUNCPTR(sbc_enc_change_state); + + g_object_class_install_property(object_class, PROP_MODE, + g_param_spec_enum("mode", "Mode", + "Encoding mode", GST_TYPE_SBC_MODE, + SBC_ENC_DEFAULT_MODE, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_ALLOCATION, + g_param_spec_enum("allocation", "Allocation", + "Allocation method", GST_TYPE_SBC_ALLOCATION, + SBC_ENC_DEFAULT_ALLOCATION, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_BLOCKS, + g_param_spec_enum("blocks", "Blocks", + "Blocks", GST_TYPE_SBC_BLOCKS, + SBC_ENC_DEFAULT_BLOCKS, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_SUBBANDS, + g_param_spec_enum("subbands", "Sub bands", + "Number of sub bands", GST_TYPE_SBC_SUBBANDS, + SBC_ENC_DEFAULT_SUB_BANDS, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_BITPOOL, + g_param_spec_int("bitpool", "Bitpool", + "Bitpool (use 1 for automatic selection)", + SBC_ENC_BITPOOL_AUTO, SBC_ENC_BITPOOL_MAX, + SBC_ENC_BITPOOL_AUTO, G_PARAM_READWRITE)); + + GST_DEBUG_CATEGORY_INIT(sbc_enc_debug, "sbcenc", 0, + "SBC encoding element"); +} + +static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass) +{ + self->sinkpad = gst_pad_new_from_static_template( + &sbc_enc_sink_factory, "sink"); + gst_pad_set_setcaps_function(self->sinkpad, + GST_DEBUG_FUNCPTR(sbc_enc_sink_setcaps)); + gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); + + self->srcpad = gst_pad_new_from_static_template( + &sbc_enc_src_factory, "src"); + gst_pad_set_getcaps_function(self->srcpad, + GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps)); + gst_pad_set_setcaps_function(self->srcpad, + GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps)); + gst_element_add_pad(GST_ELEMENT(self), self->srcpad); + + gst_pad_set_chain_function(self->sinkpad, + GST_DEBUG_FUNCPTR(sbc_enc_chain)); + + self->subbands = SBC_ENC_DEFAULT_SUB_BANDS; + self->blocks = SBC_ENC_DEFAULT_BLOCKS; + self->mode = SBC_ENC_DEFAULT_MODE; + self->allocation = SBC_ENC_DEFAULT_ALLOCATION; + self->rate = SBC_ENC_DEFAULT_RATE; + self->channels = SBC_ENC_DEFAULT_CHANNELS; + self->bitpool = SBC_ENC_BITPOOL_AUTO; + + self->frame_length = 0; + self->frame_duration = 0; + + self->adapter = gst_adapter_new(); +} + +gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "sbcenc", + GST_RANK_NONE, GST_TYPE_SBC_ENC); +} + + diff --git a/audio/gstsbcenc.h b/audio/gstsbcenc.h new file mode 100644 index 0000000..0329351 --- /dev/null +++ b/audio/gstsbcenc.h @@ -0,0 +1,75 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#include "sbc.h" + +G_BEGIN_DECLS + +#define GST_TYPE_SBC_ENC \ + (gst_sbc_enc_get_type()) +#define GST_SBC_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_ENC,GstSbcEnc)) +#define GST_SBC_ENC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_ENC,GstSbcEncClass)) +#define GST_IS_SBC_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_ENC)) +#define GST_IS_SBC_ENC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_ENC)) + +typedef struct _GstSbcEnc GstSbcEnc; +typedef struct _GstSbcEncClass GstSbcEncClass; + +struct _GstSbcEnc { + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + GstAdapter *adapter; + + gint rate; + gint channels; + gint mode; + gint blocks; + gint allocation; + gint subbands; + gint bitpool; + + guint codesize; + gint frame_length; + gint frame_duration; + + sbc_t sbc; +}; + +struct _GstSbcEncClass { + GstElementClass parent_class; +}; + +GType gst_sbc_enc_get_type(void); + +gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin); + +G_END_DECLS diff --git a/audio/gstsbcparse.c b/audio/gstsbcparse.c new file mode 100644 index 0000000..a44b52c --- /dev/null +++ b/audio/gstsbcparse.c @@ -0,0 +1,220 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gstpragma.h" +#include "gstsbcutil.h" +#include "gstsbcparse.h" + +GST_DEBUG_CATEGORY_STATIC(sbc_parse_debug); +#define GST_CAT_DEFAULT sbc_parse_debug + +GST_BOILERPLATE(GstSbcParse, gst_sbc_parse, GstElement, GST_TYPE_ELEMENT); + +static const GstElementDetails sbc_parse_details = + GST_ELEMENT_DETAILS("Bluetooth SBC parser", + "Codec/Parser/Audio", + "Parse a SBC audio stream", + "Marcel Holtmann "); + +static GstStaticPadTemplate sbc_parse_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc," + "parsed = (boolean) false")); + +static GstStaticPadTemplate sbc_parse_src_factory = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, " + "blocks = (int) { 4, 8, 12, 16 }, " + "subbands = (int) { 4, 8 }, " + "allocation = (string) { \"snr\", \"loudness\" }," + "bitpool = (int) [ 2, 64 ]," + "parsed = (boolean) true")); + +static GstFlowReturn sbc_parse_chain(GstPad *pad, GstBuffer *buffer) +{ + GstSbcParse *parse = GST_SBC_PARSE(gst_pad_get_parent(pad)); + GstFlowReturn res = GST_FLOW_OK; + guint size, offset = 0; + guint8 *data; + + /* FIXME use a gstadpter */ + if (parse->buffer) { + GstBuffer *temp; + temp = buffer; + buffer = gst_buffer_span(parse->buffer, 0, buffer, + GST_BUFFER_SIZE(parse->buffer) + + GST_BUFFER_SIZE(buffer)); + gst_buffer_unref(parse->buffer); + gst_buffer_unref(temp); + parse->buffer = NULL; + } + + data = GST_BUFFER_DATA(buffer); + size = GST_BUFFER_SIZE(buffer); + + while (offset < size) { + GstBuffer *output; + int consumed; + + consumed = sbc_parse(&parse->new_sbc, data + offset, + size - offset); + if (consumed <= 0) + break; + + if (parse->first_parsing || (memcmp(&parse->sbc, + &parse->new_sbc, sizeof(sbc_t)) != 0)) { + + memcpy(&parse->sbc, &parse->new_sbc, sizeof(sbc_t)); + if (parse->outcaps != NULL) + gst_caps_unref(parse->outcaps); + + parse->outcaps = gst_sbc_parse_caps_from_sbc( + &parse->sbc); + + parse->first_parsing = FALSE; + } + + res = gst_pad_alloc_buffer_and_set_caps(parse->srcpad, + GST_BUFFER_OFFSET_NONE, + consumed, parse->outcaps, &output); + + if (res != GST_FLOW_OK) + goto done; + + memcpy(GST_BUFFER_DATA(output), data + offset, consumed); + + res = gst_pad_push(parse->srcpad, output); + if (res != GST_FLOW_OK) + goto done; + + offset += consumed; + } + + if (offset < size) + parse->buffer = gst_buffer_create_sub(buffer, + offset, size - offset); + +done: + gst_buffer_unref(buffer); + gst_object_unref(parse); + + return res; +} + +static GstStateChangeReturn sbc_parse_change_state(GstElement *element, + GstStateChange transition) +{ + GstSbcParse *parse = GST_SBC_PARSE(element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG("Setup subband codec"); + + parse->channels = -1; + parse->rate = -1; + parse->first_parsing = TRUE; + + sbc_init(&parse->sbc, 0); + break; + + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG("Finish subband codec"); + + if (parse->buffer) { + gst_buffer_unref(parse->buffer); + parse->buffer = NULL; + } + if (parse->outcaps != NULL) { + gst_caps_unref(parse->outcaps); + parse->outcaps = NULL; + } + + sbc_finish(&parse->sbc); + break; + + default: + break; + } + + return parent_class->change_state(element, transition); +} + +static void gst_sbc_parse_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_parse_sink_factory)); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_parse_src_factory)); + + gst_element_class_set_details(element_class, &sbc_parse_details); +} + +static void gst_sbc_parse_class_init(GstSbcParseClass *klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + element_class->change_state = GST_DEBUG_FUNCPTR(sbc_parse_change_state); + + GST_DEBUG_CATEGORY_INIT(sbc_parse_debug, "sbcparse", 0, + "SBC parsing element"); +} + +static void gst_sbc_parse_init(GstSbcParse *self, GstSbcParseClass *klass) +{ + self->sinkpad = gst_pad_new_from_static_template( + &sbc_parse_sink_factory, "sink"); + gst_pad_set_chain_function(self->sinkpad, + GST_DEBUG_FUNCPTR(sbc_parse_chain)); + gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); + + self->srcpad = gst_pad_new_from_static_template( + &sbc_parse_src_factory, "src"); + gst_element_add_pad(GST_ELEMENT(self), self->srcpad); + + self->outcaps = NULL; + self->buffer = NULL; + self->channels = -1; + self->rate = -1; + self->first_parsing = TRUE; +} + +gboolean gst_sbc_parse_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "sbcparse", GST_RANK_NONE, + GST_TYPE_SBC_PARSE); +} + diff --git a/audio/gstsbcparse.h b/audio/gstsbcparse.h new file mode 100644 index 0000000..ecb8be4 --- /dev/null +++ b/audio/gstsbcparse.h @@ -0,0 +1,69 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include "sbc.h" + +G_BEGIN_DECLS + +#define GST_TYPE_SBC_PARSE \ + (gst_sbc_parse_get_type()) +#define GST_SBC_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_PARSE,GstSbcParse)) +#define GST_SBC_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_PARSE,GstSbcParseClass)) +#define GST_IS_SBC_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_PARSE)) +#define GST_IS_SBC_PARSE_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_PARSE)) + +typedef struct _GstSbcParse GstSbcParse; +typedef struct _GstSbcParseClass GstSbcParseClass; + +struct _GstSbcParse { + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + + GstBuffer *buffer; + + sbc_t sbc; + sbc_t new_sbc; + GstCaps *outcaps; + gboolean first_parsing; + + gint channels; + gint rate; +}; + +struct _GstSbcParseClass { + GstElementClass parent_class; +}; + +GType gst_sbc_parse_get_type(void); + +gboolean gst_sbc_parse_plugin_init(GstPlugin *plugin); + +G_END_DECLS diff --git a/audio/gstsbcutil.c b/audio/gstsbcutil.c new file mode 100644 index 0000000..63c90c2 --- /dev/null +++ b/audio/gstsbcutil.c @@ -0,0 +1,521 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include "gstsbcutil.h" + +/* + * Selects one rate from a list of possible rates + * TODO - use a better approach to this (it is selecting the last element) + */ +gint gst_sbc_select_rate_from_list(const GValue *value) +{ + guint size = gst_value_list_get_size(value); + return g_value_get_int(gst_value_list_get_value(value, size-1)); +} + +/* + * Selects one number of channels option from a range of possible numbers + * TODO - use a better approach to this (it is selecting the maximum value) + */ +gint gst_sbc_select_channels_from_range(const GValue *value) +{ + return gst_value_get_int_range_max(value); +} + +/* + * Selects one number of blocks from a list of possible blocks + * TODO - use a better approach to this (it is selecting the last element) + */ +gint gst_sbc_select_blocks_from_list(const GValue *value) +{ + guint size = gst_value_list_get_size(value); + return g_value_get_int(gst_value_list_get_value(value, size-1)); +} + +/* + * Selects one number of subbands from a list + * TODO - use a better approach to this (it is selecting the last element) + */ +gint gst_sbc_select_subbands_from_list(const GValue *value) +{ + guint size = gst_value_list_get_size(value); + return g_value_get_int(gst_value_list_get_value(value, size-1)); +} + +/* + * Selects one bitpool option from a range + * TODO - use a better approach to this (it is selecting the maximum value) + */ +gint gst_sbc_select_bitpool_from_range(const GValue *value) +{ + return gst_value_get_int_range_max(value); +} + +/* + * Selects one allocation mode from the ones on the list + * TODO - use a better approach + */ +const gchar *gst_sbc_get_allocation_from_list(const GValue *value) +{ + guint size = gst_value_list_get_size(value); + return g_value_get_string(gst_value_list_get_value(value, size-1)); +} + +/* + * Selects one mode from the ones on the list + */ +const gchar *gst_sbc_get_mode_from_list(const GValue *list, gint channels) +{ + unsigned int i; + const GValue *value; + const gchar *aux; + gboolean joint, stereo, dual, mono; + guint size = gst_value_list_get_size(list); + + joint = stereo = dual = mono = FALSE; + + for (i = 0; i < size; i++) { + value = gst_value_list_get_value(list, i); + aux = g_value_get_string(value); + if (strcmp("joint", aux) == 0) + joint = TRUE; + else if (strcmp("stereo", aux) == 0) + stereo = TRUE; + else if (strcmp("dual", aux) == 0) + dual = TRUE; + else if (strcmp("mono", aux) == 0) + mono = TRUE; + } + + if (channels == 1 && mono) + return "mono"; + else if (channels == 2) { + if (joint) + return "joint"; + else if (stereo) + return "stereo"; + else if (dual) + return "dual"; + } + + return NULL; +} + +gint gst_sbc_parse_rate_from_sbc(gint frequency) +{ + switch (frequency) { + case SBC_FREQ_16000: + return 16000; + case SBC_FREQ_32000: + return 32000; + case SBC_FREQ_44100: + return 44100; + case SBC_FREQ_48000: + return 48000; + default: + return 0; + } +} + +gint gst_sbc_parse_rate_to_sbc(gint rate) +{ + switch (rate) { + case 16000: + return SBC_FREQ_16000; + case 32000: + return SBC_FREQ_32000; + case 44100: + return SBC_FREQ_44100; + case 48000: + return SBC_FREQ_48000; + default: + return -1; + } +} + +gint gst_sbc_get_channel_number(gint mode) +{ + switch (mode) { + case SBC_MODE_JOINT_STEREO: + case SBC_MODE_STEREO: + case SBC_MODE_DUAL_CHANNEL: + return 2; + case SBC_MODE_MONO: + return 1; + default: + return 0; + } +} + +gint gst_sbc_parse_subbands_from_sbc(gint subbands) +{ + switch (subbands) { + case SBC_SB_4: + return 4; + case SBC_SB_8: + return 8; + default: + return 0; + } +} + +gint gst_sbc_parse_subbands_to_sbc(gint subbands) +{ + switch (subbands) { + case 4: + return SBC_SB_4; + case 8: + return SBC_SB_8; + default: + return -1; + } +} + +gint gst_sbc_parse_blocks_from_sbc(gint blocks) +{ + switch (blocks) { + case SBC_BLK_4: + return 4; + case SBC_BLK_8: + return 8; + case SBC_BLK_12: + return 12; + case SBC_BLK_16: + return 16; + default: + return 0; + } +} + +gint gst_sbc_parse_blocks_to_sbc(gint blocks) +{ + switch (blocks) { + case 4: + return SBC_BLK_4; + case 8: + return SBC_BLK_8; + case 12: + return SBC_BLK_12; + case 16: + return SBC_BLK_16; + default: + return -1; + } +} + +const gchar *gst_sbc_parse_mode_from_sbc(gint mode) +{ + switch (mode) { + case SBC_MODE_MONO: + return "mono"; + case SBC_MODE_DUAL_CHANNEL: + return "dual"; + case SBC_MODE_STEREO: + return "stereo"; + case SBC_MODE_JOINT_STEREO: + case SBC_MODE_AUTO: + return "joint"; + default: + return NULL; + } +} + +gint gst_sbc_parse_mode_to_sbc(const gchar *mode) +{ + if (g_ascii_strcasecmp(mode, "joint") == 0) + return SBC_MODE_JOINT_STEREO; + else if (g_ascii_strcasecmp(mode, "stereo") == 0) + return SBC_MODE_STEREO; + else if (g_ascii_strcasecmp(mode, "dual") == 0) + return SBC_MODE_DUAL_CHANNEL; + else if (g_ascii_strcasecmp(mode, "mono") == 0) + return SBC_MODE_MONO; + else if (g_ascii_strcasecmp(mode, "auto") == 0) + return SBC_MODE_JOINT_STEREO; + else + return -1; +} + +const gchar *gst_sbc_parse_allocation_from_sbc(gint alloc) +{ + switch (alloc) { + case SBC_AM_LOUDNESS: + return "loudness"; + case SBC_AM_SNR: + return "snr"; + case SBC_AM_AUTO: + return "loudness"; + default: + return NULL; + } +} + +gint gst_sbc_parse_allocation_to_sbc(const gchar *allocation) +{ + if (g_ascii_strcasecmp(allocation, "loudness") == 0) + return SBC_AM_LOUDNESS; + else if (g_ascii_strcasecmp(allocation, "snr") == 0) + return SBC_AM_SNR; + else + return SBC_AM_LOUDNESS; +} + +GstCaps *gst_sbc_parse_caps_from_sbc(sbc_t *sbc) +{ + GstCaps *caps; + const gchar *mode_str; + const gchar *allocation_str; + + mode_str = gst_sbc_parse_mode_from_sbc(sbc->mode); + allocation_str = gst_sbc_parse_allocation_from_sbc(sbc->allocation); + caps = gst_caps_new_simple("audio/x-sbc", + "rate", G_TYPE_INT, + gst_sbc_parse_rate_from_sbc(sbc->frequency), + "channels", G_TYPE_INT, + gst_sbc_get_channel_number(sbc->mode), + "mode", G_TYPE_STRING, mode_str, + "subbands", G_TYPE_INT, + gst_sbc_parse_subbands_from_sbc(sbc->subbands), + "blocks", G_TYPE_INT, + gst_sbc_parse_blocks_from_sbc(sbc->blocks), + "allocation", G_TYPE_STRING, allocation_str, + "bitpool", G_TYPE_INT, sbc->bitpool, + NULL); + + return caps; +} + +/* + * Given a GstCaps, this will return a fixed GstCaps on sucessfull conversion. + * If an error occurs, it will return NULL and error_message will contain the + * error message. + * + * error_message must be passed NULL, if an error occurs, the caller has the + * ownership of the error_message, it must be freed after use. + */ +GstCaps *gst_sbc_util_caps_fixate(GstCaps *caps, gchar **error_message) +{ + GstCaps *result; + GstStructure *structure; + const GValue *value; + gboolean error = FALSE; + gint temp, rate, channels, blocks, subbands, bitpool; + const gchar *allocation = NULL; + const gchar *mode = NULL; + + g_assert(*error_message == NULL); + + structure = gst_caps_get_structure(caps, 0); + + if (!gst_structure_has_field(structure, "rate")) { + error = TRUE; + *error_message = g_strdup("no rate"); + goto error; + } else { + value = gst_structure_get_value(structure, "rate"); + if (GST_VALUE_HOLDS_LIST(value)) + temp = gst_sbc_select_rate_from_list(value); + else + temp = g_value_get_int(value); + rate = temp; + } + + if (!gst_structure_has_field(structure, "channels")) { + error = TRUE; + *error_message = g_strdup("no channels"); + goto error; + } else { + value = gst_structure_get_value(structure, "channels"); + if (GST_VALUE_HOLDS_INT_RANGE(value)) + temp = gst_sbc_select_channels_from_range(value); + else + temp = g_value_get_int(value); + channels = temp; + } + + if (!gst_structure_has_field(structure, "blocks")) { + error = TRUE; + *error_message = g_strdup("no blocks."); + goto error; + } else { + value = gst_structure_get_value(structure, "blocks"); + if (GST_VALUE_HOLDS_LIST(value)) + temp = gst_sbc_select_blocks_from_list(value); + else + temp = g_value_get_int(value); + blocks = temp; + } + + if (!gst_structure_has_field(structure, "subbands")) { + error = TRUE; + *error_message = g_strdup("no subbands"); + goto error; + } else { + value = gst_structure_get_value(structure, "subbands"); + if (GST_VALUE_HOLDS_LIST(value)) + temp = gst_sbc_select_subbands_from_list(value); + else + temp = g_value_get_int(value); + subbands = temp; + } + + if (!gst_structure_has_field(structure, "bitpool")) { + error = TRUE; + *error_message = g_strdup("no bitpool"); + goto error; + } else { + value = gst_structure_get_value(structure, "bitpool"); + if (GST_VALUE_HOLDS_INT_RANGE(value)) + temp = gst_sbc_select_bitpool_from_range(value); + else + temp = g_value_get_int(value); + bitpool = temp; + } + + if (!gst_structure_has_field(structure, "allocation")) { + error = TRUE; + *error_message = g_strdup("no allocation"); + goto error; + } else { + value = gst_structure_get_value(structure, "allocation"); + if (GST_VALUE_HOLDS_LIST(value)) + allocation = gst_sbc_get_allocation_from_list(value); + else + allocation = g_value_get_string(value); + } + + if (!gst_structure_has_field(structure, "mode")) { + error = TRUE; + *error_message = g_strdup("no mode"); + goto error; + } else { + value = gst_structure_get_value(structure, "mode"); + if (GST_VALUE_HOLDS_LIST(value)) { + mode = gst_sbc_get_mode_from_list(value, channels); + } else + mode = g_value_get_string(value); + } + + /* perform validation + * if channels is 1, we must have channel mode = mono + * if channels is 2, we can't have channel mode = mono */ + if ( (channels == 1 && (strcmp(mode, "mono") != 0) ) || + ( channels == 2 && ( strcmp(mode, "mono") == 0))) { + *error_message = g_strdup_printf("Invalid combination of " + "channels (%d) and channel mode (%s)", + channels, mode); + error = TRUE; + } + +error: + if (error) + return NULL; + + result = gst_caps_new_simple("audio/x-sbc", + "rate", G_TYPE_INT, rate, + "channels", G_TYPE_INT, channels, + "mode", G_TYPE_STRING, mode, + "blocks", G_TYPE_INT, blocks, + "subbands", G_TYPE_INT, subbands, + "allocation", G_TYPE_STRING, allocation, + "bitpool", G_TYPE_INT, bitpool, + NULL); + + return result; +} + +/** + * Sets the int field_value to the param "field" on the structure. + * value is used to do the operation, it must be a uninitialized (zero-filled) + * GValue, it will be left unitialized at the end of the function. + */ +void gst_sbc_util_set_structure_int_param(GstStructure *structure, + const gchar *field, gint field_value, + GValue *value) +{ + value = g_value_init(value, G_TYPE_INT); + g_value_set_int(value, field_value); + gst_structure_set_value(structure, field, value); + g_value_unset(value); +} + +/** + * Sets the string field_value to the param "field" on the structure. + * value is used to do the operation, it must be a uninitialized (zero-filled) + * GValue, it will be left unitialized at the end of the function. + */ +void gst_sbc_util_set_structure_string_param(GstStructure *structure, + const gchar *field, const gchar *field_value, + GValue *value) +{ + value = g_value_init(value, G_TYPE_STRING); + g_value_set_string(value, field_value); + gst_structure_set_value(structure, field, value); + g_value_unset(value); +} + +gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps) +{ + GstStructure *structure; + gint rate, channels, subbands, blocks, bitpool; + const gchar *mode; + const gchar *allocation; + + g_assert(gst_caps_is_fixed(caps)); + + structure = gst_caps_get_structure(caps, 0); + + if (!gst_structure_get_int(structure, "rate", &rate)) + return FALSE; + if (!gst_structure_get_int(structure, "channels", &channels)) + return FALSE; + if (!gst_structure_get_int(structure, "subbands", &subbands)) + return FALSE; + if (!gst_structure_get_int(structure, "blocks", &blocks)) + return FALSE; + if (!gst_structure_get_int(structure, "bitpool", &bitpool)) + return FALSE; + + if (!(mode = gst_structure_get_string(structure, "mode"))) + return FALSE; + if (!(allocation = gst_structure_get_string(structure, "allocation"))) + return FALSE; + + if (channels == 1 && strcmp(mode, "mono") != 0) + return FALSE; + + sbc->frequency = gst_sbc_parse_rate_to_sbc(rate); + sbc->blocks = gst_sbc_parse_blocks_to_sbc(blocks); + sbc->subbands = gst_sbc_parse_subbands_to_sbc(subbands); + sbc->bitpool = bitpool; + sbc->mode = gst_sbc_parse_mode_to_sbc(mode); + sbc->allocation = gst_sbc_parse_allocation_to_sbc(allocation); + + return TRUE; +} + diff --git a/audio/gstsbcutil.h b/audio/gstsbcutil.h new file mode 100644 index 0000000..a7f84d5 --- /dev/null +++ b/audio/gstsbcutil.h @@ -0,0 +1,75 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include "sbc.h" +#include + +#define SBC_AM_AUTO 0x02 +#define SBC_MODE_AUTO 0x04 + +gint gst_sbc_select_rate_from_list(const GValue *value); + +gint gst_sbc_select_channels_from_range(const GValue *value); + +gint gst_sbc_select_blocks_from_list(const GValue *value); + +gint gst_sbc_select_subbands_from_list(const GValue *value); + +gint gst_sbc_select_bitpool_from_range(const GValue *value); + +const gchar *gst_sbc_get_allocation_from_list(const GValue *value); + +const gchar *gst_sbc_get_mode_from_list(const GValue *value, gint channels); + +gint gst_sbc_get_channel_number(gint mode); +gint gst_sbc_parse_rate_from_sbc(gint frequency); +gint gst_sbc_parse_rate_to_sbc(gint rate); + +gint gst_sbc_parse_subbands_from_sbc(gint subbands); +gint gst_sbc_parse_subbands_to_sbc(gint subbands); + +gint gst_sbc_parse_blocks_from_sbc(gint blocks); +gint gst_sbc_parse_blocks_to_sbc(gint blocks); + +const gchar *gst_sbc_parse_mode_from_sbc(gint mode); +gint gst_sbc_parse_mode_to_sbc(const gchar *mode); + +const gchar *gst_sbc_parse_allocation_from_sbc(gint alloc); +gint gst_sbc_parse_allocation_to_sbc(const gchar *allocation); + +GstCaps* gst_sbc_parse_caps_from_sbc(sbc_t *sbc); + +GstCaps* gst_sbc_util_caps_fixate(GstCaps *caps, gchar** error_message); + +void gst_sbc_util_set_structure_int_param(GstStructure *structure, + const gchar* field, gint field_value, + GValue *value); + +void gst_sbc_util_set_structure_string_param(GstStructure *structure, + const gchar* field, const gchar* field_value, + GValue *value); + +gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps); + diff --git a/audio/headset.c b/audio/headset.c new file mode 100644 index 0000000..dff10d1 --- /dev/null +++ b/audio/headset.c @@ -0,0 +1,2951 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "log.h" +#include "device.h" +#include "manager.h" +#include "error.h" +#include "telephony.h" +#include "headset.h" +#include "glib-helper.h" +#include "btio.h" +#include "dbus-common.h" +#include "../src/adapter.h" +#include "../src/device.h" + +#define DC_TIMEOUT 3 + +#define RING_INTERVAL 3 + +#define BUF_SIZE 1024 + +#define HEADSET_GAIN_SPEAKER 'S' +#define HEADSET_GAIN_MICROPHONE 'M' + +static struct { + gboolean telephony_ready; /* Telephony plugin initialized */ + uint32_t features; /* HFP AG features */ + const struct indicator *indicators; /* Available HFP indicators */ + int er_mode; /* Event reporting mode */ + int er_ind; /* Event reporting for indicators */ + int rh; /* Response and Hold state */ + char *number; /* Incoming phone number */ + int number_type; /* Incoming number type */ + guint ring_timer; /* For incoming call indication */ + const char *chld; /* Response to AT+CHLD=? */ +} ag = { + .telephony_ready = FALSE, + .features = 0, + .er_mode = 3, + .er_ind = 0, + .rh = BTRH_NOT_SUPPORTED, + .number = NULL, + .number_type = 0, + .ring_timer = 0, +}; + +static gboolean sco_hci = TRUE; +static gboolean fast_connectable = FALSE; + +static GSList *active_devices = NULL; + +static char *str_state[] = { + "HEADSET_STATE_DISCONNECTED", + "HEADSET_STATE_CONNECTING", + "HEADSET_STATE_CONNECTED", + "HEADSET_STATE_PLAY_IN_PROGRESS", + "HEADSET_STATE_PLAYING", +}; + +struct headset_state_callback { + headset_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct headset_nrec_callback { + unsigned int id; + headset_nrec_cb cb; + void *user_data; +}; + +struct connect_cb { + unsigned int id; + headset_stream_cb_t cb; + void *cb_data; +}; + +struct pending_connect { + DBusMessage *msg; + DBusPendingCall *call; + GIOChannel *io; + int err; + headset_state_t target_state; + GSList *callbacks; + uint16_t svclass; +}; + +struct headset_slc { + char buf[BUF_SIZE]; + int data_start; + int data_length; + + gboolean cli_active; + gboolean cme_enabled; + gboolean cwa_enabled; + gboolean pending_ring; + gboolean inband_ring; + gboolean nrec; + gboolean nrec_req; + + int sp_gain; + int mic_gain; + + unsigned int hf_features; +}; + +struct headset { + uint32_t hsp_handle; + uint32_t hfp_handle; + + int rfcomm_ch; + + GIOChannel *rfcomm; + GIOChannel *tmp_rfcomm; + GIOChannel *sco; + guint sco_id; + + gboolean auto_dc; + + guint dc_timer; + + gboolean hfp_active; + gboolean search_hfp; + + headset_state_t state; + struct pending_connect *pending; + + headset_lock_t lock; + struct headset_slc *slc; + GSList *nrec_cbs; +}; + +struct event { + const char *cmd; + int (*callback) (struct audio_device *device, const char *buf); +}; + +static GSList *headset_callbacks = NULL; + +static void error_connect_failed(DBusConnection *conn, DBusMessage *msg, + int err) +{ + DBusMessage *reply = btd_error_failed(msg, + err < 0 ? strerror(-err) : "Connect failed"); + g_dbus_send_message(conn, reply); +} + +static int rfcomm_connect(struct audio_device *device, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id); +static int get_records(struct audio_device *device, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id); + +static void print_ag_features(uint32_t features) +{ + GString *gstr; + char *str; + + if (features == 0) { + DBG("HFP AG features: (none)"); + return; + } + + gstr = g_string_new("HFP AG features: "); + + if (features & AG_FEATURE_THREE_WAY_CALLING) + g_string_append(gstr, "\"Three-way calling\" "); + if (features & AG_FEATURE_EC_ANDOR_NR) + g_string_append(gstr, "\"EC and/or NR function\" "); + if (features & AG_FEATURE_VOICE_RECOGNITION) + g_string_append(gstr, "\"Voice recognition function\" "); + if (features & AG_FEATURE_INBAND_RINGTONE) + g_string_append(gstr, "\"In-band ring tone capability\" "); + if (features & AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG) + g_string_append(gstr, "\"Attach a number to a voice tag\" "); + if (features & AG_FEATURE_REJECT_A_CALL) + g_string_append(gstr, "\"Ability to reject a call\" "); + if (features & AG_FEATURE_ENHANCED_CALL_STATUS) + g_string_append(gstr, "\"Enhanced call status\" "); + if (features & AG_FEATURE_ENHANCED_CALL_CONTROL) + g_string_append(gstr, "\"Enhanced call control\" "); + if (features & AG_FEATURE_EXTENDED_ERROR_RESULT_CODES) + g_string_append(gstr, "\"Extended Error Result Codes\" "); + + str = g_string_free(gstr, FALSE); + + DBG("%s", str); + + g_free(str); +} + +static void print_hf_features(uint32_t features) +{ + GString *gstr; + char *str; + + if (features == 0) { + DBG("HFP HF features: (none)"); + return; + } + + gstr = g_string_new("HFP HF features: "); + + if (features & HF_FEATURE_EC_ANDOR_NR) + g_string_append(gstr, "\"EC and/or NR function\" "); + if (features & HF_FEATURE_CALL_WAITING_AND_3WAY) + g_string_append(gstr, "\"Call waiting and 3-way calling\" "); + if (features & HF_FEATURE_CLI_PRESENTATION) + g_string_append(gstr, "\"CLI presentation capability\" "); + if (features & HF_FEATURE_VOICE_RECOGNITION) + g_string_append(gstr, "\"Voice recognition activation\" "); + if (features & HF_FEATURE_REMOTE_VOLUME_CONTROL) + g_string_append(gstr, "\"Remote volume control\" "); + if (features & HF_FEATURE_ENHANCED_CALL_STATUS) + g_string_append(gstr, "\"Enhanced call status\" "); + if (features & HF_FEATURE_ENHANCED_CALL_CONTROL) + g_string_append(gstr, "\"Enhanced call control\" "); + + str = g_string_free(gstr, FALSE); + + DBG("%s", str); + + g_free(str); +} + +static const char *state2str(headset_state_t state) +{ + switch (state) { + case HEADSET_STATE_DISCONNECTED: + return "disconnected"; + case HEADSET_STATE_CONNECTING: + return "connecting"; + case HEADSET_STATE_CONNECTED: + case HEADSET_STATE_PLAY_IN_PROGRESS: + return "connected"; + case HEADSET_STATE_PLAYING: + return "playing"; + } + + return NULL; +} + +static int headset_send_valist(struct headset *hs, char *format, va_list ap) +{ + char rsp[BUF_SIZE]; + ssize_t total_written, count; + int fd; + + count = vsnprintf(rsp, sizeof(rsp), format, ap); + + if (count < 0) + return -EINVAL; + + if (!hs->rfcomm) { + error("headset_send: the headset is not connected"); + return -EIO; + } + + total_written = 0; + fd = g_io_channel_unix_get_fd(hs->rfcomm); + + while (total_written < count) { + ssize_t written; + + written = write(fd, rsp + total_written, + count - total_written); + if (written < 0) + return -errno; + + total_written += written; + } + + return 0; +} + +static int headset_send(struct headset *hs, char *format, ...) +{ + va_list ap; + int ret; + + va_start(ap, format); + ret = headset_send_valist(hs, format, ap); + va_end(ap); + + return ret; +} + +static int supported_features(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + int err; + + if (strlen(buf) < 9) + return -EINVAL; + + slc->hf_features = strtoul(&buf[8], NULL, 10); + + print_hf_features(slc->hf_features); + + err = headset_send(hs, "\r\n+BRSF: %u\r\n", ag.features); + if (err < 0) + return err; + + return headset_send(hs, "\r\nOK\r\n"); +} + +static char *indicator_ranges(const struct indicator *indicators) +{ + int i; + GString *gstr; + + gstr = g_string_new("\r\n+CIND: "); + + for (i = 0; indicators[i].desc != NULL; i++) { + if (i == 0) + g_string_append_printf(gstr, "(\"%s\",(%s))", + indicators[i].desc, + indicators[i].range); + else + g_string_append_printf(gstr, ",(\"%s\",(%s))", + indicators[i].desc, + indicators[i].range); + } + + g_string_append(gstr, "\r\n"); + + return g_string_free(gstr, FALSE); +} + +static char *indicator_values(const struct indicator *indicators) +{ + int i; + GString *gstr; + + gstr = g_string_new("\r\n+CIND: "); + + for (i = 0; indicators[i].desc != NULL; i++) { + if (i == 0) + g_string_append_printf(gstr, "%d", indicators[i].val); + else + g_string_append_printf(gstr, ",%d", indicators[i].val); + } + + g_string_append(gstr, "\r\n"); + + return g_string_free(gstr, FALSE); +} + +static int report_indicators(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + int err; + char *str; + + if (strlen(buf) < 8) + return -EINVAL; + + if (ag.indicators == NULL) { + error("HFP AG indicators not initialized"); + return headset_send(hs, "\r\nERROR\r\n"); + } + + if (buf[7] == '=') + str = indicator_ranges(ag.indicators); + else + str = indicator_values(ag.indicators); + + err = headset_send(hs, str); + + g_free(str); + + if (err < 0) + return err; + + return headset_send(hs, "\r\nOK\r\n"); +} + +static void pending_connect_complete(struct connect_cb *cb, struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (hs->pending->err < 0) + cb->cb(NULL, cb->cb_data); + else + cb->cb(dev, cb->cb_data); +} + +static void pending_connect_finalize(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + + if (p == NULL) + return; + + if (p->svclass) + bt_cancel_discovery(&dev->src, &dev->dst); + + g_slist_foreach(p->callbacks, (GFunc) pending_connect_complete, dev); + + g_slist_foreach(p->callbacks, (GFunc) g_free, NULL); + g_slist_free(p->callbacks); + + if (p->io) { + g_io_channel_shutdown(p->io, TRUE, NULL); + g_io_channel_unref(p->io); + } + + if (p->msg) + dbus_message_unref(p->msg); + + if (p->call) { + dbus_pending_call_cancel(p->call); + dbus_pending_call_unref(p->call); + } + + g_free(p); + + hs->pending = NULL; +} + +static void pending_connect_init(struct headset *hs, headset_state_t target_state) +{ + if (hs->pending) { + if (hs->pending->target_state < target_state) + hs->pending->target_state = target_state; + return; + } + + hs->pending = g_new0(struct pending_connect, 1); + hs->pending->target_state = target_state; +} + +static unsigned int connect_cb_new(struct headset *hs, + headset_state_t target_state, + headset_stream_cb_t func, + void *user_data) +{ + struct connect_cb *cb; + unsigned int free_cb_id = 1; + + pending_connect_init(hs, target_state); + + if (!func) + return 0; + + cb = g_new(struct connect_cb, 1); + + cb->cb = func; + cb->cb_data = user_data; + cb->id = free_cb_id++; + + hs->pending->callbacks = g_slist_append(hs->pending->callbacks, + cb); + + return cb->id; +} + +static void send_foreach_headset(GSList *devices, + int (*cmp) (struct headset *hs), + char *format, ...) +{ + GSList *l; + va_list ap; + + for (l = devices; l != NULL; l = l->next) { + struct audio_device *device = l->data; + struct headset *hs = device->headset; + int ret; + + assert(hs != NULL); + + if (cmp && cmp(hs) != 0) + continue; + + va_start(ap, format); + ret = headset_send_valist(hs, format, ap); + if (ret < 0) + error("Failed to send to headset: %s (%d)", + strerror(-ret), -ret); + va_end(ap); + } +} + +static int cli_cmp(struct headset *hs) +{ + struct headset_slc *slc = hs->slc; + + if (!hs->hfp_active) + return -1; + + if (slc->cli_active) + return 0; + else + return -1; +} + +static gboolean ring_timer_cb(gpointer data) +{ + send_foreach_headset(active_devices, NULL, "\r\nRING\r\n"); + + if (ag.number) + send_foreach_headset(active_devices, cli_cmp, + "\r\n+CLIP: \"%s\",%d\r\n", + ag.number, ag.number_type); + + return TRUE; +} + +static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + int sk; + struct audio_device *dev = user_data; + struct headset *hs = dev->headset; + struct headset_slc *slc = hs->slc; + struct pending_connect *p = hs->pending; + + if (err) { + error("%s", err->message); + + if (p != NULL) { + p->err = -errno; + if (p->msg) + error_connect_failed(dev->conn, p->msg, p->err); + pending_connect_finalize(dev); + } + + if (hs->rfcomm) + headset_set_state(dev, HEADSET_STATE_CONNECTED); + else + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); + + return; + } + + DBG("SCO socket opened for headset %s", dev->path); + + sk = g_io_channel_unix_get_fd(chan); + + DBG("SCO fd=%d", sk); + + if (p) { + p->io = NULL; + if (p->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(dev->conn, reply); + } + + pending_connect_finalize(dev); + } + + fcntl(sk, F_SETFL, 0); + + headset_set_state(dev, HEADSET_STATE_PLAYING); + + if (slc->pending_ring) { + ring_timer_cb(NULL); + ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, + ring_timer_cb, + NULL); + slc->pending_ring = FALSE; + } +} + +static int sco_connect(struct audio_device *dev, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id) +{ + struct headset *hs = dev->headset; + GError *err = NULL; + GIOChannel *io; + + if (hs->state != HEADSET_STATE_CONNECTED) + return -EINVAL; + + io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return -EIO; + } + + hs->sco = io; + + headset_set_state(dev, HEADSET_STATE_PLAY_IN_PROGRESS); + + pending_connect_init(hs, HEADSET_STATE_PLAYING); + + if (cb) { + unsigned int id = connect_cb_new(hs, HEADSET_STATE_PLAYING, + cb, user_data); + if (cb_id) + *cb_id = id; + } + + return 0; +} + +static int hfp_cmp(struct headset *hs) +{ + if (hs->hfp_active) + return 0; + else + return -1; +} + +static void hfp_slc_complete(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + + DBG("HFP Service Level Connection established"); + + headset_set_state(dev, HEADSET_STATE_CONNECTED); + + if (p == NULL) + return; + + if (p->target_state == HEADSET_STATE_CONNECTED) { + if (p->msg) { + DBusMessage *reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(dev->conn, reply); + } + pending_connect_finalize(dev); + return; + } + + p->err = sco_connect(dev, NULL, NULL, NULL); + if (p->err < 0) { + if (p->msg) + error_connect_failed(dev->conn, p->msg, p->err); + pending_connect_finalize(dev); + } +} + +static int telephony_generic_rsp(struct audio_device *device, cme_error_t err) +{ + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + + if (err != CME_ERROR_NONE) { + if (slc->cme_enabled) + return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err); + else + return headset_send(hs, "\r\nERROR\r\n"); + } + + return headset_send(hs, "\r\nOK\r\n"); +} + +int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err) +{ + struct audio_device *device = telephony_device; + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + int ret; + + if (err != CME_ERROR_NONE) + return telephony_generic_rsp(telephony_device, err); + + ret = headset_send(hs, "\r\nOK\r\n"); + if (ret < 0) + return ret; + + if (hs->state != HEADSET_STATE_CONNECTING) + return 0; + + if (slc->hf_features & HF_FEATURE_CALL_WAITING_AND_3WAY && + ag.features & AG_FEATURE_THREE_WAY_CALLING) + return 0; + + hfp_slc_complete(device); + + return 0; +} + +static int event_reporting(struct audio_device *dev, const char *buf) +{ + char **tokens; /* , , , , */ + + if (strlen(buf) < 13) + return -EINVAL; + + tokens = g_strsplit(&buf[8], ",", 5); + if (g_strv_length(tokens) < 4) { + g_strfreev(tokens); + return -EINVAL; + } + + ag.er_mode = atoi(tokens[0]); + ag.er_ind = atoi(tokens[3]); + + g_strfreev(tokens); + tokens = NULL; + + DBG("Event reporting (CMER): mode=%d, ind=%d", + ag.er_mode, ag.er_ind); + + switch (ag.er_ind) { + case 0: + case 1: + telephony_event_reporting_req(dev, ag.er_ind); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int call_hold(struct audio_device *dev, const char *buf) +{ + struct headset *hs = dev->headset; + int err; + + if (strlen(buf) < 9) + return -EINVAL; + + if (buf[8] != '?') { + telephony_call_hold_req(dev, &buf[8]); + return 0; + } + + err = headset_send(hs, "\r\n+CHLD: (%s)\r\n", ag.chld); + if (err < 0) + return err; + + err = headset_send(hs, "\r\nOK\r\n"); + if (err < 0) + return err; + + if (hs->state != HEADSET_STATE_CONNECTING) + return 0; + + hfp_slc_complete(dev); + + return 0; +} + +int telephony_key_press_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int key_press(struct audio_device *device, const char *buf) +{ + if (strlen(buf) < 9) + return -EINVAL; + + g_dbus_emit_signal(device->conn, device->path, + AUDIO_HEADSET_INTERFACE, "AnswerRequested", + DBUS_TYPE_INVALID); + + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } + + telephony_key_press_req(device, &buf[8]); + + return 0; +} + +int telephony_answer_call_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int answer_call(struct audio_device *device, const char *buf) +{ + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } + + if (ag.number) { + g_free(ag.number); + ag.number = NULL; + } + + telephony_answer_call_req(device); + + return 0; +} + +int telephony_terminate_call_rsp(void *telephony_device, + cme_error_t err) +{ + struct audio_device *device = telephony_device; + struct headset *hs = device->headset; + + if (err != CME_ERROR_NONE) + return telephony_generic_rsp(telephony_device, err); + + g_dbus_emit_signal(device->conn, device->path, + AUDIO_HEADSET_INTERFACE, "CallTerminated", + DBUS_TYPE_INVALID); + + return headset_send(hs, "\r\nOK\r\n"); +} + +static int terminate_call(struct audio_device *device, const char *buf) +{ + if (ag.number) { + g_free(ag.number); + ag.number = NULL; + } + + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } + + telephony_terminate_call_req(device); + + return 0; +} + +static int cli_notification(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + + if (strlen(buf) < 9) + return -EINVAL; + + slc->cli_active = buf[8] == '1' ? TRUE : FALSE; + + return headset_send(hs, "\r\nOK\r\n"); +} + +int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int response_and_hold(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + + if (strlen(buf) < 8) + return -EINVAL; + + if (ag.rh == BTRH_NOT_SUPPORTED) + return telephony_generic_rsp(device, CME_ERROR_NOT_SUPPORTED); + + if (buf[7] == '=') { + telephony_response_and_hold_req(device, atoi(&buf[8]) < 0); + return 0; + } + + if (ag.rh >= 0) + headset_send(hs, "\r\n+BTRH: %d\r\n", ag.rh); + + return headset_send(hs, "\r\nOK\r\n", ag.rh); +} + +int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int last_dialed_number(struct audio_device *device, const char *buf) +{ + telephony_last_dialed_number_req(device); + + return 0; +} + +int telephony_dial_number_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int dial_number(struct audio_device *device, const char *buf) +{ + char number[BUF_SIZE]; + size_t buf_len; + + buf_len = strlen(buf); + + if (buf[buf_len - 1] != ';') { + DBG("Rejecting non-voice call dial request"); + return -EINVAL; + } + + memset(number, 0, sizeof(number)); + strncpy(number, &buf[3], buf_len - 4); + + telephony_dial_number_req(device, number); + + return 0; +} + +static int headset_set_gain(struct audio_device *device, uint16_t gain, char type) +{ + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + const char *name, *property; + + if (gain > 15) { + error("Invalid gain value: %u", gain); + return -EINVAL; + } + + switch (type) { + case HEADSET_GAIN_SPEAKER: + if (slc->sp_gain == gain) { + DBG("Ignoring no-change in speaker gain"); + return -EALREADY; + } + name = "SpeakerGainChanged"; + property = "SpeakerGain"; + slc->sp_gain = gain; + break; + case HEADSET_GAIN_MICROPHONE: + if (slc->mic_gain == gain) { + DBG("Ignoring no-change in microphone gain"); + return -EALREADY; + } + name = "MicrophoneGainChanged"; + property = "MicrophoneGain"; + slc->mic_gain = gain; + break; + default: + error("Unknown gain setting"); + return -EINVAL; + } + + g_dbus_emit_signal(device->conn, device->path, + AUDIO_HEADSET_INTERFACE, name, + DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID); + + emit_property_changed(device->conn, device->path, + AUDIO_HEADSET_INTERFACE, property, + DBUS_TYPE_UINT16, &gain); + + return 0; +} + +static int signal_gain_setting(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + dbus_uint16_t gain; + int err; + + if (strlen(buf) < 8) { + error("Too short string for Gain setting"); + return -EINVAL; + } + + gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10); + + err = headset_set_gain(device, gain, buf[5]); + if (err < 0 && err != -EALREADY) + return err; + + return headset_send(hs, "\r\nOK\r\n"); +} + +int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int dtmf_tone(struct audio_device *device, const char *buf) +{ + char tone; + + if (strlen(buf) < 8) { + error("Too short string for DTMF tone"); + return -EINVAL; + } + + tone = buf[7]; + if (tone >= '#' && tone <= 'D') + telephony_transmit_dtmf_req(device, tone); + else + return -EINVAL; + + return 0; +} + +int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int subscriber_number(struct audio_device *device, const char *buf) +{ + telephony_subscriber_number_req(device); + + return 0; +} + +int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int list_current_calls(struct audio_device *device, const char *buf) +{ + telephony_list_current_calls_req(device); + + return 0; +} + +static int extended_errors(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + + if (strlen(buf) < 9) + return -EINVAL; + + if (buf[8] == '1') { + slc->cme_enabled = TRUE; + DBG("CME errors enabled for headset %p", hs); + } else { + slc->cme_enabled = FALSE; + DBG("CME errors disabled for headset %p", hs); + } + + return headset_send(hs, "\r\nOK\r\n"); +} + +static int call_waiting_notify(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + + if (strlen(buf) < 9) + return -EINVAL; + + if (buf[8] == '1') { + slc->cwa_enabled = TRUE; + DBG("Call waiting notification enabled for headset %p", hs); + } else { + slc->cwa_enabled = FALSE; + DBG("Call waiting notification disabled for headset %p", hs); + } + + return headset_send(hs, "\r\nOK\r\n"); +} + +int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +int telephony_call_hold_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err) +{ + struct audio_device *device = telephony_device; + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + + if (err == CME_ERROR_NONE) { + GSList *l; + + for (l = hs->nrec_cbs; l; l = l->next) { + struct headset_nrec_callback *nrec_cb = l->data; + + nrec_cb->cb(device, slc->nrec_req, nrec_cb->user_data); + } + + slc->nrec = hs->slc->nrec_req; + } + + return telephony_generic_rsp(telephony_device, err); +} + +int telephony_voice_dial_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +int telephony_operator_selection_ind(int mode, const char *oper) +{ + if (!active_devices) + return -ENODEV; + + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+COPS: %d,0,\"%s\"\r\n", + mode, oper); + return 0; +} + +static int operator_selection(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + + if (strlen(buf) < 8) + return -EINVAL; + + switch (buf[7]) { + case '?': + telephony_operator_selection_req(device); + break; + case '=': + return headset_send(hs, "\r\nOK\r\n"); + default: + return -EINVAL; + } + + return 0; +} + +static int nr_and_ec(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + + if (strlen(buf) < 9) + return -EINVAL; + + if (buf[8] == '0') + slc->nrec_req = FALSE; + else + slc->nrec_req = TRUE; + + telephony_nr_and_ec_req(device, slc->nrec_req); + + return 0; +} + +static int voice_dial(struct audio_device *device, const char *buf) +{ + gboolean enable; + + if (strlen(buf) < 9) + return -EINVAL; + + if (buf[8] == '0') + enable = FALSE; + else + enable = TRUE; + + telephony_voice_dial_req(device, enable); + + return 0; +} + +static struct event event_callbacks[] = { + { "ATA", answer_call }, + { "ATD", dial_number }, + { "AT+VG", signal_gain_setting }, + { "AT+BRSF", supported_features }, + { "AT+CIND", report_indicators }, + { "AT+CMER", event_reporting }, + { "AT+CHLD", call_hold }, + { "AT+CHUP", terminate_call }, + { "AT+CKPD", key_press }, + { "AT+CLIP", cli_notification }, + { "AT+BTRH", response_and_hold }, + { "AT+BLDN", last_dialed_number }, + { "AT+VTS", dtmf_tone }, + { "AT+CNUM", subscriber_number }, + { "AT+CLCC", list_current_calls }, + { "AT+CMEE", extended_errors }, + { "AT+CCWA", call_waiting_notify }, + { "AT+COPS", operator_selection }, + { "AT+NREC", nr_and_ec }, + { "AT+BVRA", voice_dial }, + { 0 } +}; + +static int handle_event(struct audio_device *device, const char *buf) +{ + struct event *ev; + + DBG("Received %s", buf); + + for (ev = event_callbacks; ev->cmd; ev++) { + if (!strncmp(buf, ev->cmd, strlen(ev->cmd))) + return ev->callback(device, buf); + } + + return -EINVAL; +} + +static void close_sco(struct audio_device *device) +{ + struct headset *hs = device->headset; + + if (hs->sco) { + int sock = g_io_channel_unix_get_fd(hs->sco); + shutdown(sock, SHUT_RDWR); + g_io_channel_shutdown(hs->sco, TRUE, NULL); + g_io_channel_unref(hs->sco); + hs->sco = NULL; + } + + if (hs->sco_id) { + g_source_remove(hs->sco_id); + hs->sco_id = 0; + } +} + +static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, + struct audio_device *device) +{ + struct headset *hs; + struct headset_slc *slc; + unsigned char buf[BUF_SIZE]; + ssize_t bytes_read; + size_t free_space; + int fd; + + if (cond & G_IO_NVAL) + return FALSE; + + hs = device->headset; + slc = hs->slc; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + DBG("ERR or HUP on RFCOMM socket"); + goto failed; + } + + fd = g_io_channel_unix_get_fd(chan); + + bytes_read = read(fd, buf, sizeof(buf) - 1); + if (bytes_read < 0) + return TRUE; + + free_space = sizeof(slc->buf) - slc->data_start - + slc->data_length - 1; + + if (free_space < (size_t) bytes_read) { + /* Very likely that the HS is sending us garbage so + * just ignore the data and disconnect */ + error("Too much data to fit incomming buffer"); + goto failed; + } + + memcpy(&slc->buf[slc->data_start], buf, bytes_read); + slc->data_length += bytes_read; + + /* Make sure the data is null terminated so we can use string + * functions */ + slc->buf[slc->data_start + slc->data_length] = '\0'; + + while (slc->data_length > 0) { + char *cr; + int err; + off_t cmd_len; + + cr = strchr(&slc->buf[slc->data_start], '\r'); + if (!cr) + break; + + cmd_len = 1 + (off_t) cr - (off_t) &slc->buf[slc->data_start]; + *cr = '\0'; + + if (cmd_len > 1) + err = handle_event(device, &slc->buf[slc->data_start]); + else + /* Silently skip empty commands */ + err = 0; + + if (err == -EINVAL) { + error("Badly formated or unrecognized command: %s", + &slc->buf[slc->data_start]); + err = headset_send(hs, "\r\nERROR\r\n"); + } else if (err < 0) + error("Error handling command %s: %s (%d)", + &slc->buf[slc->data_start], + strerror(-err), -err); + + slc->data_start += cmd_len; + slc->data_length -= cmd_len; + + if (!slc->data_length) + slc->data_start = 0; + } + + return TRUE; + +failed: + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + + return FALSE; +} + +static gboolean sco_cb(GIOChannel *chan, GIOCondition cond, + struct audio_device *device) +{ + if (cond & G_IO_NVAL) + return FALSE; + + error("Audio connection got disconnected"); + + pending_connect_finalize(device); + headset_set_state(device, HEADSET_STATE_CONNECTED); + + return FALSE; +} + +void headset_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct audio_device *dev = user_data; + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + char hs_address[18]; + + if (err) { + error("%s", err->message); + goto failed; + } + + /* For HFP telephony isn't ready just disconnect */ + if (hs->hfp_active && !ag.telephony_ready) { + error("Unable to accept HFP connection since the telephony " + "subsystem isn't initialized"); + goto failed; + } + + hs->rfcomm = hs->tmp_rfcomm; + hs->tmp_rfcomm = NULL; + + ba2str(&dev->dst, hs_address); + + if (p) + p->io = NULL; + else + hs->auto_dc = FALSE; + + g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL, + (GIOFunc) rfcomm_io_cb, dev); + + DBG("%s: Connected to %s", dev->path, hs_address); + + hs->slc = g_new0(struct headset_slc, 1); + hs->slc->sp_gain = 15; + hs->slc->mic_gain = 15; + hs->slc->nrec = TRUE; + + /* In HFP mode wait for Service Level Connection */ + if (hs->hfp_active) + return; + + headset_set_state(dev, HEADSET_STATE_CONNECTED); + + if (p && p->target_state == HEADSET_STATE_PLAYING) { + p->err = sco_connect(dev, NULL, NULL, NULL); + if (p->err < 0) + goto failed; + return; + } + + if (p && p->msg) { + DBusMessage *reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(dev->conn, reply); + } + + pending_connect_finalize(dev); + + return; + +failed: + if (p && p->msg) + error_connect_failed(dev->conn, p->msg, p->err); + pending_connect_finalize(dev); + if (hs->rfcomm) + headset_set_state(dev, HEADSET_STATE_CONNECTED); + else + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); +} + +static int headset_set_channel(struct headset *headset, + const sdp_record_t *record, uint16_t svc) +{ + int ch; + sdp_list_t *protos; + + if (sdp_get_access_protos(record, &protos) < 0) { + error("Unable to get access protos from headset record"); + return -1; + } + + ch = sdp_get_proto_port(protos, RFCOMM_UUID); + + sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(protos, NULL); + + if (ch <= 0) { + error("Unable to get RFCOMM channel from Headset record"); + return -1; + } + + headset->rfcomm_ch = ch; + + if (svc == HANDSFREE_SVCLASS_ID) { + headset->hfp_handle = record->handle; + DBG("Discovered Handsfree service on channel %d", ch); + } else { + headset->hsp_handle = record->handle; + DBG("Discovered Headset service on channel %d", ch); + } + + return 0; +} + +static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct audio_device *dev = user_data; + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + sdp_record_t *record = NULL; + sdp_list_t *r; + uuid_t uuid; + + assert(hs->pending != NULL); + + if (err < 0) { + error("Unable to get service record: %s (%d)", + strerror(-err), -err); + p->err = -err; + if (p->msg) + error_connect_failed(dev->conn, p->msg, p->err); + goto failed; + } + + if (!recs || !recs->data) { + error("No records found"); + goto failed_not_supported; + } + + sdp_uuid16_create(&uuid, p->svclass); + + for (r = recs; r != NULL; r = r->next) { + sdp_list_t *classes; + uuid_t class; + + record = r->data; + + if (sdp_get_service_classes(record, &classes) < 0) { + error("Unable to get service classes from record"); + continue; + } + + memcpy(&class, classes->data, sizeof(uuid)); + + sdp_list_free(classes, free); + + + if (sdp_uuid_cmp(&class, &uuid) == 0) + break; + } + + if (r == NULL) { + error("No record found with UUID 0x%04x", p->svclass); + goto failed_not_supported; + } + + if (headset_set_channel(hs, record, p->svclass) < 0) { + error("Unable to extract RFCOMM channel from service record"); + goto failed_not_supported; + } + + /* Set svclass to 0 so we can easily check that SDP is no-longer + * going on (to know if bt_cancel_discovery needs to be called) */ + p->svclass = 0; + + err = rfcomm_connect(dev, NULL, NULL, NULL); + if (err < 0) { + error("Unable to connect: %s (%d)", strerror(-err), -err); + p->err = -err; + error_connect_failed(dev->conn, p->msg, p->err); + goto failed; + } + + return; + +failed_not_supported: + if (p->svclass == HANDSFREE_SVCLASS_ID && + get_records(dev, NULL, NULL, NULL) == 0) + return; + if (p->msg) { + DBusMessage *reply = btd_error_not_supported(p->msg); + g_dbus_send_message(dev->conn, reply); + } +failed: + p->svclass = 0; + pending_connect_finalize(dev); + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); +} + +static int get_records(struct audio_device *device, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id) +{ + struct headset *hs = device->headset; + uint16_t svclass; + uuid_t uuid; + int err; + + if (hs->pending && hs->pending->svclass == HANDSFREE_SVCLASS_ID) + svclass = HEADSET_SVCLASS_ID; + else + svclass = hs->search_hfp ? HANDSFREE_SVCLASS_ID : + HEADSET_SVCLASS_ID; + + sdp_uuid16_create(&uuid, svclass); + + err = bt_search_service(&device->src, &device->dst, &uuid, + get_record_cb, device, NULL); + if (err < 0) + return err; + + if (hs->pending) { + hs->pending->svclass = svclass; + return 0; + } + + headset_set_state(device, HEADSET_STATE_CONNECTING); + + pending_connect_init(hs, HEADSET_STATE_CONNECTED); + + hs->pending->svclass = svclass; + + if (cb) { + unsigned int id; + id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, + cb, user_data); + if (cb_id) + *cb_id = id; + } + + return 0; +} + +static int rfcomm_connect(struct audio_device *dev, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id) +{ + struct headset *hs = dev->headset; + char address[18]; + GError *err = NULL; + + if (!manager_allow_headset_connection(dev)) + return -ECONNREFUSED; + + if (hs->rfcomm_ch < 0) + return get_records(dev, cb, user_data, cb_id); + + ba2str(&dev->dst, address); + + DBG("%s: Connecting to %s channel %d", dev->path, address, + hs->rfcomm_ch); + + hs->tmp_rfcomm = bt_io_connect(BT_IO_RFCOMM, headset_connect_cb, dev, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_CHANNEL, hs->rfcomm_ch, + BT_IO_OPT_INVALID); + + hs->rfcomm_ch = -1; + + if (!hs->tmp_rfcomm) { + error("%s", err->message); + g_error_free(err); + return -EIO; + } + + hs->hfp_active = hs->hfp_handle != 0 ? TRUE : FALSE; + + headset_set_state(dev, HEADSET_STATE_CONNECTING); + + pending_connect_init(hs, HEADSET_STATE_CONNECTED); + + if (cb) { + unsigned int id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, + cb, user_data); + if (cb_id) + *cb_id = id; + } + + return 0; +} + +static DBusMessage *hs_stop(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply = NULL; + + if (hs->state < HEADSET_STATE_PLAY_IN_PROGRESS) + return btd_error_not_connected(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + headset_set_state(device, HEADSET_STATE_CONNECTED); + + return reply; +} + +static DBusMessage *hs_is_playing(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply; + dbus_bool_t playing; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + playing = (hs->state == HEADSET_STATE_PLAYING); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &playing, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *hs_disconnect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + char hs_address[18]; + + if (hs->state == HEADSET_STATE_DISCONNECTED) + return btd_error_not_connected(msg); + + headset_shutdown(device); + ba2str(&device->dst, hs_address); + info("Disconnected from %s, %s", hs_address, device->path); + + return dbus_message_new_method_return(msg); + +} + +static DBusMessage *hs_is_connected(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + DBusMessage *reply; + dbus_bool_t connected; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + connected = (device->headset->state >= HEADSET_STATE_CONNECTED); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *hs_connect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + int err; + + if (hs->state == HEADSET_STATE_CONNECTING) + return btd_error_in_progress(msg); + else if (hs->state > HEADSET_STATE_CONNECTING) + return btd_error_already_connected(msg); + + if (hs->hfp_handle && !ag.telephony_ready) + return btd_error_not_ready(msg); + + device->auto_connect = FALSE; + + err = rfcomm_connect(device, NULL, NULL, NULL); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + hs->auto_dc = FALSE; + + hs->pending->msg = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *hs_ring(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply = NULL; + int err; + + if (hs->state < HEADSET_STATE_CONNECTED) + return btd_error_not_connected(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (ag.ring_timer) { + DBG("IndicateCall received when already indicating"); + goto done; + } + + err = headset_send(hs, "\r\nRING\r\n"); + if (err < 0) { + dbus_message_unref(reply); + return btd_error_failed(msg, strerror(-err)); + } + + ring_timer_cb(NULL); + ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb, + NULL); + +done: + return reply; +} + +static DBusMessage *hs_cancel_call(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply = NULL; + + if (hs->state < HEADSET_STATE_CONNECTED) + return btd_error_not_connected(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } else + DBG("Got CancelCall method call but no call is active"); + + return reply; +} + +static DBusMessage *hs_play(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + int err; + + if (sco_hci) { + error("Refusing Headset.Play() because SCO HCI routing " + "is enabled"); + return btd_error_not_available(msg); + } + + switch (hs->state) { + case HEADSET_STATE_DISCONNECTED: + case HEADSET_STATE_CONNECTING: + return btd_error_not_connected(msg); + case HEADSET_STATE_PLAY_IN_PROGRESS: + if (hs->pending && hs->pending->msg == NULL) { + hs->pending->msg = dbus_message_ref(msg); + return NULL; + } + return btd_error_busy(msg); + case HEADSET_STATE_PLAYING: + return btd_error_already_connected(msg); + case HEADSET_STATE_CONNECTED: + default: + break; + } + + err = sco_connect(device, NULL, NULL, NULL); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + hs->pending->msg = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *hs_get_speaker_gain(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + DBusMessage *reply; + dbus_uint16_t gain; + + if (hs->state < HEADSET_STATE_CONNECTED) + return btd_error_not_available(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + gain = (dbus_uint16_t) slc->sp_gain; + + dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *hs_get_mic_gain(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + struct headset_slc *slc = hs->slc; + DBusMessage *reply; + dbus_uint16_t gain; + + if (hs->state < HEADSET_STATE_CONNECTED || slc == NULL) + return btd_error_not_available(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + gain = (dbus_uint16_t) slc->mic_gain; + + dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *hs_set_gain(DBusConnection *conn, + DBusMessage *msg, + void *data, uint16_t gain, + char type) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply; + int err; + + if (hs->state < HEADSET_STATE_CONNECTED) + return btd_error_not_connected(msg); + + err = headset_set_gain(device, gain, type); + if (err < 0) + return btd_error_invalid_args(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (hs->state == HEADSET_STATE_PLAYING) { + err = headset_send(hs, "\r\n+VG%c=%u\r\n", type, gain); + if (err < 0) { + dbus_message_unref(reply); + return btd_error_failed(msg, strerror(-err)); + } + } + + return reply; +} + +static DBusMessage *hs_set_speaker_gain(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + uint16_t gain; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID)) + return NULL; + + return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_SPEAKER); +} + +static DBusMessage *hs_set_mic_gain(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + uint16_t gain; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID)) + return NULL; + + return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_MICROPHONE); +} + +static DBusMessage *hs_get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + gboolean value; + const char *state; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + + /* Playing */ + value = (device->headset->state == HEADSET_STATE_PLAYING); + dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value); + + /* State */ + state = state2str(device->headset->state); + if (state) + dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state); + + /* Connected */ + value = (device->headset->state >= HEADSET_STATE_CONNECTED); + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); + + if (!value) + goto done; + + /* SpeakerGain */ + dict_append_entry(&dict, "SpeakerGain", + DBUS_TYPE_UINT16, + &device->headset->slc->sp_gain); + + /* MicrophoneGain */ + dict_append_entry(&dict, "MicrophoneGain", + DBUS_TYPE_UINT16, + &device->headset->slc->mic_gain); + +done: + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *hs_set_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *property; + DBusMessageIter iter; + DBusMessageIter sub; + uint16_t gain; + + if (!dbus_message_iter_init(msg, &iter)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &property); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return btd_error_invalid_args(msg); + dbus_message_iter_recurse(&iter, &sub); + + if (g_str_equal("SpeakerGain", property)) { + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &gain); + return hs_set_gain(conn, msg, data, gain, + HEADSET_GAIN_SPEAKER); + } else if (g_str_equal("MicrophoneGain", property)) { + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &gain); + return hs_set_gain(conn, msg, data, gain, + HEADSET_GAIN_MICROPHONE); + } + + return btd_error_invalid_args(msg); +} +static GDBusMethodTable headset_methods[] = { + { "Connect", "", "", hs_connect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", hs_disconnect }, + { "IsConnected", "", "b", hs_is_connected }, + { "IndicateCall", "", "", hs_ring }, + { "CancelCall", "", "", hs_cancel_call }, + { "Play", "", "", hs_play, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Stop", "", "", hs_stop }, + { "IsPlaying", "", "b", hs_is_playing, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetSpeakerGain", "", "q", hs_get_speaker_gain, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetMicrophoneGain", "", "q", hs_get_mic_gain, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "SetSpeakerGain", "q", "", hs_set_speaker_gain, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "SetMicrophoneGain", "q", "", hs_set_mic_gain, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetProperties", "", "a{sv}",hs_get_properties }, + { "SetProperty", "sv", "", hs_set_property }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable headset_signals[] = { + { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "AnswerRequested", "" }, + { "Stopped", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Playing", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "SpeakerGainChanged", "q", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "MicrophoneGainChanged", "q", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "CallTerminated", "" }, + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +void headset_update(struct audio_device *dev, uint16_t svc, + const char *uuidstr) +{ + struct headset *headset = dev->headset; + const sdp_record_t *record; + + record = btd_device_get_record(dev->btd_dev, uuidstr); + if (!record) + return; + + switch (svc) { + case HANDSFREE_SVCLASS_ID: + if (headset->hfp_handle && + (headset->hfp_handle != record->handle)) { + error("More than one HFP record found on device"); + return; + } + + headset->hfp_handle = record->handle; + break; + + case HEADSET_SVCLASS_ID: + if (headset->hsp_handle && + (headset->hsp_handle != record->handle)) { + error("More than one HSP record found on device"); + return; + } + + headset->hsp_handle = record->handle; + + /* Ignore this record if we already have access to HFP */ + if (headset->hfp_handle) + return; + + break; + + default: + DBG("Invalid record passed to headset_update"); + return; + } +} + +static int headset_close_rfcomm(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + GIOChannel *rfcomm = hs->tmp_rfcomm ? hs->tmp_rfcomm : hs->rfcomm; + + if (rfcomm) { + g_io_channel_shutdown(rfcomm, TRUE, NULL); + g_io_channel_unref(rfcomm); + hs->tmp_rfcomm = NULL; + hs->rfcomm = NULL; + } + + g_free(hs->slc); + hs->slc = NULL; + + return 0; +} + +static void headset_free(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (hs->dc_timer) { + g_source_remove(hs->dc_timer); + hs->dc_timer = 0; + } + + close_sco(dev); + + headset_close_rfcomm(dev); + + g_slist_foreach(hs->nrec_cbs, (GFunc) g_free, NULL); + g_slist_free(hs->nrec_cbs); + + g_free(hs); + dev->headset = NULL; +} + +static void path_unregister(void *data) +{ + struct audio_device *dev = data; + struct headset *hs = dev->headset; + + if (hs->state > HEADSET_STATE_DISCONNECTED) { + DBG("Headset unregistered while device was connected!"); + headset_shutdown(dev); + } + + DBG("Unregistered interface %s on path %s", + AUDIO_HEADSET_INTERFACE, dev->path); + + headset_free(dev); +} + +void headset_unregister(struct audio_device *dev) +{ + g_dbus_unregister_interface(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE); +} + +struct headset *headset_init(struct audio_device *dev, uint16_t svc, + const char *uuidstr) +{ + struct headset *hs; + const sdp_record_t *record; + + hs = g_new0(struct headset, 1); + hs->rfcomm_ch = -1; + hs->search_hfp = server_is_enabled(&dev->src, HANDSFREE_SVCLASS_ID); + + record = btd_device_get_record(dev->btd_dev, uuidstr); + if (!record) + goto register_iface; + + switch (svc) { + case HANDSFREE_SVCLASS_ID: + hs->hfp_handle = record->handle; + break; + + case HEADSET_SVCLASS_ID: + hs->hsp_handle = record->handle; + break; + + default: + DBG("Invalid record passed to headset_init"); + g_free(hs); + return NULL; + } + +register_iface: + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + headset_methods, headset_signals, NULL, + dev, path_unregister)) { + g_free(hs); + return NULL; + } + + DBG("Registered interface %s on path %s", + AUDIO_HEADSET_INTERFACE, dev->path); + + return hs; +} + +uint32_t headset_config_init(GKeyFile *config) +{ + GError *err = NULL; + char *str; + + /* Use the default values if there is no config file */ + if (config == NULL) + return ag.features; + + str = g_key_file_get_string(config, "General", "SCORouting", + &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + if (strcmp(str, "PCM") == 0) + sco_hci = FALSE; + else if (strcmp(str, "HCI") == 0) + sco_hci = TRUE; + else + error("Invalid Headset Routing value: %s", str); + g_free(str); + } + + /* Init fast connectable option */ + str = g_key_file_get_string(config, "Headset", "FastConnectable", + &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + fast_connectable = strcmp(str, "true") == 0; + if (fast_connectable) + manager_set_fast_connectable(FALSE); + g_free(str); + } + + return ag.features; +} + +static gboolean hs_dc_timeout(struct audio_device *dev) +{ + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); + return FALSE; +} + +gboolean headset_cancel_stream(struct audio_device *dev, unsigned int id) +{ + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + GSList *l; + struct connect_cb *cb = NULL; + + if (!p) + return FALSE; + + for (l = p->callbacks; l != NULL; l = l->next) { + struct connect_cb *tmp = l->data; + + if (tmp->id == id) { + cb = tmp; + break; + } + } + + if (!cb) + return FALSE; + + p->callbacks = g_slist_remove(p->callbacks, cb); + g_free(cb); + + if (p->callbacks || p->msg) + return TRUE; + + if (hs->auto_dc) { + if (hs->rfcomm) + hs->dc_timer = g_timeout_add_seconds(DC_TIMEOUT, + (GSourceFunc) hs_dc_timeout, + dev); + else + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); + } + + return TRUE; +} + +static gboolean dummy_connect_complete(struct audio_device *dev) +{ + pending_connect_finalize(dev); + return FALSE; +} + +unsigned int headset_request_stream(struct audio_device *dev, + headset_stream_cb_t cb, + void *user_data) +{ + struct headset *hs = dev->headset; + unsigned int id; + + if (hs->state == HEADSET_STATE_PLAYING) { + id = connect_cb_new(hs, HEADSET_STATE_PLAYING, cb, user_data); + g_idle_add((GSourceFunc) dummy_connect_complete, dev); + return id; + } + + if (hs->dc_timer) { + g_source_remove(hs->dc_timer); + hs->dc_timer = 0; + } + + if (hs->state == HEADSET_STATE_CONNECTING || + hs->state == HEADSET_STATE_PLAY_IN_PROGRESS) + return connect_cb_new(hs, HEADSET_STATE_PLAYING, cb, user_data); + + if (hs->rfcomm == NULL) { + if (rfcomm_connect(dev, cb, user_data, &id) < 0) + return 0; + hs->auto_dc = TRUE; + } else if (sco_connect(dev, cb, user_data, &id) < 0) + return 0; + + hs->pending->target_state = HEADSET_STATE_PLAYING; + + return id; +} + +unsigned int headset_config_stream(struct audio_device *dev, + gboolean auto_dc, + headset_stream_cb_t cb, + void *user_data) +{ + struct headset *hs = dev->headset; + unsigned int id = 0; + + if (hs->dc_timer) { + g_source_remove(hs->dc_timer); + hs->dc_timer = 0; + } + + if (hs->state == HEADSET_STATE_CONNECTING) + return connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, + user_data); + + if (hs->rfcomm) + goto done; + + if (rfcomm_connect(dev, cb, user_data, &id) < 0) + return 0; + + hs->auto_dc = auto_dc; + hs->pending->target_state = HEADSET_STATE_CONNECTED; + + return id; + +done: + id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, user_data); + g_idle_add((GSourceFunc) dummy_connect_complete, dev); + return id; +} + +unsigned int headset_suspend_stream(struct audio_device *dev, + headset_stream_cb_t cb, + void *user_data) +{ + struct headset *hs = dev->headset; + unsigned int id; + int sock; + + if (hs->state == HEADSET_STATE_DISCONNECTED || + hs->state == HEADSET_STATE_CONNECTING) + return 0; + + if (hs->dc_timer) { + g_source_remove(hs->dc_timer); + hs->dc_timer = 0; + } + + sock = g_io_channel_unix_get_fd(hs->sco); + + /* shutdown but leave the socket open and wait for hup */ + shutdown(sock, SHUT_RDWR); + + id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, user_data); + + return id; +} + +gboolean get_hfp_active(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->hfp_active; +} + +void set_hfp_active(struct audio_device *dev, gboolean active) +{ + struct headset *hs = dev->headset; + + hs->hfp_active = active; +} + +GIOChannel *headset_get_rfcomm(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->tmp_rfcomm; +} + +int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *io) +{ + struct headset *hs = dev->headset; + + if (hs->tmp_rfcomm) + return -EALREADY; + + hs->tmp_rfcomm = g_io_channel_ref(io); + + return 0; +} + +int headset_connect_sco(struct audio_device *dev, GIOChannel *io) +{ + struct headset *hs = dev->headset; + struct headset_slc *slc = hs->slc; + + if (hs->sco) + return -EISCONN; + + hs->sco = g_io_channel_ref(io); + + if (slc->pending_ring) { + ring_timer_cb(NULL); + ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, + ring_timer_cb, + NULL); + slc->pending_ring = FALSE; + } + + return 0; +} + +void headset_set_state(struct audio_device *dev, headset_state_t state) +{ + struct headset *hs = dev->headset; + struct headset_slc *slc = hs->slc; + gboolean value; + const char *state_str; + headset_state_t old_state = hs->state; + GSList *l; + + if (old_state == state) + return; + + state_str = state2str(state); + + switch (state) { + case HEADSET_STATE_DISCONNECTED: + value = FALSE; + close_sco(dev); + headset_close_rfcomm(dev); + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Disconnected", + DBUS_TYPE_INVALID); + if (hs->state > HEADSET_STATE_CONNECTING) { + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); + telephony_device_disconnected(dev); + } + active_devices = g_slist_remove(active_devices, dev); + break; + case HEADSET_STATE_CONNECTING: + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + break; + case HEADSET_STATE_CONNECTED: + close_sco(dev); + if (hs->state != HEADSET_STATE_PLAY_IN_PROGRESS) + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + if (hs->state < state) { + if (ag.features & AG_FEATURE_INBAND_RINGTONE) + slc->inband_ring = TRUE; + else + slc->inband_ring = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Connected", + DBUS_TYPE_INVALID); + value = TRUE; + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Connected", + DBUS_TYPE_BOOLEAN, &value); + active_devices = g_slist_append(active_devices, dev); + telephony_device_connected(dev); + } else if (hs->state == HEADSET_STATE_PLAYING) { + value = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Stopped", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Playing", + DBUS_TYPE_BOOLEAN, &value); + } + break; + case HEADSET_STATE_PLAY_IN_PROGRESS: + break; + case HEADSET_STATE_PLAYING: + value = TRUE; + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + hs->sco_id = g_io_add_watch(hs->sco, + G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) sco_cb, dev); + + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "Playing", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "Playing", + DBUS_TYPE_BOOLEAN, &value); + + if (slc->sp_gain >= 0) + headset_send(hs, "\r\n+VGS=%u\r\n", slc->sp_gain); + if (slc->mic_gain >= 0) + headset_send(hs, "\r\n+VGM=%u\r\n", slc->mic_gain); + break; + } + + hs->state = state; + + DBG("State changed %s: %s -> %s", dev->path, str_state[old_state], + str_state[state]); + + for (l = headset_callbacks; l != NULL; l = l->next) { + struct headset_state_callback *cb = l->data; + cb->cb(dev, old_state, state, cb->user_data); + } +} + +headset_state_t headset_get_state(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->state; +} + +int headset_get_channel(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->rfcomm_ch; +} + +gboolean headset_is_active(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (hs->state != HEADSET_STATE_DISCONNECTED) + return TRUE; + + return FALSE; +} + +headset_lock_t headset_get_lock(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->lock; +} + +gboolean headset_lock(struct audio_device *dev, headset_lock_t lock) +{ + struct headset *hs = dev->headset; + + if (hs->lock & lock) + return FALSE; + + hs->lock |= lock; + + return TRUE; +} + +gboolean headset_unlock(struct audio_device *dev, headset_lock_t lock) +{ + struct headset *hs = dev->headset; + + if (!(hs->lock & lock)) + return FALSE; + + hs->lock &= ~lock; + + if (hs->lock) + return TRUE; + + if (hs->state == HEADSET_STATE_PLAYING) + headset_set_state(dev, HEADSET_STATE_CONNECTED); + + if (hs->auto_dc) { + if (hs->state == HEADSET_STATE_CONNECTED) + hs->dc_timer = g_timeout_add_seconds(DC_TIMEOUT, + (GSourceFunc) hs_dc_timeout, + dev); + else + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); + } + + return TRUE; +} + +gboolean headset_suspend(struct audio_device *dev, void *data) +{ + return TRUE; +} + +gboolean headset_play(struct audio_device *dev, void *data) +{ + return TRUE; +} + +int headset_get_sco_fd(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (!hs->sco) + return -1; + + return g_io_channel_unix_get_fd(hs->sco); +} + +gboolean headset_get_nrec(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (!hs->slc) + return TRUE; + + return hs->slc->nrec; +} + +unsigned int headset_add_nrec_cb(struct audio_device *dev, + headset_nrec_cb cb, void *user_data) +{ + struct headset *hs = dev->headset; + struct headset_nrec_callback *nrec_cb; + static unsigned int id = 0; + + nrec_cb = g_new(struct headset_nrec_callback, 1); + nrec_cb->cb = cb; + nrec_cb->user_data = user_data; + nrec_cb->id = ++id; + + hs->nrec_cbs = g_slist_prepend(hs->nrec_cbs, nrec_cb); + + return nrec_cb->id; +} + +gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id) +{ + struct headset *hs = dev->headset; + GSList *l; + + for (l = hs->nrec_cbs; l != NULL; l = l->next) { + struct headset_nrec_callback *cb = l->data; + if (cb && cb->id == id) { + hs->nrec_cbs = g_slist_remove(hs->nrec_cbs, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} + +gboolean headset_get_inband(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (!hs->slc) + return TRUE; + + return hs->slc->inband_ring; +} + +gboolean headset_get_sco_hci(struct audio_device *dev) +{ + return sco_hci; +} + +void headset_shutdown(struct audio_device *dev) +{ + struct pending_connect *p = dev->headset->pending; + + if (p && p->msg) + error_connect_failed(dev->conn, p->msg, ECANCELED); + + pending_connect_finalize(dev); + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); +} + +int telephony_event_ind(int index) +{ + if (!active_devices) + return -ENODEV; + + if (!ag.er_ind) { + DBG("telephony_report_event called but events are disabled"); + return -EINVAL; + } + + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+CIEV: %d,%d\r\n", index + 1, + ag.indicators[index].val); + + return 0; +} + +int telephony_response_and_hold_ind(int rh) +{ + if (!active_devices) + return -ENODEV; + + ag.rh = rh; + + /* If we aren't in any response and hold state don't send anything */ + if (ag.rh < 0) + return 0; + + send_foreach_headset(active_devices, hfp_cmp, "\r\n+BTRH: %d\r\n", + ag.rh); + + return 0; +} + +int telephony_incoming_call_ind(const char *number, int type) +{ + struct audio_device *dev; + struct headset *hs; + struct headset_slc *slc; + + if (fast_connectable) + manager_set_fast_connectable(TRUE); + + if (!active_devices) + return -ENODEV; + + /* Get the latest connected device */ + dev = active_devices->data; + hs = dev->headset; + slc = hs->slc; + + if (ag.ring_timer) { + DBG("telephony_incoming_call_ind: already calling"); + return -EBUSY; + } + + /* With HSP 1.2 the RING messages should *not* be sent if inband + * ringtone is being used */ + if (!hs->hfp_active && slc->inband_ring) + return 0; + + g_free(ag.number); + ag.number = g_strdup(number); + ag.number_type = type; + + if (slc->inband_ring && hs->hfp_active && + hs->state != HEADSET_STATE_PLAYING) { + slc->pending_ring = TRUE; + return 0; + } + + ring_timer_cb(NULL); + ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb, + NULL); + + return 0; +} + +int telephony_calling_stopped_ind(void) +{ + struct audio_device *dev; + + if (fast_connectable) + manager_set_fast_connectable(FALSE); + + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } + + if (!active_devices) + return 0; + + /* In case SCO isn't fully up yet */ + dev = active_devices->data; + + if (!dev->headset->slc->pending_ring && !ag.ring_timer) + return -EINVAL; + + dev->headset->slc->pending_ring = FALSE; + + return 0; +} + +int telephony_ready_ind(uint32_t features, + const struct indicator *indicators, int rh, + const char *chld) +{ + ag.telephony_ready = TRUE; + ag.features = features; + ag.indicators = indicators; + ag.rh = rh; + ag.chld = chld; + + DBG("Telephony plugin initialized"); + + print_ag_features(ag.features); + + return 0; +} + +int telephony_deinit(void) +{ + g_free(ag.number); + + memset(&ag, 0, sizeof(ag)); + + ag.er_mode = 3; + ag.rh = BTRH_NOT_SUPPORTED; + + DBG("Telephony deinitialized"); + + return 0; +} + +int telephony_list_current_call_ind(int idx, int dir, int status, int mode, + int mprty, const char *number, + int type) +{ + if (!active_devices) + return -ENODEV; + + if (number && strlen(number) > 0) + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n", + idx, dir, status, mode, mprty, number, type); + else + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+CLCC: %d,%d,%d,%d,%d\r\n", + idx, dir, status, mode, mprty); + + return 0; +} + +int telephony_subscriber_number_ind(const char *number, int type, int service) +{ + if (!active_devices) + return -ENODEV; + + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+CNUM: ,%s,%d,,%d\r\n", + number, type, service); + + return 0; +} + +static int cwa_cmp(struct headset *hs) +{ + if (!hs->hfp_active) + return -1; + + if (hs->slc->cwa_enabled) + return 0; + else + return -1; +} + +int telephony_call_waiting_ind(const char *number, int type) +{ + if (!active_devices) + return -ENODEV; + + send_foreach_headset(active_devices, cwa_cmp, + "\r\n+CCWA: \"%s\",%d\r\n", + number, type); + + return 0; +} + +unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data) +{ + struct headset_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct headset_state_callback, 1); + state_cb->cb = cb; + state_cb->user_data = user_data; + state_cb->id = ++id; + + headset_callbacks = g_slist_append(headset_callbacks, state_cb); + + return state_cb->id; +} + +gboolean headset_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = headset_callbacks; l != NULL; l = l->next) { + struct headset_state_callback *cb = l->data; + if (cb && cb->id == id) { + headset_callbacks = g_slist_remove(headset_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/audio/headset.h b/audio/headset.h new file mode 100644 index 0000000..7ce88c8 --- /dev/null +++ b/audio/headset.h @@ -0,0 +1,109 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define AUDIO_HEADSET_INTERFACE "org.bluez.Headset" + +#define DEFAULT_HS_AG_CHANNEL 12 +#define DEFAULT_HF_AG_CHANNEL 13 + +typedef enum { + HEADSET_STATE_DISCONNECTED, + HEADSET_STATE_CONNECTING, + HEADSET_STATE_CONNECTED, + HEADSET_STATE_PLAY_IN_PROGRESS, + HEADSET_STATE_PLAYING +} headset_state_t; + +typedef enum { + HEADSET_LOCK_READ = 1, + HEADSET_LOCK_WRITE = 1 << 1, +} headset_lock_t; + +typedef void (*headset_state_cb) (struct audio_device *dev, + headset_state_t old_state, + headset_state_t new_state, + void *user_data); +typedef void (*headset_nrec_cb) (struct audio_device *dev, + gboolean nrec, + void *user_data); + +unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data); +gboolean headset_remove_state_cb(unsigned int id); + +typedef void (*headset_stream_cb_t) (struct audio_device *dev, void *user_data); + +void headset_connect_cb(GIOChannel *chan, GError *err, gpointer user_data); + +GIOChannel *headset_get_rfcomm(struct audio_device *dev); + +struct headset *headset_init(struct audio_device *dev, uint16_t svc, + const char *uuidstr); + +void headset_unregister(struct audio_device *dev); + +uint32_t headset_config_init(GKeyFile *config); + +void headset_update(struct audio_device *dev, uint16_t svc, + const char *uuidstr); + +unsigned int headset_config_stream(struct audio_device *dev, + gboolean auto_dc, + headset_stream_cb_t cb, + void *user_data); +unsigned int headset_request_stream(struct audio_device *dev, + headset_stream_cb_t cb, + void *user_data); +unsigned int headset_suspend_stream(struct audio_device *dev, + headset_stream_cb_t cb, + void *user_data); +gboolean headset_cancel_stream(struct audio_device *dev, unsigned int id); + +gboolean get_hfp_active(struct audio_device *dev); +void set_hfp_active(struct audio_device *dev, gboolean active); + +void headset_set_authorized(struct audio_device *dev); +int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *chan); +int headset_connect_sco(struct audio_device *dev, GIOChannel *io); + +headset_state_t headset_get_state(struct audio_device *dev); +void headset_set_state(struct audio_device *dev, headset_state_t state); + +int headset_get_channel(struct audio_device *dev); + +int headset_get_sco_fd(struct audio_device *dev); +gboolean headset_get_nrec(struct audio_device *dev); +unsigned int headset_add_nrec_cb(struct audio_device *dev, + headset_nrec_cb cb, void *user_data); +gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id); +gboolean headset_get_inband(struct audio_device *dev); +gboolean headset_get_sco_hci(struct audio_device *dev); + +gboolean headset_is_active(struct audio_device *dev); + +headset_lock_t headset_get_lock(struct audio_device *dev); +gboolean headset_lock(struct audio_device *dev, headset_lock_t lock); +gboolean headset_unlock(struct audio_device *dev, headset_lock_t lock); +gboolean headset_suspend(struct audio_device *dev, void *data); +gboolean headset_play(struct audio_device *dev, void *data); +void headset_shutdown(struct audio_device *dev); diff --git a/audio/ipc.c b/audio/ipc.c new file mode 100644 index 0000000..1bdad78 --- /dev/null +++ b/audio/ipc.c @@ -0,0 +1,133 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "ipc.h" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/* This table contains the string representation for messages types */ +static const char *strtypes[] = { + "BT_REQUEST", + "BT_RESPONSE", + "BT_INDICATION", + "BT_ERROR", +}; + +/* This table contains the string representation for messages names */ +static const char *strnames[] = { + "BT_GET_CAPABILITIES", + "BT_OPEN", + "BT_SET_CONFIGURATION", + "BT_NEW_STREAM", + "BT_START_STREAM", + "BT_STOP_STREAM", + "BT_SUSPEND_STREAM", + "BT_RESUME_STREAM", + "BT_CONTROL", +}; + +int bt_audio_service_open(void) +{ + int sk; + int err; + struct sockaddr_un addr = { + AF_UNIX, BT_IPC_SOCKET_NAME + }; + + sk = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sk < 0) { + err = errno; + fprintf(stderr, "%s: Cannot open socket: %s (%d)\n", + __FUNCTION__, strerror(err), err); + errno = err; + return -1; + } + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + err = errno; + fprintf(stderr, "%s: connect() failed: %s (%d)\n", + __FUNCTION__, strerror(err), err); + close(sk); + errno = err; + return -1; + } + + return sk; +} + +int bt_audio_service_close(int sk) +{ + return close(sk); +} + +int bt_audio_service_get_data_fd(int sk) +{ + char cmsg_b[CMSG_SPACE(sizeof(int))], m; + int err, ret; + struct iovec iov = { &m, sizeof(m) }; + struct msghdr msgh; + struct cmsghdr *cmsg; + + memset(&msgh, 0, sizeof(msgh)); + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = &cmsg_b; + msgh.msg_controllen = CMSG_LEN(sizeof(int)); + + ret = recvmsg(sk, &msgh, 0); + if (ret < 0) { + err = errno; + fprintf(stderr, "%s: Unable to receive fd: %s (%d)\n", + __FUNCTION__, strerror(err), err); + errno = err; + return -1; + } + + /* Receive auxiliary data in msgh */ + for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msgh, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + memcpy(&ret, CMSG_DATA(cmsg), sizeof(int)); + return ret; + } + } + + errno = EINVAL; + return -1; +} + +const char *bt_audio_strtype(uint8_t type) +{ + if (type >= ARRAY_SIZE(strtypes)) + return NULL; + + return strtypes[type]; +} + +const char *bt_audio_strname(uint8_t name) +{ + if (name >= ARRAY_SIZE(strnames)) + return NULL; + + return strnames[name]; +} diff --git a/audio/ipc.h b/audio/ipc.h new file mode 100644 index 0000000..d69b97e --- /dev/null +++ b/audio/ipc.h @@ -0,0 +1,360 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* + Message sequence chart of streaming sequence for A2DP transport + + Audio daemon User + on snd_pcm_open + <--BT_GET_CAPABILITIES_REQ + + BT_GET_CAPABILITIES_RSP--> + + on snd_pcm_hw_params + <--BT_SETCONFIGURATION_REQ + + BT_SET_CONFIGURATION_RSP--> + + on snd_pcm_prepare + <--BT_START_STREAM_REQ + + + BT_START_STREAM_RSP--> + + BT_NEW_STREAM_IND --> + + < streams data > + .......... + + on snd_pcm_drop/snd_pcm_drain + + <--BT_STOP_STREAM_REQ + + + BT_STOP_STREAM_RSP--> + + on IPC close or appl crash + + + */ + +#ifndef BT_AUDIOCLIENT_H +#define BT_AUDIOCLIENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include + +#define BT_SUGGESTED_BUFFER_SIZE 512 +#define BT_IPC_SOCKET_NAME "\0/org/bluez/audio" + +/* Generic message header definition, except for RESPONSE messages */ +typedef struct { + uint8_t type; + uint8_t name; + uint16_t length; +} __attribute__ ((packed)) bt_audio_msg_header_t; + +typedef struct { + bt_audio_msg_header_t h; + uint8_t posix_errno; +} __attribute__ ((packed)) bt_audio_error_t; + +/* Message types */ +#define BT_REQUEST 0 +#define BT_RESPONSE 1 +#define BT_INDICATION 2 +#define BT_ERROR 3 + +/* Messages names */ +#define BT_GET_CAPABILITIES 0 +#define BT_OPEN 1 +#define BT_SET_CONFIGURATION 2 +#define BT_NEW_STREAM 3 +#define BT_START_STREAM 4 +#define BT_STOP_STREAM 5 +#define BT_CLOSE 6 +#define BT_CONTROL 7 +#define BT_DELAY_REPORT 8 + +#define BT_CAPABILITIES_TRANSPORT_A2DP 0 +#define BT_CAPABILITIES_TRANSPORT_SCO 1 +#define BT_CAPABILITIES_TRANSPORT_ANY 2 + +#define BT_CAPABILITIES_ACCESS_MODE_READ 1 +#define BT_CAPABILITIES_ACCESS_MODE_WRITE 2 +#define BT_CAPABILITIES_ACCESS_MODE_READWRITE 3 + +#define BT_FLAG_AUTOCONNECT 1 + +struct bt_get_capabilities_req { + bt_audio_msg_header_t h; + char source[18]; /* Address of the local Device */ + char destination[18];/* Address of the remote Device */ + char object[128]; /* DBus object path */ + uint8_t transport; /* Requested transport */ + uint8_t flags; /* Requested flags */ + uint8_t seid; /* Requested capability configuration */ +} __attribute__ ((packed)); + +/** + * SBC Codec parameters as per A2DP profile 1.0 § 4.3 + */ + +/* A2DP seid are 6 bytes long so HSP/HFP are assigned to 7-8 bits */ +#define BT_A2DP_SEID_RANGE (1 << 6) - 1 + +#define BT_A2DP_SBC_SOURCE 0x00 +#define BT_A2DP_SBC_SINK 0x01 +#define BT_A2DP_MPEG12_SOURCE 0x02 +#define BT_A2DP_MPEG12_SINK 0x03 +#define BT_A2DP_MPEG24_SOURCE 0x04 +#define BT_A2DP_MPEG24_SINK 0x05 +#define BT_A2DP_ATRAC_SOURCE 0x06 +#define BT_A2DP_ATRAC_SINK 0x07 +#define BT_A2DP_UNKNOWN_SOURCE 0x08 +#define BT_A2DP_UNKNOWN_SINK 0x09 + +#define BT_SBC_SAMPLING_FREQ_16000 (1 << 3) +#define BT_SBC_SAMPLING_FREQ_32000 (1 << 2) +#define BT_SBC_SAMPLING_FREQ_44100 (1 << 1) +#define BT_SBC_SAMPLING_FREQ_48000 1 + +#define BT_A2DP_CHANNEL_MODE_MONO (1 << 3) +#define BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define BT_A2DP_CHANNEL_MODE_STEREO (1 << 1) +#define BT_A2DP_CHANNEL_MODE_JOINT_STEREO 1 + +#define BT_A2DP_BLOCK_LENGTH_4 (1 << 3) +#define BT_A2DP_BLOCK_LENGTH_8 (1 << 2) +#define BT_A2DP_BLOCK_LENGTH_12 (1 << 1) +#define BT_A2DP_BLOCK_LENGTH_16 1 + +#define BT_A2DP_SUBBANDS_4 (1 << 1) +#define BT_A2DP_SUBBANDS_8 1 + +#define BT_A2DP_ALLOCATION_SNR (1 << 1) +#define BT_A2DP_ALLOCATION_LOUDNESS 1 + +#define BT_MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define BT_MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define BT_MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define BT_MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define BT_MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define BT_MPEG_SAMPLING_FREQ_48000 1 + +#define BT_MPEG_LAYER_1 (1 << 2) +#define BT_MPEG_LAYER_2 (1 << 1) +#define BT_MPEG_LAYER_3 1 + +#define BT_HFP_CODEC_PCM 0x00 + +#define BT_PCM_FLAG_NREC 0x01 +#define BT_PCM_FLAG_PCM_ROUTING 0x02 + +#define BT_WRITE_LOCK (1 << 1) +#define BT_READ_LOCK 1 + +typedef struct { + uint8_t seid; + uint8_t transport; + uint8_t type; + uint8_t length; + uint8_t configured; + uint8_t lock; + uint8_t data[0]; +} __attribute__ ((packed)) codec_capabilities_t; + +typedef struct { + codec_capabilities_t capability; + uint8_t channel_mode; + uint8_t frequency; + uint8_t allocation_method; + uint8_t subbands; + uint8_t block_length; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) sbc_capabilities_t; + +typedef struct { + codec_capabilities_t capability; + uint8_t channel_mode; + uint8_t crc; + uint8_t layer; + uint8_t frequency; + uint8_t mpf; + uint16_t bitrate; +} __attribute__ ((packed)) mpeg_capabilities_t; + +typedef struct { + codec_capabilities_t capability; + uint8_t flags; + uint16_t sampling_rate; +} __attribute__ ((packed)) pcm_capabilities_t; + +struct bt_get_capabilities_rsp { + bt_audio_msg_header_t h; + char source[18]; /* Address of the local Device */ + char destination[18];/* Address of the remote Device */ + char object[128]; /* DBus object path */ + uint8_t data[0]; /* First codec_capabilities_t */ +} __attribute__ ((packed)); + +struct bt_open_req { + bt_audio_msg_header_t h; + char source[18]; /* Address of the local Device */ + char destination[18];/* Address of the remote Device */ + char object[128]; /* DBus object path */ + uint8_t seid; /* Requested capability configuration to lock */ + uint8_t lock; /* Requested lock */ +} __attribute__ ((packed)); + +struct bt_open_rsp { + bt_audio_msg_header_t h; + char source[18]; /* Address of the local Device */ + char destination[18];/* Address of the remote Device */ + char object[128]; /* DBus object path */ +} __attribute__ ((packed)); + +struct bt_set_configuration_req { + bt_audio_msg_header_t h; + codec_capabilities_t codec; /* Requested codec */ +} __attribute__ ((packed)); + +struct bt_set_configuration_rsp { + bt_audio_msg_header_t h; + uint16_t link_mtu; /* Max length that transport supports */ +} __attribute__ ((packed)); + +#define BT_STREAM_ACCESS_READ 0 +#define BT_STREAM_ACCESS_WRITE 1 +#define BT_STREAM_ACCESS_READWRITE 2 +struct bt_start_stream_req { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_start_stream_rsp { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +/* This message is followed by one byte of data containing the stream data fd + as ancilliary data */ +struct bt_new_stream_ind { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_stop_stream_req { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_stop_stream_rsp { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_close_req { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_close_rsp { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_suspend_stream_ind { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_resume_stream_ind { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +#define BT_CONTROL_KEY_POWER 0x40 +#define BT_CONTROL_KEY_VOL_UP 0x41 +#define BT_CONTROL_KEY_VOL_DOWN 0x42 +#define BT_CONTROL_KEY_MUTE 0x43 +#define BT_CONTROL_KEY_PLAY 0x44 +#define BT_CONTROL_KEY_STOP 0x45 +#define BT_CONTROL_KEY_PAUSE 0x46 +#define BT_CONTROL_KEY_RECORD 0x47 +#define BT_CONTROL_KEY_REWIND 0x48 +#define BT_CONTROL_KEY_FAST_FORWARD 0x49 +#define BT_CONTROL_KEY_EJECT 0x4A +#define BT_CONTROL_KEY_FORWARD 0x4B +#define BT_CONTROL_KEY_BACKWARD 0x4C + +struct bt_control_req { + bt_audio_msg_header_t h; + uint8_t mode; /* Control Mode */ + uint8_t key; /* Control Key */ +} __attribute__ ((packed)); + +struct bt_control_rsp { + bt_audio_msg_header_t h; + uint8_t mode; /* Control Mode */ + uint8_t key; /* Control Key */ +} __attribute__ ((packed)); + +struct bt_control_ind { + bt_audio_msg_header_t h; + uint8_t mode; /* Control Mode */ + uint8_t key; /* Control Key */ +} __attribute__ ((packed)); + +struct bt_delay_report_req { + bt_audio_msg_header_t h; + uint16_t delay; +} __attribute__ ((packed)); + +struct bt_delay_report_ind { + bt_audio_msg_header_t h; + uint16_t delay; +} __attribute__ ((packed)); + +/* Function declaration */ + +/* Opens a connection to the audio service: return a socket descriptor */ +int bt_audio_service_open(void); + +/* Closes a connection to the audio service */ +int bt_audio_service_close(int sk); + +/* Receives stream data file descriptor : must be called after a +BT_STREAMFD_IND message is returned */ +int bt_audio_service_get_data_fd(int sk); + +/* Human readable message type string */ +const char *bt_audio_strtype(uint8_t type); + +/* Human readable message name string */ +const char *bt_audio_strname(uint8_t name); + +#ifdef __cplusplus +} +#endif + +#endif /* BT_AUDIOCLIENT_H */ diff --git a/audio/main.c b/audio/main.c new file mode 100644 index 0000000..745c307 --- /dev/null +++ b/audio/main.c @@ -0,0 +1,194 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "glib-helper.h" +#include "btio.h" +#include "plugin.h" +#include "log.h" +#include "device.h" +#include "headset.h" +#include "manager.h" +#include "gateway.h" + +static GIOChannel *sco_server = NULL; + +static GKeyFile *load_config_file(const char *file) +{ + GError *err = NULL; + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + + g_key_file_set_list_separator(keyfile, ','); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + error("Parsing %s failed: %s", file, err->message); + g_error_free(err); + g_key_file_free(keyfile); + return NULL; + } + + return keyfile; +} + +static void sco_server_cb(GIOChannel *chan, GError *err, gpointer data) +{ + int sk; + struct audio_device *device; + char addr[18]; + bdaddr_t src, dst; + + if (err) { + error("sco_server_cb: %s", err->message); + return; + } + + bt_io_get(chan, BT_IO_SCO, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, addr, + BT_IO_OPT_INVALID); + if (err) { + error("bt_io_get: %s", err->message); + goto drop; + } + + device = manager_find_device(NULL, &src, &dst, AUDIO_HEADSET_INTERFACE, + FALSE); + if (!device) + device = manager_find_device(NULL, &src, &dst, + AUDIO_GATEWAY_INTERFACE, + FALSE); + + if (!device) + goto drop; + + if (device->headset) { + if (headset_get_state(device) < HEADSET_STATE_CONNECTED) { + DBG("Refusing SCO from non-connected headset"); + goto drop; + } + + if (!get_hfp_active(device)) { + error("Refusing non-HFP SCO connect attempt from %s", + addr); + goto drop; + } + + if (headset_connect_sco(device, chan) < 0) + goto drop; + + headset_set_state(device, HEADSET_STATE_PLAYING); + } else if (device->gateway) { + if (!gateway_is_connected(device)) { + DBG("Refusing SCO from non-connected AG"); + goto drop; + } + + if (gateway_connect_sco(device, chan) < 0) + goto drop; + } else + goto drop; + + sk = g_io_channel_unix_get_fd(chan); + fcntl(sk, F_SETFL, 0); + + DBG("Accepted SCO connection from %s", addr); + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static DBusConnection *connection; + +static int audio_init(void) +{ + GKeyFile *config; + gboolean enable_sco; + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (connection == NULL) + return -EIO; + + config = load_config_file(CONFIGDIR "/audio.conf"); + + if (audio_manager_init(connection, config, &enable_sco) < 0) + goto failed; + + if (!enable_sco) + return 0; + + sco_server = bt_io_listen(BT_IO_SCO, sco_server_cb, NULL, NULL, + NULL, NULL, + BT_IO_OPT_INVALID); + if (!sco_server) { + error("Unable to start SCO server socket"); + goto failed; + } + + return 0; + +failed: + audio_manager_exit(); + + if (connection) { + dbus_connection_unref(connection); + connection = NULL; + } + + return -EIO; +} + +static void audio_exit(void) +{ + if (sco_server) { + g_io_channel_shutdown(sco_server, TRUE, NULL); + g_io_channel_unref(sco_server); + sco_server = NULL; + } + + audio_manager_exit(); + + dbus_connection_unref(connection); +} + +BLUETOOTH_PLUGIN_DEFINE(audio, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, audio_init, audio_exit) diff --git a/audio/manager.c b/audio/manager.c new file mode 100644 index 0000000..7e206be --- /dev/null +++ b/audio/manager.c @@ -0,0 +1,1415 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "glib-helper.h" +#include "btio.h" +#include "../src/adapter.h" +#include "../src/manager.h" +#include "../src/device.h" + +#include "log.h" +#include "textfile.h" +#include "ipc.h" +#include "device.h" +#include "error.h" +#include "avdtp.h" +#include "media.h" +#include "a2dp.h" +#include "headset.h" +#include "gateway.h" +#include "sink.h" +#include "source.h" +#include "control.h" +#include "manager.h" +#include "sdpd.h" +#include "telephony.h" +#include "unix.h" + +#ifndef DBUS_TYPE_UNIX_FD +#define DBUS_TYPE_UNIX_FD -1 +#endif + +typedef enum { + HEADSET = 1 << 0, + GATEWAY = 1 << 1, + SINK = 1 << 2, + SOURCE = 1 << 3, + CONTROL = 1 << 4, + TARGET = 1 << 5, + INVALID = 1 << 6 +} audio_service_type; + +typedef enum { + GENERIC_AUDIO = 0, + ADVANCED_AUDIO, + AV_REMOTE, + GET_RECORDS +} audio_sdp_state_t; + +struct audio_adapter { + struct btd_adapter *btd_adapter; + gboolean powered; + uint32_t hsp_ag_record_id; + uint32_t hfp_ag_record_id; + uint32_t hfp_hs_record_id; + GIOChannel *hsp_ag_server; + GIOChannel *hfp_ag_server; + GIOChannel *hfp_hs_server; + gint ref; +}; + +static gboolean auto_connect = TRUE; +static int max_connected_headsets = 1; +static DBusConnection *connection = NULL; +static GKeyFile *config = NULL; +static GSList *adapters = NULL; +static GSList *devices = NULL; + +static struct enabled_interfaces enabled = { + .hfp = TRUE, + .headset = TRUE, + .gateway = FALSE, + .sink = TRUE, + .source = FALSE, + .control = TRUE, + .socket = TRUE, + .media = FALSE +}; + +static struct audio_adapter *find_adapter(GSList *list, + struct btd_adapter *btd_adapter) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct audio_adapter *adapter = l->data; + + if (adapter->btd_adapter == btd_adapter) + return adapter; + } + + return NULL; +} + +gboolean server_is_enabled(bdaddr_t *src, uint16_t svc) +{ + switch (svc) { + case HEADSET_SVCLASS_ID: + return enabled.headset; + case HEADSET_AGW_SVCLASS_ID: + return FALSE; + case HANDSFREE_SVCLASS_ID: + return enabled.headset && enabled.hfp; + case HANDSFREE_AGW_SVCLASS_ID: + return enabled.gateway; + case AUDIO_SINK_SVCLASS_ID: + return enabled.sink; + case AUDIO_SOURCE_SVCLASS_ID: + return enabled.source; + case AV_REMOTE_TARGET_SVCLASS_ID: + case AV_REMOTE_SVCLASS_ID: + return enabled.control; + default: + return FALSE; + } +} + +static void handle_uuid(const char *uuidstr, struct audio_device *device) +{ + uuid_t uuid; + uint16_t uuid16; + + if (bt_string2uuid(&uuid, uuidstr) < 0) { + error("%s not detected as an UUID-128", uuidstr); + return; + } + + if (!sdp_uuid128_to_uuid(&uuid) && uuid.type != SDP_UUID16) { + error("Could not convert %s to a UUID-16", uuidstr); + return; + } + + uuid16 = uuid.value.uuid16; + + if (!server_is_enabled(&device->src, uuid16)) { + DBG("server not enabled for %s (0x%04x)", uuidstr, uuid16); + return; + } + + switch (uuid16) { + case HEADSET_SVCLASS_ID: + DBG("Found Headset record"); + if (device->headset) + headset_update(device, uuid16, uuidstr); + else + device->headset = headset_init(device, uuid16, + uuidstr); + break; + case HEADSET_AGW_SVCLASS_ID: + DBG("Found Headset AG record"); + break; + case HANDSFREE_SVCLASS_ID: + DBG("Found Handsfree record"); + if (device->headset) + headset_update(device, uuid16, uuidstr); + else + device->headset = headset_init(device, uuid16, + uuidstr); + break; + case HANDSFREE_AGW_SVCLASS_ID: + DBG("Found Handsfree AG record"); + if (enabled.gateway && (device->gateway == NULL)) + device->gateway = gateway_init(device); + break; + case AUDIO_SINK_SVCLASS_ID: + DBG("Found Audio Sink"); + if (device->sink == NULL) + device->sink = sink_init(device); + break; + case AUDIO_SOURCE_SVCLASS_ID: + DBG("Found Audio Source"); + if (device->source == NULL) + device->source = source_init(device); + break; + case AV_REMOTE_SVCLASS_ID: + case AV_REMOTE_TARGET_SVCLASS_ID: + DBG("Found AV %s", uuid16 == AV_REMOTE_SVCLASS_ID ? + "Remote" : "Target"); + if (device->control) + control_update(device, uuid16); + else + device->control = control_init(device, uuid16); + if (device->sink && sink_is_active(device)) + avrcp_connect(device); + break; + default: + DBG("Unrecognized UUID: 0x%04X", uuid16); + break; + } +} + +static sdp_record_t *hsp_ag_record(uint8_t ch) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; + uuid_t l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_record_t *record; + sdp_list_t *aproto, *proto[2]; + sdp_data_t *channel; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&svclass_uuid, HEADSET_AGW_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID); + profile.version = 0x0102; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Headset Audio Gateway", 0, 0); + + sdp_data_free(channel); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static sdp_record_t *hfp_hs_record(uint8_t ch) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; + uuid_t l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_record_t *record; + sdp_list_t *aproto, *proto[2]; + sdp_data_t *channel; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&svclass_uuid, HANDSFREE_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID); + profile.version = 0x0105; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Hands-Free", 0, 0); + + sdp_data_free(channel); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static sdp_record_t *hfp_ag_record(uint8_t ch, uint32_t feat) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; + uuid_t l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *channel, *features; + uint8_t netid = 0x01; + uint16_t sdpfeat; + sdp_data_t *network; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + network = sdp_data_alloc(SDP_UINT8, &netid); + if (!network) { + sdp_record_free(record); + return NULL; + } + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&svclass_uuid, HANDSFREE_AGW_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID); + profile.version = 0x0105; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + sdpfeat = (uint16_t) feat & 0xF; + features = sdp_data_alloc(SDP_UINT16, &sdpfeat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Hands-Free Audio Gateway", 0, 0); + + sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network); + + sdp_data_free(channel); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static void headset_auth_cb(DBusError *derr, void *user_data) +{ + struct audio_device *device = user_data; + GError *err = NULL; + GIOChannel *io; + + if (device->hs_preauth_id) { + g_source_remove(device->hs_preauth_id); + device->hs_preauth_id = 0; + } + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + return; + } + + io = headset_get_rfcomm(device); + + if (!bt_io_accept(io, headset_connect_cb, device, NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + return; + } +} + +static gboolean hs_preauth_cb(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + struct audio_device *device = user_data; + + DBG("Headset disconnected during authorization"); + + audio_device_cancel_authorization(device, headset_auth_cb, device); + + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + + device->hs_preauth_id = 0; + + return FALSE; +} + +static void ag_confirm(GIOChannel *chan, gpointer data) +{ + const char *server_uuid, *remote_uuid; + struct audio_device *device; + gboolean hfp_active; + bdaddr_t src, dst; + int perr; + GError *err = NULL; + uint8_t ch; + + bt_io_get(chan, BT_IO_RFCOMM, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_CHANNEL, &ch, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + goto drop; + } + + if (ch == DEFAULT_HS_AG_CHANNEL) { + hfp_active = FALSE; + server_uuid = HSP_AG_UUID; + remote_uuid = HSP_HS_UUID; + } else { + hfp_active = TRUE; + server_uuid = HFP_AG_UUID; + remote_uuid = HFP_HS_UUID; + } + + device = manager_get_device(&src, &dst, TRUE); + if (!device) + goto drop; + + if (!manager_allow_headset_connection(device)) { + DBG("Refusing headset: too many existing connections"); + goto drop; + } + + if (!device->headset) { + btd_device_add_uuid(device->btd_dev, remote_uuid); + if (!device->headset) + goto drop; + } + + if (headset_get_state(device) > HEADSET_STATE_DISCONNECTED) { + DBG("Refusing new connection since one already exists"); + goto drop; + } + + set_hfp_active(device, hfp_active); + + if (headset_connect_rfcomm(device, chan) < 0) { + error("headset_connect_rfcomm failed"); + goto drop; + } + + headset_set_state(device, HEADSET_STATE_CONNECTING); + + perr = audio_device_request_authorization(device, server_uuid, + headset_auth_cb, device); + if (perr < 0) { + DBG("Authorization denied: %s", strerror(-perr)); + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + return; + } + + device->hs_preauth_id = g_io_add_watch(chan, + G_IO_NVAL | G_IO_HUP | G_IO_ERR, + hs_preauth_cb, device); + + device->auto_connect = auto_connect; + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static void gateway_auth_cb(DBusError *derr, void *user_data) +{ + struct audio_device *device = user_data; + + if (derr && dbus_error_is_set(derr)) + error("Access denied: %s", derr->message); + else { + char ag_address[18]; + + ba2str(&device->dst, ag_address); + DBG("Accepted AG connection from %s for %s", + ag_address, device->path); + + gateway_start_service(device); + } +} + +static void hf_io_cb(GIOChannel *chan, gpointer data) +{ + bdaddr_t src, dst; + GError *err = NULL; + uint8_t ch; + const char *server_uuid, *remote_uuid; + uint16_t svclass; + struct audio_device *device; + int perr; + + bt_io_get(chan, BT_IO_RFCOMM, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_CHANNEL, &ch, + BT_IO_OPT_INVALID); + + if (err) { + error("%s", err->message); + g_error_free(err); + return; + } + + server_uuid = HFP_AG_UUID; + remote_uuid = HFP_HS_UUID; + svclass = HANDSFREE_AGW_SVCLASS_ID; + + device = manager_get_device(&src, &dst, TRUE); + if (!device) + goto drop; + + if (!device->gateway) { + btd_device_add_uuid(device->btd_dev, remote_uuid); + if (!device->gateway) + goto drop; + } + + if (gateway_is_connected(device)) { + DBG("Refusing new connection since one already exists"); + goto drop; + } + + if (gateway_connect_rfcomm(device, chan) < 0) { + error("Allocating new GIOChannel failed!"); + goto drop; + } + + perr = audio_device_request_authorization(device, server_uuid, + gateway_auth_cb, device); + if (perr < 0) { + DBG("Authorization denied!"); + goto drop; + } + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static int headset_server_init(struct audio_adapter *adapter) +{ + uint8_t chan = DEFAULT_HS_AG_CHANNEL; + sdp_record_t *record; + gboolean master = TRUE; + GError *err = NULL; + uint32_t features; + GIOChannel *io; + bdaddr_t src; + + if (config) { + gboolean tmp; + + tmp = g_key_file_get_boolean(config, "General", "Master", + &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else + master = tmp; + } + + adapter_get_address(adapter->btd_adapter, &src); + + io = bt_io_listen(BT_IO_RFCOMM, NULL, ag_confirm, adapter, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) + goto failed; + + adapter->hsp_ag_server = io; + + record = hsp_ag_record(chan); + if (!record) { + error("Unable to allocate new service record"); + goto failed; + } + + if (add_record_to_server(&src, record) < 0) { + error("Unable to register HS AG service record"); + sdp_record_free(record); + goto failed; + } + adapter->hsp_ag_record_id = record->handle; + + features = headset_config_init(config); + + if (!enabled.hfp) + return 0; + + chan = DEFAULT_HF_AG_CHANNEL; + + io = bt_io_listen(BT_IO_RFCOMM, NULL, ag_confirm, adapter, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) + goto failed; + + adapter->hfp_ag_server = io; + + record = hfp_ag_record(chan, features); + if (!record) { + error("Unable to allocate new service record"); + goto failed; + } + + if (add_record_to_server(&src, record) < 0) { + error("Unable to register HF AG service record"); + sdp_record_free(record); + goto failed; + } + adapter->hfp_ag_record_id = record->handle; + + return 0; + +failed: + error("%s", err->message); + g_error_free(err); + if (adapter->hsp_ag_server) { + g_io_channel_shutdown(adapter->hsp_ag_server, TRUE, NULL); + g_io_channel_unref(adapter->hsp_ag_server); + adapter->hsp_ag_server = NULL; + } + + if (adapter->hfp_ag_server) { + g_io_channel_shutdown(adapter->hfp_ag_server, TRUE, NULL); + g_io_channel_unref(adapter->hfp_ag_server); + adapter->hfp_ag_server = NULL; + } + + return -1; +} + +static int gateway_server_init(struct audio_adapter *adapter) +{ + uint8_t chan = DEFAULT_HFP_HS_CHANNEL; + sdp_record_t *record; + gboolean master = TRUE; + GError *err = NULL; + GIOChannel *io; + bdaddr_t src; + + if (config) { + gboolean tmp; + + tmp = g_key_file_get_boolean(config, "General", "Master", + &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else + master = tmp; + } + + adapter_get_address(adapter->btd_adapter, &src); + + io = bt_io_listen(BT_IO_RFCOMM, NULL, hf_io_cb, adapter, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return -1; + } + + adapter->hfp_hs_server = io; + record = hfp_hs_record(chan); + if (!record) { + error("Unable to allocate new service record"); + return -1; + } + + if (add_record_to_server(&src, record) < 0) { + error("Unable to register HFP HS service record"); + sdp_record_free(record); + g_io_channel_unref(adapter->hfp_hs_server); + adapter->hfp_hs_server = NULL; + return -1; + } + + adapter->hfp_hs_record_id = record->handle; + + return 0; +} + +static int audio_probe(struct btd_device *device, GSList *uuids) +{ + struct btd_adapter *adapter = device_get_adapter(device); + bdaddr_t src, dst; + struct audio_device *audio_dev; + + adapter_get_address(adapter, &src); + device_get_address(device, &dst); + + audio_dev = manager_get_device(&src, &dst, TRUE); + if (!audio_dev) { + DBG("unable to get a device object"); + return -1; + } + + g_slist_foreach(uuids, (GFunc) handle_uuid, audio_dev); + + return 0; +} + +static void audio_remove(struct btd_device *device) +{ + struct audio_device *dev; + const char *path; + + path = device_get_path(device); + + dev = manager_find_device(path, NULL, NULL, NULL, FALSE); + if (!dev) + return; + + devices = g_slist_remove(devices, dev); + + audio_device_unregister(dev); + +} + +static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp) +{ + adp->ref++; + + DBG("%p: ref=%d", adp, adp->ref); + + return adp; +} + +static void audio_adapter_unref(struct audio_adapter *adp) +{ + adp->ref--; + + DBG("%p: ref=%d", adp, adp->ref); + + if (adp->ref > 0) + return; + + adapters = g_slist_remove(adapters, adp); + btd_adapter_unref(adp->btd_adapter); + g_free(adp); +} + +static struct audio_adapter *audio_adapter_create(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + + adp = g_new0(struct audio_adapter, 1); + adp->btd_adapter = btd_adapter_ref(adapter); + + return audio_adapter_ref(adp); +} + +static struct audio_adapter *audio_adapter_get(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + + adp = find_adapter(adapters, adapter); + if (!adp) { + adp = audio_adapter_create(adapter); + if (!adp) + return NULL; + adapters = g_slist_append(adapters, adp); + } else + audio_adapter_ref(adp); + + return adp; +} + +static void state_changed(struct btd_adapter *adapter, gboolean powered) +{ + struct audio_adapter *adp; + static gboolean telephony = FALSE; + GSList *l; + + DBG("%s powered %s", adapter_get_path(adapter), + powered ? "on" : "off"); + + /* ignore powered change, adapter is powering down */ + if (powered && adapter_powering_down(adapter)) + return; + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + adp->powered = powered; + + if (powered) { + /* telephony driver already initialized*/ + if (telephony == TRUE) + return; + telephony_init(); + telephony = TRUE; + return; + } + + /* telephony not initialized just ignore power down */ + if (telephony == FALSE) + return; + + for (l = adapters; l; l = l->next) { + adp = l->data; + + if (adp->powered == TRUE) + return; + } + + telephony_exit(); + telephony = FALSE; +} + +static int headset_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + int err; + + DBG("path %s", path); + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + err = headset_server_init(adp); + if (err < 0) { + audio_adapter_unref(adp); + return err; + } + + btd_adapter_register_powered_callback(adapter, state_changed); + state_changed(adapter, TRUE); + + return 0; +} + +static void headset_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + if (adp->hsp_ag_record_id) { + remove_record_from_server(adp->hsp_ag_record_id); + adp->hsp_ag_record_id = 0; + } + + if (adp->hsp_ag_server) { + g_io_channel_shutdown(adp->hsp_ag_server, TRUE, NULL); + g_io_channel_unref(adp->hsp_ag_server); + adp->hsp_ag_server = NULL; + } + + if (adp->hfp_ag_record_id) { + remove_record_from_server(adp->hfp_ag_record_id); + adp->hfp_ag_record_id = 0; + } + + if (adp->hfp_ag_server) { + g_io_channel_shutdown(adp->hfp_ag_server, TRUE, NULL); + g_io_channel_unref(adp->hfp_ag_server); + adp->hfp_ag_server = NULL; + } + + btd_adapter_unregister_powered_callback(adapter, state_changed); + + audio_adapter_unref(adp); +} + +static int gateway_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + return gateway_server_init(adp); +} + +static void gateway_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + if (adp->hfp_hs_record_id) { + remove_record_from_server(adp->hfp_hs_record_id); + adp->hfp_hs_record_id = 0; + } + + if (adp->hfp_hs_server) { + g_io_channel_unref(adp->hfp_hs_server); + adp->hfp_hs_server = NULL; + } + + audio_adapter_unref(adp); +} + +static int a2dp_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + int err; + + DBG("path %s", path); + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + adapter_get_address(adapter, &src); + + err = a2dp_register(connection, &src, config); + if (err < 0) + audio_adapter_unref(adp); + + return err; +} + +static void a2dp_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + adapter_get_address(adapter, &src); + a2dp_unregister(&src); + audio_adapter_unref(adp); +} + +static int avrcp_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + + DBG("path %s", path); + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + adapter_get_address(adapter, &src); + + return avrcp_register(connection, &src, config); +} + +static void avrcp_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + adapter_get_address(adapter, &src); + avrcp_unregister(&src); + audio_adapter_unref(adp); +} + +static int media_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + + DBG("path %s", path); + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + adapter_get_address(adapter, &src); + + return media_register(connection, path, &src); +} + +static void media_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + media_unregister(path); + audio_adapter_unref(adp); +} + +static struct btd_device_driver audio_driver = { + .name = "audio", + .uuids = BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID, + ADVANCED_AUDIO_UUID, A2DP_SOURCE_UUID, A2DP_SINK_UUID, + AVRCP_TARGET_UUID, AVRCP_REMOTE_UUID), + .probe = audio_probe, + .remove = audio_remove, +}; + +static struct btd_adapter_driver headset_server_driver = { + .name = "audio-headset", + .probe = headset_server_probe, + .remove = headset_server_remove, +}; + +static struct btd_adapter_driver gateway_server_driver = { + .name = "audio-gateway", + .probe = gateway_server_probe, + .remove = gateway_server_remove, +}; + +static struct btd_adapter_driver a2dp_server_driver = { + .name = "audio-a2dp", + .probe = a2dp_server_probe, + .remove = a2dp_server_remove, +}; + +static struct btd_adapter_driver avrcp_server_driver = { + .name = "audio-control", + .probe = avrcp_server_probe, + .remove = avrcp_server_remove, +}; + +static struct btd_adapter_driver media_server_driver = { + .name = "media", + .probe = media_server_probe, + .remove = media_server_remove, +}; + +int audio_manager_init(DBusConnection *conn, GKeyFile *conf, + gboolean *enable_sco) +{ + char **list; + int i; + gboolean b; + GError *err = NULL; + + connection = dbus_connection_ref(conn); + + if (!conf) + goto proceed; + + config = conf; + + list = g_key_file_get_string_list(config, "General", "Enable", + NULL, NULL); + for (i = 0; list && list[i] != NULL; i++) { + if (g_str_equal(list[i], "Headset")) + enabled.headset = TRUE; + else if (g_str_equal(list[i], "Gateway")) + enabled.gateway = TRUE; + else if (g_str_equal(list[i], "Sink")) + enabled.sink = TRUE; + else if (g_str_equal(list[i], "Source")) + enabled.source = TRUE; + else if (g_str_equal(list[i], "Control")) + enabled.control = TRUE; + else if (g_str_equal(list[i], "Socket")) + enabled.socket = TRUE; + else if (g_str_equal(list[i], "Media")) + enabled.media = TRUE; + } + g_strfreev(list); + + list = g_key_file_get_string_list(config, "General", "Disable", + NULL, NULL); + for (i = 0; list && list[i] != NULL; i++) { + if (g_str_equal(list[i], "Headset")) + enabled.headset = FALSE; + else if (g_str_equal(list[i], "Gateway")) + enabled.gateway = FALSE; + else if (g_str_equal(list[i], "Sink")) + enabled.sink = FALSE; + else if (g_str_equal(list[i], "Source")) + enabled.source = FALSE; + else if (g_str_equal(list[i], "Control")) + enabled.control = FALSE; + else if (g_str_equal(list[i], "Socket")) + enabled.socket = FALSE; + else if (g_str_equal(list[i], "Media")) + enabled.media = FALSE; + } + g_strfreev(list); + + b = g_key_file_get_boolean(config, "General", "AutoConnect", &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else + auto_connect = b; + + b = g_key_file_get_boolean(config, "Headset", "HFP", + &err); + if (err) + g_clear_error(&err); + else + enabled.hfp = b; + + err = NULL; + i = g_key_file_get_integer(config, "Headset", "MaxConnected", + &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_clear_error(&err); + } else + max_connected_headsets = i; + +proceed: + if (enabled.socket) + unix_init(); + + if (enabled.media) + btd_register_adapter_driver(&media_server_driver); + + if (enabled.headset) + btd_register_adapter_driver(&headset_server_driver); + + if (enabled.gateway) + btd_register_adapter_driver(&gateway_server_driver); + + if (enabled.source || enabled.sink) + btd_register_adapter_driver(&a2dp_server_driver); + + if (enabled.control) + btd_register_adapter_driver(&avrcp_server_driver); + + btd_register_device_driver(&audio_driver); + + *enable_sco = (enabled.gateway || enabled.headset); + + return 0; +} + +void audio_manager_exit(void) +{ + /* Bail out early if we haven't been initialized */ + if (connection == NULL) + return; + + dbus_connection_unref(connection); + connection = NULL; + + if (config) { + g_key_file_free(config); + config = NULL; + } + + if (enabled.socket) + unix_exit(); + + if (enabled.media) + btd_unregister_adapter_driver(&media_server_driver); + + if (enabled.headset) + btd_unregister_adapter_driver(&headset_server_driver); + + if (enabled.gateway) + btd_unregister_adapter_driver(&gateway_server_driver); + + if (enabled.source || enabled.sink) + btd_unregister_adapter_driver(&a2dp_server_driver); + + if (enabled.control) + btd_unregister_adapter_driver(&avrcp_server_driver); + + btd_unregister_device_driver(&audio_driver); +} + +struct audio_device *manager_find_device(const char *path, + const bdaddr_t *src, + const bdaddr_t *dst, + const char *interface, + gboolean connected) +{ + GSList *l; + + for (l = devices; l != NULL; l = l->next) { + struct audio_device *dev = l->data; + + if ((path && (strcmp(path, "")) && strcmp(dev->path, path))) + continue; + + if ((src && bacmp(src, BDADDR_ANY)) && bacmp(&dev->src, src)) + continue; + + if ((dst && bacmp(dst, BDADDR_ANY)) && bacmp(&dev->dst, dst)) + continue; + + if (interface && !strcmp(AUDIO_HEADSET_INTERFACE, interface) + && !dev->headset) + continue; + + if (interface && !strcmp(AUDIO_GATEWAY_INTERFACE, interface) + && !dev->gateway) + continue; + + if (interface && !strcmp(AUDIO_SINK_INTERFACE, interface) + && !dev->sink) + continue; + + if (interface && !strcmp(AUDIO_SOURCE_INTERFACE, interface) + && !dev->source) + continue; + + if (interface && !strcmp(AUDIO_CONTROL_INTERFACE, interface) + && !dev->control) + continue; + + if (connected && !audio_device_is_active(dev, interface)) + continue; + + return dev; + } + + return NULL; +} + +struct audio_device *manager_get_device(const bdaddr_t *src, + const bdaddr_t *dst, + gboolean create) +{ + struct audio_device *dev; + struct btd_adapter *adapter; + struct btd_device *device; + char addr[18]; + const char *path; + + dev = manager_find_device(NULL, src, dst, NULL, FALSE); + if (dev) + return dev; + + if (!create) + return NULL; + + ba2str(src, addr); + + adapter = manager_find_adapter(src); + if (!adapter) { + error("Unable to get a btd_adapter object for %s", + addr); + return NULL; + } + + ba2str(dst, addr); + + device = adapter_get_device(connection, adapter, addr); + if (!device) { + error("Unable to get btd_device object for %s", addr); + return NULL; + } + + path = device_get_path(device); + + dev = audio_device_register(connection, device, path, src, dst); + if (!dev) + return NULL; + + devices = g_slist_append(devices, dev); + + return dev; +} + +gboolean manager_allow_headset_connection(struct audio_device *device) +{ + GSList *l; + int connected = 0; + + for (l = devices; l != NULL; l = l->next) { + struct audio_device *dev = l->data; + struct headset *hs = dev->headset; + + if (dev == device) + continue; + + if (bacmp(&dev->src, &device->src)) + continue; + + if (!hs) + continue; + + if (headset_get_state(dev) > HEADSET_STATE_DISCONNECTED) + connected++; + + if (connected >= max_connected_headsets) + return FALSE; + } + + return TRUE; +} + +void manager_set_fast_connectable(gboolean enable) +{ + GSList *l; + + for (l = adapters; l != NULL; l = l->next) { + struct audio_adapter *adapter = l->data; + + if (btd_adapter_set_fast_connectable(adapter->btd_adapter, + enable)) + error("Changing fast connectable for hci%d failed", + adapter_get_dev_id(adapter->btd_adapter)); + } +} diff --git a/audio/manager.h b/audio/manager.h new file mode 100644 index 0000000..0bf7663 --- /dev/null +++ b/audio/manager.h @@ -0,0 +1,56 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct enabled_interfaces { + gboolean hfp; + gboolean headset; + gboolean gateway; + gboolean sink; + gboolean source; + gboolean control; + gboolean socket; + gboolean media; +}; + +int audio_manager_init(DBusConnection *conn, GKeyFile *config, + gboolean *enable_sco); +void audio_manager_exit(void); + +gboolean server_is_enabled(bdaddr_t *src, uint16_t svc); + +struct audio_device *manager_find_device(const char *path, + const bdaddr_t *src, + const bdaddr_t *dst, + const char *interface, + gboolean connected); + +struct audio_device *manager_get_device(const bdaddr_t *src, + const bdaddr_t *dst, + gboolean create); + +gboolean manager_allow_headset_connection(struct audio_device *device); + +/* TRUE to enable fast connectable and FALSE to disable fast connectable for all + * audio adapters. */ +void manager_set_fast_connectable(gboolean enable); diff --git a/audio/media.c b/audio/media.c new file mode 100644 index 0000000..0efb0a8 --- /dev/null +++ b/audio/media.c @@ -0,0 +1,714 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +#include "../src/adapter.h" +#include "../src/dbus-common.h" + +#include "log.h" +#include "error.h" +#include "device.h" +#include "avdtp.h" +#include "media.h" +#include "transport.h" +#include "a2dp.h" +#include "headset.h" +#include "manager.h" + +#ifndef DBUS_TYPE_UNIX_FD +#define DBUS_TYPE_UNIX_FD -1 +#endif + +#define MEDIA_INTERFACE "org.bluez.Media" +#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint" + +#define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */ + +struct media_adapter { + bdaddr_t src; /* Adapter address */ + char *path; /* Adapter path */ + DBusConnection *conn; /* Adapter connection */ + GSList *endpoints; /* Endpoints list */ +}; + +struct endpoint_request { + DBusMessage *msg; + DBusPendingCall *call; + media_endpoint_cb_t cb; + void *user_data; +}; + +struct media_endpoint { + struct a2dp_sep *sep; + char *sender; /* Endpoint DBus bus id */ + char *path; /* Endpoint object path */ + char *uuid; /* Endpoint property UUID */ + uint8_t codec; /* Endpoint codec */ + uint8_t *capabilities; /* Endpoint property capabilities */ + size_t size; /* Endpoint capabilities size */ + guint hs_watch; + guint watch; + struct endpoint_request *request; + struct media_transport *transport; + struct media_adapter *adapter; +}; + +static GSList *adapters = NULL; + +static void endpoint_request_free(struct endpoint_request *request) +{ + if (request->call) + dbus_pending_call_unref(request->call); + + dbus_message_unref(request->msg); + g_free(request); +} + +static void media_endpoint_cancel(struct media_endpoint *endpoint) +{ + struct endpoint_request *request = endpoint->request; + + if (request->call) + dbus_pending_call_cancel(request->call); + + endpoint_request_free(request); + endpoint->request = NULL; +} + +static void media_endpoint_remove(struct media_endpoint *endpoint) +{ + struct media_adapter *adapter = endpoint->adapter; + + if (g_slist_find(adapter->endpoints, endpoint) == NULL) + return; + + info("Endpoint unregistered: sender=%s path=%s", endpoint->sender, + endpoint->path); + + adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint); + + if (endpoint->sep) + a2dp_remove_sep(endpoint->sep); + + if (endpoint->hs_watch) + headset_remove_state_cb(endpoint->hs_watch); + + if (endpoint->request) + media_endpoint_cancel(endpoint); + + if (endpoint->transport) + media_transport_destroy(endpoint->transport); + + g_dbus_remove_watch(adapter->conn, endpoint->watch); + g_free(endpoint->capabilities); + g_free(endpoint->sender); + g_free(endpoint->path); + g_free(endpoint->uuid); + g_free(endpoint); +} + +static void media_endpoint_exit(DBusConnection *connection, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + endpoint->watch = 0; + media_endpoint_remove(endpoint); +} + +static void headset_setconf_cb(struct media_endpoint *endpoint, void *ret, + int size, void *user_data) +{ + struct audio_device *dev = user_data; + + if (ret != NULL) + return; + + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); +} + +static void headset_state_changed(struct audio_device *dev, + headset_state_t old_state, + headset_state_t new_state, + void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + DBG(""); + + switch (new_state) { + case HEADSET_STATE_DISCONNECTED: + media_endpoint_clear_configuration(endpoint); + break; + case HEADSET_STATE_CONNECTING: + media_endpoint_set_configuration(endpoint, dev, NULL, 0, + headset_setconf_cb, dev); + break; + case HEADSET_STATE_CONNECTED: + break; + case HEADSET_STATE_PLAY_IN_PROGRESS: + break; + case HEADSET_STATE_PLAYING: + break; + } +} + +static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter, + const char *sender, + const char *path, + const char *uuid, + gboolean delay_reporting, + uint8_t codec, + uint8_t *capabilities, + int size, + int *err) +{ + struct media_endpoint *endpoint; + + endpoint = g_new0(struct media_endpoint, 1); + endpoint->sender = g_strdup(sender); + endpoint->path = g_strdup(path); + endpoint->uuid = g_strdup(uuid); + endpoint->codec = codec; + + if (size > 0) { + endpoint->capabilities = g_new(uint8_t, size); + memcpy(endpoint->capabilities, capabilities, size); + endpoint->size = size; + } + + endpoint->adapter = adapter; + + if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) { + endpoint->sep = a2dp_add_sep(&adapter->src, + AVDTP_SEP_TYPE_SOURCE, codec, + delay_reporting, endpoint, err); + if (endpoint->sep == NULL) + goto failed; + } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) { + endpoint->sep = a2dp_add_sep(&adapter->src, + AVDTP_SEP_TYPE_SINK, codec, + delay_reporting, endpoint, err); + if (endpoint->sep == NULL) + goto failed; + } else if (strcasecmp(uuid, HFP_AG_UUID) == 0 || + g_strcmp0(uuid, HSP_AG_UUID) == 0) { + struct audio_device *dev; + + endpoint->hs_watch = headset_add_state_cb(headset_state_changed, + endpoint); + dev = manager_find_device(NULL, &adapter->src, BDADDR_ANY, + AUDIO_HEADSET_INTERFACE, TRUE); + if (dev) + media_endpoint_set_configuration(endpoint, dev, NULL, + 0, headset_setconf_cb, + dev); + } else { + if (err) + *err = -EINVAL; + goto failed; + } + + endpoint->watch = g_dbus_add_disconnect_watch(adapter->conn, sender, + media_endpoint_exit, endpoint, + NULL); + + adapter->endpoints = g_slist_append(adapter->endpoints, endpoint); + info("Endpoint registered: sender=%s path=%s", sender, path); + + if (err) + *err = 0; + return endpoint; + +failed: + g_free(endpoint); + return NULL; +} + +static struct media_endpoint *media_adapter_find_endpoint( + struct media_adapter *adapter, + const char *sender, + const char *path, + const char *uuid) +{ + GSList *l; + + for (l = adapter->endpoints; l; l = l->next) { + struct media_endpoint *endpoint = l->data; + + if (sender && g_strcmp0(endpoint->sender, sender) != 0) + continue; + + if (path && g_strcmp0(endpoint->path, path) != 0) + continue; + + if (uuid && g_strcmp0(endpoint->uuid, uuid) != 0) + continue; + + return endpoint; + } + + return NULL; +} + +const char *media_endpoint_get_sender(struct media_endpoint *endpoint) +{ + return endpoint->sender; +} + +static int parse_properties(DBusMessageIter *props, const char **uuid, + gboolean *delay_reporting, uint8_t *codec, + uint8_t **capabilities, int *size) +{ + gboolean has_uuid = FALSE; + gboolean has_codec = FALSE; + + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "UUID") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, uuid); + has_uuid = TRUE; + } else if (strcasecmp(key, "Codec") == 0) { + if (var != DBUS_TYPE_BYTE) + return -EINVAL; + dbus_message_iter_get_basic(&value, codec); + has_codec = TRUE; + } else if (strcasecmp(key, "DelayReporting") == 0) { + if (var != DBUS_TYPE_BOOLEAN) + return -EINVAL; + dbus_message_iter_get_basic(&value, delay_reporting); + } else if (strcasecmp(key, "Capabilities") == 0) { + DBusMessageIter array; + + if (var != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, capabilities, + size); + } + + dbus_message_iter_next(props); + } + + return (has_uuid && has_codec) ? 0 : -EINVAL; +} + +static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + DBusMessageIter args, props; + const char *sender, *path, *uuid; + gboolean delay_reporting = FALSE; + uint8_t codec; + uint8_t *capabilities; + int size = 0; + int err; + + sender = dbus_message_get_sender(msg); + + dbus_message_iter_init(msg, &args); + + dbus_message_iter_get_basic(&args, &path); + dbus_message_iter_next(&args); + + if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL) + return btd_error_already_exists(msg); + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + return btd_error_invalid_args(msg); + + if (parse_properties(&props, &uuid, &delay_reporting, &codec, + &capabilities, &size) < 0) + return btd_error_invalid_args(msg); + + if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting, + codec, capabilities, size, &err) == FALSE) { + if (err == -EPROTONOSUPPORT) + return btd_error_not_supported(msg); + else + return btd_error_invalid_args(msg); + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + struct media_endpoint *endpoint; + const char *sender, *path; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return NULL; + + sender = dbus_message_get_sender(msg); + + endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL); + if (endpoint == NULL) + return btd_error_does_not_exist(msg); + + media_endpoint_remove(endpoint); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static GDBusMethodTable media_methods[] = { + { "RegisterEndpoint", "oa{sv}", "", register_endpoint }, + { "UnregisterEndpoint", "o", "", unregister_endpoint }, + { }, +}; + +static void path_free(void *data) +{ + struct media_adapter *adapter = data; + + g_slist_foreach(adapter->endpoints, (GFunc) media_endpoint_release, + NULL); + g_slist_free(adapter->endpoints); + + dbus_connection_unref(adapter->conn); + + adapters = g_slist_remove(adapters, adapter); + + g_free(adapter->path); + g_free(adapter); +} + +int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src) +{ + struct media_adapter *adapter; + + if (DBUS_TYPE_UNIX_FD < 0) + return -EPERM; + + adapter = g_new0(struct media_adapter, 1); + adapter->conn = dbus_connection_ref(conn); + bacpy(&adapter->src, src); + adapter->path = g_strdup(path); + + if (!g_dbus_register_interface(conn, path, MEDIA_INTERFACE, + media_methods, NULL, NULL, + adapter, path_free)) { + error("D-Bus failed to register %s path", path); + path_free(adapter); + return -1; + } + + adapters = g_slist_append(adapters, adapter); + + return 0; +} + +void media_unregister(const char *path) +{ + GSList *l; + + for (l = adapters; l; l = l->next) { + struct media_adapter *adapter = l->data; + + if (g_strcmp0(path, adapter->path) == 0) { + g_dbus_unregister_interface(adapter->conn, path, + MEDIA_INTERFACE); + return; + } + } +} + +size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint, + uint8_t **capabilities) +{ + *capabilities = endpoint->capabilities; + return endpoint->size; +} + +static void endpoint_reply(DBusPendingCall *call, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + struct endpoint_request *request = endpoint->request; + DBusMessage *reply; + DBusError err; + gboolean value; + void *ret = NULL; + int size = -1; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("Endpoint replied with an error: %s", + err.name); + + /* Clear endpoint configuration in case of NO_REPLY error */ + if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { + if (request->cb) + request->cb(endpoint, NULL, size, + request->user_data); + media_endpoint_clear_configuration(endpoint); + dbus_message_unref(reply); + dbus_error_free(&err); + return; + } + + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, + "SelectConfiguration")) { + DBusMessageIter args, array; + uint8_t *configuration; + + dbus_message_iter_init(reply, &args); + + dbus_message_iter_recurse(&args, &array); + + dbus_message_iter_get_fixed_array(&array, &configuration, &size); + + ret = configuration; + goto done; + } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) { + error("Wrong reply signature: %s", err.message); + dbus_error_free(&err); + goto done; + } + + size = 1; + value = TRUE; + ret = &value; + +done: + dbus_message_unref(reply); + + if (request->cb) + request->cb(endpoint, ret, size, request->user_data); + + endpoint_request_free(request); + endpoint->request = NULL; +} + +static gboolean media_endpoint_async_call(DBusConnection *conn, + DBusMessage *msg, + struct media_endpoint *endpoint, + media_endpoint_cb_t cb, + void *user_data) +{ + struct endpoint_request *request; + + if (endpoint->request) + return FALSE; + + request = g_new0(struct endpoint_request, 1); + + /* Timeout should be less than avdtp request timeout (4 seconds) */ + if (dbus_connection_send_with_reply(conn, msg, &request->call, + REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + g_free(request); + return FALSE; + } + + dbus_pending_call_set_notify(request->call, endpoint_reply, endpoint, NULL); + + request->msg = msg; + request->cb = cb; + request->user_data = user_data; + endpoint->request = request; + + DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg), + dbus_message_get_destination(msg), + dbus_message_get_path(msg)); + + return TRUE; +} + +gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint, + struct audio_device *device, + uint8_t *configuration, size_t size, + media_endpoint_cb_t cb, + void *user_data) +{ + DBusConnection *conn; + DBusMessage *msg; + const char *path; + DBusMessageIter iter; + + if (endpoint->transport != NULL || endpoint->request != NULL) + return FALSE; + + conn = endpoint->adapter->conn; + + endpoint->transport = media_transport_create(conn, endpoint, device, + configuration, size); + if (endpoint->transport == NULL) + return FALSE; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SetConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return FALSE; + } + + dbus_message_iter_init_append(msg, &iter); + + path = media_transport_get_path(endpoint->transport); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + + transport_get_properties(endpoint->transport, &iter); + + return media_endpoint_async_call(conn, msg, endpoint, cb, user_data); +} + +gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint, + uint8_t *capabilities, + size_t length, + media_endpoint_cb_t cb, + void *user_data) +{ + DBusConnection *conn; + DBusMessage *msg; + + if (endpoint->request != NULL) + return FALSE; + + conn = endpoint->adapter->conn; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SelectConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return FALSE; + } + + dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, + &capabilities, length, + DBUS_TYPE_INVALID); + + return media_endpoint_async_call(conn, msg, endpoint, cb, user_data); +} + +void media_endpoint_clear_configuration(struct media_endpoint *endpoint) +{ + DBusConnection *conn; + DBusMessage *msg; + const char *path; + + if (endpoint->transport == NULL) + return; + + if (endpoint->request) + media_endpoint_cancel(endpoint); + + conn = endpoint->adapter->conn; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "ClearConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + goto done; + } + + path = media_transport_get_path(endpoint->transport); + dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + g_dbus_send_message(conn, msg); +done: + media_transport_destroy(endpoint->transport); + endpoint->transport = NULL; +} + +void media_endpoint_release(struct media_endpoint *endpoint) +{ + DBusMessage *msg; + + DBG("sender=%s path=%s", endpoint->sender, endpoint->path); + + /* already exit */ + if (endpoint->watch == 0) + return; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "Release"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + g_dbus_send_message(endpoint->adapter->conn, msg); + + media_endpoint_remove(endpoint); +} + +struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint) +{ + return endpoint->sep; +} + +const char *media_endpoint_get_uuid(struct media_endpoint *endpoint) +{ + return endpoint->uuid; +} + +uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint) +{ + return endpoint->codec; +} + +struct media_transport *media_endpoint_get_transport( + struct media_endpoint *endpoint) +{ + return endpoint->transport; +} diff --git a/audio/media.h b/audio/media.h new file mode 100644 index 0000000..d089103 --- /dev/null +++ b/audio/media.h @@ -0,0 +1,54 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct media_endpoint; + +typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint, + void *ret, int size, void *user_data); + +int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src); +void media_unregister(const char *path); + +const char *media_endpoint_get_sender(struct media_endpoint *endpoint); + +size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint, + uint8_t **capabilities); +gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint, + struct audio_device *device, + uint8_t *configuration, size_t size, + media_endpoint_cb_t cb, + void *user_data); +gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint, + uint8_t *capabilities, + size_t length, + media_endpoint_cb_t cb, + void *user_data); +void media_endpoint_clear_configuration(struct media_endpoint *endpoint); +void media_endpoint_release(struct media_endpoint *endpoint); + +struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint); +const char *media_endpoint_get_uuid(struct media_endpoint *endpoint); +uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint); +struct media_transport *media_endpoint_get_transport( + struct media_endpoint *endpoint); diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c new file mode 100644 index 0000000..799f17f --- /dev/null +++ b/audio/pcm_bluetooth.c @@ -0,0 +1,1784 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "ipc.h" +#include "sbc.h" +#include "rtp.h" + +//#define ENABLE_DEBUG + +#define UINT_SECS_MAX (UINT_MAX / 1000000 - 1) + +#define MIN_PERIOD_TIME 1 + +#define BUFFER_SIZE 2048 + +#ifdef ENABLE_DEBUG +#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg) +#else +#define DBG(fmt, arg...) +#endif + +#ifndef SOL_SCO +#define SOL_SCO 17 +#endif + +#ifndef SCO_TXBUFS +#define SCO_TXBUFS 0x03 +#endif + +#ifndef SCO_RXBUFS +#define SCO_RXBUFS 0x04 +#endif + +#ifndef MIN +# define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef MAX +# define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +/* adapted from glibc sys/time.h timersub() macro */ +#define priv_timespecsub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \ + if ((result)->tv_nsec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_nsec += 1000000000; \ + } \ + } while (0) + +struct bluetooth_a2dp { + sbc_capabilities_t sbc_capabilities; + sbc_t sbc; /* Codec data */ + int sbc_initialized; /* Keep track if the encoder is initialized */ + unsigned int codesize; /* SBC codesize */ + int samples; /* Number of encoded samples */ + uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */ + unsigned int count; /* Codec transfer buffer counter */ + + int nsamples; /* Cumulative number of codec samples */ + uint16_t seq_num; /* Cumulative packet sequence */ + int frame_count; /* Current frames in buffer*/ +}; + +struct bluetooth_alsa_config { + char device[18]; /* Address of the remote Device */ + int has_device; + uint8_t transport; /* Requested transport */ + int has_transport; + uint16_t rate; + int has_rate; + uint8_t channel_mode; /* A2DP only */ + int has_channel_mode; + uint8_t allocation_method; /* A2DP only */ + int has_allocation_method; + uint8_t subbands; /* A2DP only */ + int has_subbands; + uint8_t block_length; /* A2DP only */ + int has_block_length; + uint8_t bitpool; /* A2DP only */ + int has_bitpool; + int autoconnect; +}; + +struct bluetooth_data { + snd_pcm_ioplug_t io; + struct bluetooth_alsa_config alsa_config; /* ALSA resource file parameters */ + volatile snd_pcm_sframes_t hw_ptr; + int transport; /* chosen transport SCO or AD2P */ + unsigned int link_mtu; /* MTU for selected transport channel */ + volatile struct pollfd stream; /* Audio stream filedescriptor */ + struct pollfd server; /* Audio daemon filedescriptor */ + uint8_t buffer[BUFFER_SIZE]; /* Encoded transfer buffer */ + unsigned int count; /* Transfer buffer counter */ + struct bluetooth_a2dp a2dp; /* A2DP data */ + + pthread_t hw_thread; /* Makes virtual hw pointer move */ + int pipefd[2]; /* Inter thread communication */ + int stopped; + sig_atomic_t reset; /* Request XRUN handling */ +}; + +static int audioservice_send(int sk, const bt_audio_msg_header_t *msg); +static int audioservice_expect(int sk, bt_audio_msg_header_t *outmsg, + int expected_type); + +static int bluetooth_start(snd_pcm_ioplug_t *io) +{ + DBG("bluetooth_start %p", io); + + return 0; +} + +static int bluetooth_stop(snd_pcm_ioplug_t *io) +{ + DBG("bluetooth_stop %p", io); + + return 0; +} + +static void *playback_hw_thread(void *param) +{ + struct bluetooth_data *data = param; + unsigned int prev_periods; + double period_time; + struct timespec start; + struct pollfd fds[2]; + int poll_timeout; + + data->server.events = POLLIN; + /* note: only errors for data->stream.events */ + + fds[0] = data->server; + fds[1] = data->stream; + + prev_periods = 0; + period_time = 1000000.0 * data->io.period_size / data->io.rate; + if (period_time > (int) (MIN_PERIOD_TIME * 1000)) + poll_timeout = (int) (period_time / 1000.0f); + else + poll_timeout = MIN_PERIOD_TIME; + + clock_gettime(CLOCK_MONOTONIC, &start); + + while (1) { + unsigned int dtime, periods; + struct timespec cur, delta; + int ret; + + if (data->stopped) + goto iter_sleep; + + if (data->reset) { + DBG("Handle XRUN in hw-thread."); + data->reset = 0; + clock_gettime(CLOCK_MONOTONIC, &start); + prev_periods = 0; + } + + clock_gettime(CLOCK_MONOTONIC, &cur); + + priv_timespecsub(&cur, &start, &delta); + + dtime = delta.tv_sec * 1000000 + delta.tv_nsec / 1000; + periods = 1.0 * dtime / period_time; + + if (periods > prev_periods) { + char c = 'w'; + int frags = periods - prev_periods, n; + + data->hw_ptr += frags * data->io.period_size; + data->hw_ptr %= data->io.buffer_size; + + for (n = 0; n < frags; n++) { + /* Notify user that hardware pointer + * has moved * */ + if (write(data->pipefd[1], &c, 1) < 0) + pthread_testcancel(); + } + + /* Reset point of reference to avoid too big values + * that wont fit an unsigned int */ + if ((unsigned int) delta.tv_sec < UINT_SECS_MAX) + prev_periods = periods; + else { + prev_periods = 0; + clock_gettime(CLOCK_MONOTONIC, &start); + } + } + +iter_sleep: + /* sleep up to one period interval */ + ret = poll(fds, 2, poll_timeout); + + if (ret < 0) { + if (errno != EINTR) { + SNDERR("poll error: %s (%d)", strerror(errno), + errno); + break; + } + } else if (ret > 0) { + ret = (fds[0].revents) ? 0 : 1; + SNDERR("poll fd %d revents %d", ret, fds[ret].revents); + if (fds[ret].revents & (POLLERR | POLLHUP | POLLNVAL)) + break; + } + + /* Offer opportunity to be canceled by main thread */ + pthread_testcancel(); + } + + data->hw_thread = 0; + pthread_exit(NULL); +} + +static int bluetooth_playback_start(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + int err; + + DBG("%p", io); + + data->stopped = 0; + + if (data->hw_thread) + return 0; + + err = pthread_create(&data->hw_thread, 0, playback_hw_thread, data); + + return -err; +} + +static int bluetooth_playback_stop(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + + DBG("%p", io); + + data->stopped = 1; + + return 0; +} + +static snd_pcm_sframes_t bluetooth_pointer(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + + return data->hw_ptr; +} + +static void bluetooth_exit(struct bluetooth_data *data) +{ + struct bluetooth_a2dp *a2dp = &data->a2dp; + + if (data->server.fd >= 0) + bt_audio_service_close(data->server.fd); + + if (data->stream.fd >= 0) + close(data->stream.fd); + + if (data->hw_thread) { + pthread_cancel(data->hw_thread); + pthread_join(data->hw_thread, 0); + } + + if (a2dp->sbc_initialized) + sbc_finish(&a2dp->sbc); + + if (data->pipefd[0] > 0) + close(data->pipefd[0]); + + if (data->pipefd[1] > 0) + close(data->pipefd[1]); + + free(data); +} + +static int bluetooth_close(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + + DBG("%p", io); + + bluetooth_exit(data); + + return 0; +} + +static int bluetooth_prepare(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + char c = 'w'; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_req *req = (void *) buf; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + uint32_t period_count = io->buffer_size / io->period_size; + int opt_name, err; + struct timeval t = { 0, period_count }; + + DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", + io->period_size, io->buffer_size); + + data->reset = 0; + + /* As we're gonna receive messages on the server socket, we have to stop the + hw thread that is polling on it, if any */ + if (data->hw_thread) { + pthread_cancel(data->hw_thread); + pthread_join(data->hw_thread, 0); + data->hw_thread = 0; + } + + if (io->stream == SND_PCM_STREAM_PLAYBACK) + /* If not null for playback, xmms doesn't display time + * correctly */ + data->hw_ptr = 0; + else + /* ALSA library is really picky on the fact hw_ptr is not null. + * If it is, capture won't start */ + data->hw_ptr = io->period_size; + + /* send start */ + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_START_STREAM; + req->h.length = sizeof(*req); + + err = audioservice_send(data->server.fd, &req->h); + if (err < 0) + return err; + + rsp->h.length = sizeof(*rsp); + err = audioservice_expect(data->server.fd, &rsp->h, + BT_START_STREAM); + if (err < 0) + return err; + + ind->h.length = sizeof(*ind); + err = audioservice_expect(data->server.fd, &ind->h, + BT_NEW_STREAM); + if (err < 0) + return err; + + if (data->stream.fd >= 0) + close(data->stream.fd); + + data->stream.fd = bt_audio_service_get_data_fd(data->server.fd); + if (data->stream.fd < 0) { + return -errno; + } + + if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? + SO_SNDTIMEO : SO_RCVTIMEO; + + if (setsockopt(data->stream.fd, SOL_SOCKET, opt_name, &t, + sizeof(t)) < 0) + return -errno; + } else { + opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? + SCO_TXBUFS : SCO_RXBUFS; + + if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count, + sizeof(period_count)) == 0) + return 0; + + opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? + SO_SNDBUF : SO_RCVBUF; + + if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count, + sizeof(period_count)) == 0) + return 0; + + /* FIXME : handle error codes */ + } + + /* wake up any client polling at us */ + err = write(data->pipefd[1], &c, 1); + if (err < 0) + return err; + + return 0; +} + +static int bluetooth_hsp_hw_params(snd_pcm_ioplug_t *io, + snd_pcm_hw_params_t *params) +{ + struct bluetooth_data *data = io->private_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_open_req *open_req = (void *) buf; + struct bt_open_rsp *open_rsp = (void *) buf; + struct bt_set_configuration_req *req = (void *) buf; + struct bt_set_configuration_rsp *rsp = (void *) buf; + int err; + + DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", + io->period_size, io->buffer_size); + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + open_req->h.type = BT_REQUEST; + open_req->h.name = BT_OPEN; + open_req->h.length = sizeof(*open_req); + + strncpy(open_req->destination, data->alsa_config.device, 18); + open_req->seid = BT_A2DP_SEID_RANGE + 1; + open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ? + BT_WRITE_LOCK : BT_READ_LOCK); + + err = audioservice_send(data->server.fd, &open_req->h); + if (err < 0) + return err; + + open_rsp->h.length = sizeof(*open_rsp); + err = audioservice_expect(data->server.fd, &open_rsp->h, + BT_OPEN); + if (err < 0) + return err; + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_SET_CONFIGURATION; + req->h.length = sizeof(*req); + + req->codec.transport = BT_CAPABILITIES_TRANSPORT_SCO; + req->codec.seid = BT_A2DP_SEID_RANGE + 1; + req->codec.length = sizeof(pcm_capabilities_t); + + req->h.length += req->codec.length - sizeof(req->codec); + err = audioservice_send(data->server.fd, &req->h); + if (err < 0) + return err; + + rsp->h.length = sizeof(*rsp); + err = audioservice_expect(data->server.fd, &rsp->h, + BT_SET_CONFIGURATION); + if (err < 0) + return err; + + data->transport = BT_CAPABILITIES_TRANSPORT_SCO; + data->link_mtu = rsp->link_mtu; + + return 0; +} + +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case BT_SBC_SAMPLING_FREQ_16000: + case BT_SBC_SAMPLING_FREQ_32000: + return 53; + case BT_SBC_SAMPLING_FREQ_44100: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + DBG("Invalid channel mode %u", mode); + return 53; + } + case BT_SBC_SAMPLING_FREQ_48000: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + DBG("Invalid channel mode %u", mode); + return 51; + } + default: + DBG("Invalid sampling freq %u", freq); + return 53; + } +} + +static int bluetooth_a2dp_init(struct bluetooth_data *data, + snd_pcm_hw_params_t *params) +{ + struct bluetooth_alsa_config *cfg = &data->alsa_config; + sbc_capabilities_t *cap = &data->a2dp.sbc_capabilities; + unsigned int max_bitpool, min_bitpool, rate, channels; + int dir; + + snd_pcm_hw_params_get_rate(params, &rate, &dir); + snd_pcm_hw_params_get_channels(params, &channels); + + switch (rate) { + case 48000: + cap->frequency = BT_SBC_SAMPLING_FREQ_48000; + break; + case 44100: + cap->frequency = BT_SBC_SAMPLING_FREQ_44100; + break; + case 32000: + cap->frequency = BT_SBC_SAMPLING_FREQ_32000; + break; + case 16000: + cap->frequency = BT_SBC_SAMPLING_FREQ_16000; + break; + default: + DBG("Rate %d not supported", rate); + return -1; + } + + if (cfg->has_channel_mode) + cap->channel_mode = cfg->channel_mode; + else if (channels == 2) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + } else { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + } + + if (!cap->channel_mode) { + DBG("No supported channel modes"); + return -1; + } + + if (cfg->has_block_length) + cap->block_length = cfg->block_length; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) + cap->block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) + cap->block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) + cap->block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) + cap->block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + DBG("No supported block lengths"); + return -1; + } + + if (cfg->has_subbands) + cap->subbands = cfg->subbands; + if (cap->subbands & BT_A2DP_SUBBANDS_8) + cap->subbands = BT_A2DP_SUBBANDS_8; + else if (cap->subbands & BT_A2DP_SUBBANDS_4) + cap->subbands = BT_A2DP_SUBBANDS_4; + else { + DBG("No supported subbands"); + return -1; + } + + if (cfg->has_allocation_method) + cap->allocation_method = cfg->allocation_method; + if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) + cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) + cap->allocation_method = BT_A2DP_ALLOCATION_SNR; + + if (cfg->has_bitpool) + min_bitpool = max_bitpool = cfg->bitpool; + else { + min_bitpool = MAX(MIN_BITPOOL, cap->min_bitpool); + max_bitpool = MIN(default_bitpool(cap->frequency, + cap->channel_mode), + cap->max_bitpool); + } + + cap->min_bitpool = min_bitpool; + cap->max_bitpool = max_bitpool; + + return 0; +} + +static void bluetooth_a2dp_setup(struct bluetooth_a2dp *a2dp) +{ + sbc_capabilities_t active_capabilities = a2dp->sbc_capabilities; + + if (a2dp->sbc_initialized) + sbc_reinit(&a2dp->sbc, 0); + else + sbc_init(&a2dp->sbc, 0); + a2dp->sbc_initialized = 1; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000) + a2dp->sbc.frequency = SBC_FREQ_16000; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_32000) + a2dp->sbc.frequency = SBC_FREQ_32000; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_44100) + a2dp->sbc.frequency = SBC_FREQ_44100; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_48000) + a2dp->sbc.frequency = SBC_FREQ_48000; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + a2dp->sbc.mode = SBC_MODE_MONO; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + a2dp->sbc.mode = SBC_MODE_STEREO; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; + + a2dp->sbc.allocation = active_capabilities.allocation_method + == BT_A2DP_ALLOCATION_SNR ? SBC_AM_SNR + : SBC_AM_LOUDNESS; + + switch (active_capabilities.subbands) { + case BT_A2DP_SUBBANDS_4: + a2dp->sbc.subbands = SBC_SB_4; + break; + case BT_A2DP_SUBBANDS_8: + a2dp->sbc.subbands = SBC_SB_8; + break; + } + + switch (active_capabilities.block_length) { + case BT_A2DP_BLOCK_LENGTH_4: + a2dp->sbc.blocks = SBC_BLK_4; + break; + case BT_A2DP_BLOCK_LENGTH_8: + a2dp->sbc.blocks = SBC_BLK_8; + break; + case BT_A2DP_BLOCK_LENGTH_12: + a2dp->sbc.blocks = SBC_BLK_12; + break; + case BT_A2DP_BLOCK_LENGTH_16: + a2dp->sbc.blocks = SBC_BLK_16; + break; + } + + a2dp->sbc.bitpool = active_capabilities.max_bitpool; + a2dp->codesize = sbc_get_codesize(&a2dp->sbc); + a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); +} + +static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io, + snd_pcm_hw_params_t *params) +{ + struct bluetooth_data *data = io->private_data; + struct bluetooth_a2dp *a2dp = &data->a2dp; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_open_req *open_req = (void *) buf; + struct bt_open_rsp *open_rsp = (void *) buf; + struct bt_set_configuration_req *req = (void *) buf; + struct bt_set_configuration_rsp *rsp = (void *) buf; + int err; + + DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", + io->period_size, io->buffer_size); + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + open_req->h.type = BT_REQUEST; + open_req->h.name = BT_OPEN; + open_req->h.length = sizeof(*open_req); + + strncpy(open_req->destination, data->alsa_config.device, 18); + open_req->seid = a2dp->sbc_capabilities.capability.seid; + open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ? + BT_WRITE_LOCK : BT_READ_LOCK); + + err = audioservice_send(data->server.fd, &open_req->h); + if (err < 0) + return err; + + open_rsp->h.length = sizeof(*open_rsp); + err = audioservice_expect(data->server.fd, &open_rsp->h, + BT_OPEN); + if (err < 0) + return err; + + err = bluetooth_a2dp_init(data, params); + if (err < 0) + return err; + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_SET_CONFIGURATION; + req->h.length = sizeof(*req); + + memcpy(&req->codec, &a2dp->sbc_capabilities, + sizeof(a2dp->sbc_capabilities)); + + req->codec.transport = BT_CAPABILITIES_TRANSPORT_A2DP; + req->codec.length = sizeof(a2dp->sbc_capabilities); + req->h.length += req->codec.length - sizeof(req->codec); + + err = audioservice_send(data->server.fd, &req->h); + if (err < 0) + return err; + + rsp->h.length = sizeof(*rsp); + err = audioservice_expect(data->server.fd, &rsp->h, + BT_SET_CONFIGURATION); + if (err < 0) + return err; + + data->transport = BT_CAPABILITIES_TRANSPORT_A2DP; + data->link_mtu = rsp->link_mtu; + + /* Setup SBC encoder now we agree on parameters */ + bluetooth_a2dp_setup(a2dp); + + DBG("\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", + a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, + a2dp->sbc.bitpool); + + return 0; +} + +static int bluetooth_poll_descriptors(snd_pcm_ioplug_t *io, + struct pollfd *pfd, unsigned int space) +{ + struct bluetooth_data *data = io->private_data; + + assert(io); + + if (space < 1) + return 0; + + pfd[0].fd = data->stream.fd; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + + return 1; +} + +static int bluetooth_poll_revents(snd_pcm_ioplug_t *io ATTRIBUTE_UNUSED, + struct pollfd *pfds, unsigned int nfds, + unsigned short *revents) +{ + assert(pfds && nfds == 1 && revents); + + *revents = pfds[0].revents; + + return 0; +} + +static int bluetooth_playback_poll_descriptors_count(snd_pcm_ioplug_t *io) +{ + return 2; +} + +static int bluetooth_playback_poll_descriptors(snd_pcm_ioplug_t *io, + struct pollfd *pfd, unsigned int space) +{ + struct bluetooth_data *data = io->private_data; + + DBG(""); + + assert(data->pipefd[0] >= 0); + + if (space < 2) + return 0; + + pfd[0].fd = data->pipefd[0]; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + pfd[1].fd = data->stream.fd; + pfd[1].events = POLLERR | POLLHUP | POLLNVAL; + pfd[1].revents = 0; + + return 2; +} + +static int bluetooth_playback_poll_revents(snd_pcm_ioplug_t *io, + struct pollfd *pfds, unsigned int nfds, + unsigned short *revents) +{ + static char buf[1]; + int ret; + + DBG(""); + + assert(pfds); + assert(nfds == 2); + assert(revents); + assert(pfds[0].fd >= 0); + assert(pfds[1].fd >= 0); + + if (io->state != SND_PCM_STATE_PREPARED) + ret = read(pfds[0].fd, buf, 1); + + if (pfds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) + io->state = SND_PCM_STATE_DISCONNECTED; + + *revents = (pfds[0].revents & POLLIN) ? POLLOUT : 0; + + return 0; +} + + +static snd_pcm_sframes_t bluetooth_hsp_read(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + struct bluetooth_data *data = io->private_data; + snd_pcm_uframes_t frames_to_write, ret; + unsigned char *buff; + unsigned int frame_size = 0; + int nrecv; + + DBG("areas->step=%u areas->first=%u offset=%lu size=%lu io->nonblock=%u", + areas->step, areas->first, offset, size, io->nonblock); + + frame_size = areas->step / 8; + + if (data->count > 0) + goto proceed; + + nrecv = recv(data->stream.fd, data->buffer, data->link_mtu, + io->nonblock ? MSG_DONTWAIT : 0); + + if (nrecv < 0) { + ret = (errno == EPIPE) ? -EIO : -errno; + goto done; + } + + if ((unsigned int) nrecv != data->link_mtu) { + ret = -EIO; + SNDERR(strerror(-ret)); + goto done; + } + + /* Increment hardware transmition pointer */ + data->hw_ptr = (data->hw_ptr + data->link_mtu / frame_size) % + io->buffer_size; + +proceed: + buff = (unsigned char *) areas->addr + + (areas->first + areas->step * offset) / 8; + + if ((data->count + size * frame_size) <= data->link_mtu) + frames_to_write = size; + else + frames_to_write = (data->link_mtu - data->count) / frame_size; + + memcpy(buff, data->buffer + data->count, frame_size * frames_to_write); + data->count += (frame_size * frames_to_write); + data->count %= data->link_mtu; + + /* Return written frames count */ + ret = frames_to_write; + +done: + DBG("returning %lu", ret); + return ret; +} + +static snd_pcm_sframes_t bluetooth_hsp_write(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + struct bluetooth_data *data = io->private_data; + snd_pcm_sframes_t ret = 0; + snd_pcm_uframes_t frames_to_read; + uint8_t *buff; + int rsend, frame_size; + + DBG("areas->step=%u areas->first=%u offset=%lu, size=%lu io->nonblock=%u", + areas->step, areas->first, offset, size, io->nonblock); + + if (io->hw_ptr > io->appl_ptr) { + ret = bluetooth_playback_stop(io); + if (ret == 0) + ret = -EPIPE; + goto done; + } + + frame_size = areas->step / 8; + if ((data->count + size * frame_size) <= data->link_mtu) + frames_to_read = size; + else + frames_to_read = (data->link_mtu - data->count) / frame_size; + + DBG("count=%d frames_to_read=%lu", data->count, frames_to_read); + + /* Ready for more data */ + buff = (uint8_t *) areas->addr + + (areas->first + areas->step * offset) / 8; + memcpy(data->buffer + data->count, buff, frame_size * frames_to_read); + + /* Remember we have some frames in the pipe now */ + data->count += frames_to_read * frame_size; + if (data->count != data->link_mtu) { + ret = frames_to_read; + goto done; + } + + rsend = send(data->stream.fd, data->buffer, data->link_mtu, + io->nonblock ? MSG_DONTWAIT : 0); + if (rsend > 0) { + /* Reset count pointer */ + data->count = 0; + + ret = frames_to_read; + } else if (rsend < 0) + ret = (errno == EPIPE) ? -EIO : -errno; + else + ret = -EIO; + +done: + DBG("returning %ld", ret); + return ret; +} + +static snd_pcm_sframes_t bluetooth_a2dp_read(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, snd_pcm_uframes_t size) +{ + snd_pcm_uframes_t ret = 0; + return ret; +} + +static int avdtp_write(struct bluetooth_data *data) +{ + int ret = 0; + struct rtp_header *header; + struct rtp_payload *payload; + struct bluetooth_a2dp *a2dp = &data->a2dp; + + header = (void *) a2dp->buffer; + payload = (void *) (a2dp->buffer + sizeof(*header)); + + memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload)); + + payload->frame_count = a2dp->frame_count; + header->v = 2; + header->pt = 1; + header->sequence_number = htons(a2dp->seq_num); + header->timestamp = htonl(a2dp->nsamples); + header->ssrc = htonl(1); + + ret = send(data->stream.fd, a2dp->buffer, a2dp->count, MSG_DONTWAIT); + if (ret < 0) { + DBG("send returned %d errno %s.", ret, strerror(errno)); + ret = -errno; + } + + /* Reset buffer of data to send */ + a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + a2dp->frame_count = 0; + a2dp->samples = 0; + a2dp->seq_num++; + + return ret; +} + +static snd_pcm_sframes_t bluetooth_a2dp_write(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, snd_pcm_uframes_t size) +{ + struct bluetooth_data *data = io->private_data; + struct bluetooth_a2dp *a2dp = &data->a2dp; + snd_pcm_sframes_t ret = 0; + unsigned int bytes_left; + int frame_size, encoded; + ssize_t written; + uint8_t *buff; + + DBG("areas->step=%u areas->first=%u offset=%lu size=%lu", + areas->step, areas->first, offset, size); + DBG("hw_ptr=%lu appl_ptr=%lu diff=%lu", io->hw_ptr, io->appl_ptr, + io->appl_ptr - io->hw_ptr); + + /* Calutate starting pointers */ + frame_size = areas->step / 8; + bytes_left = size * frame_size; + buff = (uint8_t *) areas->addr + + (areas->first + areas->step * (offset)) / 8; + + /* Check for underrun */ + if (io->hw_ptr > io->appl_ptr) { + ret = bluetooth_playback_stop(io); + if (ret == 0) + ret = -EPIPE; + data->reset = 1; + return ret; + } + + /* Check if we should autostart */ + if (io->state == SND_PCM_STATE_PREPARED) { + snd_pcm_sw_params_t *swparams; + snd_pcm_uframes_t threshold; + + snd_pcm_sw_params_malloc(&swparams); + if (!snd_pcm_sw_params_current(io->pcm, swparams) && + !snd_pcm_sw_params_get_start_threshold(swparams, + &threshold)) { + if (io->appl_ptr >= threshold) { + ret = snd_pcm_start(io->pcm); + if (ret != 0) + return ret; + } + } + + snd_pcm_sw_params_free(swparams); + } + + /* Check if we have any left over data from the last write */ + if (data->count > 0) { + unsigned int additional_bytes_needed = + a2dp->codesize - data->count; + if (additional_bytes_needed > bytes_left) + goto out; + + memcpy(data->buffer + data->count, buff, + additional_bytes_needed); + + /* Enough data to encode (sbc wants 1k blocks) */ + encoded = sbc_encode(&a2dp->sbc, data->buffer, a2dp->codesize, + a2dp->buffer + a2dp->count, + sizeof(a2dp->buffer) - a2dp->count, + &written); + if (encoded <= 0) { + DBG("Encoding error %d", encoded); + goto done; + } + + /* Increment a2dp buffers */ + a2dp->count += written; + a2dp->frame_count++; + a2dp->samples += encoded / frame_size; + a2dp->nsamples += encoded / frame_size; + + /* No space left for another frame then send */ + if (a2dp->count + written >= data->link_mtu) { + avdtp_write(data); + DBG("sending packet %d, count %d, link_mtu %u", + a2dp->seq_num, a2dp->count, + data->link_mtu); + } + + /* Increment up buff pointer to take into account + * the data processed */ + buff += additional_bytes_needed; + bytes_left -= additional_bytes_needed; + + /* Since data has been process mark it as zero */ + data->count = 0; + } + + + /* Process this buffer in full chunks */ + while (bytes_left >= a2dp->codesize) { + /* Enough data to encode (sbc wants 1k blocks) */ + encoded = sbc_encode(&a2dp->sbc, buff, a2dp->codesize, + a2dp->buffer + a2dp->count, + sizeof(a2dp->buffer) - a2dp->count, + &written); + if (encoded <= 0) { + DBG("Encoding error %d", encoded); + goto done; + } + + /* Increment up buff pointer to take into account + * the data processed */ + buff += a2dp->codesize; + bytes_left -= a2dp->codesize; + + /* Increment a2dp buffers */ + a2dp->count += written; + a2dp->frame_count++; + a2dp->samples += encoded / frame_size; + a2dp->nsamples += encoded / frame_size; + + /* No space left for another frame then send */ + if (a2dp->count + written >= data->link_mtu) { + avdtp_write(data); + DBG("sending packet %d, count %d, link_mtu %u", + a2dp->seq_num, a2dp->count, + data->link_mtu); + } + } + +out: + /* Copy the extra to our temp buffer for the next write */ + if (bytes_left > 0) { + memcpy(data->buffer + data->count, buff, bytes_left); + data->count += bytes_left; + bytes_left = 0; + } + +done: + DBG("returning %ld", size - bytes_left / frame_size); + + return size - bytes_left / frame_size; +} + +static int bluetooth_playback_delay(snd_pcm_ioplug_t *io, + snd_pcm_sframes_t *delayp) +{ + DBG(""); + + /* This updates io->hw_ptr value using pointer() function */ + snd_pcm_hwsync(io->pcm); + + *delayp = io->appl_ptr - io->hw_ptr; + if ((io->state == SND_PCM_STATE_RUNNING) && (*delayp < 0)) { + io->callback->stop(io); + io->state = SND_PCM_STATE_XRUN; + *delayp = 0; + } + + /* This should never fail, ALSA API is really not + prepared to handle a non zero return value */ + return 0; +} + +static snd_pcm_ioplug_callback_t bluetooth_hsp_playback = { + .start = bluetooth_playback_start, + .stop = bluetooth_playback_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_hsp_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_hsp_write, + .poll_descriptors_count = bluetooth_playback_poll_descriptors_count, + .poll_descriptors = bluetooth_playback_poll_descriptors, + .poll_revents = bluetooth_playback_poll_revents, + .delay = bluetooth_playback_delay, +}; + +static snd_pcm_ioplug_callback_t bluetooth_hsp_capture = { + .start = bluetooth_start, + .stop = bluetooth_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_hsp_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_hsp_read, + .poll_descriptors = bluetooth_poll_descriptors, + .poll_revents = bluetooth_poll_revents, +}; + +static snd_pcm_ioplug_callback_t bluetooth_a2dp_playback = { + .start = bluetooth_playback_start, + .stop = bluetooth_playback_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_a2dp_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_a2dp_write, + .poll_descriptors_count = bluetooth_playback_poll_descriptors_count, + .poll_descriptors = bluetooth_playback_poll_descriptors, + .poll_revents = bluetooth_playback_poll_revents, + .delay = bluetooth_playback_delay, +}; + +static snd_pcm_ioplug_callback_t bluetooth_a2dp_capture = { + .start = bluetooth_start, + .stop = bluetooth_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_a2dp_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_a2dp_read, + .poll_descriptors = bluetooth_poll_descriptors, + .poll_revents = bluetooth_poll_revents, +}; + +#define ARRAY_NELEMS(a) (sizeof((a)) / sizeof((a)[0])) + +static int bluetooth_hsp_hw_constraint(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + snd_pcm_access_t access_list[] = { + SND_PCM_ACCESS_RW_INTERLEAVED, + /* Mmap access is really useless fo this driver, but we + * support it because some pieces of software out there + * insist on using it */ + SND_PCM_ACCESS_MMAP_INTERLEAVED + }; + unsigned int format_list[] = { + SND_PCM_FORMAT_S16 + }; + int err; + + /* access type */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, + ARRAY_NELEMS(access_list), access_list); + if (err < 0) + return err; + + /* supported formats */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, + ARRAY_NELEMS(format_list), format_list); + if (err < 0) + return err; + + /* supported channels */ + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, + 1, 1); + if (err < 0) + return err; + + /* supported rate */ + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, + 8000, 8000); + if (err < 0) + return err; + + /* supported block size */ + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, + data->link_mtu, data->link_mtu); + if (err < 0) + return err; + + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, + 2, 200); + if (err < 0) + return err; + + return 0; +} + +static int bluetooth_a2dp_hw_constraint(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + struct bluetooth_a2dp *a2dp = &data->a2dp; + struct bluetooth_alsa_config *cfg = &data->alsa_config; + snd_pcm_access_t access_list[] = { + SND_PCM_ACCESS_RW_INTERLEAVED, + /* Mmap access is really useless fo this driver, but we + * support it because some pieces of software out there + * insist on using it */ + SND_PCM_ACCESS_MMAP_INTERLEAVED + }; + unsigned int format_list[] = { + SND_PCM_FORMAT_S16 + }; + unsigned int rate_list[4]; + unsigned int rate_count; + int err, min_channels, max_channels; + unsigned int period_list[] = { + 2048, + 4096, /* e.g. 23.2msec/period (stereo 16bit at 44.1kHz) */ + 8192 + }; + + /* access type */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, + ARRAY_NELEMS(access_list), access_list); + if (err < 0) + return err; + + /* supported formats */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, + ARRAY_NELEMS(format_list), format_list); + if (err < 0) + return err; + + /* supported channels */ + if (cfg->has_channel_mode) + a2dp->sbc_capabilities.channel_mode = cfg->channel_mode; + + if (a2dp->sbc_capabilities.channel_mode & + BT_A2DP_CHANNEL_MODE_MONO) + min_channels = 1; + else + min_channels = 2; + + if (a2dp->sbc_capabilities.channel_mode & + (~BT_A2DP_CHANNEL_MODE_MONO)) + max_channels = 2; + else + max_channels = 1; + + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, + min_channels, max_channels); + if (err < 0) + return err; + + /* supported buffer sizes + * (can be used as 3*8192, 6*4096, 12*2048, ...) */ + err = snd_pcm_ioplug_set_param_minmax(io, + SND_PCM_IOPLUG_HW_BUFFER_BYTES, + 8192*3, 8192*3); + if (err < 0) + return err; + + /* supported block sizes: */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, + ARRAY_NELEMS(period_list), period_list); + if (err < 0) + return err; + + /* supported rates */ + rate_count = 0; + if (cfg->has_rate) { + rate_list[rate_count] = cfg->rate; + rate_count++; + } else { + if (a2dp->sbc_capabilities.frequency & + BT_SBC_SAMPLING_FREQ_16000) { + rate_list[rate_count] = 16000; + rate_count++; + } + + if (a2dp->sbc_capabilities.frequency & + BT_SBC_SAMPLING_FREQ_32000) { + rate_list[rate_count] = 32000; + rate_count++; + } + + if (a2dp->sbc_capabilities.frequency & + BT_SBC_SAMPLING_FREQ_44100) { + rate_list[rate_count] = 44100; + rate_count++; + } + + if (a2dp->sbc_capabilities.frequency & + BT_SBC_SAMPLING_FREQ_48000) { + rate_list[rate_count] = 48000; + rate_count++; + } + } + + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_RATE, + rate_count, rate_list); + if (err < 0) + return err; + + return 0; +} + +static int bluetooth_parse_config(snd_config_t *conf, + struct bluetooth_alsa_config *bt_config) +{ + snd_config_iterator_t i, next; + + memset(bt_config, 0, sizeof(struct bluetooth_alsa_config)); + + /* Set defaults */ + bt_config->autoconnect = 1; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id, *value; + + if (snd_config_get_id(n, &id) < 0) + continue; + + if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0) + continue; + + if (strcmp(id, "autoconnect") == 0) { + int b; + + b = snd_config_get_bool(n); + if (b < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->autoconnect = b; + continue; + } + + if (strcmp(id, "device") == 0 || strcmp(id, "bdaddr") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->has_device = 1; + strncpy(bt_config->device, value, 18); + continue; + } + + if (strcmp(id, "profile") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + if (strcmp(value, "auto") == 0) { + bt_config->transport = BT_CAPABILITIES_TRANSPORT_ANY; + bt_config->has_transport = 1; + } else if (strcmp(value, "voice") == 0 || + strcmp(value, "hfp") == 0) { + bt_config->transport = BT_CAPABILITIES_TRANSPORT_SCO; + bt_config->has_transport = 1; + } else if (strcmp(value, "hifi") == 0 || + strcmp(value, "a2dp") == 0) { + bt_config->transport = BT_CAPABILITIES_TRANSPORT_A2DP; + bt_config->has_transport = 1; + } + continue; + } + + if (strcmp(id, "rate") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->rate = atoi(value); + bt_config->has_rate = 1; + continue; + } + + if (strcmp(id, "mode") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + if (strcmp(value, "mono") == 0) { + bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + bt_config->has_channel_mode = 1; + } else if (strcmp(value, "dual") == 0) { + bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + bt_config->has_channel_mode = 1; + } else if (strcmp(value, "stereo") == 0) { + bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + bt_config->has_channel_mode = 1; + } else if (strcmp(value, "joint") == 0) { + bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + bt_config->has_channel_mode = 1; + } + continue; + } + + if (strcmp(id, "allocation") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + if (strcmp(value, "loudness") == 0) { + bt_config->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + bt_config->has_allocation_method = 1; + } else if (strcmp(value, "snr") == 0) { + bt_config->allocation_method = BT_A2DP_ALLOCATION_SNR; + bt_config->has_allocation_method = 1; + } + continue; + } + + if (strcmp(id, "subbands") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->subbands = atoi(value); + bt_config->has_subbands = 1; + continue; + } + + if (strcmp(id, "blocks") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->block_length = atoi(value); + bt_config->has_block_length = 1; + continue; + } + + if (strcmp(id, "bitpool") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->bitpool = atoi(value); + bt_config->has_bitpool = 1; + continue; + } + + SNDERR("Unknown field %s", id); + return -EINVAL; + } + + return 0; +} + +static int audioservice_send(int sk, const bt_audio_msg_header_t *msg) +{ + int err; + uint16_t length; + + length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE; + + DBG("sending %s:%s", bt_audio_strtype(msg->type), + bt_audio_strname(msg->name)); + if (send(sk, msg, length, 0) > 0) + err = 0; + else { + err = -errno; + SNDERR("Error sending data to audio service: %s(%d)", + strerror(errno), errno); + } + + return err; +} + +static int audioservice_recv(int sk, bt_audio_msg_header_t *inmsg) +{ + int err; + ssize_t ret; + const char *type, *name; + uint16_t length; + + length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE; + + DBG("trying to receive msg from audio service..."); + + ret = recv(sk, inmsg, length, 0); + if (ret < 0) { + err = -errno; + SNDERR("Error receiving IPC data from bluetoothd: %s (%d)", + strerror(errno), errno); + } else if ((size_t) ret < sizeof(bt_audio_msg_header_t)) { + SNDERR("Too short (%d bytes) IPC packet from bluetoothd", ret); + err = -EINVAL; + } else { + type = bt_audio_strtype(inmsg->type); + name = bt_audio_strname(inmsg->name); + if (type && name) { + DBG("Received %s - %s", type, name); + err = 0; + } else { + err = -EINVAL; + SNDERR("Bogus message type %d - name %d" + " received from audio service", + inmsg->type, inmsg->name); + } + + } + + return err; +} + +static int audioservice_expect(int sk, bt_audio_msg_header_t *rsp, + int expected_name) +{ + bt_audio_error_t *error; + int err = audioservice_recv(sk, rsp); + + if (err != 0) + return err; + + if (rsp->name != expected_name) { + err = -EINVAL; + SNDERR("Bogus message %s received while %s was expected", + bt_audio_strname(rsp->name), + bt_audio_strname(expected_name)); + } + + if (rsp->type == BT_ERROR) { + error = (void *) rsp; + SNDERR("%s failed : %s(%d)", + bt_audio_strname(rsp->name), + strerror(error->posix_errno), + error->posix_errno); + return -error->posix_errno; + } + + return err; +} + +static int bluetooth_parse_capabilities(struct bluetooth_data *data, + struct bt_get_capabilities_rsp *rsp) +{ + int bytes_left = rsp->h.length - sizeof(*rsp); + codec_capabilities_t *codec = (void *) rsp->data; + + data->transport = codec->transport; + + if (codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) + return 0; + + while (bytes_left > 0) { + if ((codec->type == BT_A2DP_SBC_SINK) && + !(codec->lock & BT_WRITE_LOCK)) + break; + + bytes_left -= codec->length; + codec = (void *) codec + codec->length; + } + + if (bytes_left <= 0 || + codec->length != sizeof(data->a2dp.sbc_capabilities)) + return -EINVAL; + + memcpy(&data->a2dp.sbc_capabilities, codec, codec->length); + + return 0; +} + +static int bluetooth_init(struct bluetooth_data *data, + snd_pcm_stream_t stream, snd_config_t *conf) +{ + int sk, err; + struct bluetooth_alsa_config *alsa_conf = &data->alsa_config; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_get_capabilities_req *req = (void *) buf; + struct bt_get_capabilities_rsp *rsp = (void *) buf; + + memset(data, 0, sizeof(struct bluetooth_data)); + + err = bluetooth_parse_config(conf, alsa_conf); + if (err < 0) + return err; + + data->server.fd = -1; + data->stream.fd = -1; + + sk = bt_audio_service_open(); + if (sk <= 0) { + err = -errno; + goto failed; + } + + data->server.fd = sk; + data->server.events = POLLIN; + + data->pipefd[0] = -1; + data->pipefd[1] = -1; + + if (pipe(data->pipefd) < 0) { + err = -errno; + goto failed; + } + if (fcntl(data->pipefd[0], F_SETFL, O_NONBLOCK) < 0) { + err = -errno; + goto failed; + } + if (fcntl(data->pipefd[1], F_SETFL, O_NONBLOCK) < 0) { + err = -errno; + goto failed; + } + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_GET_CAPABILITIES; + req->h.length = sizeof(*req); + + if (alsa_conf->autoconnect) + req->flags |= BT_FLAG_AUTOCONNECT; + strncpy(req->destination, alsa_conf->device, 18); + if (alsa_conf->has_transport) + req->transport = alsa_conf->transport; + else + req->transport = BT_CAPABILITIES_TRANSPORT_ANY; + + err = audioservice_send(data->server.fd, &req->h); + if (err < 0) + goto failed; + + rsp->h.length = 0; + err = audioservice_expect(data->server.fd, &rsp->h, + BT_GET_CAPABILITIES); + if (err < 0) + goto failed; + + bluetooth_parse_capabilities(data, rsp); + + return 0; + +failed: + if (sk >= 0) + bt_audio_service_close(sk); + return err; +} + +SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth); + +SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) +{ + struct bluetooth_data *data; + int err; + + DBG("Bluetooth PCM plugin (%s)", + stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); + + data = malloc(sizeof(struct bluetooth_data)); + if (!data) { + err = -ENOMEM; + goto error; + } + + err = bluetooth_init(data, stream, conf); + if (err < 0) + goto error; + + data->io.version = SND_PCM_IOPLUG_VERSION; + data->io.name = "Bluetooth Audio Device"; + data->io.mmap_rw = 0; /* No direct mmap communication */ + data->io.private_data = data; + + if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? + &bluetooth_a2dp_playback : + &bluetooth_a2dp_capture; + else + data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? + &bluetooth_hsp_playback : + &bluetooth_hsp_capture; + + err = snd_pcm_ioplug_create(&data->io, name, stream, mode); + if (err < 0) + goto error; + + if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + err = bluetooth_a2dp_hw_constraint(&data->io); + else + err = bluetooth_hsp_hw_constraint(&data->io); + + if (err < 0) { + snd_pcm_ioplug_delete(&data->io); + goto error; + } + + *pcmp = data->io.pcm; + + return 0; + +error: + if (data) + bluetooth_exit(data); + + return err; +} + +SND_PCM_PLUGIN_SYMBOL(bluetooth); diff --git a/audio/rtp.h b/audio/rtp.h new file mode 100644 index 0000000..45fddcf --- /dev/null +++ b/audio/rtp.h @@ -0,0 +1,76 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct rtp_header { + unsigned cc:4; + unsigned x:1; + unsigned p:1; + unsigned v:2; + + unsigned pt:7; + unsigned m:1; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { + unsigned frame_count:4; + unsigned rfa0:1; + unsigned is_last_fragment:1; + unsigned is_first_fragment:1; + unsigned is_fragmented:1; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct rtp_header { + unsigned v:2; + unsigned p:1; + unsigned x:1; + unsigned cc:4; + + unsigned m:1; + unsigned pt:7; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { + unsigned is_fragmented:1; + unsigned is_first_fragment:1; + unsigned is_last_fragment:1; + unsigned rfa0:1; + unsigned frame_count:4; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif diff --git a/audio/sink.c b/audio/sink.c new file mode 100644 index 0000000..2d5db18 --- /dev/null +++ b/audio/sink.c @@ -0,0 +1,743 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include + +#include "log.h" + +#include "device.h" +#include "avdtp.h" +#include "media.h" +#include "a2dp.h" +#include "error.h" +#include "sink.h" +#include "dbus-common.h" +#include "../src/adapter.h" +#include "../src/device.h" + +#define STREAM_SETUP_RETRY_TIMER 2 + +struct pending_request { + DBusConnection *conn; + DBusMessage *msg; + unsigned int id; +}; + +struct sink { + struct audio_device *dev; + struct avdtp *session; + struct avdtp_stream *stream; + unsigned int cb_id; + guint retry_id; + avdtp_session_state_t session_state; + avdtp_state_t stream_state; + sink_state_t state; + struct pending_request *connect; + struct pending_request *disconnect; + DBusConnection *conn; +}; + +struct sink_state_callback { + sink_state_cb cb; + void *user_data; + unsigned int id; +}; + +static GSList *sink_callbacks = NULL; + +static unsigned int avdtp_callback_id = 0; + +static char *str_state[] = { + "SINK_STATE_DISCONNECTED", + "SINK_STATE_CONNECTING", + "SINK_STATE_CONNECTED", + "SINK_STATE_PLAYING", +}; + +static const char *state2str(sink_state_t state) +{ + switch (state) { + case SINK_STATE_DISCONNECTED: + return "disconnected"; + case SINK_STATE_CONNECTING: + return "connecting"; + case SINK_STATE_CONNECTED: + return "connected"; + case SINK_STATE_PLAYING: + return "playing"; + default: + error("Invalid sink state %d", state); + return NULL; + } +} + +static void sink_set_state(struct audio_device *dev, sink_state_t new_state) +{ + struct sink *sink = dev->sink; + const char *state_str; + sink_state_t old_state = sink->state; + GSList *l; + + sink->state = new_state; + + state_str = state2str(new_state); + if (state_str) + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + + DBG("State changed %s: %s -> %s", dev->path, str_state[old_state], + str_state[new_state]); + + for (l = sink_callbacks; l != NULL; l = l->next) { + struct sink_state_callback *cb = l->data; + cb->cb(dev, old_state, new_state, cb->user_data); + } +} + +static void avdtp_state_callback(struct audio_device *dev, + struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data) +{ + struct sink *sink = dev->sink; + + if (sink == NULL) + return; + + switch (new_state) { + case AVDTP_SESSION_STATE_DISCONNECTED: + if (sink->state != SINK_STATE_CONNECTING) { + gboolean value = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, "Disconnected", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); + } + sink_set_state(dev, SINK_STATE_DISCONNECTED); + break; + case AVDTP_SESSION_STATE_CONNECTING: + sink_set_state(dev, SINK_STATE_CONNECTING); + break; + case AVDTP_SESSION_STATE_CONNECTED: + break; + } + + sink->session_state = new_state; +} + +static void pending_request_free(struct audio_device *dev, + struct pending_request *pending) +{ + if (pending->conn) + dbus_connection_unref(pending->conn); + if (pending->msg) + dbus_message_unref(pending->msg); + if (pending->id) + a2dp_cancel(dev, pending->id); + + g_free(pending); +} + +static void stream_state_changed(struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data) +{ + struct audio_device *dev = user_data; + struct sink *sink = dev->sink; + gboolean value; + + if (err) + return; + + switch (new_state) { + case AVDTP_STATE_IDLE: + if (sink->disconnect) { + DBusMessage *reply; + struct pending_request *p; + + p = sink->disconnect; + sink->disconnect = NULL; + + reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(p->conn, reply); + pending_request_free(dev, p); + } + + if (sink->session) { + avdtp_unref(sink->session); + sink->session = NULL; + } + sink->stream = NULL; + sink->cb_id = 0; + break; + case AVDTP_STATE_OPEN: + if (old_state == AVDTP_STATE_CONFIGURED && + sink->state == SINK_STATE_CONNECTING) { + value = TRUE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Connected", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Connected", + DBUS_TYPE_BOOLEAN, &value); + } else if (old_state == AVDTP_STATE_STREAMING) { + value = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Stopped", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Playing", + DBUS_TYPE_BOOLEAN, &value); + } + sink_set_state(dev, SINK_STATE_CONNECTED); + break; + case AVDTP_STATE_STREAMING: + value = TRUE; + g_dbus_emit_signal(dev->conn, dev->path, AUDIO_SINK_INTERFACE, + "Playing", DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, "Playing", + DBUS_TYPE_BOOLEAN, &value); + sink_set_state(dev, SINK_STATE_PLAYING); + break; + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + break; + } + + sink->stream_state = new_state; +} + +static void error_failed(DBusConnection *conn, DBusMessage *msg, + const char *desc) +{ + DBusMessage *reply = btd_error_failed(msg, desc); + g_dbus_send_message(conn, reply); +} + +static gboolean stream_setup_retry(gpointer user_data) +{ + struct sink *sink = user_data; + struct pending_request *pending = sink->connect; + + sink->retry_id = 0; + + if (sink->stream_state >= AVDTP_STATE_OPEN) { + DBG("Stream successfully created, after XCASE connect:connect"); + if (pending->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(pending->msg); + g_dbus_send_message(pending->conn, reply); + } + } else { + DBG("Stream setup failed, after XCASE connect:connect"); + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + } + + sink->connect = NULL; + pending_request_free(sink->dev, pending); + + return FALSE; +} + +static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + struct sink *sink = user_data; + struct pending_request *pending; + + pending = sink->connect; + + pending->id = 0; + + if (stream) { + DBG("Stream successfully created"); + + if (pending->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(pending->msg); + g_dbus_send_message(pending->conn, reply); + } + + sink->connect = NULL; + pending_request_free(sink->dev, pending); + + return; + } + + avdtp_unref(sink->session); + sink->session = NULL; + if (avdtp_error_category(err) == AVDTP_ERRNO + && avdtp_error_posix_errno(err) != EHOSTDOWN) { + DBG("connect:connect XCASE detected"); + sink->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER, + stream_setup_retry, + sink); + } else { + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + sink->connect = NULL; + pending_request_free(sink->dev, pending); + DBG("Stream setup failed : %s", avdtp_strerror(err)); + } +} + +static void select_complete(struct avdtp *session, struct a2dp_sep *sep, + GSList *caps, void *user_data) +{ + struct sink *sink = user_data; + struct pending_request *pending; + int id; + + pending = sink->connect; + pending->id = 0; + + id = a2dp_config(session, sep, stream_setup_complete, caps, sink); + if (id == 0) + goto failed; + + pending->id = id; + return; + +failed: + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + pending_request_free(sink->dev, pending); + sink->connect = NULL; + avdtp_unref(sink->session); + sink->session = NULL; +} + +static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err, + void *user_data) +{ + struct sink *sink = user_data; + struct pending_request *pending; + int id; + + if (!sink->connect) { + avdtp_unref(sink->session); + sink->session = NULL; + return; + } + + pending = sink->connect; + + if (err) { + avdtp_unref(sink->session); + sink->session = NULL; + if (avdtp_error_category(err) == AVDTP_ERRNO + && avdtp_error_posix_errno(err) != EHOSTDOWN) { + DBG("connect:connect XCASE detected"); + sink->retry_id = + g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER, + stream_setup_retry, + sink); + } else + goto failed; + return; + } + + DBG("Discovery complete"); + + id = a2dp_select_capabilities(sink->session, AVDTP_SEP_TYPE_SINK, NULL, + select_complete, sink); + if (id == 0) + goto failed; + + pending->id = id; + return; + +failed: + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + pending_request_free(sink->dev, pending); + sink->connect = NULL; + avdtp_unref(sink->session); + sink->session = NULL; +} + +gboolean sink_setup_stream(struct sink *sink, struct avdtp *session) +{ + if (sink->connect || sink->disconnect) + return FALSE; + + if (session && !sink->session) + sink->session = avdtp_ref(session); + + if (!sink->session) + return FALSE; + + avdtp_set_auto_disconnect(sink->session, FALSE); + + if (avdtp_discover(sink->session, discovery_complete, sink) < 0) + return FALSE; + + sink->connect = g_new0(struct pending_request, 1); + + return TRUE; +} + +static DBusMessage *sink_connect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *dev = data; + struct sink *sink = dev->sink; + struct pending_request *pending; + + if (!sink->session) + sink->session = avdtp_get(&dev->src, &dev->dst); + + if (!sink->session) + return btd_error_failed(msg, "Unable to get a session"); + + if (sink->connect || sink->disconnect) + return btd_error_busy(msg); + + if (sink->stream_state >= AVDTP_STATE_OPEN) + return btd_error_already_connected(msg); + + if (!sink_setup_stream(sink, NULL)) + return btd_error_failed(msg, "Failed to create a stream"); + + dev->auto_connect = FALSE; + + pending = sink->connect; + + pending->conn = dbus_connection_ref(conn); + pending->msg = dbus_message_ref(msg); + + DBG("stream creation in progress"); + + return NULL; +} + +static DBusMessage *sink_disconnect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct sink *sink = device->sink; + struct pending_request *pending; + int err; + + if (!sink->session) + return btd_error_not_connected(msg); + + if (sink->connect || sink->disconnect) + return btd_error_busy(msg); + + if (sink->stream_state < AVDTP_STATE_OPEN) { + DBusMessage *reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + avdtp_unref(sink->session); + sink->session = NULL; + return reply; + } + + err = avdtp_close(sink->session, sink->stream, FALSE); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + pending = g_new0(struct pending_request, 1); + pending->conn = dbus_connection_ref(conn); + pending->msg = dbus_message_ref(msg); + sink->disconnect = pending; + + return NULL; +} + +static DBusMessage *sink_is_connected(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct sink *sink = device->sink; + DBusMessage *reply; + dbus_bool_t connected; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + connected = (sink->stream_state >= AVDTP_STATE_CONFIGURED); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *sink_get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct sink *sink = device->sink; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + const char *state; + gboolean value; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + /* Playing */ + value = (sink->stream_state == AVDTP_STATE_STREAMING); + dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value); + + /* Connected */ + value = (sink->stream_state >= AVDTP_STATE_CONFIGURED); + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); + + /* State */ + state = state2str(sink->state); + if (state) + dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable sink_methods[] = { + { "Connect", "", "", sink_connect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", sink_disconnect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "IsConnected", "", "b", sink_is_connected, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetProperties", "", "a{sv}",sink_get_properties }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable sink_signals[] = { + { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Playing", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Stopped", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +static void sink_free(struct audio_device *dev) +{ + struct sink *sink = dev->sink; + + if (sink->cb_id) + avdtp_stream_remove_cb(sink->session, sink->stream, + sink->cb_id); + + if (sink->session) + avdtp_unref(sink->session); + + if (sink->connect) + pending_request_free(dev, sink->connect); + + if (sink->disconnect) + pending_request_free(dev, sink->disconnect); + + if (sink->retry_id) + g_source_remove(sink->retry_id); + + g_free(sink); + dev->sink = NULL; +} + +static void path_unregister(void *data) +{ + struct audio_device *dev = data; + + DBG("Unregistered interface %s on path %s", + AUDIO_SINK_INTERFACE, dev->path); + + sink_free(dev); +} + +void sink_unregister(struct audio_device *dev) +{ + g_dbus_unregister_interface(dev->conn, dev->path, + AUDIO_SINK_INTERFACE); +} + +struct sink *sink_init(struct audio_device *dev) +{ + struct sink *sink; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + sink_methods, sink_signals, NULL, + dev, path_unregister)) + return NULL; + + DBG("Registered interface %s on path %s", + AUDIO_SINK_INTERFACE, dev->path); + + if (avdtp_callback_id == 0) + avdtp_callback_id = avdtp_add_state_cb(avdtp_state_callback, + NULL); + + sink = g_new0(struct sink, 1); + + sink->dev = dev; + + return sink; +} + +gboolean sink_is_active(struct audio_device *dev) +{ + struct sink *sink = dev->sink; + + if (sink->session) + return TRUE; + + return FALSE; +} + +avdtp_state_t sink_get_state(struct audio_device *dev) +{ + struct sink *sink = dev->sink; + + return sink->stream_state; +} + +gboolean sink_new_stream(struct audio_device *dev, struct avdtp *session, + struct avdtp_stream *stream) +{ + struct sink *sink = dev->sink; + + if (sink->stream) + return FALSE; + + if (!sink->session) + sink->session = avdtp_ref(session); + + sink->stream = stream; + + sink->cb_id = avdtp_stream_add_cb(session, stream, + stream_state_changed, dev); + + return TRUE; +} + +gboolean sink_shutdown(struct sink *sink) +{ + if (!sink->session) + return FALSE; + + avdtp_set_device_disconnect(sink->session, TRUE); + + /* cancel pending connect */ + if (sink->connect) { + struct pending_request *pending = sink->connect; + + if (pending->msg) + error_failed(pending->conn, pending->msg, + "Stream setup failed"); + pending_request_free(sink->dev, pending); + sink->connect = NULL; + + avdtp_unref(sink->session); + sink->session = NULL; + + return TRUE; + } + + /* disconnect already ongoing */ + if (sink->disconnect) + return TRUE; + + if (!sink->stream) + return FALSE; + + if (avdtp_close(sink->session, sink->stream, FALSE) < 0) + return FALSE; + + return TRUE; +} + +unsigned int sink_add_state_cb(sink_state_cb cb, void *user_data) +{ + struct sink_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct sink_state_callback, 1); + state_cb->cb = cb; + state_cb->user_data = user_data; + state_cb->id = ++id; + + sink_callbacks = g_slist_append(sink_callbacks, state_cb); + + return state_cb->id; +} + +gboolean sink_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = sink_callbacks; l != NULL; l = l->next) { + struct sink_state_callback *cb = l->data; + if (cb && cb->id == id) { + sink_callbacks = g_slist_remove(sink_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/audio/sink.h b/audio/sink.h new file mode 100644 index 0000000..7b1902b --- /dev/null +++ b/audio/sink.h @@ -0,0 +1,49 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define AUDIO_SINK_INTERFACE "org.bluez.AudioSink" + +typedef enum { + SINK_STATE_DISCONNECTED, + SINK_STATE_CONNECTING, + SINK_STATE_CONNECTED, + SINK_STATE_PLAYING, +} sink_state_t; + +typedef void (*sink_state_cb) (struct audio_device *dev, + sink_state_t old_state, + sink_state_t new_state, + void *user_data); + +unsigned int sink_add_state_cb(sink_state_cb cb, void *user_data); +gboolean sink_remove_state_cb(unsigned int id); + +struct sink *sink_init(struct audio_device *dev); +void sink_unregister(struct audio_device *dev); +gboolean sink_is_active(struct audio_device *dev); +avdtp_state_t sink_get_state(struct audio_device *dev); +gboolean sink_new_stream(struct audio_device *dev, struct avdtp *session, + struct avdtp_stream *stream); +gboolean sink_setup_stream(struct sink *sink, struct avdtp *session); +gboolean sink_shutdown(struct sink *sink); diff --git a/audio/source.c b/audio/source.c new file mode 100644 index 0000000..6d266f2 --- /dev/null +++ b/audio/source.c @@ -0,0 +1,633 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2009 Joao Paulo Rechi Vita + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include + +#include "log.h" + +#include "device.h" +#include "avdtp.h" +#include "media.h" +#include "a2dp.h" +#include "error.h" +#include "source.h" +#include "dbus-common.h" +#include "../src/adapter.h" +#include "../src/device.h" + +#define STREAM_SETUP_RETRY_TIMER 2 + +struct pending_request { + DBusConnection *conn; + DBusMessage *msg; + unsigned int id; +}; + +struct source { + struct audio_device *dev; + struct avdtp *session; + struct avdtp_stream *stream; + unsigned int cb_id; + guint retry_id; + avdtp_session_state_t session_state; + avdtp_state_t stream_state; + source_state_t state; + struct pending_request *connect; + struct pending_request *disconnect; + DBusConnection *conn; +}; + +struct source_state_callback { + source_state_cb cb; + void *user_data; + unsigned int id; +}; + +static GSList *source_callbacks = NULL; + +static unsigned int avdtp_callback_id = 0; + +static const char *state2str(source_state_t state) +{ + switch (state) { + case SOURCE_STATE_DISCONNECTED: + return "disconnected"; + case SOURCE_STATE_CONNECTING: + return "connecting"; + case SOURCE_STATE_CONNECTED: + return "connected"; + case SOURCE_STATE_PLAYING: + return "playing"; + default: + error("Invalid source state %d", state); + return NULL; + } +} + +static void source_set_state(struct audio_device *dev, source_state_t new_state) +{ + struct source *source = dev->source; + const char *state_str; + source_state_t old_state = source->state; + GSList *l; + + source->state = new_state; + + state_str = state2str(new_state); + if (state_str) + emit_property_changed(dev->conn, dev->path, + AUDIO_SOURCE_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + + for (l = source_callbacks; l != NULL; l = l->next) { + struct source_state_callback *cb = l->data; + cb->cb(dev, old_state, new_state, cb->user_data); + } +} + +static void avdtp_state_callback(struct audio_device *dev, + struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data) +{ + struct source *source = dev->source; + + if (source == NULL) + return; + + switch (new_state) { + case AVDTP_SESSION_STATE_DISCONNECTED: + source_set_state(dev, SOURCE_STATE_DISCONNECTED); + break; + case AVDTP_SESSION_STATE_CONNECTING: + source_set_state(dev, SOURCE_STATE_CONNECTING); + break; + case AVDTP_SESSION_STATE_CONNECTED: + break; + } + + source->session_state = new_state; +} + +static void pending_request_free(struct audio_device *dev, + struct pending_request *pending) +{ + if (pending->conn) + dbus_connection_unref(pending->conn); + if (pending->msg) + dbus_message_unref(pending->msg); + if (pending->id) + a2dp_cancel(dev, pending->id); + + g_free(pending); +} + +static void stream_state_changed(struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data) +{ + struct audio_device *dev = user_data; + struct source *source = dev->source; + + if (err) + return; + + switch (new_state) { + case AVDTP_STATE_IDLE: + if (source->disconnect) { + DBusMessage *reply; + struct pending_request *p; + + p = source->disconnect; + source->disconnect = NULL; + + reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(p->conn, reply); + pending_request_free(dev, p); + } + + if (source->session) { + avdtp_unref(source->session); + source->session = NULL; + } + source->stream = NULL; + source->cb_id = 0; + break; + case AVDTP_STATE_OPEN: + source_set_state(dev, SOURCE_STATE_CONNECTED); + break; + case AVDTP_STATE_STREAMING: + source_set_state(dev, SOURCE_STATE_PLAYING); + break; + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + break; + } + + source->stream_state = new_state; +} + +static void error_failed(DBusConnection *conn, DBusMessage *msg, + const char *desc) +{ + DBusMessage *reply = btd_error_failed(msg, desc); + g_dbus_send_message(conn, reply); +} + +static gboolean stream_setup_retry(gpointer user_data) +{ + struct source *source = user_data; + struct pending_request *pending = source->connect; + + source->retry_id = 0; + + if (source->stream_state >= AVDTP_STATE_OPEN) { + DBG("Stream successfully created, after XCASE connect:connect"); + if (pending->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(pending->msg); + g_dbus_send_message(pending->conn, reply); + } + } else { + DBG("Stream setup failed, after XCASE connect:connect"); + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + } + + source->connect = NULL; + pending_request_free(source->dev, pending); + + return FALSE; +} + +static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + struct source *source = user_data; + struct pending_request *pending; + + pending = source->connect; + + pending->id = 0; + + if (stream) { + DBG("Stream successfully created"); + + if (pending->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(pending->msg); + g_dbus_send_message(pending->conn, reply); + } + + source->connect = NULL; + pending_request_free(source->dev, pending); + + return; + } + + avdtp_unref(source->session); + source->session = NULL; + if (avdtp_error_category(err) == AVDTP_ERRNO + && avdtp_error_posix_errno(err) != EHOSTDOWN) { + DBG("connect:connect XCASE detected"); + source->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER, + stream_setup_retry, + source); + } else { + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + source->connect = NULL; + pending_request_free(source->dev, pending); + DBG("Stream setup failed : %s", avdtp_strerror(err)); + } +} + +static void select_complete(struct avdtp *session, struct a2dp_sep *sep, + GSList *caps, void *user_data) +{ + struct source *source = user_data; + struct pending_request *pending; + int id; + + pending = source->connect; + + pending->id = 0; + + if (caps == NULL) + goto failed; + + id = a2dp_config(session, sep, stream_setup_complete, caps, source); + if (id == 0) + goto failed; + + pending->id = id; + return; + +failed: + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + pending_request_free(source->dev, pending); + source->connect = NULL; + avdtp_unref(source->session); + source->session = NULL; +} + +static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err, + void *user_data) +{ + struct source *source = user_data; + struct pending_request *pending; + int id; + + pending = source->connect; + + if (err) { + avdtp_unref(source->session); + source->session = NULL; + if (avdtp_error_category(err) == AVDTP_ERRNO + && avdtp_error_posix_errno(err) != EHOSTDOWN) { + DBG("connect:connect XCASE detected"); + source->retry_id = + g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER, + stream_setup_retry, + source); + } else + goto failed; + return; + } + + DBG("Discovery complete"); + + id = a2dp_select_capabilities(source->session, AVDTP_SEP_TYPE_SOURCE, NULL, + select_complete, source); + if (id == 0) + goto failed; + + pending->id = id; + return; + +failed: + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + pending_request_free(source->dev, pending); + source->connect = NULL; + avdtp_unref(source->session); + source->session = NULL; +} + +gboolean source_setup_stream(struct source *source, struct avdtp *session) +{ + if (source->connect || source->disconnect) + return FALSE; + + if (session && !source->session) + source->session = avdtp_ref(session); + + if (!source->session) + return FALSE; + + avdtp_set_auto_disconnect(source->session, FALSE); + + if (avdtp_discover(source->session, discovery_complete, source) < 0) + return FALSE; + + source->connect = g_new0(struct pending_request, 1); + + return TRUE; +} + +static DBusMessage *source_connect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *dev = data; + struct source *source = dev->source; + struct pending_request *pending; + + if (!source->session) + source->session = avdtp_get(&dev->src, &dev->dst); + + if (!source->session) + return btd_error_failed(msg, "Unable to get a session"); + + if (source->connect || source->disconnect) + return btd_error_busy(msg); + + if (source->stream_state >= AVDTP_STATE_OPEN) + return btd_error_already_connected(msg); + + if (!source_setup_stream(source, NULL)) + return btd_error_failed(msg, "Failed to create a stream"); + + dev->auto_connect = FALSE; + + pending = source->connect; + + pending->conn = dbus_connection_ref(conn); + pending->msg = dbus_message_ref(msg); + + DBG("stream creation in progress"); + + return NULL; +} + +static DBusMessage *source_disconnect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct source *source = device->source; + struct pending_request *pending; + int err; + + if (!source->session) + return btd_error_not_connected(msg); + + if (source->connect || source->disconnect) + return btd_error_busy(msg); + + if (source->stream_state < AVDTP_STATE_OPEN) { + DBusMessage *reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + avdtp_unref(source->session); + source->session = NULL; + return reply; + } + + err = avdtp_close(source->session, source->stream, FALSE); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + pending = g_new0(struct pending_request, 1); + pending->conn = dbus_connection_ref(conn); + pending->msg = dbus_message_ref(msg); + source->disconnect = pending; + + return NULL; +} + +static DBusMessage *source_get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct source *source = device->source; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + const char *state; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + /* State */ + state = state2str(source->state); + if (state) + dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable source_methods[] = { + { "Connect", "", "", source_connect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", source_disconnect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "GetProperties", "", "a{sv}",source_get_properties }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable source_signals[] = { + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +static void source_free(struct audio_device *dev) +{ + struct source *source = dev->source; + + if (source->cb_id) + avdtp_stream_remove_cb(source->session, source->stream, + source->cb_id); + + if (source->session) + avdtp_unref(source->session); + + if (source->connect) + pending_request_free(dev, source->connect); + + if (source->disconnect) + pending_request_free(dev, source->disconnect); + + if (source->retry_id) + g_source_remove(source->retry_id); + + g_free(source); + dev->source = NULL; +} + +static void path_unregister(void *data) +{ + struct audio_device *dev = data; + + DBG("Unregistered interface %s on path %s", + AUDIO_SOURCE_INTERFACE, dev->path); + + source_free(dev); +} + +void source_unregister(struct audio_device *dev) +{ + g_dbus_unregister_interface(dev->conn, dev->path, + AUDIO_SOURCE_INTERFACE); +} + +struct source *source_init(struct audio_device *dev) +{ + struct source *source; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_SOURCE_INTERFACE, + source_methods, source_signals, NULL, + dev, path_unregister)) + return NULL; + + DBG("Registered interface %s on path %s", + AUDIO_SOURCE_INTERFACE, dev->path); + + if (avdtp_callback_id == 0) + avdtp_callback_id = avdtp_add_state_cb(avdtp_state_callback, + NULL); + + source = g_new0(struct source, 1); + + source->dev = dev; + + return source; +} + +gboolean source_is_active(struct audio_device *dev) +{ + struct source *source = dev->source; + + if (source->session) + return TRUE; + + return FALSE; +} + +avdtp_state_t source_get_state(struct audio_device *dev) +{ + struct source *source = dev->source; + + return source->stream_state; +} + +gboolean source_new_stream(struct audio_device *dev, struct avdtp *session, + struct avdtp_stream *stream) +{ + struct source *source = dev->source; + + if (source->stream) + return FALSE; + + if (!source->session) + source->session = avdtp_ref(session); + + source->stream = stream; + + source->cb_id = avdtp_stream_add_cb(session, stream, + stream_state_changed, dev); + + return TRUE; +} + +gboolean source_shutdown(struct source *source) +{ + if (!source->stream) + return FALSE; + + if (avdtp_close(source->session, source->stream, FALSE) < 0) + return FALSE; + + return TRUE; +} + +unsigned int source_add_state_cb(source_state_cb cb, void *user_data) +{ + struct source_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct source_state_callback, 1); + state_cb->cb = cb; + state_cb->user_data = user_data; + state_cb->id = ++id; + + source_callbacks = g_slist_append(source_callbacks, state_cb); + + return state_cb->id; +} + +gboolean source_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = source_callbacks; l != NULL; l = l->next) { + struct source_state_callback *cb = l->data; + if (cb && cb->id == id) { + source_callbacks = g_slist_remove(source_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/audio/source.h b/audio/source.h new file mode 100644 index 0000000..7837284 --- /dev/null +++ b/audio/source.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2009 Joao Paulo Rechi Vita + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define AUDIO_SOURCE_INTERFACE "org.bluez.AudioSource" + +typedef enum { + SOURCE_STATE_DISCONNECTED, + SOURCE_STATE_CONNECTING, + SOURCE_STATE_CONNECTED, + SOURCE_STATE_PLAYING, +} source_state_t; + +typedef void (*source_state_cb) (struct audio_device *dev, + source_state_t old_state, + source_state_t new_state, + void *user_data); + +unsigned int source_add_state_cb(source_state_cb cb, void *user_data); +gboolean source_remove_state_cb(unsigned int id); + +struct source *source_init(struct audio_device *dev); +void source_unregister(struct audio_device *dev); +gboolean source_is_active(struct audio_device *dev); +avdtp_state_t source_get_state(struct audio_device *dev); +gboolean source_new_stream(struct audio_device *dev, struct avdtp *session, + struct avdtp_stream *stream); +gboolean source_setup_stream(struct source *source, struct avdtp *session); +gboolean source_shutdown(struct source *source); diff --git a/audio/telephony-dummy.c b/audio/telephony-dummy.c new file mode 100644 index 0000000..1f89079 --- /dev/null +++ b/audio/telephony-dummy.c @@ -0,0 +1,433 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "telephony.h" +#include "error.h" + +#define TELEPHONY_DUMMY_IFACE "org.bluez.TelephonyTest" +#define TELEPHONY_DUMMY_PATH "/org/bluez/test" + +static DBusConnection *connection = NULL; + +static const char *chld_str = "0,1,1x,2,2x,3,4"; +static char *subscriber_number = NULL; +static char *active_call_number = NULL; +static int active_call_status = 0; +static int active_call_dir = 0; + +static gboolean events_enabled = FALSE; + +static struct indicator dummy_indicators[] = +{ + { "battchg", "0-5", 5, TRUE }, + { "signal", "0-5", 5, TRUE }, + { "service", "0,1", 1, TRUE }, + { "call", "0,1", 0, TRUE }, + { "callsetup", "0-3", 0, TRUE }, + { "callheld", "0-2", 0, FALSE }, + { "roam", "0,1", 0, TRUE }, + { NULL } +}; + +void telephony_device_connected(void *telephony_device) +{ + DBG("telephony-dummy: device %p connected", telephony_device); +} + +void telephony_device_disconnected(void *telephony_device) +{ + DBG("telephony-dummy: device %p disconnected", telephony_device); + events_enabled = FALSE; +} + +void telephony_event_reporting_req(void *telephony_device, int ind) +{ + events_enabled = ind == 1 ? TRUE : FALSE; + + telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_response_and_hold_req(void *telephony_device, int rh) +{ + telephony_response_and_hold_rsp(telephony_device, + CME_ERROR_NOT_SUPPORTED); +} + +void telephony_last_dialed_number_req(void *telephony_device) +{ + telephony_last_dialed_number_rsp(telephony_device, CME_ERROR_NONE); + + /* Notify outgoing call set-up successfully initiated */ + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + + active_call_status = CALL_STATUS_ALERTING; + active_call_dir = CALL_DIR_OUTGOING; +} + +void telephony_terminate_call_req(void *telephony_device) +{ + g_free(active_call_number); + active_call_number = NULL; + + telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE); + + if (telephony_get_indicator(dummy_indicators, "callsetup") > 0) + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + else + telephony_update_indicator(dummy_indicators, "call", + EV_CALL_INACTIVE); +} + +void telephony_answer_call_req(void *telephony_device) +{ + telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE); + + telephony_update_indicator(dummy_indicators, "call", EV_CALL_ACTIVE); + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + + active_call_status = CALL_STATUS_ACTIVE; +} + +void telephony_dial_number_req(void *telephony_device, const char *number) +{ + g_free(active_call_number); + active_call_number = g_strdup(number); + + DBG("telephony-dummy: dial request to %s", active_call_number); + + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); + + /* Notify outgoing call set-up successfully initiated */ + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + + active_call_status = CALL_STATUS_ALERTING; + active_call_dir = CALL_DIR_OUTGOING; +} + +void telephony_transmit_dtmf_req(void *telephony_device, char tone) +{ + DBG("telephony-dummy: transmit dtmf: %c", tone); + telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_subscriber_number_req(void *telephony_device) +{ + DBG("telephony-dummy: subscriber number request"); + if (subscriber_number) + telephony_subscriber_number_ind(subscriber_number, + NUMBER_TYPE_TELEPHONY, + SUBSCRIBER_SERVICE_VOICE); + telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_list_current_calls_req(void *telephony_device) +{ + DBG("telephony-dummy: list current calls request"); + if (active_call_number) + telephony_list_current_call_ind(1, active_call_dir, + active_call_status, + CALL_MODE_VOICE, + CALL_MULTIPARTY_NO, + active_call_number, + NUMBER_TYPE_TELEPHONY); + telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_operator_selection_req(void *telephony_device) +{ + telephony_operator_selection_ind(OPERATOR_MODE_AUTO, "DummyOperator"); + telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_call_hold_req(void *telephony_device, const char *cmd) +{ + DBG("telephony-dymmy: got call hold request %s", cmd); + telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-dummy: got %s NR and EC request", + enable ? "enable" : "disable"); + + telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_voice_dial_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-dummy: got %s voice dial request", + enable ? "enable" : "disable"); + + g_dbus_emit_signal(connection, TELEPHONY_DUMMY_PATH, + TELEPHONY_DUMMY_IFACE, "VoiceDial", + DBUS_TYPE_INVALID); + + telephony_voice_dial_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_key_press_req(void *telephony_device, const char *keys) +{ + DBG("telephony-dummy: got key press request for %s", keys); + telephony_key_press_rsp(telephony_device, CME_ERROR_NONE); +} + +/* D-Bus method handlers */ +static DBusMessage *outgoing_call(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *number; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + DBG("telephony-dummy: outgoing call to %s", number); + + g_free(active_call_number); + active_call_number = g_strdup(number); + + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + + active_call_status = CALL_STATUS_ALERTING; + active_call_dir = CALL_DIR_OUTGOING; + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *incoming_call(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *number; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + DBG("telephony-dummy: incoming call to %s", number); + + g_free(active_call_number); + active_call_number = g_strdup(number); + + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_INCOMING); + + active_call_status = CALL_STATUS_INCOMING; + active_call_dir = CALL_DIR_INCOMING; + + telephony_incoming_call_ind(number, NUMBER_TYPE_TELEPHONY); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *cancel_call(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + DBG("telephony-dummy: cancel call"); + + g_free(active_call_number); + active_call_number = NULL; + + if (telephony_get_indicator(dummy_indicators, "callsetup") > 0) { + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + telephony_calling_stopped_ind(); + } + + if (telephony_get_indicator(dummy_indicators, "call") > 0) + telephony_update_indicator(dummy_indicators, "call", + EV_CALL_INACTIVE); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *signal_strength(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_uint32_t strength; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &strength, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (strength > 5) + return btd_error_invalid_args(msg); + + telephony_update_indicator(dummy_indicators, "signal", strength); + + DBG("telephony-dummy: signal strength set to %u", strength); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *battery_level(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_uint32_t level; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &level, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (level > 5) + return btd_error_invalid_args(msg); + + telephony_update_indicator(dummy_indicators, "battchg", level); + + DBG("telephony-dummy: battery level set to %u", level); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *roaming_status(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_bool_t roaming; + int val; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &roaming, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + val = roaming ? EV_ROAM_ACTIVE : EV_ROAM_INACTIVE; + + telephony_update_indicator(dummy_indicators, "roam", val); + + DBG("telephony-dummy: roaming status set to %d", val); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *registration_status(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_bool_t registration; + int val; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, ®istration, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + val = registration ? EV_SERVICE_PRESENT : EV_SERVICE_NONE; + + telephony_update_indicator(dummy_indicators, "service", val); + + DBG("telephony-dummy: registration status set to %d", val); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_subscriber_number(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + const char *number; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + g_free(subscriber_number); + subscriber_number = g_strdup(number); + + DBG("telephony-dummy: subscriber number set to %s", number); + + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable dummy_methods[] = { + { "OutgoingCall", "s", "", outgoing_call }, + { "IncomingCall", "s", "", incoming_call }, + { "CancelCall", "", "", cancel_call }, + { "SignalStrength", "u", "", signal_strength }, + { "BatteryLevel", "u", "", battery_level }, + { "RoamingStatus", "b", "", roaming_status }, + { "RegistrationStatus", "b", "", registration_status }, + { "SetSubscriberNumber","s", "", set_subscriber_number }, + { } +}; + +static GDBusSignalTable dummy_signals[] = { + { "VoiceDial", "" }, + { } +}; + +int telephony_init(void) +{ + uint32_t features = AG_FEATURE_REJECT_A_CALL | + AG_FEATURE_ENHANCED_CALL_STATUS | + AG_FEATURE_EXTENDED_ERROR_RESULT_CODES; + + DBG(""); + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + + if (g_dbus_register_interface(connection, TELEPHONY_DUMMY_PATH, + TELEPHONY_DUMMY_IFACE, + dummy_methods, dummy_signals, + NULL, NULL, NULL) == FALSE) { + error("telephony-dummy interface %s init failed on path %s", + TELEPHONY_DUMMY_IFACE, TELEPHONY_DUMMY_PATH); + return -1; + } + + telephony_ready_ind(features, dummy_indicators, BTRH_NOT_SUPPORTED, + chld_str); + + return 0; +} + +void telephony_exit(void) +{ + DBG(""); + + g_dbus_unregister_interface(connection, TELEPHONY_DUMMY_PATH, + TELEPHONY_DUMMY_IFACE); + dbus_connection_unref(connection); + connection = NULL; + + telephony_deinit(); +} diff --git a/audio/telephony-maemo5.c b/audio/telephony-maemo5.c new file mode 100644 index 0000000..350df9e --- /dev/null +++ b/audio/telephony-maemo5.c @@ -0,0 +1,2104 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "telephony.h" +#include "error.h" + +/* SSC D-Bus definitions */ +#define SSC_DBUS_NAME "com.nokia.phone.SSC" +#define SSC_DBUS_IFACE "com.nokia.phone.SSC" +#define SSC_DBUS_PATH "/com/nokia/phone/SSC" + +/* libcsnet D-Bus definitions */ +#define NETWORK_BUS_NAME "com.nokia.phone.net" +#define NETWORK_INTERFACE "Phone.Net" +#define NETWORK_PATH "/com/nokia/phone/net" + +/* Mask bits for supported services */ +#define NETWORK_MASK_GPRS_SUPPORT 0x01 +#define NETWORK_MASK_CS_SERVICES 0x02 +#define NETWORK_MASK_EGPRS_SUPPORT 0x04 +#define NETWORK_MASK_HSDPA_AVAIL 0x08 +#define NETWORK_MASK_HSUPA_AVAIL 0x10 + +/* network get cell info: cell type */ +#define NETWORK_UNKNOWN_CELL 0 +#define NETWORK_GSM_CELL 1 +#define NETWORK_WCDMA_CELL 2 + +enum net_registration_status { + NETWORK_REG_STATUS_HOME = 0x00, + NETWORK_REG_STATUS_ROAM, + NETWORK_REG_STATUS_ROAM_BLINK, + NETWORK_REG_STATUS_NOSERV, + NETWORK_REG_STATUS_NOSERV_SEARCHING, + NETWORK_REG_STATUS_NOSERV_NOTSEARCHING, + NETWORK_REG_STATUS_NOSERV_NOSIM, + NETWORK_REG_STATUS_POWER_OFF = 0x08, + NETWORK_REG_STATUS_NSPS, + NETWORK_REG_STATUS_NSPS_NO_COVERAGE, + NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW +}; + +enum network_types { + NETWORK_GSM_HOME_PLMN = 0, + NETWORK_GSM_PREFERRED_PLMN, + NETWORK_GSM_FORBIDDEN_PLMN, + NETWORK_GSM_OTHER_PLMN, + NETWORK_GSM_NO_PLMN_AVAIL +}; + +enum network_alpha_tag_name_type { + NETWORK_HARDCODED_LATIN_OPER_NAME = 0, + NETWORK_HARDCODED_USC2_OPER_NAME, + NETWORK_NITZ_SHORT_OPER_NAME, + NETWORK_NITZ_FULL_OPER_NAME, +}; + +#define TELEPHONY_MAEMO_PATH "/com/nokia/MaemoTelephony" +#define TELEPHONY_MAEMO_INTERFACE "com.nokia.MaemoTelephony" + +#define CALLERID_BASE "/var/lib/bluetooth/maemo-callerid-" +#define ALLOWED_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-allowed" +#define RESTRICTED_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-restricted" +#define NONE_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-none" + +static uint32_t callerid = 0; + +/* CSD CALL plugin D-Bus definitions */ +#define CSD_CALL_BUS_NAME "com.nokia.csd.Call" +#define CSD_CALL_INTERFACE "com.nokia.csd.Call" +#define CSD_CALL_INSTANCE "com.nokia.csd.Call.Instance" +#define CSD_CALL_CONFERENCE "com.nokia.csd.Call.Conference" +#define CSD_CALL_PATH "/com/nokia/csd/call" +#define CSD_CALL_CONFERENCE_PATH "/com/nokia/csd/call/conference" + +/* Call status values as exported by the CSD CALL plugin */ +#define CSD_CALL_STATUS_IDLE 0 +#define CSD_CALL_STATUS_CREATE 1 +#define CSD_CALL_STATUS_COMING 2 +#define CSD_CALL_STATUS_PROCEEDING 3 +#define CSD_CALL_STATUS_MO_ALERTING 4 +#define CSD_CALL_STATUS_MT_ALERTING 5 +#define CSD_CALL_STATUS_WAITING 6 +#define CSD_CALL_STATUS_ANSWERED 7 +#define CSD_CALL_STATUS_ACTIVE 8 +#define CSD_CALL_STATUS_MO_RELEASE 9 +#define CSD_CALL_STATUS_MT_RELEASE 10 +#define CSD_CALL_STATUS_HOLD_INITIATED 11 +#define CSD_CALL_STATUS_HOLD 12 +#define CSD_CALL_STATUS_RETRIEVE_INITIATED 13 +#define CSD_CALL_STATUS_RECONNECT_PENDING 14 +#define CSD_CALL_STATUS_TERMINATED 15 +#define CSD_CALL_STATUS_SWAP_INITIATED 16 + +#define CALL_FLAG_NONE 0 +#define CALL_FLAG_PRESENTATION_ALLOWED 0x01 +#define CALL_FLAG_PRESENTATION_RESTRICTED 0x02 + +/* SIM Phonebook D-Bus definitions */ +#define SIM_PHONEBOOK_BUS_NAME "com.nokia.phone.SIM" +#define SIM_PHONEBOOK_INTERFACE "Phone.Sim.Phonebook" +#define SIM_PHONEBOOK_PATH "/com/nokia/phone/SIM/phonebook" + +#define PHONEBOOK_INDEX_FIRST_ENTRY 0xFFFF +#define PHONEBOOK_INDEX_NEXT_FREE_LOCATION 0xFFFE + +enum sim_phonebook_type { + SIM_PHONEBOOK_TYPE_ADN = 0x0, + SIM_PHONEBOOK_TYPE_SDN, + SIM_PHONEBOOK_TYPE_FDN, + SIM_PHONEBOOK_TYPE_VMBX, + SIM_PHONEBOOK_TYPE_MBDN, + SIM_PHONEBOOK_TYPE_EN, + SIM_PHONEBOOK_TYPE_MSISDN +}; + +enum sim_phonebook_location_type { + SIM_PHONEBOOK_LOCATION_EXACT = 0x0, + SIM_PHONEBOOK_LOCATION_NEXT +}; + +struct csd_call { + char *object_path; + int status; + gboolean originating; + gboolean emergency; + gboolean on_hold; + gboolean conference; + char *number; + gboolean setup; +}; + +static struct { + uint8_t status; + uint16_t lac; + uint32_t cell_id; + uint32_t operator_code; + uint32_t country_code; + uint8_t network_type; + uint8_t supported_services; + uint16_t signals_bar; + char *operator_name; +} net = { + .status = NETWORK_REG_STATUS_NOSERV, + .lac = 0, + .cell_id = 0, + .operator_code = 0, + .country_code = 0, + .network_type = NETWORK_GSM_NO_PLMN_AVAIL, + .supported_services = 0, + .signals_bar = 0, + .operator_name = NULL, +}; + +static DBusConnection *connection = NULL; + +static GSList *calls = NULL; + +/* Reference count for determining the call indicator status */ +static GSList *active_calls = NULL; + +static char *msisdn = NULL; /* Subscriber number */ +static char *vmbx = NULL; /* Voice mailbox number */ + +/* HAL battery namespace key values */ +static int battchg_cur = -1; /* "battery.charge_level.current" */ +static int battchg_last = -1; /* "battery.charge_level.last_full" */ +static int battchg_design = -1; /* "battery.charge_level.design" */ + +static gboolean get_calls_active = FALSE; + +static gboolean events_enabled = FALSE; + +/* Supported set of call hold operations */ +static const char *chld_str = "0,1,1x,2,2x,3,4"; + +static char *last_dialed_number = NULL; + +/* Timer for tracking call creation requests */ +static guint create_request_timer = 0; + +static struct indicator maemo_indicators[] = +{ + { "battchg", "0-5", 5, TRUE }, + { "signal", "0-5", 0, TRUE }, + { "service", "0,1", 0, TRUE }, + { "call", "0,1", 0, TRUE }, + { "callsetup", "0-3", 0, TRUE }, + { "callheld", "0-2", 0, FALSE }, + { "roam", "0,1", 0, TRUE }, + { NULL } +}; + +static char *call_status_str[] = { + "IDLE", + "CREATE", + "COMING", + "PROCEEDING", + "MO_ALERTING", + "MT_ALERTING", + "WAITING", + "ANSWERED", + "ACTIVE", + "MO_RELEASE", + "MT_RELEASE", + "HOLD_INITIATED", + "HOLD", + "RETRIEVE_INITIATED", + "RECONNECT_PENDING", + "TERMINATED", + "SWAP_INITIATED", + "???" +}; + +static struct csd_call *find_call(const char *path) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (g_str_equal(call->object_path, path)) + return call; + } + + return NULL; +} + +static struct csd_call *find_non_held_call(void) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == CSD_CALL_STATUS_IDLE) + continue; + + if (call->status != CSD_CALL_STATUS_HOLD) + return call; + } + + return NULL; +} + +static struct csd_call *find_non_idle_call(void) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status != CSD_CALL_STATUS_IDLE) + return call; + } + + return NULL; +} + +static struct csd_call *find_call_with_status(int status) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == status) + return call; + } + + return NULL; +} + +static int release_conference(void) +{ + DBusMessage *msg; + + DBG("telephony-maemo: releasing conference call"); + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + CSD_CALL_CONFERENCE_PATH, + CSD_CALL_INSTANCE, + "Release"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int release_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Release"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int answer_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Answer"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int split_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Split"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int unhold_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Unhold"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int hold_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Hold"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int swap_calls(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Swap"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int create_conference(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Conference"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int call_transfer(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Transfer"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int number_type(const char *number) +{ + if (number == NULL) + return NUMBER_TYPE_TELEPHONY; + + if (number[0] == '+' || strncmp(number, "00", 2) == 0) + return NUMBER_TYPE_INTERNATIONAL; + + return NUMBER_TYPE_TELEPHONY; +} + +void telephony_device_connected(void *telephony_device) +{ + struct csd_call *coming; + + DBG("telephony-maemo: device %p connected", telephony_device); + + coming = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + if (coming) { + if (find_call_with_status(CSD_CALL_STATUS_ACTIVE)) + telephony_call_waiting_ind(coming->number, + number_type(coming->number)); + else + telephony_incoming_call_ind(coming->number, + number_type(coming->number)); + } +} + +void telephony_device_disconnected(void *telephony_device) +{ + DBG("telephony-maemo: device %p disconnected", telephony_device); + events_enabled = FALSE; +} + +void telephony_event_reporting_req(void *telephony_device, int ind) +{ + events_enabled = ind == 1 ? TRUE : FALSE; + + telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_response_and_hold_req(void *telephony_device, int rh) +{ + telephony_response_and_hold_rsp(telephony_device, + CME_ERROR_NOT_SUPPORTED); +} + +void telephony_last_dialed_number_req(void *telephony_device) +{ + DBG("telephony-maemo: last dialed number request"); + + if (last_dialed_number) + telephony_dial_number_req(telephony_device, + last_dialed_number); + else + telephony_last_dialed_number_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); +} + +void telephony_terminate_call_req(void *telephony_device) +{ + struct csd_call *call; + int err; + + call = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + if (!call) + call = find_non_idle_call(); + + if (!call) { + error("No active call"); + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + if (call->conference) + err = release_conference(); + else + err = release_call(call); + + if (err < 0) + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_answer_call_req(void *telephony_device) +{ + struct csd_call *call; + + call = find_call_with_status(CSD_CALL_STATUS_COMING); + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_PROCEEDING); + + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_WAITING); + + if (!call) { + telephony_answer_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + if (answer_call(call) < 0) + telephony_answer_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE); +} + +static int send_method_call(const char *dest, const char *path, + const char *interface, const char *method, + DBusPendingCallNotifyFunction cb, + void *user_data, int type, ...) +{ + DBusMessage *msg; + DBusPendingCall *call; + va_list args; + + msg = dbus_message_new_method_call(dest, path, interface, method); + if (!msg) { + error("Unable to allocate new D-Bus %s message", method); + return -ENOMEM; + } + + va_start(args, type); + + if (!dbus_message_append_args_valist(msg, type, args)) { + dbus_message_unref(msg); + va_end(args); + return -EIO; + } + + va_end(args); + + if (!cb) { + g_dbus_send_message(connection, msg); + return 0; + } + + if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) { + error("Sending %s failed", method); + dbus_message_unref(msg); + return -EIO; + } + + dbus_pending_call_set_notify(call, cb, user_data, NULL); + dbus_pending_call_unref(call); + dbus_message_unref(msg); + + return 0; +} + +static const char *memory_dial_lookup(int location) +{ + if (location == 1) + return vmbx; + else + return NULL; +} + +void telephony_dial_number_req(void *telephony_device, const char *number) +{ + uint32_t flags = callerid; + int ret; + + DBG("telephony-maemo: dial request to %s", number); + + if (strncmp(number, "*31#", 4) == 0) { + number += 4; + flags = CALL_FLAG_PRESENTATION_ALLOWED; + } else if (strncmp(number, "#31#", 4) == 0) { + number += 4; + flags = CALL_FLAG_PRESENTATION_RESTRICTED; + } else if (number[0] == '>') { + const char *location = &number[1]; + + number = memory_dial_lookup(strtol(&number[1], NULL, 0)); + if (!number) { + error("No number at memory location %s", location); + telephony_dial_number_rsp(telephony_device, + CME_ERROR_INVALID_INDEX); + return; + } + } + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "CreateWith", + NULL, NULL, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID); + if (ret < 0) { + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_transmit_dtmf_req(void *telephony_device, char tone) +{ + int ret; + char buf[2] = { tone, '\0' }, *buf_ptr = buf; + + DBG("telephony-maemo: transmit dtmf: %s", buf); + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "SendDTMF", + NULL, NULL, + DBUS_TYPE_STRING, &buf_ptr, + DBUS_TYPE_INVALID); + if (ret < 0) { + telephony_transmit_dtmf_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_subscriber_number_req(void *telephony_device) +{ + DBG("telephony-maemo: subscriber number request"); + if (msisdn) + telephony_subscriber_number_ind(msisdn, + number_type(msisdn), + SUBSCRIBER_SERVICE_VOICE); + telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE); +} + +static int csd_status_to_hfp(struct csd_call *call) +{ + switch (call->status) { + case CSD_CALL_STATUS_IDLE: + case CSD_CALL_STATUS_MO_RELEASE: + case CSD_CALL_STATUS_MT_RELEASE: + case CSD_CALL_STATUS_TERMINATED: + return -1; + case CSD_CALL_STATUS_CREATE: + return CALL_STATUS_DIALING; + case CSD_CALL_STATUS_WAITING: + return CALL_STATUS_WAITING; + case CSD_CALL_STATUS_PROCEEDING: + /* PROCEEDING can happen in outgoing/incoming */ + if (call->originating) + return CALL_STATUS_DIALING; + else + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_COMING: + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_MO_ALERTING: + return CALL_STATUS_ALERTING; + case CSD_CALL_STATUS_MT_ALERTING: + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_ANSWERED: + case CSD_CALL_STATUS_ACTIVE: + case CSD_CALL_STATUS_RECONNECT_PENDING: + case CSD_CALL_STATUS_SWAP_INITIATED: + case CSD_CALL_STATUS_HOLD_INITIATED: + return CALL_STATUS_ACTIVE; + case CSD_CALL_STATUS_RETRIEVE_INITIATED: + case CSD_CALL_STATUS_HOLD: + return CALL_STATUS_HELD; + default: + return -1; + } +} + +void telephony_list_current_calls_req(void *telephony_device) +{ + GSList *l; + int i; + + DBG("telephony-maemo: list current calls request"); + + for (l = calls, i = 1; l != NULL; l = l->next, i++) { + struct csd_call *call = l->data; + int status, direction, multiparty; + + status = csd_status_to_hfp(call); + if (status < 0) + continue; + + direction = call->originating ? + CALL_DIR_OUTGOING : CALL_DIR_INCOMING; + + multiparty = call->conference ? + CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO; + + telephony_list_current_call_ind(i, direction, status, + CALL_MODE_VOICE, multiparty, + call->number, + number_type(call->number)); + } + + telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_operator_selection_req(void *telephony_device) +{ + telephony_operator_selection_ind(OPERATOR_MODE_AUTO, + net.operator_name ? net.operator_name : ""); + telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE); +} + +static void foreach_call_with_status(int status, + int (*func)(struct csd_call *call)) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == status) + func(call); + } +} + +void telephony_call_hold_req(void *telephony_device, const char *cmd) +{ + const char *idx; + struct csd_call *call; + int err = 0; + + DBG("telephony-maemo: got call hold request %s", cmd); + + if (strlen(cmd) > 1) + idx = &cmd[1]; + else + idx = NULL; + + if (idx) + call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1); + else + call = NULL; + + switch (cmd[0]) { + case '0': + foreach_call_with_status(CSD_CALL_STATUS_HOLD, release_call); + foreach_call_with_status(CSD_CALL_STATUS_WAITING, + release_call); + break; + case '1': + if (idx) { + if (call) + err = release_call(call); + break; + } + foreach_call_with_status(CSD_CALL_STATUS_ACTIVE, release_call); + call = find_call_with_status(CSD_CALL_STATUS_WAITING); + if (call) + err = answer_call(call); + break; + case '2': + if (idx) { + if (call) + err = split_call(call); + } else { + struct csd_call *held, *wait; + + call = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + held = find_call_with_status(CSD_CALL_STATUS_HOLD); + wait = find_call_with_status(CSD_CALL_STATUS_WAITING); + + if (wait) + err = answer_call(wait); + else if (call && held) + err = swap_calls(); + else { + if (call) + err = hold_call(call); + if (held) + err = unhold_call(held); + } + } + break; + case '3': + if (find_call_with_status(CSD_CALL_STATUS_HOLD) || + find_call_with_status(CSD_CALL_STATUS_WAITING)) + err = create_conference(); + break; + case '4': + err = call_transfer(); + break; + default: + DBG("Unknown call hold request"); + break; + } + + if (err) + telephony_call_hold_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-maemo: got %s NR and EC request", + enable ? "enable" : "disable"); + telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_key_press_req(void *telephony_device, const char *keys) +{ + struct csd_call *active, *waiting; + int err; + + DBG("telephony-maemo: got key press request for %s", keys); + + waiting = find_call_with_status(CSD_CALL_STATUS_COMING); + if (!waiting) + waiting = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + if (!waiting) + waiting = find_call_with_status(CSD_CALL_STATUS_PROCEEDING); + + active = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + + if (waiting) + err = answer_call(waiting); + else if (active) + err = release_call(active); + else + err = 0; + + if (err < 0) + telephony_key_press_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_key_press_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_voice_dial_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-maemo: got %s voice dial request", + enable ? "enable" : "disable"); + + telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED); +} + +static void handle_incoming_call(DBusMessage *msg) +{ + const char *number, *call_path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &call_path, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Call.Coming() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + DBG("Incoming call to %s from number %s", call_path, number); + + g_free(call->number); + call->number = g_strdup(number); + + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_INCOMING); + + if (find_call_with_status(CSD_CALL_STATUS_ACTIVE)) + telephony_call_waiting_ind(call->number, + number_type(call->number)); + else + telephony_incoming_call_ind(call->number, + number_type(call->number)); +} + +static void handle_outgoing_call(DBusMessage *msg) +{ + const char *number, *call_path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &call_path, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Call.Created() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + DBG("Outgoing call from %s to number %s", call_path, number); + + g_free(call->number); + call->number = g_strdup(number); + + g_free(last_dialed_number); + last_dialed_number = g_strdup(number); + + if (create_request_timer) { + g_source_remove(create_request_timer); + create_request_timer = 0; + } +} + +static gboolean create_timeout(gpointer user_data) +{ + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + create_request_timer = 0; + return FALSE; +} + +static void handle_create_requested(DBusMessage *msg) +{ + DBG("Call.CreateRequested()"); + + if (create_request_timer) + g_source_remove(create_request_timer); + + create_request_timer = g_timeout_add_seconds(5, create_timeout, NULL); + + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); +} + +static void handle_call_status(DBusMessage *msg, const char *call_path) +{ + struct csd_call *call; + dbus_uint32_t status, cause_type, cause; + int callheld = telephony_get_indicator(maemo_indicators, "callheld"); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &status, + DBUS_TYPE_UINT32, &cause_type, + DBUS_TYPE_UINT32, &cause, + DBUS_TYPE_INVALID)) { + error("Unexpected paramters in Instance.CallStatus() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + if (status > 16) { + error("Invalid call status %u", status); + return; + } + + DBG("Call %s changed from %s to %s", call_path, + call_status_str[call->status], call_status_str[status]); + + if (call->status == (int) status) { + DBG("Ignoring CSD Call state change to existing state"); + return; + } + + call->status = (int) status; + + switch (status) { + case CSD_CALL_STATUS_IDLE: + if (call->setup) { + telephony_update_indicator(maemo_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (!call->originating) + telephony_calling_stopped_ind(); + } + + g_free(call->number); + call->number = NULL; + call->originating = FALSE; + call->emergency = FALSE; + call->on_hold = FALSE; + call->conference = FALSE; + call->setup = FALSE; + break; + case CSD_CALL_STATUS_CREATE: + call->originating = TRUE; + call->setup = TRUE; + break; + case CSD_CALL_STATUS_COMING: + call->originating = FALSE; + call->setup = TRUE; + break; + case CSD_CALL_STATUS_PROCEEDING: + break; + case CSD_CALL_STATUS_MO_ALERTING: + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + break; + case CSD_CALL_STATUS_MT_ALERTING: + break; + case CSD_CALL_STATUS_WAITING: + break; + case CSD_CALL_STATUS_ANSWERED: + break; + case CSD_CALL_STATUS_ACTIVE: + if (call->on_hold) { + call->on_hold = FALSE; + if (find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + else + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_NONE); + } else { + if (!g_slist_find(active_calls, call)) + active_calls = g_slist_prepend(active_calls, call); + if (g_slist_length(active_calls) == 1) + telephony_update_indicator(maemo_indicators, + "call", + EV_CALL_ACTIVE); + /* Upgrade callheld status if necessary */ + if (callheld == EV_CALLHELD_ON_HOLD) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + telephony_update_indicator(maemo_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (!call->originating) + telephony_calling_stopped_ind(); + call->setup = FALSE; + } + break; + case CSD_CALL_STATUS_MO_RELEASE: + case CSD_CALL_STATUS_MT_RELEASE: + active_calls = g_slist_remove(active_calls, call); + if (g_slist_length(active_calls) == 0) + telephony_update_indicator(maemo_indicators, "call", + EV_CALL_INACTIVE); + break; + case CSD_CALL_STATUS_HOLD_INITIATED: + break; + case CSD_CALL_STATUS_HOLD: + call->on_hold = TRUE; + if (find_non_held_call()) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + else + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_ON_HOLD); + break; + case CSD_CALL_STATUS_RETRIEVE_INITIATED: + break; + case CSD_CALL_STATUS_RECONNECT_PENDING: + break; + case CSD_CALL_STATUS_TERMINATED: + if (call->on_hold && + !find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_NONE); + else if (callheld == EV_CALLHELD_MULTIPLE && + find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_ON_HOLD); + break; + case CSD_CALL_STATUS_SWAP_INITIATED: + break; + default: + error("Unknown call status %u", status); + break; + } +} + +static void handle_conference(DBusMessage *msg, gboolean joined) +{ + const char *path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Conference.%s", + dbus_message_get_member(msg)); + return; + } + + call = find_call(path); + if (!call) { + error("Conference signal for unknown call %s", path); + return; + } + + DBG("Call %s %s the conference", path, joined ? "joined" : "left"); + + call->conference = joined; +} + +static void get_operator_name_reply(DBusPendingCall *pending_call, + void *user_data) +{ + DBusMessage *reply; + DBusError err; + const char *name; + dbus_int32_t net_err; + + reply = dbus_pending_call_steal_reply(pending_call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("get_operator_name failed: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(reply, &err, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INT32, &net_err, + DBUS_TYPE_INVALID)) { + error("Unexpected get_operator_name reply parameters: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + if (net_err != 0) { + error("get_operator_name failed with code %d", net_err); + goto done; + } + + if (strlen(name) == 0) + goto done; + + g_free(net.operator_name); + net.operator_name = g_strdup(name); + + DBG("telephony-maemo: operator name updated: %s", name); + +done: + dbus_message_unref(reply); +} + +static void resolve_operator_name(uint32_t operator, uint32_t country) +{ + uint8_t name_type = NETWORK_HARDCODED_LATIN_OPER_NAME; + + send_method_call(NETWORK_BUS_NAME, NETWORK_PATH, + NETWORK_INTERFACE, "get_operator_name", + get_operator_name_reply, NULL, + DBUS_TYPE_BYTE, &name_type, + DBUS_TYPE_UINT32, &operator, + DBUS_TYPE_UINT32, &country, + DBUS_TYPE_INVALID); +} + +static void update_registration_status(uint8_t status, uint16_t lac, + uint32_t cell_id, + uint32_t operator_code, + uint32_t country_code, + uint8_t network_type, + uint8_t supported_services) +{ + if (net.status != status) { + switch (status) { + case NETWORK_REG_STATUS_HOME: + telephony_update_indicator(maemo_indicators, "roam", + EV_ROAM_INACTIVE); + if (net.status >= NETWORK_REG_STATUS_NOSERV) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_PRESENT); + break; + case NETWORK_REG_STATUS_ROAM: + case NETWORK_REG_STATUS_ROAM_BLINK: + telephony_update_indicator(maemo_indicators, "roam", + EV_ROAM_ACTIVE); + if (net.status >= NETWORK_REG_STATUS_NOSERV) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_PRESENT); + break; + case NETWORK_REG_STATUS_NOSERV: + case NETWORK_REG_STATUS_NOSERV_SEARCHING: + case NETWORK_REG_STATUS_NOSERV_NOTSEARCHING: + case NETWORK_REG_STATUS_NOSERV_NOSIM: + case NETWORK_REG_STATUS_POWER_OFF: + case NETWORK_REG_STATUS_NSPS: + case NETWORK_REG_STATUS_NSPS_NO_COVERAGE: + case NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW: + if (net.status < NETWORK_REG_STATUS_NOSERV) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_NONE); + break; + } + + net.status = status; + } + + net.lac = lac; + net.cell_id = cell_id; + + if (net.operator_code != operator_code || + net.country_code != country_code) { + g_free(net.operator_name); + net.operator_name = NULL; + resolve_operator_name(operator_code, country_code); + net.operator_code = operator_code; + net.country_code = country_code; + } + + net.network_type = network_type; + net.supported_services = supported_services; +} + +static void handle_registration_status_change(DBusMessage *msg) +{ + uint8_t status; + dbus_uint16_t lac, network_type, supported_services; + dbus_uint32_t cell_id, operator_code, country_code; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_BYTE, &status, + DBUS_TYPE_UINT16, &lac, + DBUS_TYPE_UINT32, &cell_id, + DBUS_TYPE_UINT32, &operator_code, + DBUS_TYPE_UINT32, &country_code, + DBUS_TYPE_BYTE, &network_type, + DBUS_TYPE_BYTE, &supported_services, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in registration_status_change"); + return; + } + + update_registration_status(status, lac, cell_id, operator_code, + country_code, network_type, + supported_services); +} + +static void update_signal_strength(uint8_t signals_bar) +{ + int signal; + + if (signals_bar > 100) { + DBG("signals_bar greater than expected: %u", signals_bar); + signals_bar = 100; + } + + if (net.signals_bar == signals_bar) + return; + + /* A simple conversion from 0-100 to 0-5 (used by HFP) */ + signal = (signals_bar + 20) / 21; + + telephony_update_indicator(maemo_indicators, "signal", signal); + + net.signals_bar = signals_bar; + + DBG("Signal strength updated: %u/100, %d/5", signals_bar, signal); +} + +static void handle_signal_strength_change(DBusMessage *msg) +{ + uint8_t signals_bar, rssi_in_dbm; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_BYTE, &signals_bar, + DBUS_TYPE_BYTE, &rssi_in_dbm, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in signal_strength_change"); + return; + } + + update_signal_strength(signals_bar); +} + +static gboolean iter_get_basic_args(DBusMessageIter *iter, + int first_arg_type, ...) +{ + int type; + va_list ap; + + va_start(ap, first_arg_type); + + for (type = first_arg_type; type != DBUS_TYPE_INVALID; + type = va_arg(ap, int)) { + void *value = va_arg(ap, void *); + int real_type = dbus_message_iter_get_arg_type(iter); + + if (real_type != type) { + error("iter_get_basic_args: expected %c but got %c", + (char) type, (char) real_type); + break; + } + + dbus_message_iter_get_basic(iter, value); + dbus_message_iter_next(iter); + } + + va_end(ap); + + return type == DBUS_TYPE_INVALID ? TRUE : FALSE; +} + +static void hal_battery_level_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + dbus_int32_t level; + int *value = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (dbus_message_get_args(reply, &err, + DBUS_TYPE_INT32, &level, + DBUS_TYPE_INVALID) == FALSE) { + error("Unable to parse GetPropertyInteger reply: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + *value = (int) level; + + if (value == &battchg_last) + DBG("telephony-maemo: battery.charge_level.last_full is %d", + *value); + else if (value == &battchg_design) + DBG("telephony-maemo: battery.charge_level.design is %d", + *value); + else + DBG("telephony-maemo: battery.charge_level.current is %d", + *value); + + if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) { + int new, max; + + if (battchg_last > 0) + max = battchg_last; + else + max = battchg_design; + + new = battchg_cur * 5 / max; + + telephony_update_indicator(maemo_indicators, "battchg", new); + } +done: + dbus_message_unref(reply); +} + +static void hal_get_integer(const char *path, const char *key, void *user_data) +{ + send_method_call("org.freedesktop.Hal", path, + "org.freedesktop.Hal.Device", + "GetPropertyInteger", + hal_battery_level_reply, user_data, + DBUS_TYPE_STRING, &key, + DBUS_TYPE_INVALID); +} + +static void handle_hal_property_modified(DBusMessage *msg) +{ + DBusMessageIter iter, array; + dbus_int32_t num_changes; + const char *path; + + path = dbus_message_get_path(msg); + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_get_basic(&iter, &num_changes); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) { + DBusMessageIter prop; + const char *name; + dbus_bool_t added, removed; + + dbus_message_iter_recurse(&array, &prop); + + if (!iter_get_basic_args(&prop, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &added, + DBUS_TYPE_BOOLEAN, &removed, + DBUS_TYPE_INVALID)) { + error("Invalid hal PropertyModified parameters"); + break; + } + + if (g_str_equal(name, "battery.charge_level.last_full")) + hal_get_integer(path, name, &battchg_last); + else if (g_str_equal(name, "battery.charge_level.current")) + hal_get_integer(path, name, &battchg_cur); + else if (g_str_equal(name, "battery.charge_level.design")) + hal_get_integer(path, name, &battchg_design); + + dbus_message_iter_next(&array); + } +} + +static void csd_call_free(struct csd_call *call) +{ + if (!call) + return; + + g_free(call->object_path); + g_free(call->number); + + g_free(call); +} + +static void parse_call_list(DBusMessageIter *iter) +{ + do { + DBusMessageIter call_iter; + struct csd_call *call; + const char *object_path, *number; + dbus_uint32_t status; + dbus_bool_t originating, terminating, emerg, on_hold, conf; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) { + error("Unexpected signature in GetCallInfoAll reply"); + break; + } + + dbus_message_iter_recurse(iter, &call_iter); + + if (!iter_get_basic_args(&call_iter, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_UINT32, &status, + DBUS_TYPE_BOOLEAN, &originating, + DBUS_TYPE_BOOLEAN, &terminating, + DBUS_TYPE_BOOLEAN, &emerg, + DBUS_TYPE_BOOLEAN, &on_hold, + DBUS_TYPE_BOOLEAN, &conf, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Parsing call D-Bus parameters failed"); + break; + } + + call = find_call(object_path); + if (!call) { + call = g_new0(struct csd_call, 1); + call->object_path = g_strdup(object_path); + call->status = (int) status; + calls = g_slist_append(calls, call); + DBG("telephony-maemo: new csd call instance at %s", + object_path); + } + + if (call->status == CSD_CALL_STATUS_IDLE) + continue; + + /* CSD gives incorrect call_hold property sometimes */ + if ((call->status != CSD_CALL_STATUS_HOLD && on_hold) || + (call->status == CSD_CALL_STATUS_HOLD && + !on_hold)) { + error("Conflicting call status and on_hold property!"); + on_hold = call->status == CSD_CALL_STATUS_HOLD; + } + + call->originating = originating; + call->on_hold = on_hold; + call->conference = conf; + g_free(call->number); + call->number = g_strdup(number); + + } while (dbus_message_iter_next(iter)); +} + +static void signal_strength_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + uint8_t signals_bar, rssi_in_dbm; + dbus_int32_t net_err; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("Unable to get signal strength: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(reply, &err, + DBUS_TYPE_BYTE, &signals_bar, + DBUS_TYPE_BYTE, &rssi_in_dbm, + DBUS_TYPE_INT32, &net_err, + DBUS_TYPE_INVALID)) { + error("Unable to parse signal_strength reply: %s, %s", + err.name, err.message); + dbus_error_free(&err); + return; + } + + if (net_err != 0) { + error("get_signal_strength failed with code %d", net_err); + return; + } + + update_signal_strength(signals_bar); + +done: + dbus_message_unref(reply); +} + +static int get_signal_strength(void) +{ + return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH, + NETWORK_INTERFACE, "get_signal_strength", + signal_strength_reply, NULL, + DBUS_TYPE_INVALID); +} + +static void registration_status_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + uint8_t status; + dbus_uint16_t lac, network_type, supported_services; + dbus_uint32_t cell_id, operator_code, country_code; + dbus_int32_t net_err; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("Unable to get registration status: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(reply, &err, + DBUS_TYPE_BYTE, &status, + DBUS_TYPE_UINT16, &lac, + DBUS_TYPE_UINT32, &cell_id, + DBUS_TYPE_UINT32, &operator_code, + DBUS_TYPE_UINT32, &country_code, + DBUS_TYPE_BYTE, &network_type, + DBUS_TYPE_BYTE, &supported_services, + DBUS_TYPE_INT32, &net_err, + DBUS_TYPE_INVALID)) { + error("Unable to parse registration_status_change reply:" + " %s, %s", err.name, err.message); + dbus_error_free(&err); + return; + } + + if (net_err != 0) { + error("get_registration_status failed with code %d", net_err); + return; + } + + update_registration_status(status, lac, cell_id, operator_code, + country_code, network_type, + supported_services); + + get_signal_strength(); + +done: + dbus_message_unref(reply); +} + +static int get_registration_status(void) +{ + return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH, + NETWORK_INTERFACE, "get_registration_status", + registration_status_reply, NULL, + DBUS_TYPE_INVALID); +} + +static void call_info_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub;; + + get_calls_active = FALSE; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("csd replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in GetCallInfoAll return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + parse_call_list(&sub); + + get_registration_status(); + +done: + dbus_message_unref(reply); +} + +static void hal_find_device_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub; + const char *path; + char match_string[256]; + int type; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in FindDeviceByCapability return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + type = dbus_message_iter_get_arg_type(&sub); + + if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) { + error("No hal device with battery capability found"); + goto done; + } + + dbus_message_iter_get_basic(&sub, &path); + + DBG("telephony-maemo: found battery device at %s", path); + + snprintf(match_string, sizeof(match_string), + "type='signal'," + "path='%s'," + "interface='org.freedesktop.Hal.Device'," + "member='PropertyModified'", path); + dbus_bus_add_match(connection, match_string, NULL); + + hal_get_integer(path, "battery.charge_level.last_full", &battchg_last); + hal_get_integer(path, "battery.charge_level.current", &battchg_cur); + hal_get_integer(path, "battery.charge_level.design", &battchg_design); + +done: + dbus_message_unref(reply); +} + +static void phonebook_read_reply(DBusPendingCall *call, void *user_data) +{ + DBusError derr; + DBusMessage *reply; + const char *name, *number; + char **number_type = user_data; + dbus_int32_t current_location, err; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&derr); + if (dbus_set_error_from_message(&derr, reply)) { + error("SIM.Phonebook replied with an error: %s, %s", + derr.name, derr.message); + dbus_error_free(&derr); + goto done; + } + + dbus_error_init(&derr); + if (dbus_message_get_args(reply, &derr, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INT32, ¤t_location, + DBUS_TYPE_INT32, &err, + DBUS_TYPE_INVALID) == FALSE) { + error("Unable to parse SIM.Phonebook.read arguments: %s, %s", + derr.name, derr.message); + dbus_error_free(&derr); + goto done; + } + + if (err != 0) { + error("SIM.Phonebook.read failed with error %d", err); + if (number_type == &vmbx) + vmbx = g_strdup(getenv("VMBX_NUMBER")); + goto done; + } + + if (number_type == &msisdn) { + g_free(msisdn); + msisdn = g_strdup(number); + DBG("Got MSISDN %s (%s)", number, name); + } else { + g_free(vmbx); + vmbx = g_strdup(number); + DBG("Got voice mailbox number %s (%s)", number, name); + } + +done: + dbus_message_unref(reply); +} + +static void csd_init(void) +{ + dbus_uint32_t location; + uint8_t pb_type, location_type; + int ret; + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "GetCallInfoAll", + call_info_reply, NULL, DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to sent GetCallInfoAll method call"); + return; + } + + get_calls_active = TRUE; + + pb_type = SIM_PHONEBOOK_TYPE_MSISDN; + location = PHONEBOOK_INDEX_FIRST_ENTRY; + location_type = SIM_PHONEBOOK_LOCATION_NEXT; + + ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH, + SIM_PHONEBOOK_INTERFACE, "read", + phonebook_read_reply, &msisdn, + DBUS_TYPE_BYTE, &pb_type, + DBUS_TYPE_INT32, &location, + DBUS_TYPE_BYTE, &location_type, + DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()"); + return; + } + + pb_type = SIM_PHONEBOOK_TYPE_MBDN; + location = PHONEBOOK_INDEX_FIRST_ENTRY; + location_type = SIM_PHONEBOOK_LOCATION_NEXT; + + ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH, + SIM_PHONEBOOK_INTERFACE, "read", + phonebook_read_reply, &vmbx, + DBUS_TYPE_BYTE, &pb_type, + DBUS_TYPE_INT32, &location, + DBUS_TYPE_BYTE, &location_type, + DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()"); + return; + } +} + +static uint32_t get_callflag(const char *callerid_setting) +{ + if (callerid_setting != NULL) { + if (g_str_equal(callerid_setting, "allowed")) + return CALL_FLAG_PRESENTATION_ALLOWED; + else if (g_str_equal(callerid_setting, "restricted")) + return CALL_FLAG_PRESENTATION_RESTRICTED; + else + return CALL_FLAG_NONE; + } else + return CALL_FLAG_NONE; +} + +static void generate_flag_file(const char *filename) +{ + int fd; + + if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS) || + g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS) || + g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS)) + return; + + fd = open(filename, O_WRONLY | O_CREAT, 0); + if (fd >= 0) + close(fd); +} + +static void save_callerid_to_file(const char *callerid_setting) +{ + char callerid_file[FILENAME_MAX]; + + snprintf(callerid_file, sizeof(callerid_file), "%s%s", + CALLERID_BASE, callerid_setting); + + if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS)) + rename(ALLOWED_FLAG_FILE, callerid_file); + else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS)) + rename(RESTRICTED_FLAG_FILE, callerid_file); + else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS)) + rename(NONE_FLAG_FILE, callerid_file); + else + generate_flag_file(callerid_file); +} + +static uint32_t callerid_from_file(void) +{ + if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS)) + return CALL_FLAG_PRESENTATION_ALLOWED; + else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS)) + return CALL_FLAG_PRESENTATION_RESTRICTED; + else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS)) + return CALL_FLAG_NONE; + else + return CALL_FLAG_NONE; +} + +static DBusMessage *set_callerid(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *callerid_setting; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, + &callerid_setting, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + if (g_str_equal(callerid_setting, "allowed") || + g_str_equal(callerid_setting, "restricted") || + g_str_equal(callerid_setting, "none")) { + save_callerid_to_file(callerid_setting); + callerid = get_callflag(callerid_setting); + DBG("telephony-maemo setting callerid flag: %s", + callerid_setting); + return dbus_message_new_method_return(msg); + } + + error("telephony-maemo: invalid argument %s for method call" + " SetCallerId", callerid_setting); + return btd_error_invalid_args(msg); +} + +static GDBusMethodTable telephony_maemo_methods[] = { + {"SetCallerId", "s", "", set_callerid, + G_DBUS_METHOD_FLAG_ASYNC}, + { } +}; + +static void handle_modem_state(DBusMessage *msg) +{ + const char *state; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &state, + DBUS_TYPE_INVALID)) { + error("Unexpected modem state parameters"); + return; + } + + DBG("SSC modem state: %s", state); + + if (calls != NULL || get_calls_active) + return; + + if (g_str_equal(state, "cmt_ready") || g_str_equal(state, "online")) + csd_init(); +} + +static void modem_state_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError err; + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("get_modem_status: %s, %s", err.name, err.message); + dbus_error_free(&err); + } else + handle_modem_state(reply); + + dbus_message_unref(reply); +} + +static DBusHandlerResult signal_filter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path = dbus_message_get_path(msg); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Coming")) + handle_incoming_call(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Created")) + handle_outgoing_call(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, + "CreateRequested")) + handle_create_requested(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INSTANCE, "CallStatus")) + handle_call_status(msg, path); + else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Joined")) + handle_conference(msg, TRUE); + else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Left")) + handle_conference(msg, FALSE); + else if (dbus_message_is_signal(msg, NETWORK_INTERFACE, + "registration_status_change")) + handle_registration_status_change(msg); + else if (dbus_message_is_signal(msg, NETWORK_INTERFACE, + "signal_strength_change")) + handle_signal_strength_change(msg); + else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device", + "PropertyModified")) + handle_hal_property_modified(msg); + else if (dbus_message_is_signal(msg, SSC_DBUS_IFACE, + "modem_state_changed_ind")) + handle_modem_state(msg); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int telephony_init(void) +{ + const char *battery_cap = "battery"; + uint32_t features = AG_FEATURE_EC_ANDOR_NR | + AG_FEATURE_INBAND_RINGTONE | + AG_FEATURE_REJECT_A_CALL | + AG_FEATURE_ENHANCED_CALL_STATUS | + AG_FEATURE_ENHANCED_CALL_CONTROL | + AG_FEATURE_EXTENDED_ERROR_RESULT_CODES | + AG_FEATURE_THREE_WAY_CALLING; + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + + if (!dbus_connection_add_filter(connection, signal_filter, + NULL, NULL)) + error("Can't add signal filter"); + + dbus_bus_add_match(connection, + "type=signal,interface=" CSD_CALL_INTERFACE, NULL); + dbus_bus_add_match(connection, + "type=signal,interface=" CSD_CALL_INSTANCE, NULL); + dbus_bus_add_match(connection, + "type=signal,interface=" CSD_CALL_CONFERENCE, NULL); + dbus_bus_add_match(connection, + "type=signal,interface=" NETWORK_INTERFACE, NULL); + dbus_bus_add_match(connection, + "type=signal,interface=" SSC_DBUS_IFACE + ",member=modem_state_changed_ind", NULL); + + if (send_method_call(SSC_DBUS_NAME, SSC_DBUS_PATH, SSC_DBUS_IFACE, + "get_modem_state", modem_state_reply, + NULL, DBUS_TYPE_INVALID) < 0) + error("Unable to send " SSC_DBUS_IFACE ".get_modem_state()"); + + generate_flag_file(NONE_FLAG_FILE); + callerid = callerid_from_file(); + + if (!g_dbus_register_interface(connection, TELEPHONY_MAEMO_PATH, + TELEPHONY_MAEMO_INTERFACE, telephony_maemo_methods, + NULL, NULL, NULL, NULL)) { + error("telephony-maemo interface %s init failed on path %s", + TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH); + } + + DBG("telephony-maemo registering %s interface on path %s", + TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH); + + telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED, + chld_str); + if (send_method_call("org.freedesktop.Hal", + "/org/freedesktop/Hal/Manager", + "org.freedesktop.Hal.Manager", + "FindDeviceByCapability", + hal_find_device_reply, NULL, + DBUS_TYPE_STRING, &battery_cap, + DBUS_TYPE_INVALID) < 0) + error("Unable to send HAL method call"); + + return 0; +} + +void telephony_exit(void) +{ + g_slist_foreach(calls, (GFunc) csd_call_free, NULL); + g_slist_free(calls); + calls = NULL; + + dbus_connection_remove_filter(connection, signal_filter, NULL); + + dbus_connection_unref(connection); + connection = NULL; + + telephony_deinit(); +} diff --git a/audio/telephony-maemo6.c b/audio/telephony-maemo6.c new file mode 100644 index 0000000..0cef7dd --- /dev/null +++ b/audio/telephony-maemo6.c @@ -0,0 +1,1993 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "telephony.h" +#include "error.h" + +/* SSC D-Bus definitions */ +#define SSC_DBUS_NAME "com.nokia.phone.SSC" +#define SSC_DBUS_IFACE "com.nokia.phone.SSC" +#define SSC_DBUS_PATH "/com/nokia/phone/SSC" + +/* libcsnet D-Bus definitions */ +#define CSD_CSNET_BUS_NAME "com.nokia.csd.CSNet" +#define CSD_CSNET_PATH "/com/nokia/csd/csnet" +#define CSD_CSNET_IFACE "com.nokia.csd.CSNet" +#define CSD_CSNET_REGISTRATION "com.nokia.csd.CSNet.NetworkRegistration" +#define CSD_CSNET_OPERATOR "com.nokia.csd.CSNet.NetworkOperator" +#define CSD_CSNET_SIGNAL "com.nokia.csd.CSNet.SignalStrength" + +enum net_registration_status { + NETWORK_REG_STATUS_HOME, + NETWORK_REG_STATUS_ROAMING, + NETWORK_REG_STATUS_OFFLINE, + NETWORK_REG_STATUS_SEARCHING, + NETWORK_REG_STATUS_NO_SIM, + NETWORK_REG_STATUS_POWEROFF, + NETWORK_REG_STATUS_POWERSAFE, + NETWORK_REG_STATUS_NO_COVERAGE, + NETWORK_REG_STATUS_REJECTED, + NETWORK_REG_STATUS_UNKOWN +}; + +/* CSD CALL plugin D-Bus definitions */ +#define CSD_CALL_BUS_NAME "com.nokia.csd.Call" +#define CSD_CALL_INTERFACE "com.nokia.csd.Call" +#define CSD_CALL_INSTANCE "com.nokia.csd.Call.Instance" +#define CSD_CALL_CONFERENCE "com.nokia.csd.Call.Conference" +#define CSD_CALL_PATH "/com/nokia/csd/call" +#define CSD_CALL_CONFERENCE_PATH "/com/nokia/csd/call/conference" + +/* Call status values as exported by the CSD CALL plugin */ +#define CSD_CALL_STATUS_IDLE 0 +#define CSD_CALL_STATUS_CREATE 1 +#define CSD_CALL_STATUS_COMING 2 +#define CSD_CALL_STATUS_PROCEEDING 3 +#define CSD_CALL_STATUS_MO_ALERTING 4 +#define CSD_CALL_STATUS_MT_ALERTING 5 +#define CSD_CALL_STATUS_WAITING 6 +#define CSD_CALL_STATUS_ANSWERED 7 +#define CSD_CALL_STATUS_ACTIVE 8 +#define CSD_CALL_STATUS_MO_RELEASE 9 +#define CSD_CALL_STATUS_MT_RELEASE 10 +#define CSD_CALL_STATUS_HOLD_INITIATED 11 +#define CSD_CALL_STATUS_HOLD 12 +#define CSD_CALL_STATUS_RETRIEVE_INITIATED 13 +#define CSD_CALL_STATUS_RECONNECT_PENDING 14 +#define CSD_CALL_STATUS_TERMINATED 15 +#define CSD_CALL_STATUS_SWAP_INITIATED 16 + +#define CALL_FLAG_NONE 0 +#define CALL_FLAG_PRESENTATION_ALLOWED 0x01 +#define CALL_FLAG_PRESENTATION_RESTRICTED 0x02 + +/* SIM Phonebook D-Bus definitions */ +#define CSD_SIMPB_BUS_NAME "com.nokia.csd.SIM" +#define CSD_SIMPB_INTERFACE "com.nokia.csd.SIM.Phonebook" +#define CSD_SIMPB_PATH "/com/nokia/csd/sim/phonebook" + +#define CSD_SIMPB_TYPE_ADN "ADN" +#define CSD_SIMPB_TYPE_FDN "FDN" +#define CSD_SIMPB_TYPE_SDN "SDN" +#define CSD_SIMPB_TYPE_VMBX "VMBX" +#define CSD_SIMPB_TYPE_MBDN "MBDN" +#define CSD_SIMPB_TYPE_EN "EN" +#define CSD_SIMPB_TYPE_MSISDN "MSISDN" + +struct csd_call { + char *object_path; + int status; + gboolean originating; + gboolean emergency; + gboolean on_hold; + gboolean conference; + char *number; + gboolean setup; +}; + +static struct { + char *operator_name; + uint8_t status; + int32_t signal_bars; +} net = { + .operator_name = NULL, + .status = NETWORK_REG_STATUS_UNKOWN, + /* Init as 0 meaning inactive mode. In modem power off state + * can be be -1, but we treat all values as 0s regardless + * inactive or power off. */ + .signal_bars = 0, +}; + +struct pending_req { + DBusPendingCall *call; + void *user_data; +}; + +static int get_property(const char *iface, const char *prop); + +static DBusConnection *connection = NULL; + +static GSList *calls = NULL; +static GSList *watches = NULL; +static GSList *pending = NULL; + +/* Reference count for determining the call indicator status */ +static GSList *active_calls = NULL; + +static char *msisdn = NULL; /* Subscriber number */ +static char *vmbx = NULL; /* Voice mailbox number */ + +/* HAL battery namespace key values */ +static int battchg_cur = -1; /* "battery.charge_level.current" */ +static int battchg_last = -1; /* "battery.charge_level.last_full" */ +static int battchg_design = -1; /* "battery.charge_level.design" */ + +static gboolean get_calls_active = FALSE; + +static gboolean events_enabled = FALSE; + +/* Supported set of call hold operations */ +static const char *chld_str = "0,1,1x,2,2x,3,4"; + +/* Timer for tracking call creation requests */ +static guint create_request_timer = 0; + +static struct indicator maemo_indicators[] = +{ + { "battchg", "0-5", 5, TRUE }, + /* signal strength in terms of bars */ + { "signal", "0-5", 0, TRUE }, + { "service", "0,1", 0, TRUE }, + { "call", "0,1", 0, TRUE }, + { "callsetup", "0-3", 0, TRUE }, + { "callheld", "0-2", 0, FALSE }, + { "roam", "0,1", 0, TRUE }, + { NULL } +}; + +static char *call_status_str[] = { + "IDLE", + "CREATE", + "COMING", + "PROCEEDING", + "MO_ALERTING", + "MT_ALERTING", + "WAITING", + "ANSWERED", + "ACTIVE", + "MO_RELEASE", + "MT_RELEASE", + "HOLD_INITIATED", + "HOLD", + "RETRIEVE_INITIATED", + "RECONNECT_PENDING", + "TERMINATED", + "SWAP_INITIATED", + "???" +}; + +static struct csd_call *find_call(const char *path) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (g_str_equal(call->object_path, path)) + return call; + } + + return NULL; +} + +static struct csd_call *find_non_held_call(void) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == CSD_CALL_STATUS_IDLE) + continue; + + if (call->status != CSD_CALL_STATUS_HOLD) + return call; + } + + return NULL; +} + +static struct csd_call *find_non_idle_call(void) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status != CSD_CALL_STATUS_IDLE) + return call; + } + + return NULL; +} + +static struct csd_call *find_call_with_status(int status) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == status) + return call; + } + + return NULL; +} + +static int release_conference(void) +{ + DBusMessage *msg; + + DBG("telephony-maemo6: releasing conference call"); + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + CSD_CALL_CONFERENCE_PATH, + CSD_CALL_INSTANCE, + "Release"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int release_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Release"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int answer_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Answer"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int split_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Split"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int unhold_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Unhold"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int hold_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Hold"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int swap_calls(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Swap"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int create_conference(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Conference"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int call_transfer(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Transfer"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int number_type(const char *number) +{ + if (number == NULL) + return NUMBER_TYPE_TELEPHONY; + + if (number[0] == '+' || strncmp(number, "00", 2) == 0) + return NUMBER_TYPE_INTERNATIONAL; + + return NUMBER_TYPE_TELEPHONY; +} + +void telephony_device_connected(void *telephony_device) +{ + struct csd_call *coming; + + DBG("telephony-maemo6: device %p connected", telephony_device); + + coming = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + if (coming) { + if (find_call_with_status(CSD_CALL_STATUS_ACTIVE)) + telephony_call_waiting_ind(coming->number, + number_type(coming->number)); + else + telephony_incoming_call_ind(coming->number, + number_type(coming->number)); + } +} + +static void pending_req_finalize(struct pending_req *req) +{ + if (!dbus_pending_call_get_completed(req->call)) + dbus_pending_call_cancel(req->call); + + dbus_pending_call_unref(req->call); + g_free(req); +} + +static void remove_pending_by_data(gpointer data, gpointer user_data) +{ + struct pending_req *req = data; + + if (req->user_data == user_data) { + pending = g_slist_remove(pending, req); + pending_req_finalize(req); + } +} + +void telephony_device_disconnected(void *telephony_device) +{ + DBG("telephony-maemo6: device %p disconnected", telephony_device); + events_enabled = FALSE; + + g_slist_foreach(pending, remove_pending_by_data, telephony_device); +} + +void telephony_event_reporting_req(void *telephony_device, int ind) +{ + events_enabled = ind == 1 ? TRUE : FALSE; + + telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_response_and_hold_req(void *telephony_device, int rh) +{ + telephony_response_and_hold_rsp(telephony_device, + CME_ERROR_NOT_SUPPORTED); +} + +void telephony_terminate_call_req(void *telephony_device) +{ + struct csd_call *call; + struct csd_call *alerting; + int err; + + call = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + if (!call) + call = find_non_idle_call(); + + if (!call) { + error("No active call"); + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + alerting = find_call_with_status(CSD_CALL_STATUS_MO_ALERTING); + if (call->on_hold && alerting) + err = release_call(alerting); + else if (call->conference) + err = release_conference(); + else + err = release_call(call); + + if (err < 0) + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_answer_call_req(void *telephony_device) +{ + struct csd_call *call; + + call = find_call_with_status(CSD_CALL_STATUS_COMING); + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_PROCEEDING); + + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_WAITING); + + if (!call) { + telephony_answer_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + if (answer_call(call) < 0) + telephony_answer_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE); +} + +static int send_method_call(const char *dest, const char *path, + const char *interface, const char *method, + DBusPendingCallNotifyFunction cb, + void *user_data, int type, ...) +{ + DBusMessage *msg; + DBusPendingCall *call; + va_list args; + struct pending_req *req; + + msg = dbus_message_new_method_call(dest, path, interface, method); + if (!msg) { + error("Unable to allocate new D-Bus %s message", method); + return -ENOMEM; + } + + va_start(args, type); + + if (!dbus_message_append_args_valist(msg, type, args)) { + dbus_message_unref(msg); + va_end(args); + return -EIO; + } + + va_end(args); + + if (!cb) { + g_dbus_send_message(connection, msg); + return 0; + } + + if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) { + error("Sending %s failed", method); + dbus_message_unref(msg); + return -EIO; + } + + dbus_pending_call_set_notify(call, cb, user_data, NULL); + + req = g_new0(struct pending_req, 1); + req->call = call; + req->user_data = user_data; + + pending = g_slist_prepend(pending, req); + dbus_message_unref(msg); + + return 0; +} + +static struct pending_req *find_request(const DBusPendingCall *call) +{ + GSList *l; + + for (l = pending; l; l = l->next) { + struct pending_req *req = l->data; + + if (req->call == call) + return req; + } + + return NULL; +} + +static void remove_pending(DBusPendingCall *call) +{ + struct pending_req *req = find_request(call); + + pending = g_slist_remove(pending, req); + pending_req_finalize(req); +} + +static void last_number_call_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + void *telephony_device = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("csd replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + } else + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); + + dbus_message_unref(reply); + remove_pending(call); +} + +void telephony_last_dialed_number_req(void *telephony_device) +{ + int ret; + + DBG("telephony-maemo6: last dialed number request"); + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "CreateFromLast", + last_number_call_reply, telephony_device, + DBUS_TYPE_INVALID); + if (ret < 0) + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); +} + +static const char *memory_dial_lookup(int location) +{ + if (location == 1) + return vmbx; + else + return NULL; +} + +void telephony_dial_number_req(void *telephony_device, const char *number) +{ + int ret; + + DBG("telephony-maemo6: dial request to %s", number); + + if (strncmp(number, "*31#", 4) == 0) + number += 4; + else if (strncmp(number, "#31#", 4) == 0) + number += 4; + else if (number[0] == '>') { + const char *location = &number[1]; + + number = memory_dial_lookup(strtol(&number[1], NULL, 0)); + if (!number) { + error("No number at memory location %s", location); + telephony_dial_number_rsp(telephony_device, + CME_ERROR_INVALID_INDEX); + return; + } + } + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "Create", + NULL, NULL, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID); + if (ret < 0) { + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_transmit_dtmf_req(void *telephony_device, char tone) +{ + int ret; + char buf[2] = { tone, '\0' }, *buf_ptr = buf; + + DBG("telephony-maemo6: transmit dtmf: %s", buf); + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "SendDTMF", + NULL, NULL, + DBUS_TYPE_STRING, &buf_ptr, + DBUS_TYPE_INVALID); + if (ret < 0) { + telephony_transmit_dtmf_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_subscriber_number_req(void *telephony_device) +{ + DBG("telephony-maemo6: subscriber number request"); + if (msisdn) + telephony_subscriber_number_ind(msisdn, + number_type(msisdn), + SUBSCRIBER_SERVICE_VOICE); + telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE); +} + +static int csd_status_to_hfp(struct csd_call *call) +{ + switch (call->status) { + case CSD_CALL_STATUS_IDLE: + case CSD_CALL_STATUS_MO_RELEASE: + case CSD_CALL_STATUS_MT_RELEASE: + case CSD_CALL_STATUS_TERMINATED: + return -1; + case CSD_CALL_STATUS_CREATE: + return CALL_STATUS_DIALING; + case CSD_CALL_STATUS_WAITING: + return CALL_STATUS_WAITING; + case CSD_CALL_STATUS_PROCEEDING: + /* PROCEEDING can happen in outgoing/incoming */ + if (call->originating) + return CALL_STATUS_DIALING; + else + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_COMING: + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_MO_ALERTING: + return CALL_STATUS_ALERTING; + case CSD_CALL_STATUS_MT_ALERTING: + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_ANSWERED: + case CSD_CALL_STATUS_ACTIVE: + case CSD_CALL_STATUS_RECONNECT_PENDING: + case CSD_CALL_STATUS_SWAP_INITIATED: + case CSD_CALL_STATUS_HOLD_INITIATED: + return CALL_STATUS_ACTIVE; + case CSD_CALL_STATUS_RETRIEVE_INITIATED: + case CSD_CALL_STATUS_HOLD: + return CALL_STATUS_HELD; + default: + return -1; + } +} + +void telephony_list_current_calls_req(void *telephony_device) +{ + GSList *l; + int i; + + DBG("telephony-maemo6: list current calls request"); + + for (l = calls, i = 1; l != NULL; l = l->next, i++) { + struct csd_call *call = l->data; + int status, direction, multiparty; + + status = csd_status_to_hfp(call); + if (status < 0) + continue; + + direction = call->originating ? + CALL_DIR_OUTGOING : CALL_DIR_INCOMING; + + multiparty = call->conference ? + CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO; + + telephony_list_current_call_ind(i, direction, status, + CALL_MODE_VOICE, multiparty, + call->number, + number_type(call->number)); + } + + telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_operator_selection_req(void *telephony_device) +{ + telephony_operator_selection_ind(OPERATOR_MODE_AUTO, + net.operator_name ? net.operator_name : ""); + telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE); +} + +static void foreach_call_with_status(int status, + int (*func)(struct csd_call *call)) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == status) + func(call); + } +} + +void telephony_call_hold_req(void *telephony_device, const char *cmd) +{ + const char *idx; + struct csd_call *call; + int err = 0; + + DBG("telephony-maemo6: got call hold request %s", cmd); + + if (strlen(cmd) > 1) + idx = &cmd[1]; + else + idx = NULL; + + if (idx) + call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1); + else + call = NULL; + + switch (cmd[0]) { + case '0': + if (find_call_with_status(CSD_CALL_STATUS_WAITING)) + foreach_call_with_status(CSD_CALL_STATUS_WAITING, + release_call); + else + foreach_call_with_status(CSD_CALL_STATUS_HOLD, + release_call); + break; + case '1': + if (idx) { + if (call) + err = release_call(call); + break; + } + foreach_call_with_status(CSD_CALL_STATUS_ACTIVE, release_call); + call = find_call_with_status(CSD_CALL_STATUS_WAITING); + if (call) + err = answer_call(call); + break; + case '2': + if (idx) { + if (call) + err = split_call(call); + } else { + struct csd_call *held, *wait; + + call = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + held = find_call_with_status(CSD_CALL_STATUS_HOLD); + wait = find_call_with_status(CSD_CALL_STATUS_WAITING); + + if (wait) + err = answer_call(wait); + else if (call && held) + err = swap_calls(); + else { + if (call) + err = hold_call(call); + if (held) + err = unhold_call(held); + } + } + break; + case '3': + if (find_call_with_status(CSD_CALL_STATUS_HOLD) || + find_call_with_status(CSD_CALL_STATUS_WAITING)) + err = create_conference(); + break; + case '4': + err = call_transfer(); + break; + default: + DBG("Unknown call hold request"); + break; + } + + if (err) + telephony_call_hold_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-maemo6: got %s NR and EC request", + enable ? "enable" : "disable"); + telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_key_press_req(void *telephony_device, const char *keys) +{ + struct csd_call *active, *waiting; + int err; + + DBG("telephony-maemo6: got key press request for %s", keys); + + waiting = find_call_with_status(CSD_CALL_STATUS_COMING); + if (!waiting) + waiting = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + if (!waiting) + waiting = find_call_with_status(CSD_CALL_STATUS_PROCEEDING); + + active = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + + if (waiting) + err = answer_call(waiting); + else if (active) + err = release_call(active); + else + err = 0; + + if (err < 0) + telephony_key_press_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_key_press_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_voice_dial_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-maemo6: got %s voice dial request", + enable ? "enable" : "disable"); + + telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED); +} + +static void handle_incoming_call(DBusMessage *msg) +{ + const char *number, *call_path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &call_path, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Call.Coming() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + DBG("Incoming call to %s from number %s", call_path, number); + + g_free(call->number); + call->number = g_strdup(number); + + if (find_call_with_status(CSD_CALL_STATUS_ACTIVE) || + find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_call_waiting_ind(call->number, + number_type(call->number)); + else + telephony_incoming_call_ind(call->number, + number_type(call->number)); + + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_INCOMING); +} + +static void handle_outgoing_call(DBusMessage *msg) +{ + const char *number, *call_path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &call_path, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Call.Created() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + DBG("Outgoing call from %s to number %s", call_path, number); + + g_free(call->number); + call->number = g_strdup(number); + + if (create_request_timer) { + g_source_remove(create_request_timer); + create_request_timer = 0; + } +} + +static gboolean create_timeout(gpointer user_data) +{ + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + create_request_timer = 0; + return FALSE; +} + +static void handle_create_requested(DBusMessage *msg) +{ + DBG("Call.CreateRequested()"); + + if (create_request_timer) + g_source_remove(create_request_timer); + + create_request_timer = g_timeout_add_seconds(5, create_timeout, NULL); + + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); +} + +static void call_set_status(struct csd_call *call, dbus_uint32_t status) +{ + dbus_uint32_t prev_status; + int callheld = telephony_get_indicator(maemo_indicators, "callheld"); + + prev_status = call->status; + DBG("Call %s changed from %s to %s", call->object_path, + call_status_str[prev_status], call_status_str[status]); + + if (prev_status == status) { + DBG("Ignoring CSD Call state change to existing state"); + return; + } + + call->status = (int) status; + + switch (status) { + case CSD_CALL_STATUS_IDLE: + if (call->setup) { + telephony_update_indicator(maemo_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (!call->originating) + telephony_calling_stopped_ind(); + } + + g_free(call->number); + call->number = NULL; + call->originating = FALSE; + call->emergency = FALSE; + call->on_hold = FALSE; + call->conference = FALSE; + call->setup = FALSE; + break; + case CSD_CALL_STATUS_CREATE: + call->originating = TRUE; + call->setup = TRUE; + break; + case CSD_CALL_STATUS_COMING: + call->originating = FALSE; + call->setup = TRUE; + break; + case CSD_CALL_STATUS_PROCEEDING: + break; + case CSD_CALL_STATUS_MO_ALERTING: + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + break; + case CSD_CALL_STATUS_MT_ALERTING: + /* Some headsets expect incoming call notification before they + * can send ATA command. When call changed status from waiting + * to alerting we need to send missing notification. Otherwise + * headsets like Nokia BH-108 or BackBeat 903 are unable to + * answer incoming call that was previously waiting. */ + if (prev_status == CSD_CALL_STATUS_WAITING) + telephony_incoming_call_ind(call->number, + number_type(call->number)); + break; + case CSD_CALL_STATUS_WAITING: + break; + case CSD_CALL_STATUS_ANSWERED: + break; + case CSD_CALL_STATUS_ACTIVE: + if (call->on_hold) { + call->on_hold = FALSE; + if (find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + else + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_NONE); + } else { + if (!g_slist_find(active_calls, call)) + active_calls = g_slist_prepend(active_calls, call); + if (g_slist_length(active_calls) == 1) + telephony_update_indicator(maemo_indicators, + "call", + EV_CALL_ACTIVE); + /* Upgrade callheld status if necessary */ + if (callheld == EV_CALLHELD_ON_HOLD) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + telephony_update_indicator(maemo_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (!call->originating) + telephony_calling_stopped_ind(); + call->setup = FALSE; + } + break; + case CSD_CALL_STATUS_MO_RELEASE: + case CSD_CALL_STATUS_MT_RELEASE: + active_calls = g_slist_remove(active_calls, call); + if (g_slist_length(active_calls) == 0) + telephony_update_indicator(maemo_indicators, "call", + EV_CALL_INACTIVE); + break; + case CSD_CALL_STATUS_HOLD_INITIATED: + break; + case CSD_CALL_STATUS_HOLD: + call->on_hold = TRUE; + if (find_non_held_call()) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + else + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_ON_HOLD); + break; + case CSD_CALL_STATUS_RETRIEVE_INITIATED: + break; + case CSD_CALL_STATUS_RECONNECT_PENDING: + break; + case CSD_CALL_STATUS_TERMINATED: + if (call->on_hold && + !find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_NONE); + else if (callheld == EV_CALLHELD_MULTIPLE && + find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_ON_HOLD); + break; + case CSD_CALL_STATUS_SWAP_INITIATED: + break; + default: + error("Unknown call status %u", status); + break; + } +} + +static void handle_call_status(DBusMessage *msg, const char *call_path) +{ + struct csd_call *call; + dbus_uint32_t status, cause_type, cause; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &status, + DBUS_TYPE_UINT32, &cause_type, + DBUS_TYPE_UINT32, &cause, + DBUS_TYPE_INVALID)) { + error("Unexpected paramters in Instance.CallStatus() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + if (status > 16) { + error("Invalid call status %u", status); + return; + } + + call_set_status(call, status); +} + +static void handle_conference(DBusMessage *msg, gboolean joined) +{ + const char *path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Conference.%s", + dbus_message_get_member(msg)); + return; + } + + call = find_call(path); + if (!call) { + error("Conference signal for unknown call %s", path); + return; + } + + DBG("Call %s %s the conference", path, joined ? "joined" : "left"); + + call->conference = joined; +} + +static uint8_t str2status(const char *state) +{ + if (g_strcmp0(state, "Home") == 0) + return NETWORK_REG_STATUS_HOME; + else if (g_strcmp0(state, "Roaming") == 0) + return NETWORK_REG_STATUS_ROAMING; + else if (g_strcmp0(state, "Offline") == 0) + return NETWORK_REG_STATUS_OFFLINE; + else if (g_strcmp0(state, "Searching") == 0) + return NETWORK_REG_STATUS_SEARCHING; + else if (g_strcmp0(state, "NoSim") == 0) + return NETWORK_REG_STATUS_NO_SIM; + else if (g_strcmp0(state, "Poweroff") == 0) + return NETWORK_REG_STATUS_POWEROFF; + else if (g_strcmp0(state, "Powersafe") == 0) + return NETWORK_REG_STATUS_POWERSAFE; + else if (g_strcmp0(state, "NoCoverage") == 0) + return NETWORK_REG_STATUS_NO_COVERAGE; + else if (g_strcmp0(state, "Reject") == 0) + return NETWORK_REG_STATUS_REJECTED; + else + return NETWORK_REG_STATUS_UNKOWN; +} + +static void update_registration_status(const char *status) +{ + uint8_t new_status; + + new_status = str2status(status); + + if (net.status == new_status) + return; + + switch (new_status) { + case NETWORK_REG_STATUS_HOME: + telephony_update_indicator(maemo_indicators, "roam", + EV_ROAM_INACTIVE); + if (net.status > NETWORK_REG_STATUS_ROAMING) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_PRESENT); + break; + case NETWORK_REG_STATUS_ROAMING: + telephony_update_indicator(maemo_indicators, "roam", + EV_ROAM_ACTIVE); + if (net.status > NETWORK_REG_STATUS_ROAMING) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_PRESENT); + break; + case NETWORK_REG_STATUS_OFFLINE: + case NETWORK_REG_STATUS_SEARCHING: + case NETWORK_REG_STATUS_NO_SIM: + case NETWORK_REG_STATUS_POWEROFF: + case NETWORK_REG_STATUS_POWERSAFE: + case NETWORK_REG_STATUS_NO_COVERAGE: + case NETWORK_REG_STATUS_REJECTED: + case NETWORK_REG_STATUS_UNKOWN: + if (net.status < NETWORK_REG_STATUS_OFFLINE) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_NONE); + break; + } + + net.status = new_status; + + DBG("telephony-maemo6: registration status changed: %s", status); +} + +static void handle_registration_changed(DBusMessage *msg) +{ + const char *status; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &status, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in RegistrationChanged"); + return; + } + + update_registration_status(status); +} + +static void update_signal_strength(int32_t signal_bars) +{ + if (signal_bars < 0) { + DBG("signal strength smaller than expected: %d < 0", + signal_bars); + signal_bars = 0; + } else if (signal_bars > 5) { + DBG("signal strength greater than expected: %d > 5", + signal_bars); + signal_bars = 5; + } + + if (net.signal_bars == signal_bars) + return; + + telephony_update_indicator(maemo_indicators, "signal", signal_bars); + + net.signal_bars = signal_bars; + DBG("telephony-maemo6: signal strength updated: %d/5", signal_bars); +} + +static void handle_signal_bars_changed(DBusMessage *msg) +{ + int32_t signal_bars; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_INT32, &signal_bars, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in SignalBarsChanged"); + return; + } + + update_signal_strength(signal_bars); +} + +static gboolean iter_get_basic_args(DBusMessageIter *iter, + int first_arg_type, ...) +{ + int type; + va_list ap; + + va_start(ap, first_arg_type); + + for (type = first_arg_type; type != DBUS_TYPE_INVALID; + type = va_arg(ap, int)) { + void *value = va_arg(ap, void *); + int real_type = dbus_message_iter_get_arg_type(iter); + + if (real_type != type) { + error("iter_get_basic_args: expected %c but got %c", + (char) type, (char) real_type); + break; + } + + dbus_message_iter_get_basic(iter, value); + dbus_message_iter_next(iter); + } + + va_end(ap); + + return type == DBUS_TYPE_INVALID ? TRUE : FALSE; +} + +static void hal_battery_level_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + dbus_int32_t level; + int *value = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_INT32, &level, + DBUS_TYPE_INVALID)) { + error("Unexpected args in hald reply"); + goto done; + } + + *value = (int) level; + + if (value == &battchg_last) + DBG("telephony-maemo6: battery.charge_level.last_full is %d", + *value); + else if (value == &battchg_design) + DBG("telephony-maemo6: battery.charge_level.design is %d", + *value); + else + DBG("telephony-maemo6: battery.charge_level.current is %d", + *value); + + if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) { + int new, max; + + if (battchg_last > 0) + max = battchg_last; + else + max = battchg_design; + + new = battchg_cur * 5 / max; + + telephony_update_indicator(maemo_indicators, "battchg", new); + } + +done: + dbus_message_unref(reply); + remove_pending(call); +} + +static void hal_get_integer(const char *path, const char *key, void *user_data) +{ + send_method_call("org.freedesktop.Hal", path, + "org.freedesktop.Hal.Device", + "GetPropertyInteger", + hal_battery_level_reply, user_data, + DBUS_TYPE_STRING, &key, + DBUS_TYPE_INVALID); +} + +static void handle_hal_property_modified(DBusMessage *msg) +{ + DBusMessageIter iter, array; + dbus_int32_t num_changes; + const char *path; + + path = dbus_message_get_path(msg); + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_get_basic(&iter, &num_changes); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) { + DBusMessageIter prop; + const char *name; + dbus_bool_t added, removed; + + dbus_message_iter_recurse(&array, &prop); + + if (!iter_get_basic_args(&prop, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &added, + DBUS_TYPE_BOOLEAN, &removed, + DBUS_TYPE_INVALID)) { + error("Invalid hal PropertyModified parameters"); + break; + } + + if (g_str_equal(name, "battery.charge_level.last_full")) + hal_get_integer(path, name, &battchg_last); + else if (g_str_equal(name, "battery.charge_level.current")) + hal_get_integer(path, name, &battchg_cur); + else if (g_str_equal(name, "battery.charge_level.design")) + hal_get_integer(path, name, &battchg_design); + + dbus_message_iter_next(&array); + } +} + +static void csd_call_free(struct csd_call *call) +{ + if (!call) + return; + + g_free(call->object_path); + g_free(call->number); + + g_free(call); +} + +static void parse_call_list(DBusMessageIter *iter) +{ + do { + DBusMessageIter call_iter; + struct csd_call *call; + const char *object_path, *number; + dbus_uint32_t status; + dbus_bool_t originating, terminating, emerg, on_hold, conf; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) { + error("Unexpected signature in GetCallInfoAll reply"); + break; + } + + dbus_message_iter_recurse(iter, &call_iter); + + if (!iter_get_basic_args(&call_iter, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_UINT32, &status, + DBUS_TYPE_BOOLEAN, &originating, + DBUS_TYPE_BOOLEAN, &terminating, + DBUS_TYPE_BOOLEAN, &emerg, + DBUS_TYPE_BOOLEAN, &on_hold, + DBUS_TYPE_BOOLEAN, &conf, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Parsing call D-Bus parameters failed"); + break; + } + + call = find_call(object_path); + if (!call) { + call = g_new0(struct csd_call, 1); + call->object_path = g_strdup(object_path); + calls = g_slist_append(calls, call); + DBG("telephony-maemo6: new csd call instance at %s", + object_path); + } + + if (status == CSD_CALL_STATUS_IDLE) + continue; + + /* CSD gives incorrect call_hold property sometimes */ + if ((call->status != CSD_CALL_STATUS_HOLD && on_hold) || + (call->status == CSD_CALL_STATUS_HOLD && + !on_hold)) { + error("Conflicting call status and on_hold property!"); + on_hold = call->status == CSD_CALL_STATUS_HOLD; + } + + call->originating = originating; + call->on_hold = on_hold; + call->conference = conf; + g_free(call->number); + call->number = g_strdup(number); + + /* Update indicators */ + call_set_status(call, status); + + } while (dbus_message_iter_next(iter)); +} + +static void update_operator_name(const char *name) +{ + if (name == NULL) + return; + + g_free(net.operator_name); + net.operator_name = g_strndup(name, 16); + DBG("telephony-maemo6: operator name updated: %s", name); +} + +static void get_property_reply(DBusPendingCall *call, void *user_data) +{ + char *prop = user_data; + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("csd replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + error("Unexpected signature in Get return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (g_strcmp0(prop, "RegistrationStatus") == 0) { + const char *status; + + dbus_message_iter_get_basic(&sub, &status); + update_registration_status(status); + + get_property(CSD_CSNET_OPERATOR, "OperatorName"); + get_property(CSD_CSNET_SIGNAL, "SignalBars"); + } else if (g_strcmp0(prop, "OperatorName") == 0) { + const char *name; + + dbus_message_iter_get_basic(&sub, &name); + update_operator_name(name); + } else if (g_strcmp0(prop, "SignalBars") == 0) { + int32_t signal_bars; + + dbus_message_iter_get_basic(&sub, &signal_bars); + update_signal_strength(signal_bars); + } + +done: + g_free(prop); + dbus_message_unref(reply); + remove_pending(call); +} + +static int get_property(const char *iface, const char *prop) +{ + return send_method_call(CSD_CSNET_BUS_NAME, CSD_CSNET_PATH, + DBUS_INTERFACE_PROPERTIES, "Get", + get_property_reply, g_strdup(prop), + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); +} + +static void handle_operator_name_changed(DBusMessage *msg) +{ + const char *name; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in OperatorNameChanged"); + return; + } + + update_operator_name(name); +} + +static void call_info_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub;; + + get_calls_active = FALSE; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("csd replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in GetCallInfoAll return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + parse_call_list(&sub); + + get_property(CSD_CSNET_REGISTRATION, "RegistrationStatus"); + +done: + dbus_message_unref(reply); + remove_pending(call); +} + + +static void phonebook_read_reply(DBusPendingCall *call, void *user_data) +{ + DBusError derr; + DBusMessage *reply; + const char *name, *number, *secondname, *additionalnumber, *email; + int index; + char **number_type = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&derr); + if (dbus_set_error_from_message(&derr, reply)) { + error("%s.ReadFirst replied with an error: %s, %s", + CSD_SIMPB_INTERFACE, derr.name, derr.message); + dbus_error_free(&derr); + if (number_type == &vmbx) + vmbx = g_strdup(getenv("VMBX_NUMBER")); + goto done; + } + + dbus_error_init(&derr); + if (dbus_message_get_args(reply, NULL, + DBUS_TYPE_INT32, &index, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_STRING, &secondname, + DBUS_TYPE_STRING, &additionalnumber, + DBUS_TYPE_STRING, &email, + DBUS_TYPE_INVALID) == FALSE) { + error("Unable to parse %s.ReadFirst arguments: %s, %s", + CSD_SIMPB_INTERFACE, derr.name, derr.message); + dbus_error_free(&derr); + goto done; + } + + if (number_type == &msisdn) { + g_free(msisdn); + msisdn = g_strdup(number); + DBG("Got MSISDN %s (%s)", number, name); + } else { + g_free(vmbx); + vmbx = g_strdup(number); + DBG("Got voice mailbox number %s (%s)", number, name); + } + +done: + dbus_message_unref(reply); + remove_pending(call); +} + +static void csd_init(void) +{ + const char *pb_type; + int ret; + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "GetCallInfoAll", + call_info_reply, NULL, DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to sent GetCallInfoAll method call"); + return; + } + + get_calls_active = TRUE; + + pb_type = CSD_SIMPB_TYPE_MSISDN; + + ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH, + CSD_SIMPB_INTERFACE, "ReadFirst", + phonebook_read_reply, &msisdn, + DBUS_TYPE_STRING, &pb_type, + DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to send " CSD_SIMPB_INTERFACE ".read()"); + return; + } + + /* Voicemail should be in MBDN index 0 */ + pb_type = CSD_SIMPB_TYPE_MBDN; + + ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH, + CSD_SIMPB_INTERFACE, "ReadFirst", + phonebook_read_reply, &vmbx, + DBUS_TYPE_STRING, &pb_type, + DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to send " CSD_SIMPB_INTERFACE ".read()"); + return; + } +} + +static void handle_modem_state(DBusMessage *msg) +{ + const char *state; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &state, + DBUS_TYPE_INVALID)) { + error("Unexpected modem state parameters"); + return; + } + + DBG("SSC modem state: %s", state); + + if (calls != NULL || get_calls_active) + return; + + if (g_str_equal(state, "cmt_ready") || g_str_equal(state, "online")) + csd_init(); +} + +static void modem_state_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError err; + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("get_modem_state: %s, %s", err.name, err.message); + dbus_error_free(&err); + } else + handle_modem_state(reply); + + dbus_message_unref(reply); + remove_pending(call); +} + +static gboolean signal_filter(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *path = dbus_message_get_path(msg); + + if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Coming")) + handle_incoming_call(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Created")) + handle_outgoing_call(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, + "CreateRequested")) + handle_create_requested(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INSTANCE, "CallStatus")) + handle_call_status(msg, path); + else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Joined")) + handle_conference(msg, TRUE); + else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Left")) + handle_conference(msg, FALSE); + else if (dbus_message_is_signal(msg, CSD_CSNET_REGISTRATION, + "RegistrationChanged")) + handle_registration_changed(msg); + else if (dbus_message_is_signal(msg, CSD_CSNET_OPERATOR, + "OperatorNameChanged")) + handle_operator_name_changed(msg); + else if (dbus_message_is_signal(msg, CSD_CSNET_SIGNAL, + "SignalBarsChanged")) + handle_signal_bars_changed(msg); + else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device", + "PropertyModified")) + handle_hal_property_modified(msg); + else if (dbus_message_is_signal(msg, SSC_DBUS_IFACE, + "modem_state_changed_ind")) + handle_modem_state(msg); + + return TRUE; +} + +static void add_watch(const char *sender, const char *path, + const char *interface, const char *member) +{ + guint watch; + + watch = g_dbus_add_signal_watch(connection, sender, path, interface, + member, signal_filter, NULL, NULL); + + watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch)); +} + +static void hal_find_device_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub; + const char *path; + int type; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in FindDeviceByCapability return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + type = dbus_message_iter_get_arg_type(&sub); + + if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) { + error("No hal device with battery capability found"); + goto done; + } + + dbus_message_iter_get_basic(&sub, &path); + + DBG("telephony-maemo6: found battery device at %s", path); + + add_watch(NULL, path, "org.freedesktop.Hal.Device", + "PropertyModified"); + + hal_get_integer(path, "battery.charge_level.last_full", &battchg_last); + hal_get_integer(path, "battery.charge_level.current", &battchg_cur); + hal_get_integer(path, "battery.charge_level.design", &battchg_design); + +done: + dbus_message_unref(reply); + remove_pending(call); +} + +int telephony_init(void) +{ + const char *battery_cap = "battery"; + uint32_t features = AG_FEATURE_EC_ANDOR_NR | + AG_FEATURE_INBAND_RINGTONE | + AG_FEATURE_REJECT_A_CALL | + AG_FEATURE_ENHANCED_CALL_STATUS | + AG_FEATURE_ENHANCED_CALL_CONTROL | + AG_FEATURE_EXTENDED_ERROR_RESULT_CODES | + AG_FEATURE_THREE_WAY_CALLING; + int i; + + DBG(""); + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + + add_watch(NULL, NULL, CSD_CALL_INTERFACE, NULL); + add_watch(NULL, NULL, CSD_CALL_INSTANCE, NULL); + add_watch(NULL, NULL, CSD_CALL_CONFERENCE, NULL); + add_watch(NULL, NULL, CSD_CSNET_REGISTRATION, "RegistrationChanged"); + add_watch(NULL, NULL, CSD_CSNET_OPERATOR, "OperatorNameChanged"); + add_watch(NULL, NULL, CSD_CSNET_SIGNAL, "SignalBarsChanged"); + add_watch(NULL, NULL, SSC_DBUS_IFACE, "modem_state_changed_ind"); + + if (send_method_call(SSC_DBUS_NAME, SSC_DBUS_PATH, SSC_DBUS_IFACE, + "get_modem_state", modem_state_reply, + NULL, DBUS_TYPE_INVALID) < 0) + error("Unable to send " SSC_DBUS_IFACE ".get_modem_state()"); + + /* Reset indicators */ + for (i = 0; maemo_indicators[i].desc != NULL; i++) { + if (g_str_equal(maemo_indicators[i].desc, "battchg")) + maemo_indicators[i].val = 5; + else + maemo_indicators[i].val = 0; + } + + telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED, + chld_str); + if (send_method_call("org.freedesktop.Hal", + "/org/freedesktop/Hal/Manager", + "org.freedesktop.Hal.Manager", + "FindDeviceByCapability", + hal_find_device_reply, NULL, + DBUS_TYPE_STRING, &battery_cap, + DBUS_TYPE_INVALID) < 0) + error("Unable to send HAL method call"); + + return 0; +} + +static void remove_watch(gpointer data) +{ + g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data)); +} + +void telephony_exit(void) +{ + DBG(""); + + g_free(net.operator_name); + net.operator_name = NULL; + + net.status = NETWORK_REG_STATUS_UNKOWN; + net.signal_bars = 0; + + g_slist_free(active_calls); + active_calls = NULL; + + g_slist_foreach(calls, (GFunc) csd_call_free, NULL); + g_slist_free(calls); + calls = NULL; + + g_slist_foreach(pending, (GFunc) pending_req_finalize, NULL); + g_slist_free(pending); + pending = NULL; + + g_slist_foreach(watches, (GFunc) remove_watch, NULL); + g_slist_free(watches); + watches = NULL; + + dbus_connection_unref(connection); + connection = NULL; + + telephony_deinit(); +} diff --git a/audio/telephony-ofono.c b/audio/telephony-ofono.c new file mode 100644 index 0000000..6f5685b --- /dev/null +++ b/audio/telephony-ofono.c @@ -0,0 +1,1627 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009-2010 Intel Corporation + * Copyright (C) 2006-2009 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "telephony.h" + +enum net_registration_status { + NETWORK_REG_STATUS_HOME = 0x00, + NETWORK_REG_STATUS_ROAM, + NETWORK_REG_STATUS_NOSERV +}; + +struct voice_call { + char *obj_path; + int status; + gboolean originating; + gboolean conference; + char *number; + guint watch; +}; + +static DBusConnection *connection = NULL; +static char *modem_obj_path = NULL; +static char *last_dialed_number = NULL; +static GSList *calls = NULL; +static GSList *watches = NULL; +static GSList *pending = NULL; + +#define OFONO_BUS_NAME "org.ofono" +#define OFONO_PATH "/" +#define OFONO_MODEM_INTERFACE "org.ofono.Modem" +#define OFONO_MANAGER_INTERFACE "org.ofono.Manager" +#define OFONO_NETWORKREG_INTERFACE "org.ofono.NetworkRegistration" +#define OFONO_VCMANAGER_INTERFACE "org.ofono.VoiceCallManager" +#define OFONO_VC_INTERFACE "org.ofono.VoiceCall" + +/* HAL battery namespace key values */ +static int battchg_cur = -1; /* "battery.charge_level.current" */ +static int battchg_last = -1; /* "battery.charge_level.last_full" */ +static int battchg_design = -1; /* "battery.charge_level.design" */ + +static struct { + uint8_t status; + uint32_t signals_bar; + char *operator_name; +} net = { + .status = NETWORK_REG_STATUS_NOSERV, + .signals_bar = 0, + .operator_name = NULL, +}; + +static const char *chld_str = "0,1,1x,2,2x,3,4"; +static char *subscriber_number = NULL; + +static gboolean events_enabled = FALSE; + +static struct indicator ofono_indicators[] = +{ + { "battchg", "0-5", 5, TRUE }, + { "signal", "0-5", 5, TRUE }, + { "service", "0,1", 1, TRUE }, + { "call", "0,1", 0, TRUE }, + { "callsetup", "0-3", 0, TRUE }, + { "callheld", "0-2", 0, FALSE }, + { "roam", "0,1", 0, TRUE }, + { NULL } +}; + +static struct voice_call *find_vc(const char *path) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct voice_call *vc = l->data; + + if (g_str_equal(vc->obj_path, path)) + return vc; + } + + return NULL; +} + +static struct voice_call *find_vc_with_status(int status) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct voice_call *vc = l->data; + + if (vc->status == status) + return vc; + } + + return NULL; +} + +static struct voice_call *find_vc_without_status(int status) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct voice_call *call = l->data; + + if (call->status != status) + return call; + } + + return NULL; +} + +static int number_type(const char *number) +{ + if (number == NULL) + return NUMBER_TYPE_TELEPHONY; + + if (number[0] == '+' || strncmp(number, "00", 2) == 0) + return NUMBER_TYPE_INTERNATIONAL; + + return NUMBER_TYPE_TELEPHONY; +} + +void telephony_device_connected(void *telephony_device) +{ + struct voice_call *coming; + + DBG("telephony-ofono: device %p connected", telephony_device); + + coming = find_vc_with_status(CALL_STATUS_ALERTING); + if (coming) { + if (find_vc_with_status(CALL_STATUS_ACTIVE)) + telephony_call_waiting_ind(coming->number, + number_type(coming->number)); + else + telephony_incoming_call_ind(coming->number, + number_type(coming->number)); + } +} + +void telephony_device_disconnected(void *telephony_device) +{ + DBG("telephony-ofono: device %p disconnected", telephony_device); + events_enabled = FALSE; +} + +void telephony_event_reporting_req(void *telephony_device, int ind) +{ + events_enabled = ind == 1 ? TRUE : FALSE; + + telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_response_and_hold_req(void *telephony_device, int rh) +{ + telephony_response_and_hold_rsp(telephony_device, + CME_ERROR_NOT_SUPPORTED); +} + +void telephony_last_dialed_number_req(void *telephony_device) +{ + DBG("telephony-ofono: last dialed number request"); + + if (last_dialed_number) + telephony_dial_number_req(telephony_device, last_dialed_number); + else + telephony_last_dialed_number_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); +} + +static int send_method_call(const char *dest, const char *path, + const char *interface, const char *method, + DBusPendingCallNotifyFunction cb, + void *user_data, int type, ...) +{ + DBusMessage *msg; + DBusPendingCall *call; + va_list args; + + msg = dbus_message_new_method_call(dest, path, interface, method); + if (!msg) { + error("Unable to allocate new D-Bus %s message", method); + return -ENOMEM; + } + + va_start(args, type); + + if (!dbus_message_append_args_valist(msg, type, args)) { + dbus_message_unref(msg); + va_end(args); + return -EIO; + } + + va_end(args); + + if (!cb) { + g_dbus_send_message(connection, msg); + return 0; + } + + if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) { + error("Sending %s failed", method); + dbus_message_unref(msg); + return -EIO; + } + + dbus_pending_call_set_notify(call, cb, user_data, NULL); + pending = g_slist_prepend(pending, call); + dbus_message_unref(msg); + + return 0; +} + +static int answer_call(struct voice_call *vc) +{ + DBG("%s", vc->number); + return send_method_call(OFONO_BUS_NAME, vc->obj_path, + OFONO_VC_INTERFACE, "Answer", + NULL, NULL, DBUS_TYPE_INVALID); +} + +static int release_call(struct voice_call *vc) +{ + DBG("%s", vc->number); + return send_method_call(OFONO_BUS_NAME, vc->obj_path, + OFONO_VC_INTERFACE, "Hangup", + NULL, NULL, DBUS_TYPE_INVALID); +} + +static int release_answer_calls() +{ + DBG(""); + return send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "ReleaseAndAnswer", + NULL, NULL, DBUS_TYPE_INVALID); +} + +static int split_call(struct voice_call *call) +{ + DBG("%s", call->number); + return send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "PrivateChat", + NULL, NULL, + DBUS_TYPE_OBJECT_PATH, + call->obj_path, + DBUS_TYPE_INVALID); + return -1; +} + +static int swap_calls(void) +{ + DBG(""); + return send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "SwapCalls", + NULL, NULL, DBUS_TYPE_INVALID); +} + +static int create_conference(void) +{ + DBG(""); + return send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "CreateMultiparty", + NULL, NULL, DBUS_TYPE_INVALID); +} + +static int release_conference(void) +{ + DBG(""); + return send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "HangupMultiparty", + NULL, NULL, DBUS_TYPE_INVALID); +} + +static int call_transfer(void) +{ + DBG(""); + return send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "Transfer", + NULL, NULL, DBUS_TYPE_INVALID); +} + +void telephony_terminate_call_req(void *telephony_device) +{ + struct voice_call *call; + struct voice_call *alerting; + int err; + + call = find_vc_with_status(CALL_STATUS_ACTIVE); + if (!call) + call = calls->data; + + if (!call) { + error("No active call"); + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + alerting = find_vc_with_status(CALL_STATUS_ALERTING); + if (call->status == CALL_STATUS_HELD && alerting) + err = release_call(alerting); + else if (call->conference) + err = release_conference(); + else + err = release_call(call); + + if (err < 0) + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_answer_call_req(void *telephony_device) +{ + struct voice_call *vc; + int ret; + + vc = find_vc_with_status(CALL_STATUS_INCOMING); + if (!vc) + vc = find_vc_with_status(CALL_STATUS_ALERTING); + + if (!vc) + vc = find_vc_with_status(CALL_STATUS_WAITING); + + if (!vc) { + telephony_answer_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + ret = answer_call(vc); + if (ret < 0) { + telephony_answer_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_dial_number_req(void *telephony_device, const char *number) +{ + const char *clir; + int ret; + + DBG("telephony-ofono: dial request to %s", number); + + if (!modem_obj_path) { + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + if (!strncmp(number, "*31#", 4)) { + number += 4; + clir = "enabled"; + } else if (!strncmp(number, "#31#", 4)) { + number += 4; + clir = "disabled"; + } else + clir = "default"; + + ret = send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "Dial", NULL, NULL, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_STRING, &clir, + DBUS_TYPE_INVALID); + + if (ret < 0) + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_transmit_dtmf_req(void *telephony_device, char tone) +{ + char *tone_string; + int ret; + + DBG("telephony-ofono: transmit dtmf: %c", tone); + + if (!modem_obj_path) { + telephony_transmit_dtmf_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + tone_string = g_strdup_printf("%c", tone); + ret = send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "SendTones", NULL, NULL, + DBUS_TYPE_STRING, &tone_string, + DBUS_TYPE_INVALID); + g_free(tone_string); + + if (ret < 0) + telephony_transmit_dtmf_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_subscriber_number_req(void *telephony_device) +{ + DBG("telephony-ofono: subscriber number request"); + + if (subscriber_number) + telephony_subscriber_number_ind(subscriber_number, + NUMBER_TYPE_TELEPHONY, + SUBSCRIBER_SERVICE_VOICE); + telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_list_current_calls_req(void *telephony_device) +{ + GSList *l; + int i; + + DBG("telephony-ofono: list current calls request"); + + for (l = calls, i = 1; l != NULL; l = l->next, i++) { + struct voice_call *vc = l->data; + int direction, multiparty; + + direction = vc->originating ? + CALL_DIR_OUTGOING : CALL_DIR_INCOMING; + + multiparty = vc->conference ? + CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO; + + DBG("call %s direction %d multiparty %d", vc->number, + direction, multiparty); + + telephony_list_current_call_ind(i, direction, vc->status, + CALL_MODE_VOICE, multiparty, + vc->number, number_type(vc->number)); + } + + telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_operator_selection_req(void *telephony_device) +{ + DBG("telephony-ofono: operator selection request"); + + telephony_operator_selection_ind(OPERATOR_MODE_AUTO, + net.operator_name ? net.operator_name : ""); + telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE); +} + +static void foreach_vc_with_status(int status, + int (*func)(struct voice_call *vc)) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct voice_call *call = l->data; + + if (call->status == status) + func(call); + } +} + +void telephony_call_hold_req(void *telephony_device, const char *cmd) +{ + const char *idx; + struct voice_call *call; + int err = 0; + + DBG("telephony-ofono: got call hold request %s", cmd); + + if (strlen(cmd) > 1) + idx = &cmd[1]; + else + idx = NULL; + + if (idx) + call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1); + else + call = NULL; + + switch (cmd[0]) { + case '0': + if (find_vc_with_status(CALL_STATUS_WAITING)) + foreach_vc_with_status(CALL_STATUS_WAITING, + release_call); + else + foreach_vc_with_status(CALL_STATUS_HELD, release_call); + break; + case '1': + if (idx) { + if (call) + err = release_call(call); + break; + } + err = release_answer_calls(); + break; + case '2': + if (idx) { + if (call) + err = split_call(call); + } else { + call = find_vc_with_status(CALL_STATUS_WAITING); + + if (call) + err = answer_call(call); + else + err = swap_calls(); + } + break; + case '3': + if (find_vc_with_status(CALL_STATUS_HELD) || + find_vc_with_status(CALL_STATUS_WAITING)) + err = create_conference(); + break; + case '4': + err = call_transfer(); + break; + default: + DBG("Unknown call hold request"); + break; + } + + if (err) + telephony_call_hold_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-ofono: got %s NR and EC request", + enable ? "enable" : "disable"); + + telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_key_press_req(void *telephony_device, const char *keys) +{ + struct voice_call *active, *incoming; + int err; + + DBG("telephony-ofono: got key press request for %s", keys); + + incoming = find_vc_with_status(CALL_STATUS_INCOMING); + + active = find_vc_with_status(CALL_STATUS_ACTIVE); + + if (incoming) + err = answer_call(incoming); + else if (active) + err = release_call(active); + else + err = 0; + + if (err < 0) + telephony_key_press_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_key_press_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_voice_dial_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-ofono: got %s voice dial request", + enable ? "enable" : "disable"); + + telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED); +} + +static gboolean iter_get_basic_args(DBusMessageIter *iter, + int first_arg_type, ...) +{ + int type; + va_list ap; + + va_start(ap, first_arg_type); + + for (type = first_arg_type; type != DBUS_TYPE_INVALID; + type = va_arg(ap, int)) { + void *value = va_arg(ap, void *); + int real_type = dbus_message_iter_get_arg_type(iter); + + if (real_type != type) { + error("iter_get_basic_args: expected %c but got %c", + (char) type, (char) real_type); + break; + } + + dbus_message_iter_get_basic(iter, value); + dbus_message_iter_next(iter); + } + + va_end(ap); + + return type == DBUS_TYPE_INVALID ? TRUE : FALSE; +} + +static void call_free(struct voice_call *vc) +{ + DBG("%s", vc->obj_path); + + if (vc->status == CALL_STATUS_ACTIVE) + telephony_update_indicator(ofono_indicators, "call", + EV_CALL_INACTIVE); + else + telephony_update_indicator(ofono_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + + if (vc->status == CALL_STATUS_INCOMING) + telephony_calling_stopped_ind(); + + g_dbus_remove_watch(connection, vc->watch); + g_free(vc->obj_path); + g_free(vc->number); + g_free(vc); +} + +static gboolean handle_vc_property_changed(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct voice_call *vc = data; + const char *obj_path = dbus_message_get_path(msg); + DBusMessageIter iter, sub; + const char *property, *state; + + DBG("path %s", obj_path); + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { + error("Unexpected signature in vc PropertyChanged signal"); + return TRUE; + } + + dbus_message_iter_get_basic(&iter, &property); + DBG("property %s", property); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &sub); + if (g_str_equal(property, "State")) { + dbus_message_iter_get_basic(&sub, &state); + DBG("State %s", state); + if (g_str_equal(state, "disconnected")) { + calls = g_slist_remove(calls, vc); + call_free(vc); + } else if (g_str_equal(state, "active")) { + telephony_update_indicator(ofono_indicators, + "call", EV_CALL_ACTIVE); + telephony_update_indicator(ofono_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (vc->status == CALL_STATUS_INCOMING) + telephony_calling_stopped_ind(); + vc->status = CALL_STATUS_ACTIVE; + } else if (g_str_equal(state, "alerting")) { + telephony_update_indicator(ofono_indicators, + "callsetup", EV_CALLSETUP_ALERTING); + vc->status = CALL_STATUS_ALERTING; + vc->originating = TRUE; + } else if (g_str_equal(state, "incoming")) { + /* state change from waiting to incoming */ + telephony_update_indicator(ofono_indicators, + "callsetup", EV_CALLSETUP_INCOMING); + telephony_incoming_call_ind(vc->number, + NUMBER_TYPE_TELEPHONY); + vc->status = CALL_STATUS_INCOMING; + vc->originating = FALSE; + } else if (g_str_equal(state, "held")) { + vc->status = CALL_STATUS_HELD; + if (find_vc_without_status(CALL_STATUS_HELD)) + telephony_update_indicator(ofono_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + else + telephony_update_indicator(ofono_indicators, + "callheld", + EV_CALLHELD_ON_HOLD); + } + } else if (g_str_equal(property, "Multiparty")) { + dbus_bool_t multiparty; + + dbus_message_iter_get_basic(&sub, &multiparty); + DBG("Multiparty %s", multiparty ? "True" : "False"); + vc->conference = multiparty; + } + + return TRUE; +} + +static struct voice_call *call_new(const char *path, DBusMessageIter *properties) +{ + struct voice_call *vc; + + DBG("%s", path); + + vc = g_new0(struct voice_call, 1); + vc->obj_path = g_strdup(path); + vc->watch = g_dbus_add_signal_watch(connection, NULL, path, + OFONO_VC_INTERFACE, "PropertyChanged", + handle_vc_property_changed, vc, NULL); + + while (dbus_message_iter_get_arg_type(properties) + == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + const char *property, *cli, *state; + dbus_bool_t multiparty; + + dbus_message_iter_recurse(properties, &entry); + dbus_message_iter_get_basic(&entry, &property); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + if (g_str_equal(property, "LineIdentification")) { + dbus_message_iter_get_basic(&value, &cli); + DBG("cli %s", cli); + vc->number = g_strdup(cli); + } else if (g_str_equal(property, "State")) { + dbus_message_iter_get_basic(&value, &state); + DBG("state %s", state); + if (g_str_equal(state, "incoming")) + vc->status = CALL_STATUS_INCOMING; + else if (g_str_equal(state, "dialing")) + vc->status = CALL_STATUS_DIALING; + else if (g_str_equal(state, "alerting")) + vc->status = CALL_STATUS_ALERTING; + else if (g_str_equal(state, "waiting")) + vc->status = CALL_STATUS_WAITING; + else if (g_str_equal(state, "held")) + vc->status = CALL_STATUS_HELD; + } else if (g_str_equal(property, "Multiparty")) { + dbus_message_iter_get_basic(&value, &multiparty); + DBG("Multipary %s", multiparty ? "True" : "False"); + vc->conference = multiparty; + } + + dbus_message_iter_next(properties); + } + + switch (vc->status) { + case CALL_STATUS_INCOMING: + DBG("CALL_STATUS_INCOMING"); + vc->originating = FALSE; + telephony_update_indicator(ofono_indicators, "callsetup", + EV_CALLSETUP_INCOMING); + telephony_incoming_call_ind(vc->number, NUMBER_TYPE_TELEPHONY); + break; + case CALL_STATUS_DIALING: + DBG("CALL_STATUS_DIALING"); + vc->originating = TRUE; + g_free(last_dialed_number); + last_dialed_number = g_strdup(vc->number); + telephony_update_indicator(ofono_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); + break; + case CALL_STATUS_ALERTING: + DBG("CALL_STATUS_ALERTING"); + vc->originating = TRUE; + g_free(last_dialed_number); + last_dialed_number = g_strdup(vc->number); + telephony_update_indicator(ofono_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + break; + case CALL_STATUS_WAITING: + DBG("CALL_STATUS_WAITING"); + vc->originating = FALSE; + telephony_update_indicator(ofono_indicators, "callsetup", + EV_CALLSETUP_INCOMING); + telephony_call_waiting_ind(vc->number, NUMBER_TYPE_TELEPHONY); + break; + } + + return vc; +} + +static void remove_pending(DBusPendingCall *call) +{ + pending = g_slist_remove(pending, call); + dbus_pending_call_unref(call); +} + +static void call_added(const char *path, DBusMessageIter *properties) +{ + struct voice_call *vc; + + DBG("%s", path); + + vc = find_vc(path); + if (vc) + return; + + vc = call_new(path, properties); + calls = g_slist_prepend(calls, vc); +} + +static void get_calls_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, entry; + + DBG(""); + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("ofono replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature"); + goto done; + } + + dbus_message_iter_recurse(&iter, &entry); + + while (dbus_message_iter_get_arg_type(&entry) + == DBUS_TYPE_STRUCT) { + const char *path; + DBusMessageIter value, properties; + + dbus_message_iter_recurse(&entry, &value); + dbus_message_iter_get_basic(&value, &path); + + dbus_message_iter_next(&value); + dbus_message_iter_recurse(&value, &properties); + + call_added(path, &properties); + + dbus_message_iter_next(&entry); + } + +done: + dbus_message_unref(reply); + remove_pending(call); +} + +static void handle_network_property(const char *property, DBusMessageIter *variant) +{ + const char *status, *operator; + unsigned int signals_bar; + + if (g_str_equal(property, "Status")) { + dbus_message_iter_get_basic(variant, &status); + DBG("Status is %s", status); + if (g_str_equal(status, "registered")) { + net.status = NETWORK_REG_STATUS_HOME; + telephony_update_indicator(ofono_indicators, + "roam", EV_ROAM_INACTIVE); + telephony_update_indicator(ofono_indicators, + "service", EV_SERVICE_PRESENT); + } else if (g_str_equal(status, "roaming")) { + net.status = NETWORK_REG_STATUS_ROAM; + telephony_update_indicator(ofono_indicators, + "roam", EV_ROAM_ACTIVE); + telephony_update_indicator(ofono_indicators, + "service", EV_SERVICE_PRESENT); + } else { + net.status = NETWORK_REG_STATUS_NOSERV; + telephony_update_indicator(ofono_indicators, + "roam", EV_ROAM_INACTIVE); + telephony_update_indicator(ofono_indicators, + "service", EV_SERVICE_NONE); + } + } else if (g_str_equal(property, "Name")) { + dbus_message_iter_get_basic(variant, &operator); + DBG("Operator is %s", operator); + g_free(net.operator_name); + net.operator_name = g_strdup(operator); + } else if (g_str_equal(property, "SignalStrength")) { + dbus_message_iter_get_basic(variant, &signals_bar); + DBG("SignalStrength is %d", signals_bar); + net.signals_bar = signals_bar; + telephony_update_indicator(ofono_indicators, "signal", + (signals_bar + 20) / 21); + } +} + +static int parse_network_properties(DBusMessageIter *properties) +{ + uint32_t features = AG_FEATURE_EC_ANDOR_NR | + AG_FEATURE_INBAND_RINGTONE | + AG_FEATURE_REJECT_A_CALL | + AG_FEATURE_ENHANCED_CALL_STATUS | + AG_FEATURE_ENHANCED_CALL_CONTROL | + AG_FEATURE_EXTENDED_ERROR_RESULT_CODES | + AG_FEATURE_THREE_WAY_CALLING; + int i; + + /* Reset indicators */ + for (i = 0; ofono_indicators[i].desc != NULL; i++) { + if (g_str_equal(ofono_indicators[i].desc, "battchg")) + ofono_indicators[i].val = 5; + else + ofono_indicators[i].val = 0; + } + + while (dbus_message_iter_get_arg_type(properties) + == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + + dbus_message_iter_recurse(properties, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + handle_network_property(key, &value); + + dbus_message_iter_next(properties); + } + + telephony_ready_ind(features, ofono_indicators, BTRH_NOT_SUPPORTED, + chld_str); + + return 0; +} + +static void get_properties_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, properties; + int ret = 0; + + DBG(""); + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("ofono replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature"); + goto done; + } + + dbus_message_iter_recurse(&iter, &properties); + + ret = parse_network_properties(&properties); + if (ret < 0) { + error("Unable to parse %s.GetProperty reply", + OFONO_NETWORKREG_INTERFACE); + goto done; + } + + ret = send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, "GetCalls", + get_calls_reply, NULL, DBUS_TYPE_INVALID); + if (ret < 0) + error("Unable to send %s.GetCalls", + OFONO_VCMANAGER_INTERFACE); + +done: + dbus_message_unref(reply); + remove_pending(call); +} + +static void network_found(const char *path) +{ + int ret; + + DBG("%s", path); + + modem_obj_path = g_strdup(path); + + ret = send_method_call(OFONO_BUS_NAME, path, + OFONO_NETWORKREG_INTERFACE, "GetProperties", + get_properties_reply, NULL, DBUS_TYPE_INVALID); + if (ret < 0) + error("Unable to send %s.GetProperties", + OFONO_NETWORKREG_INTERFACE); +} + +static void modem_removed(const char *path) +{ + if (g_strcmp0(modem_obj_path, path) != 0) + return; + + DBG("%s", path); + + g_slist_foreach(calls, (GFunc) call_free, NULL); + g_slist_free(calls); + calls = NULL; + + g_free(net.operator_name); + net.operator_name = NULL; + net.status = NETWORK_REG_STATUS_NOSERV; + net.signals_bar = 0; + + g_free(modem_obj_path); + modem_obj_path = NULL; +} + +static void parse_modem_interfaces(const char *path, DBusMessageIter *ifaces) +{ + DBG("%s", path); + + while (dbus_message_iter_get_arg_type(ifaces) == DBUS_TYPE_STRING) { + const char *iface; + + dbus_message_iter_get_basic(ifaces, &iface); + + if (g_str_equal(iface, OFONO_NETWORKREG_INTERFACE)) { + network_found(path); + return; + } + + dbus_message_iter_next(ifaces); + } + + modem_removed(path); +} + +static void modem_added(const char *path, DBusMessageIter *properties) +{ + if (modem_obj_path != NULL) { + DBG("Ignoring, modem already exist"); + return; + } + + DBG("%s", path); + + while (dbus_message_iter_get_arg_type(properties) + == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter interfaces, value, entry; + + dbus_message_iter_recurse(properties, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + if (strcasecmp(key, "Interfaces") != 0) + goto next; + + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_ARRAY) { + error("Invalid Signature"); + return; + } + + dbus_message_iter_recurse(&value, &interfaces); + + parse_modem_interfaces(path, &interfaces); + + if (modem_obj_path != NULL) + return; + + next: + dbus_message_iter_next(properties); + } +} + +static void get_modems_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, entry; + + DBG(""); + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("ofono replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + /* Skip modem selection if a modem already exist */ + if (modem_obj_path != NULL) + goto done; + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature"); + goto done; + } + + dbus_message_iter_recurse(&iter, &entry); + + while (dbus_message_iter_get_arg_type(&entry) + == DBUS_TYPE_STRUCT) { + const char *path; + DBusMessageIter item, properties; + + dbus_message_iter_recurse(&entry, &item); + dbus_message_iter_get_basic(&item, &path); + + dbus_message_iter_next(&item); + dbus_message_iter_recurse(&item, &properties); + + modem_added(path, &properties); + if (modem_obj_path != NULL) + break; + + dbus_message_iter_next(&entry); + } + +done: + dbus_message_unref(reply); + remove_pending(call); +} + +static gboolean handle_network_property_changed(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter, variant; + const char *property; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { + error("Unexpected signature in networkregistration" + " PropertyChanged signal"); + return TRUE; + } + dbus_message_iter_get_basic(&iter, &property); + DBG("in handle_registration_property_changed()," + " the property is %s", property); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &variant); + + handle_network_property(property, &variant); + + return TRUE; +} + +static void handle_modem_property(const char *path, const char *property, + DBusMessageIter *variant) +{ + DBG("%s", property); + + if (g_str_equal(property, "Interfaces")) { + DBusMessageIter interfaces; + + if (dbus_message_iter_get_arg_type(variant) + != DBUS_TYPE_ARRAY) { + error("Invalid signature"); + return; + } + + dbus_message_iter_recurse(variant, &interfaces); + parse_modem_interfaces(path, &interfaces); + } +} + +static gboolean handle_modem_property_changed(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter, variant; + const char *property, *path; + + path = dbus_message_get_path(msg); + + /* Ignore if modem already exist and paths doesn't match */ + if (modem_obj_path != NULL && + g_str_equal(path, modem_obj_path) == FALSE) + return TRUE; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { + error("Unexpected signature in %s.%s PropertyChanged signal", + dbus_message_get_interface(msg), + dbus_message_get_member(msg)); + return TRUE; + } + + dbus_message_iter_get_basic(&iter, &property); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &variant); + + handle_modem_property(path, property, &variant); + + return TRUE; +} + +static gboolean handle_vcmanager_call_added(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter, properties; + const char *path = dbus_message_get_path(msg); + + /* Ignore call if modem path doesn't math */ + if (g_strcmp0(modem_obj_path, path) != 0) + return TRUE; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) + != DBUS_TYPE_OBJECT_PATH) { + error("Unexpected signature in %s.%s signal", + dbus_message_get_interface(msg), + dbus_message_get_member(msg)); + return TRUE; + } + + dbus_message_iter_get_basic(&iter, &path); + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &properties); + + call_added(path, &properties); + + return TRUE; +} + +static void call_removed(const char *path) +{ + struct voice_call *vc; + + DBG("%s", path); + + vc = find_vc(path); + if (vc == NULL) + return; + + calls = g_slist_remove(calls, vc); + call_free(vc); +} + +static gboolean handle_vcmanager_call_removed(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path = dbus_message_get_path(msg); + + /* Ignore call if modem path doesn't math */ + if (g_strcmp0(modem_obj_path, path) != 0) + return TRUE; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + error("Unexpected signature in %s.%s signal", + dbus_message_get_interface(msg), + dbus_message_get_member(msg)); + return TRUE; + } + + call_removed(path); + + return TRUE; +} + +static gboolean handle_manager_modem_added(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter, properties; + const char *path; + + if (modem_obj_path != NULL) + return TRUE; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) + != DBUS_TYPE_OBJECT_PATH) { + error("Unexpected signature in %s.%s signal", + dbus_message_get_interface(msg), + dbus_message_get_member(msg)); + return TRUE; + } + + dbus_message_iter_get_basic(&iter, &path); + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &properties); + + modem_added(path, &properties); + + return TRUE; +} + +static gboolean handle_manager_modem_removed(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + error("Unexpected signature in %s.%s signal", + dbus_message_get_interface(msg), + dbus_message_get_member(msg)); + return TRUE; + } + + modem_removed(path); + + return TRUE; +} + +static void hal_battery_level_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply; + DBusError err; + dbus_int32_t level; + int *value = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (dbus_message_get_args(reply, &err, + DBUS_TYPE_INT32, &level, + DBUS_TYPE_INVALID) == FALSE) { + error("Unable to parse GetPropertyInteger reply: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + *value = (int) level; + + if (value == &battchg_last) + DBG("telephony-ofono: battery.charge_level.last_full" + " is %d", *value); + else if (value == &battchg_design) + DBG("telephony-ofono: battery.charge_level.design" + " is %d", *value); + else + DBG("telephony-ofono: battery.charge_level.current" + " is %d", *value); + + if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) { + int new, max; + + if (battchg_last > 0) + max = battchg_last; + else + max = battchg_design; + + new = battchg_cur * 5 / max; + + telephony_update_indicator(ofono_indicators, "battchg", new); + } +done: + dbus_message_unref(reply); + remove_pending(call); +} + +static void hal_get_integer(const char *path, const char *key, void *user_data) +{ + send_method_call("org.freedesktop.Hal", path, + "org.freedesktop.Hal.Device", + "GetPropertyInteger", + hal_battery_level_reply, user_data, + DBUS_TYPE_STRING, &key, + DBUS_TYPE_INVALID); +} + +static gboolean handle_hal_property_modified(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path; + DBusMessageIter iter, array; + dbus_int32_t num_changes; + + path = dbus_message_get_path(msg); + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) { + error("Unexpected signature in hal PropertyModified signal"); + return TRUE; + } + + dbus_message_iter_get_basic(&iter, &num_changes); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in hal PropertyModified signal"); + return TRUE; + } + + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) { + DBusMessageIter prop; + const char *name; + dbus_bool_t added, removed; + + dbus_message_iter_recurse(&array, &prop); + + if (!iter_get_basic_args(&prop, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &added, + DBUS_TYPE_BOOLEAN, &removed, + DBUS_TYPE_INVALID)) { + error("Invalid hal PropertyModified parameters"); + break; + } + + if (g_str_equal(name, "battery.charge_level.last_full")) + hal_get_integer(path, name, &battchg_last); + else if (g_str_equal(name, "battery.charge_level.current")) + hal_get_integer(path, name, &battchg_cur); + else if (g_str_equal(name, "battery.charge_level.design")) + hal_get_integer(path, name, &battchg_design); + + dbus_message_iter_next(&array); + } + + return TRUE; +} + +static void add_watch(const char *sender, const char *path, + const char *interface, const char *member, + GDBusSignalFunction function) +{ + guint watch; + + watch = g_dbus_add_signal_watch(connection, sender, path, interface, + member, function, NULL, NULL); + + watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch)); +} + +static void hal_find_device_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply; + DBusError err; + DBusMessageIter iter, sub; + int type; + const char *path; + + DBG("begin of hal_find_device_reply()"); + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in hal_find_device_reply()"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + type = dbus_message_iter_get_arg_type(&sub); + + if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) { + error("No hal device with battery capability found"); + goto done; + } + + dbus_message_iter_get_basic(&sub, &path); + + DBG("telephony-ofono: found battery device at %s", path); + + add_watch(NULL, path, "org.freedesktop.Hal.Device", + "PropertyModified", handle_hal_property_modified); + + hal_get_integer(path, "battery.charge_level.last_full", &battchg_last); + hal_get_integer(path, "battery.charge_level.current", &battchg_cur); + hal_get_integer(path, "battery.charge_level.design", &battchg_design); +done: + dbus_message_unref(reply); + remove_pending(call); +} + +static void handle_service_connect(DBusConnection *conn, void *user_data) +{ + DBG("telephony-ofono: %s found", OFONO_BUS_NAME); + + send_method_call(OFONO_BUS_NAME, OFONO_PATH, + OFONO_MANAGER_INTERFACE, "GetModems", + get_modems_reply, NULL, DBUS_TYPE_INVALID); +} + +static void handle_service_disconnect(DBusConnection *conn, void *user_data) +{ + DBG("telephony-ofono: %s exitted", OFONO_BUS_NAME); + + if (modem_obj_path) + modem_removed(modem_obj_path); +} + +int telephony_init(void) +{ + const char *battery_cap = "battery"; + int ret; + guint watch; + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + + add_watch(OFONO_BUS_NAME, NULL, OFONO_MODEM_INTERFACE, + "PropertyChanged", handle_modem_property_changed); + add_watch(OFONO_BUS_NAME, NULL, OFONO_NETWORKREG_INTERFACE, + "PropertyChanged", handle_network_property_changed); + add_watch(OFONO_BUS_NAME, NULL, OFONO_MANAGER_INTERFACE, + "ModemAdded", handle_manager_modem_added); + add_watch(OFONO_BUS_NAME, NULL, OFONO_MANAGER_INTERFACE, + "ModemRemoved", handle_manager_modem_removed); + add_watch(OFONO_BUS_NAME, NULL, OFONO_VCMANAGER_INTERFACE, + "CallAdded", handle_vcmanager_call_added); + add_watch(OFONO_BUS_NAME, NULL, OFONO_VCMANAGER_INTERFACE, + "CallRemoved", handle_vcmanager_call_removed); + + watch = g_dbus_add_service_watch(connection, OFONO_BUS_NAME, + handle_service_connect, + handle_service_disconnect, + NULL, NULL); + if (watch == 0) + return -ENOMEM; + + watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch)); + + ret = send_method_call("org.freedesktop.Hal", + "/org/freedesktop/Hal/Manager", + "org.freedesktop.Hal.Manager", + "FindDeviceByCapability", + hal_find_device_reply, NULL, + DBUS_TYPE_STRING, &battery_cap, + DBUS_TYPE_INVALID); + if (ret < 0) + return ret; + + DBG("telephony_init() successfully"); + + return ret; +} + +static void remove_watch(gpointer data) +{ + g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data)); +} + +void telephony_exit(void) +{ + DBG(""); + + g_free(last_dialed_number); + last_dialed_number = NULL; + + if (modem_obj_path) + modem_removed(modem_obj_path); + + g_slist_foreach(watches, (GFunc) remove_watch, NULL); + g_slist_free(watches); + watches = NULL; + + g_slist_foreach(pending, (GFunc) dbus_pending_call_cancel, NULL); + g_slist_foreach(pending, (GFunc) dbus_pending_call_unref, NULL); + g_slist_free(pending); + pending = NULL; + + dbus_connection_unref(connection); + connection = NULL; + + telephony_deinit(); +} diff --git a/audio/telephony.h b/audio/telephony.h new file mode 100644 index 0000000..73b390c --- /dev/null +++ b/audio/telephony.h @@ -0,0 +1,244 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include + +/* Response and hold values */ +#define BTRH_NOT_SUPPORTED -2 +#define BTRH_NONE -1 +#define BTRH_HOLD 0 +#define BTRH_ACCEPT 1 +#define BTRH_REJECT 2 + +/* HFP feature bits */ +#define AG_FEATURE_THREE_WAY_CALLING 0x0001 +#define AG_FEATURE_EC_ANDOR_NR 0x0002 +#define AG_FEATURE_VOICE_RECOGNITION 0x0004 +#define AG_FEATURE_INBAND_RINGTONE 0x0008 +#define AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG 0x0010 +#define AG_FEATURE_REJECT_A_CALL 0x0020 +#define AG_FEATURE_ENHANCED_CALL_STATUS 0x0040 +#define AG_FEATURE_ENHANCED_CALL_CONTROL 0x0080 +#define AG_FEATURE_EXTENDED_ERROR_RESULT_CODES 0x0100 + +#define HF_FEATURE_EC_ANDOR_NR 0x0001 +#define HF_FEATURE_CALL_WAITING_AND_3WAY 0x0002 +#define HF_FEATURE_CLI_PRESENTATION 0x0004 +#define HF_FEATURE_VOICE_RECOGNITION 0x0008 +#define HF_FEATURE_REMOTE_VOLUME_CONTROL 0x0010 +#define HF_FEATURE_ENHANCED_CALL_STATUS 0x0020 +#define HF_FEATURE_ENHANCED_CALL_CONTROL 0x0040 + +/* Indicator event values */ +#define EV_SERVICE_NONE 0 +#define EV_SERVICE_PRESENT 1 + +#define EV_CALL_INACTIVE 0 +#define EV_CALL_ACTIVE 1 + +#define EV_CALLSETUP_INACTIVE 0 +#define EV_CALLSETUP_INCOMING 1 +#define EV_CALLSETUP_OUTGOING 2 +#define EV_CALLSETUP_ALERTING 3 + +#define EV_CALLHELD_NONE 0 +#define EV_CALLHELD_MULTIPLE 1 +#define EV_CALLHELD_ON_HOLD 2 + +#define EV_ROAM_INACTIVE 0 +#define EV_ROAM_ACTIVE 1 + +/* Call parameters */ +#define CALL_DIR_OUTGOING 0 +#define CALL_DIR_INCOMING 1 + +#define CALL_STATUS_ACTIVE 0 +#define CALL_STATUS_HELD 1 +#define CALL_STATUS_DIALING 2 +#define CALL_STATUS_ALERTING 3 +#define CALL_STATUS_INCOMING 4 +#define CALL_STATUS_WAITING 5 + +#define CALL_MODE_VOICE 0 +#define CALL_MODE_DATA 1 +#define CALL_MODE_FAX 2 + +#define CALL_MULTIPARTY_NO 0 +#define CALL_MULTIPARTY_YES 1 + +/* Subscriber number parameters */ +#define SUBSCRIBER_SERVICE_VOICE 4 +#define SUBSCRIBER_SERVICE_FAX 5 + +/* Operator selection mode values */ +#define OPERATOR_MODE_AUTO 0 +#define OPERATOR_MODE_MANUAL 1 +#define OPERATOR_MODE_DEREGISTER 2 +#define OPERATOR_MODE_MANUAL_AUTO 4 + +/* Some common number types */ +#define NUMBER_TYPE_UNKNOWN 128 +#define NUMBER_TYPE_TELEPHONY 129 +#define NUMBER_TYPE_INTERNATIONAL 145 +#define NUMBER_TYPE_NATIONAL 161 +#define NUMBER_TYPE_VOIP 255 + +/* Extended Audio Gateway Error Result Codes */ +typedef enum { + CME_ERROR_NONE = -1, + CME_ERROR_AG_FAILURE = 0, + CME_ERROR_NO_PHONE_CONNECTION = 1, + CME_ERROR_NOT_ALLOWED = 3, + CME_ERROR_NOT_SUPPORTED = 4, + CME_ERROR_PH_SIM_PIN_REQUIRED = 5, + CME_ERROR_SIM_NOT_INSERTED = 10, + CME_ERROR_SIM_PIN_REQUIRED = 11, + CME_ERROR_SIM_PUK_REQUIRED = 12, + CME_ERROR_SIM_FAILURE = 13, + CME_ERROR_SIM_BUSY = 14, + CME_ERROR_INCORRECT_PASSWORD = 16, + CME_ERROR_SIM_PIN2_REQUIRED = 17, + CME_ERROR_SIM_PUK2_REQUIRED = 18, + CME_ERROR_MEMORY_FULL = 20, + CME_ERROR_INVALID_INDEX = 21, + CME_ERROR_MEMORY_FAILURE = 23, + CME_ERROR_TEXT_STRING_TOO_LONG = 24, + CME_ERROR_INVALID_TEXT_STRING = 25, + CME_ERROR_DIAL_STRING_TOO_LONG = 26, + CME_ERROR_INVALID_DIAL_STRING = 27, + CME_ERROR_NO_NETWORK_SERVICE = 30, + CME_ERROR_NETWORK_TIMEOUT = 31, + CME_ERROR_NETWORK_NOT_ALLOWED = 32, +} cme_error_t; + +struct indicator { + const char *desc; + const char *range; + int val; + gboolean ignore_redundant; +}; + +/* Notify telephony-*.c of connected/disconnected devices. Implemented by + * telephony-*.c + */ +void telephony_device_connected(void *telephony_device); +void telephony_device_disconnected(void *telephony_device); + +/* HF requests (sent by the handsfree device). These are implemented by + * telephony-*.c + */ +void telephony_event_reporting_req(void *telephony_device, int ind); +void telephony_response_and_hold_req(void *telephony_device, int rh); +void telephony_last_dialed_number_req(void *telephony_device); +void telephony_terminate_call_req(void *telephony_device); +void telephony_answer_call_req(void *telephony_device); +void telephony_dial_number_req(void *telephony_device, const char *number); +void telephony_transmit_dtmf_req(void *telephony_device, char tone); +void telephony_subscriber_number_req(void *telephony_device); +void telephony_list_current_calls_req(void *telephony_device); +void telephony_operator_selection_req(void *telephony_device); +void telephony_call_hold_req(void *telephony_device, const char *cmd); +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable); +void telephony_voice_dial_req(void *telephony_device, gboolean enable); +void telephony_key_press_req(void *telephony_device, const char *keys); + +/* AG responses to HF requests. These are implemented by headset.c */ +int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err); +int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err); +int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err); +int telephony_terminate_call_rsp(void *telephony_device, cme_error_t err); +int telephony_answer_call_rsp(void *telephony_device, cme_error_t err); +int telephony_dial_number_rsp(void *telephony_device, cme_error_t err); +int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err); +int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err); +int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err); +int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err); +int telephony_call_hold_rsp(void *telephony_device, cme_error_t err); +int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err); +int telephony_voice_dial_rsp(void *telephony_device, cme_error_t err); +int telephony_key_press_rsp(void *telephony_device, cme_error_t err); + +/* Event indications by AG. These are implemented by headset.c */ +int telephony_event_ind(int index); +int telephony_response_and_hold_ind(int rh); +int telephony_incoming_call_ind(const char *number, int type); +int telephony_calling_stopped_ind(void); +int telephony_ready_ind(uint32_t features, const struct indicator *indicators, + int rh, const char *chld); +int telephony_deinit(void); +int telephony_list_current_call_ind(int idx, int dir, int status, int mode, + int mprty, const char *number, + int type); +int telephony_subscriber_number_ind(const char *number, int type, + int service); +int telephony_call_waiting_ind(const char *number, int type); +int telephony_operator_selection_ind(int mode, const char *oper); + +/* Helper function for quick indicator updates */ +static inline int telephony_update_indicator(struct indicator *indicators, + const char *desc, + int new_val) +{ + int i; + struct indicator *ind = NULL; + + for (i = 0; indicators[i].desc != NULL; i++) { + if (g_str_equal(indicators[i].desc, desc)) { + ind = &indicators[i]; + break; + } + } + + if (!ind) + return -ENOENT; + + DBG("Telephony indicator \"%s\" %d->%d", desc, ind->val, new_val); + + if (ind->ignore_redundant && ind->val == new_val) { + DBG("Ignoring no-change indication"); + return 0; + } + + ind->val = new_val; + + return telephony_event_ind(i); +} + +static inline int telephony_get_indicator(const struct indicator *indicators, + const char *desc) +{ + int i; + + for (i = 0; indicators[i].desc != NULL; i++) { + if (g_str_equal(indicators[i].desc, desc)) + return indicators[i].val; + } + + return -ENOENT; +} + +int telephony_init(void); +void telephony_exit(void); diff --git a/audio/transport.c b/audio/transport.c new file mode 100644 index 0000000..8ff6c85 --- /dev/null +++ b/audio/transport.c @@ -0,0 +1,927 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +#include "../src/adapter.h" +#include "../src/dbus-common.h" + +#include "log.h" +#include "error.h" +#include "device.h" +#include "avdtp.h" +#include "media.h" +#include "transport.h" +#include "a2dp.h" +#include "headset.h" + +#ifndef DBUS_TYPE_UNIX_FD +#define DBUS_TYPE_UNIX_FD -1 +#endif + +#define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport" + +struct media_request { + DBusMessage *msg; + guint id; +}; + +struct media_owner { + struct media_transport *transport; + struct media_request *pending; + char *name; + char *accesstype; + guint watch; +}; + +struct media_transport { + DBusConnection *conn; + char *path; /* Transport object path */ + struct audio_device *device; /* Transport device */ + struct avdtp *session; /* Signalling session (a2dp only) */ + struct media_endpoint *endpoint; /* Transport endpoint */ + GSList *owners; /* Transport owners */ + uint8_t *configuration; /* Transport configuration */ + int size; /* Transport configuration size */ + int fd; /* Transport file descriptor */ + uint16_t imtu; /* Transport input mtu */ + uint16_t omtu; /* Transport output mtu */ + uint16_t delay; /* Transport delay (a2dp only) */ + unsigned int nrec_id; /* Transport nrec watch (headset only) */ + gboolean read_lock; + gboolean write_lock; + gboolean in_use; + guint (*resume) (struct media_transport *transport, + struct media_owner *owner); + guint (*suspend) (struct media_transport *transport, + struct media_owner *owner); + void (*cancel) (struct media_transport *transport, + guint id); + void (*get_properties) ( + struct media_transport *transport, + DBusMessageIter *dict); + int (*set_property) ( + struct media_transport *transport, + const char *property, + DBusMessageIter *value); +}; + +void media_transport_destroy(struct media_transport *transport) +{ + char *path; + + path = g_strdup(transport->path); + + g_dbus_unregister_interface(transport->conn, path, + MEDIA_TRANSPORT_INTERFACE); + + g_free(path); +} + +static struct media_request *media_request_create(DBusMessage *msg, guint id) +{ + struct media_request *req; + + req = g_new0(struct media_request, 1); + req->msg = dbus_message_ref(msg); + req->id = id; + + DBG("Request created: method=%s id=%u", dbus_message_get_member(msg), + id); + + return req; +} + +static void media_request_reply(struct media_request *req, + DBusConnection *conn, int err) +{ + DBusMessage *reply; + + DBG("Request %s Reply %s", dbus_message_get_member(req->msg), + strerror(err)); + + if (!err) + reply = g_dbus_create_reply(req->msg, DBUS_TYPE_INVALID); + else + reply = g_dbus_create_error(req->msg, + ERROR_INTERFACE ".Failed", + "%s", strerror(err)); + + g_dbus_send_message(conn, reply); +} + +static gboolean media_transport_release(struct media_transport *transport, + const char *accesstype) +{ + if (g_strstr_len(accesstype, -1, "r") != NULL) { + transport->read_lock = FALSE; + DBG("Transport %s: read lock released", transport->path); + } + + if (g_strstr_len(accesstype, -1, "w") != NULL) { + transport->write_lock = FALSE; + DBG("Transport %s: write lock released", transport->path); + } + + return TRUE; +} + +static void media_owner_remove(struct media_owner *owner) +{ + struct media_transport *transport = owner->transport; + struct media_request *req = owner->pending; + + if (!req) + return; + + DBG("Owner %s Request %s", owner->name, + dbus_message_get_member(req->msg)); + + if (req->id) + transport->cancel(transport, req->id); + + owner->pending = NULL; + if (req->msg) + dbus_message_unref(req->msg); + + g_free(req); +} + +static void media_owner_free(struct media_owner *owner) +{ + DBG("Owner %s", owner->name); + + media_owner_remove(owner); + + g_free(owner->name); + g_free(owner->accesstype); + g_free(owner); +} + +static void media_transport_remove(struct media_transport *transport, + struct media_owner *owner) +{ + DBG("Transport %s Owner %s", transport->path, owner->name); + + media_transport_release(transport, owner->accesstype); + + /* Reply if owner has a pending request */ + if (owner->pending) + media_request_reply(owner->pending, transport->conn, EIO); + + transport->owners = g_slist_remove(transport->owners, owner); + + if (owner->watch) + g_dbus_remove_watch(transport->conn, owner->watch); + + media_owner_free(owner); + + /* Suspend if there is no longer any owner */ + if (transport->owners == NULL && transport->in_use) + transport->suspend(transport, NULL); +} + +static gboolean media_transport_set_fd(struct media_transport *transport, + int fd, uint16_t imtu, uint16_t omtu) +{ + if (transport->fd == fd) + return TRUE; + + transport->fd = fd; + transport->imtu = imtu; + transport->omtu = omtu; + + info("%s: fd(%d) ready", transport->path, fd); + + return TRUE; +} + +static gboolean remove_owner(gpointer data) +{ + struct media_owner *owner = data; + + media_transport_remove(owner->transport, owner); + + return FALSE; +} + +static void a2dp_resume_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct media_owner *owner = user_data; + struct media_request *req = owner->pending; + struct media_transport *transport = owner->transport; + struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint); + struct avdtp_stream *stream; + int fd; + uint16_t imtu, omtu; + gboolean ret; + + req->id = 0; + + if (err) + goto fail; + + stream = a2dp_sep_get_stream(sep); + if (stream == NULL) + goto fail; + + ret = avdtp_stream_get_transport(stream, &fd, &imtu, &omtu, NULL); + if (ret == FALSE) + goto fail; + + media_transport_set_fd(transport, fd, imtu, omtu); + + if (g_strstr_len(owner->accesstype, -1, "r") == NULL) + imtu = 0; + + if (g_strstr_len(owner->accesstype, -1, "w") == NULL) + omtu = 0; + + ret = g_dbus_send_reply(transport->conn, req->msg, + DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_UINT16, &imtu, + DBUS_TYPE_UINT16, &omtu, + DBUS_TYPE_INVALID); + if (ret == FALSE) + goto fail; + + media_owner_remove(owner); + + return; + +fail: + /* Let the stream state change before removing the owner */ + g_idle_add(remove_owner, owner); +} + +static guint resume_a2dp(struct media_transport *transport, + struct media_owner *owner) +{ + struct media_endpoint *endpoint = transport->endpoint; + struct audio_device *device = transport->device; + struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); + + if (transport->session == NULL) { + transport->session = avdtp_get(&device->src, &device->dst); + if (transport->session == NULL) + return 0; + } + + if (transport->in_use == TRUE) + goto done; + + transport->in_use = a2dp_sep_lock(sep, transport->session); + if (transport->in_use == FALSE) + return 0; + +done: + return a2dp_resume(transport->session, sep, a2dp_resume_complete, + owner); +} + +static void a2dp_suspend_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct media_owner *owner = user_data; + struct media_transport *transport = owner->transport; + struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint); + + /* Release always succeeds */ + if (owner->pending) { + owner->pending->id = 0; + media_request_reply(owner->pending, transport->conn, 0); + media_owner_remove(owner); + } + + a2dp_sep_unlock(sep, transport->session); + transport->in_use = FALSE; + media_transport_remove(transport, owner); +} + +static guint suspend_a2dp(struct media_transport *transport, + struct media_owner *owner) +{ + struct media_endpoint *endpoint = transport->endpoint; + struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); + + if (!owner) { + a2dp_sep_unlock(sep, transport->session); + transport->in_use = FALSE; + return 0; + } + + return a2dp_suspend(transport->session, sep, a2dp_suspend_complete, + owner); +} + +static void cancel_a2dp(struct media_transport *transport, guint id) +{ + a2dp_cancel(transport->device, id); +} + +static void headset_resume_complete(struct audio_device *dev, void *user_data) +{ + struct media_owner *owner = user_data; + struct media_request *req = owner->pending; + struct media_transport *transport = owner->transport; + int fd; + uint16_t imtu, omtu; + gboolean ret; + + req->id = 0; + + if (dev == NULL) + goto fail; + + fd = headset_get_sco_fd(dev); + if (fd < 0) + goto fail; + + imtu = 48; + omtu = 48; + + media_transport_set_fd(transport, fd, imtu, omtu); + + if (g_strstr_len(owner->accesstype, -1, "r") == NULL) + imtu = 0; + + if (g_strstr_len(owner->accesstype, -1, "w") == NULL) + omtu = 0; + + ret = g_dbus_send_reply(transport->conn, req->msg, + DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_UINT16, &imtu, + DBUS_TYPE_UINT16, &omtu, + DBUS_TYPE_INVALID); + if (ret == FALSE) + goto fail; + + media_owner_remove(owner); + + return; + +fail: + media_transport_remove(transport, owner); +} + +static guint resume_headset(struct media_transport *transport, + struct media_owner *owner) +{ + struct audio_device *device = transport->device; + + if (transport->in_use == TRUE) + goto done; + + transport->in_use = headset_lock(device, HEADSET_LOCK_READ | + HEADSET_LOCK_WRITE); + if (transport->in_use == FALSE) + return 0; + +done: + return headset_request_stream(device, headset_resume_complete, + owner); +} + +static void headset_suspend_complete(struct audio_device *dev, void *user_data) +{ + struct media_owner *owner = user_data; + struct media_transport *transport = owner->transport; + + /* Release always succeeds */ + if (owner->pending) { + owner->pending->id = 0; + media_request_reply(owner->pending, transport->conn, 0); + media_owner_remove(owner); + } + + headset_unlock(dev, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE); + transport->in_use = FALSE; + media_transport_remove(transport, owner); +} + +static guint suspend_headset(struct media_transport *transport, + struct media_owner *owner) +{ + struct audio_device *device = transport->device; + + if (!owner) { + headset_unlock(device, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE); + transport->in_use = FALSE; + return 0; + } + + return headset_suspend_stream(device, headset_suspend_complete, owner); +} + +static void cancel_headset(struct media_transport *transport, guint id) +{ + headset_cancel_stream(transport->device, id); +} + +static void media_owner_exit(DBusConnection *connection, void *user_data) +{ + struct media_owner *owner = user_data; + + owner->watch = 0; + + media_owner_remove(owner); + + media_transport_remove(owner->transport, owner); +} + +static gboolean media_transport_acquire(struct media_transport *transport, + const char *accesstype) +{ + gboolean read_lock = FALSE, write_lock = FALSE; + + if (g_strstr_len(accesstype, -1, "r") != NULL) { + if (transport->read_lock == TRUE) + return FALSE; + read_lock = TRUE; + } + + if (g_strstr_len(accesstype, -1, "w") != NULL) { + if (transport->write_lock == TRUE) + return FALSE; + write_lock = TRUE; + } + + /* Check invalid accesstype */ + if (read_lock == FALSE && write_lock == FALSE) + return FALSE; + + if (read_lock) { + transport->read_lock = read_lock; + DBG("Transport %s: read lock acquired", transport->path); + } + + if (write_lock) { + transport->write_lock = write_lock; + DBG("Transport %s: write lock acquired", transport->path); + } + + + return TRUE; +} + +static void media_transport_add(struct media_transport *transport, + struct media_owner *owner) +{ + DBG("Transport %s Owner %s", transport->path, owner->name); + transport->owners = g_slist_append(transport->owners, owner); + owner->transport = transport; +} + +static struct media_owner *media_owner_create(DBusConnection *conn, + DBusMessage *msg, + const char *accesstype) +{ + struct media_owner *owner; + + owner = g_new0(struct media_owner, 1); + owner->name = g_strdup(dbus_message_get_sender(msg)); + owner->accesstype = g_strdup(accesstype); + owner->watch = g_dbus_add_disconnect_watch(conn, owner->name, + media_owner_exit, + owner, NULL); + + DBG("Owner created: sender=%s accesstype=%s", owner->name, + accesstype); + + return owner; +} + +static void media_owner_add(struct media_owner *owner, + struct media_request *req) +{ + DBG("Owner %s Request %s", owner->name, + dbus_message_get_member(req->msg)); + + owner->pending = req; +} + +static struct media_owner *media_transport_find_owner( + struct media_transport *transport, + const char *name) +{ + GSList *l; + + for (l = transport->owners; l; l = l->next) { + struct media_owner *owner = l->data; + + if (g_strcmp0(owner->name, name) == 0) + return owner; + } + + return NULL; +} + +static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + struct media_owner *owner; + struct media_request *req; + const char *accesstype, *sender; + guint id; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &accesstype, + DBUS_TYPE_INVALID)) + return NULL; + + sender = dbus_message_get_sender(msg); + + owner = media_transport_find_owner(transport, sender); + if (owner != NULL) + return btd_error_not_authorized(msg); + + if (media_transport_acquire(transport, accesstype) == FALSE) + return btd_error_not_authorized(msg); + + owner = media_owner_create(conn, msg, accesstype); + id = transport->resume(transport, owner); + if (id == 0) { + media_owner_free(owner); + return btd_error_not_authorized(msg); + } + + req = media_request_create(msg, id); + media_owner_add(owner, req); + media_transport_add(transport, owner); + + return NULL; +} + +static DBusMessage *release(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + struct media_owner *owner; + const char *accesstype, *sender; + struct media_request *req; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &accesstype, + DBUS_TYPE_INVALID)) + return NULL; + + sender = dbus_message_get_sender(msg); + + owner = media_transport_find_owner(transport, sender); + if (owner == NULL) + return btd_error_not_authorized(msg); + + if (g_strcmp0(owner->accesstype, accesstype) == 0) { + guint id; + + /* Not the last owner, no need to suspend */ + if (g_slist_length(transport->owners) != 1) { + media_transport_remove(transport, owner); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + } + + if (owner->pending) { + const char *member; + + member = dbus_message_get_member(owner->pending->msg); + /* Cancel Acquire request if that exist */ + if (g_str_equal(member, "Acquire")) + media_owner_remove(owner); + else + return btd_error_in_progress(msg); + } + + id = transport->suspend(transport, owner); + if (id == 0) { + media_transport_remove(transport, owner); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + } + + req = media_request_create(msg, id); + media_owner_add(owner, req); + + return NULL; + } else if (g_strstr_len(owner->accesstype, -1, accesstype) != NULL) { + media_transport_release(transport, accesstype); + g_strdelimit(owner->accesstype, accesstype, ' '); + } else + return btd_error_not_authorized(msg); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static int set_property_a2dp(struct media_transport *transport, + const char *property, + DBusMessageIter *value) +{ + if (g_strcmp0(property, "Delay") == 0) { + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16) + return -EINVAL; + dbus_message_iter_get_basic(value, &transport->delay); + + /* FIXME: send new delay */ + return 0; + } + + return -EINVAL; +} + +static int set_property_headset(struct media_transport *transport, + const char *property, + DBusMessageIter *value) +{ + if (g_strcmp0(property, "NREC") == 0) { + gboolean nrec; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) + return -EINVAL; + dbus_message_iter_get_basic(value, &nrec); + + /* FIXME: set new nrec */ + return 0; + } else if (g_strcmp0(property, "InbandRingtone") == 0) { + gboolean inband; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) + return -EINVAL; + dbus_message_iter_get_basic(value, &inband); + + /* FIXME: set new inband */ + return 0; + } + + return -EINVAL; +} + +static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + DBusMessageIter iter; + DBusMessageIter value; + const char *property, *sender; + GSList *l; + int err; + + if (!dbus_message_iter_init(msg, &iter)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &property); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return btd_error_invalid_args(msg); + dbus_message_iter_recurse(&iter, &value); + + sender = dbus_message_get_sender(msg); + err = -EINVAL; + + /* Check if sender has acquired the transport */ + for (l = transport->owners; l; l = l->next) { + struct media_owner *owner = l->data; + + if (g_strcmp0(owner->name, sender) == 0) { + err = transport->set_property(transport, property, + &value); + break; + } + } + + if (err < 0) { + if (err == -EINVAL) + return btd_error_invalid_args(msg); + return btd_error_failed(msg, strerror(-err)); + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static void get_properties_a2dp(struct media_transport *transport, + DBusMessageIter *dict) +{ + dict_append_entry(dict, "Delay", DBUS_TYPE_UINT16, &transport->delay); +} + +static void get_properties_headset(struct media_transport *transport, + DBusMessageIter *dict) +{ + gboolean nrec, inband; + const char *routing; + + nrec = headset_get_nrec(transport->device); + dict_append_entry(dict, "NREC", DBUS_TYPE_BOOLEAN, &nrec); + + inband = headset_get_inband(transport->device); + dict_append_entry(dict, "InbandRingtone", DBUS_TYPE_BOOLEAN, &inband); + + routing = headset_get_sco_hci(transport->device) ? "HCI" : "PCM"; + dict_append_entry(dict, "Routing", DBUS_TYPE_STRING, &routing); +} + +void transport_get_properties(struct media_transport *transport, + DBusMessageIter *iter) +{ + DBusMessageIter dict; + const char *uuid; + uint8_t codec; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + /* Device */ + dict_append_entry(&dict, "Device", DBUS_TYPE_OBJECT_PATH, + &transport->device->path); + + uuid = media_endpoint_get_uuid(transport->endpoint); + dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid); + + codec = media_endpoint_get_codec(transport->endpoint); + dict_append_entry(&dict, "Codec", DBUS_TYPE_BYTE, &codec); + + dict_append_array(&dict, "Configuration", DBUS_TYPE_BYTE, + &transport->configuration, transport->size); + + if (transport->get_properties) + transport->get_properties(transport, &dict); + + dbus_message_iter_close_container(iter, &dict); +} + +static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + DBusMessage *reply; + DBusMessageIter iter; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + transport_get_properties(transport, &iter); + + return reply; +} + +static GDBusMethodTable transport_methods[] = { + { "GetProperties", "", "a{sv}", get_properties }, + { "Acquire", "s", "h", acquire, + G_DBUS_METHOD_FLAG_ASYNC}, + { "Release", "s", "", release, + G_DBUS_METHOD_FLAG_ASYNC}, + { "SetProperty", "sv", "", set_property }, + { }, +}; + +static GDBusSignalTable transport_signals[] = { + { "PropertyChanged", "sv" }, + { } +}; + +static void media_transport_free(void *data) +{ + struct media_transport *transport = data; + GSList *l; + + for (l = transport->owners; l; l = l->next) + media_transport_remove(transport, l->data); + + g_slist_free(transport->owners); + + if (transport->session) + avdtp_unref(transport->session); + + if (transport->nrec_id) + headset_remove_nrec_cb(transport->device, transport->nrec_id); + + if (transport->conn) + dbus_connection_unref(transport->conn); + + g_free(transport->configuration); + g_free(transport->path); + g_free(transport); +} + +static void headset_nrec_changed(struct audio_device *dev, gboolean nrec, + void *user_data) +{ + struct media_transport *transport = user_data; + + DBG(""); + + emit_property_changed(transport->conn, transport->path, + MEDIA_TRANSPORT_INTERFACE, "NREC", + DBUS_TYPE_BOOLEAN, &nrec); +} + +struct media_transport *media_transport_create(DBusConnection *conn, + struct media_endpoint *endpoint, + struct audio_device *device, + uint8_t *configuration, + size_t size) +{ + struct media_transport *transport; + const char *uuid; + static int fd = 0; + + transport = g_new0(struct media_transport, 1); + transport->conn = dbus_connection_ref(conn); + transport->device = device; + transport->endpoint = endpoint; + transport->configuration = g_new(uint8_t, size); + memcpy(transport->configuration, configuration, size); + transport->size = size; + transport->path = g_strdup_printf("%s/fd%d", device->path, fd++); + transport->fd = -1; + + uuid = media_endpoint_get_uuid(endpoint); + if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0 || + strcasecmp(uuid, A2DP_SINK_UUID) == 0) { + transport->resume = resume_a2dp; + transport->suspend = suspend_a2dp; + transport->cancel = cancel_a2dp; + transport->get_properties = get_properties_a2dp; + transport->set_property = set_property_a2dp; + } else if (strcasecmp(uuid, HFP_AG_UUID) == 0 || + strcasecmp(uuid, HSP_AG_UUID) == 0) { + transport->resume = resume_headset; + transport->suspend = suspend_headset; + transport->cancel = cancel_headset; + transport->get_properties = get_properties_headset; + transport->set_property = set_property_headset; + transport->nrec_id = headset_add_nrec_cb(device, + headset_nrec_changed, + transport); + } else + goto fail; + + if (g_dbus_register_interface(transport->conn, transport->path, + MEDIA_TRANSPORT_INTERFACE, + transport_methods, transport_signals, NULL, + transport, media_transport_free) == FALSE) { + error("Could not register transport %s", transport->path); + goto fail; + } + + return transport; + +fail: + media_transport_free(transport); + return NULL; +} + +const char *media_transport_get_path(struct media_transport *transport) +{ + return transport->path; +} + +void media_transport_update_delay(struct media_transport *transport, + uint16_t delay) +{ + /* Check if delay really changed */ + if (transport->delay == delay) + return; + + transport->delay = delay; + + emit_property_changed(transport->conn, transport->path, + MEDIA_TRANSPORT_INTERFACE, "Delay", + DBUS_TYPE_UINT16, &transport->delay); +} diff --git a/audio/transport.h b/audio/transport.h new file mode 100644 index 0000000..be4d666 --- /dev/null +++ b/audio/transport.h @@ -0,0 +1,38 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct media_transport; + +struct media_transport *media_transport_create(DBusConnection *conn, + struct media_endpoint *endpoint, + struct audio_device *device, + uint8_t *configuration, + size_t size); + +void media_transport_destroy(struct media_transport *transport); +const char *media_transport_get_path(struct media_transport *transport); +void media_transport_update_delay(struct media_transport *transport, + uint16_t delay); +void transport_get_properties(struct media_transport *transport, + DBusMessageIter *iter); diff --git a/audio/unix.c b/audio/unix.c new file mode 100644 index 0000000..37c772d --- /dev/null +++ b/audio/unix.c @@ -0,0 +1,1917 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "log.h" +#include "ipc.h" +#include "device.h" +#include "manager.h" +#include "avdtp.h" +#include "media.h" +#include "a2dp.h" +#include "headset.h" +#include "sink.h" +#include "gateway.h" +#include "unix.h" +#include "glib-helper.h" + +#define check_nul(str) (str[sizeof(str) - 1] == '\0') + +typedef enum { + TYPE_NONE, + TYPE_HEADSET, + TYPE_GATEWAY, + TYPE_SINK, + TYPE_SOURCE +} service_type_t; + +typedef void (*notify_cb_t) (struct audio_device *dev, void *data); + +struct a2dp_data { + struct avdtp *session; + struct avdtp_stream *stream; + struct a2dp_sep *sep; +}; + +struct headset_data { + gboolean locked; +}; + +struct unix_client { + struct audio_device *dev; + GSList *caps; + service_type_t type; + char *interface; + uint8_t seid; + union { + struct a2dp_data a2dp; + struct headset_data hs; + } d; + int sock; + int lock; + int data_fd; /* To be deleted once two phase configuration is fully implemented */ + unsigned int req_id; + unsigned int cb_id; + gboolean (*cancel) (struct audio_device *dev, unsigned int id); +}; + +static GSList *clients = NULL; + +static int unix_sock = -1; + +static void client_free(struct unix_client *client) +{ + DBG("client_free(%p)", client); + + if (client->cancel && client->dev && client->req_id > 0) + client->cancel(client->dev, client->req_id); + + if (client->sock >= 0) + close(client->sock); + + if (client->caps) { + g_slist_foreach(client->caps, (GFunc) g_free, NULL); + g_slist_free(client->caps); + } + + g_free(client->interface); + g_free(client); +} + +static int set_nonblocking(int fd) +{ + long arg; + + arg = fcntl(fd, F_GETFL); + if (arg < 0) + return -errno; + + /* Return if already nonblocking */ + if (arg & O_NONBLOCK) + return 0; + + arg |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, arg) < 0) + return -errno; + + return 0; +} + +/* Pass file descriptor through local domain sockets (AF_LOCAL, formerly + * AF_UNIX) and the sendmsg() system call with the cmsg_type field of a "struct + * cmsghdr" set to SCM_RIGHTS and the data being an integer value equal to the + * handle of the file descriptor to be passed. */ +static int unix_sendmsg_fd(int sock, int fd) +{ + char cmsg_b[CMSG_SPACE(sizeof(int))], m = 'm'; + struct cmsghdr *cmsg; + struct iovec iov = { &m, sizeof(m) }; + struct msghdr msgh; + + memset(&msgh, 0, sizeof(msgh)); + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = &cmsg_b; + msgh.msg_controllen = CMSG_LEN(sizeof(int)); + + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + /* Initialize the payload */ + memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + + return sendmsg(sock, &msgh, MSG_NOSIGNAL); +} + +static void unix_ipc_sendmsg(struct unix_client *client, + const bt_audio_msg_header_t *msg) +{ + const char *type = bt_audio_strtype(msg->type); + const char *name = bt_audio_strname(msg->name); + + DBG("Audio API: %s -> %s", type, name); + + if (send(client->sock, msg, msg->length, 0) < 0) + error("Error %s(%d)", strerror(errno), errno); +} + +static void unix_ipc_error(struct unix_client *client, uint8_t name, int err) +{ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + bt_audio_error_t *rsp = (void *) buf; + + if (!g_slist_find(clients, client)) + return; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_ERROR; + rsp->h.name = name; + rsp->h.length = sizeof(*rsp); + + rsp->posix_errno = err; + + DBG("sending error %s(%d)", strerror(err), err); + unix_ipc_sendmsg(client, &rsp->h); +} + +static service_type_t select_service(struct audio_device *dev, const char *interface) +{ + if (!interface) { + if (dev->sink && avdtp_is_connected(&dev->src, &dev->dst)) + return TYPE_SINK; + else if (dev->source && avdtp_is_connected(&dev->src, + &dev->dst)) + return TYPE_SOURCE; + else if (dev->headset && headset_is_active(dev)) + return TYPE_HEADSET; + else if (dev->sink) + return TYPE_SINK; + else if (dev->source) + return TYPE_SOURCE; + else if (dev->headset) + return TYPE_HEADSET; + } else if (!strcmp(interface, AUDIO_SOURCE_INTERFACE) && dev->source) + return TYPE_SOURCE; + else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink) + return TYPE_SINK; + else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset) + return TYPE_HEADSET; + else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway) + return TYPE_GATEWAY; + + return TYPE_NONE; +} + +static void stream_state_changed(struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data) +{ + struct unix_client *client = user_data; + struct a2dp_data *a2dp = &client->d.a2dp; + + switch (new_state) { + case AVDTP_STATE_IDLE: + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + if (a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + a2dp->stream = NULL; + client->cb_id = 0; + break; + default: + break; + } +} + +static uint8_t headset_generate_capability(struct audio_device *dev, + codec_capabilities_t *codec) +{ + pcm_capabilities_t *pcm; + + codec->seid = BT_A2DP_SEID_RANGE + 1; + codec->transport = BT_CAPABILITIES_TRANSPORT_SCO; + codec->type = BT_HFP_CODEC_PCM; + codec->length = sizeof(*pcm); + + pcm = (void *) codec; + pcm->sampling_rate = 8000; + if (dev->headset) { + if (headset_get_nrec(dev)) + pcm->flags |= BT_PCM_FLAG_NREC; + if (!headset_get_sco_hci(dev)) + pcm->flags |= BT_PCM_FLAG_PCM_ROUTING; + codec->configured = headset_is_active(dev); + codec->lock = headset_get_lock(dev); + } else { + pcm->flags |= BT_PCM_FLAG_NREC; + codec->configured = TRUE; + codec->lock = 0; + } + + return codec->length; +} + +static void headset_discovery_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_get_capabilities_rsp *rsp = (void *) buf; + uint8_t length; + + client->req_id = 0; + + if (!dev) + goto failed; + + memset(buf, 0, sizeof(buf)); + + length = headset_generate_capability(dev, (void *) rsp->data); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_GET_CAPABILITIES; + rsp->h.length = sizeof(*rsp) + length; + + ba2str(&dev->src, rsp->source); + ba2str(&dev->dst, rsp->destination); + strncpy(rsp->object, dev->path, sizeof(rsp->object)); + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("discovery failed"); + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); +} + +static void headset_setup_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + + client->req_id = 0; + + if (!dev) + goto failed; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_SET_CONFIGURATION; + rsp->h.length = sizeof(*rsp); + + rsp->link_mtu = 48; + + client->data_fd = headset_get_sco_fd(dev); + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("config failed"); + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); +} + +static void gateway_setup_complete(struct audio_device *dev, GError *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + + if (err) { + unix_ipc_error(client, BT_SET_CONFIGURATION, err->code); + return; + } + + client->req_id = 0; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_SET_CONFIGURATION; + rsp->h.length = sizeof(*rsp); + + rsp->link_mtu = 48; + + client->data_fd = gateway_get_sco_fd(dev); + + unix_ipc_sendmsg(client, &rsp->h); +} + +static void headset_resume_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + + client->req_id = 0; + + if (!dev) + goto failed; + + client->data_fd = headset_get_sco_fd(dev); + if (client->data_fd < 0) { + error("Unable to get a SCO fd"); + goto failed; + } + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_START_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.type = BT_INDICATION; + ind->h.name = BT_NEW_STREAM; + ind->h.length = sizeof(*ind); + + unix_ipc_sendmsg(client, &ind->h); + + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + goto failed; + } + + return; + +failed: + error("headset_resume_complete: resume failed"); + unix_ipc_error(client, BT_START_STREAM, EIO); +} + +static void gateway_resume_complete(struct audio_device *dev, GError *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + + if (err) { + unix_ipc_error(client, BT_START_STREAM, err->code); + return; + } + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_START_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.type = BT_INDICATION; + ind->h.name = BT_NEW_STREAM; + ind->h.length = sizeof(*ind); + + unix_ipc_sendmsg(client, &ind->h); + + client->data_fd = gateway_get_sco_fd(dev); + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + unix_ipc_error(client, BT_START_STREAM, EIO); + } + + client->req_id = 0; +} + +static void headset_suspend_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_stop_stream_rsp *rsp = (void *) buf; + + if (!dev) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_STOP_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("suspend failed"); + unix_ipc_error(client, BT_STOP_STREAM, EIO); +} + +static void print_mpeg12(struct mpeg_codec_cap *mpeg) +{ + DBG("Media Codec: MPEG12" + " Channel Modes: %s%s%s%s" + " Frequencies: %s%s%s%s%s%s" + " Layers: %s%s%s" + " CRC: %s", + mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO ? "Mono " : "", + mpeg->channel_mode & MPEG_CHANNEL_MODE_DUAL_CHANNEL ? + "DualChannel " : "", + mpeg->channel_mode & MPEG_CHANNEL_MODE_STEREO ? "Stereo " : "", + mpeg->channel_mode & MPEG_CHANNEL_MODE_JOINT_STEREO ? + "JointStereo " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_16000 ? "16Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_22050 ? "22.05Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_24000 ? "24Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_32000 ? "32Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_44100 ? "44.1Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_48000 ? "48Khz " : "", + mpeg->layer & MPEG_LAYER_MP1 ? "1 " : "", + mpeg->layer & MPEG_LAYER_MP2 ? "2 " : "", + mpeg->layer & MPEG_LAYER_MP3 ? "3 " : "", + mpeg->crc ? "Yes" : "No"); +} + +static void print_sbc(struct sbc_codec_cap *sbc) +{ + DBG("Media Codec: SBC" + " Channel Modes: %s%s%s%s" + " Frequencies: %s%s%s%s" + " Subbands: %s%s" + " Blocks: %s%s%s%s" + " Bitpool: %d-%d", + sbc->channel_mode & SBC_CHANNEL_MODE_MONO ? "Mono " : "", + sbc->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL ? + "DualChannel " : "", + sbc->channel_mode & SBC_CHANNEL_MODE_STEREO ? "Stereo " : "", + sbc->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO ? "JointStereo" : "", + sbc->frequency & SBC_SAMPLING_FREQ_16000 ? "16Khz " : "", + sbc->frequency & SBC_SAMPLING_FREQ_32000 ? "32Khz " : "", + sbc->frequency & SBC_SAMPLING_FREQ_44100 ? "44.1Khz " : "", + sbc->frequency & SBC_SAMPLING_FREQ_48000 ? "48Khz " : "", + sbc->subbands & SBC_SUBBANDS_4 ? "4 " : "", + sbc->subbands & SBC_SUBBANDS_8 ? "8 " : "", + sbc->block_length & SBC_BLOCK_LENGTH_4 ? "4 " : "", + sbc->block_length & SBC_BLOCK_LENGTH_8 ? "8 " : "", + sbc->block_length & SBC_BLOCK_LENGTH_12 ? "12 " : "", + sbc->block_length & SBC_BLOCK_LENGTH_16 ? "16 " : "", + sbc->min_bitpool, sbc->max_bitpool); +} + +static int a2dp_append_codec(struct bt_get_capabilities_rsp *rsp, + struct avdtp_service_capability *cap, + uint8_t seid, + uint8_t type, + uint8_t configured, + uint8_t lock) +{ + struct avdtp_media_codec_capability *codec_cap = (void *) cap->data; + codec_capabilities_t *codec = (void *) rsp + rsp->h.length; + size_t space_left; + + if (rsp->h.length > BT_SUGGESTED_BUFFER_SIZE) + return -ENOMEM; + + space_left = BT_SUGGESTED_BUFFER_SIZE - rsp->h.length; + + /* endianess prevent direct cast */ + if (codec_cap->media_codec_type == A2DP_CODEC_SBC) { + struct sbc_codec_cap *sbc_cap = (void *) codec_cap; + sbc_capabilities_t *sbc = (void *) codec; + + if (space_left < sizeof(sbc_capabilities_t)) + return -ENOMEM; + + if (type == AVDTP_SEP_TYPE_SINK) + codec->type = BT_A2DP_SBC_SINK; + else if (type == AVDTP_SEP_TYPE_SOURCE) + codec->type = BT_A2DP_SBC_SOURCE; + else + return -EINVAL; + + codec->length = sizeof(sbc_capabilities_t); + + sbc->channel_mode = sbc_cap->channel_mode; + sbc->frequency = sbc_cap->frequency; + sbc->allocation_method = sbc_cap->allocation_method; + sbc->subbands = sbc_cap->subbands; + sbc->block_length = sbc_cap->block_length; + sbc->min_bitpool = sbc_cap->min_bitpool; + sbc->max_bitpool = sbc_cap->max_bitpool; + + print_sbc(sbc_cap); + } else if (codec_cap->media_codec_type == A2DP_CODEC_MPEG12) { + struct mpeg_codec_cap *mpeg_cap = (void *) codec_cap; + mpeg_capabilities_t *mpeg = (void *) codec; + + if (space_left < sizeof(mpeg_capabilities_t)) + return -ENOMEM; + + if (type == AVDTP_SEP_TYPE_SINK) + codec->type = BT_A2DP_MPEG12_SINK; + else if (type == AVDTP_SEP_TYPE_SOURCE) + codec->type = BT_A2DP_MPEG12_SOURCE; + else + return -EINVAL; + + codec->length = sizeof(mpeg_capabilities_t); + + mpeg->channel_mode = mpeg_cap->channel_mode; + mpeg->crc = mpeg_cap->crc; + mpeg->layer = mpeg_cap->layer; + mpeg->frequency = mpeg_cap->frequency; + mpeg->mpf = mpeg_cap->mpf; + mpeg->bitrate = mpeg_cap->bitrate; + + print_mpeg12(mpeg_cap); + } else { + size_t codec_length, type_length, total_length; + + codec_length = cap->length - (sizeof(struct avdtp_service_capability) + + sizeof(struct avdtp_media_codec_capability)); + type_length = sizeof(codec_cap->media_codec_type); + total_length = type_length + codec_length + + sizeof(codec_capabilities_t); + + if (space_left < total_length) + return -ENOMEM; + + if (type == AVDTP_SEP_TYPE_SINK) + codec->type = BT_A2DP_UNKNOWN_SINK; + else if (type == AVDTP_SEP_TYPE_SOURCE) + codec->type = BT_A2DP_UNKNOWN_SOURCE; + else + return -EINVAL; + + codec->length = total_length; + memcpy(codec->data, &codec_cap->media_codec_type, type_length); + memcpy(codec->data + type_length, codec_cap->data, + codec_length); + } + + codec->seid = seid; + codec->configured = configured; + codec->lock = lock; + rsp->h.length += codec->length; + + DBG("Append %s seid %d - length %d - total %d", + configured ? "configured" : "", seid, codec->length, + rsp->h.length); + + return 0; +} + +static void a2dp_discovery_complete(struct avdtp *session, GSList *seps, + struct avdtp_error *err, + void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_get_capabilities_rsp *rsp = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + GSList *l; + + if (!g_slist_find(clients, client)) { + DBG("Client disconnected during discovery"); + return; + } + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + client->req_id = 0; + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_GET_CAPABILITIES; + rsp->h.length = sizeof(*rsp); + ba2str(&client->dev->src, rsp->source); + ba2str(&client->dev->dst, rsp->destination); + strncpy(rsp->object, client->dev->path, sizeof(rsp->object)); + + for (l = seps; l; l = g_slist_next(l)) { + struct avdtp_remote_sep *rsep = l->data; + struct a2dp_sep *sep; + struct avdtp_service_capability *cap; + struct avdtp_stream *stream; + uint8_t type, seid, configured = 0, lock = 0; + GSList *cl; + + type = avdtp_get_type(rsep); + + if (type != AVDTP_SEP_TYPE_SINK && + type != AVDTP_SEP_TYPE_SOURCE) + continue; + + cap = avdtp_get_codec(rsep); + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + seid = avdtp_get_seid(rsep); + + if (client->seid != 0 && client->seid != seid) + continue; + + stream = avdtp_get_stream(rsep); + if (stream) { + configured = 1; + if (client->seid == seid) + cap = avdtp_stream_get_codec(stream); + } + + for (cl = clients; cl; cl = cl->next) { + struct unix_client *c = cl->data; + struct a2dp_data *ca2dp = &c->d.a2dp; + + if (ca2dp->session == session && c->seid == seid) { + lock = c->lock; + break; + } + } + + sep = a2dp_get_sep(session, stream); + if (sep && a2dp_sep_get_lock(sep)) + lock = BT_WRITE_LOCK; + + a2dp_append_codec(rsp, cap, seid, type, configured, lock); + } + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("discovery failed"); + unix_ipc_error(client, BT_GET_CAPABILITIES, EIO); + + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + + avdtp_unref(a2dp->session); + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_config_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + uint16_t imtu, omtu; + GSList *caps; + + client->req_id = 0; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + + if (!stream) + goto failed; + + if (client->cb_id > 0) + avdtp_stream_remove_cb(a2dp->session, a2dp->stream, + client->cb_id); + + a2dp->sep = sep; + a2dp->stream = stream; + + if (!avdtp_stream_get_transport(stream, &client->data_fd, &imtu, &omtu, + &caps)) { + error("Unable to get stream transport"); + goto failed; + } + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_SET_CONFIGURATION; + rsp->h.length = sizeof(*rsp); + + /* FIXME: Use imtu when fd_opt is CFG_FD_OPT_READ */ + rsp->link_mtu = omtu; + + unix_ipc_sendmsg(client, &rsp->h); + + client->cb_id = avdtp_stream_add_cb(session, stream, + stream_state_changed, client); + + return; + +failed: + error("config failed"); + + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); + + avdtp_unref(a2dp->session); + + a2dp->session = NULL; + a2dp->stream = NULL; + a2dp->sep = NULL; +} + +static void a2dp_resume_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_START_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.type = BT_RESPONSE; + ind->h.name = BT_NEW_STREAM; + rsp->h.length = sizeof(*ind); + + unix_ipc_sendmsg(client, &ind->h); + + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + goto failed; + } + + return; + +failed: + error("resume failed"); + + unix_ipc_error(client, BT_START_STREAM, EIO); + + if (client->cb_id > 0) { + avdtp_stream_remove_cb(a2dp->session, a2dp->stream, + client->cb_id); + client->cb_id = 0; + } + + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + + avdtp_unref(a2dp->session); + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_suspend_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_stop_stream_rsp *rsp = (void *) buf; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_STOP_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("suspend failed"); + + unix_ipc_error(client, BT_STOP_STREAM, EIO); +} + +static void start_discovery(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + int err = 0; + + switch (client->type) { + case TYPE_SINK: + case TYPE_SOURCE: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + err = avdtp_discover(a2dp->session, a2dp_discovery_complete, + client); + if (err) { + if (a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + goto failed; + } + break; + + case TYPE_HEADSET: + case TYPE_GATEWAY: + headset_discovery_complete(dev, client); + break; + + default: + error("No known services for device"); + goto failed; + } + + client->dev = dev; + + return; + +failed: + unix_ipc_error(client, BT_GET_CAPABILITIES, err ? : EIO); +} + +static void open_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_open_rsp *rsp = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_OPEN; + rsp->h.length = sizeof(*rsp); + + ba2str(&dev->src, rsp->source); + ba2str(&dev->dst, rsp->destination); + strncpy(rsp->object, dev->path, sizeof(rsp->object)); + + unix_ipc_sendmsg(client, &rsp->h); +} + +static void start_open(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + struct headset_data *hs; + struct avdtp_remote_sep *rsep; + gboolean unref_avdtp_on_fail = FALSE; + + switch (client->type) { + case TYPE_SINK: + case TYPE_SOURCE: + a2dp = &client->d.a2dp; + + if (!a2dp->session) { + a2dp->session = avdtp_get(&dev->src, &dev->dst); + unref_avdtp_on_fail = TRUE; + } + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (a2dp->sep) { + error("Client already has an opened session"); + goto failed; + } + + rsep = avdtp_get_remote_sep(a2dp->session, client->seid); + if (!rsep) { + error("Invalid seid %d", client->seid); + goto failed; + } + + a2dp->sep = a2dp_get(a2dp->session, rsep); + if (!a2dp->sep) { + error("seid %d not available or locked", client->seid); + goto failed; + } + + if (!a2dp_sep_lock(a2dp->sep, a2dp->session)) { + error("Unable to open seid %d", client->seid); + a2dp->sep = NULL; + goto failed; + } + + break; + + case TYPE_HEADSET: + hs = &client->d.hs; + + if (hs->locked) { + error("Client already has an opened session"); + goto failed; + } + + hs->locked = headset_lock(dev, client->lock); + if (!hs->locked) { + error("Unable to open seid %d", client->seid); + goto failed; + } + break; + + case TYPE_GATEWAY: + break; + default: + error("No known services for device"); + goto failed; + } + + client->dev = dev; + + open_complete(dev, client); + + return; + +failed: + if (unref_avdtp_on_fail && a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + unix_ipc_error(client, BT_OPEN, EINVAL); +} + +static void start_config(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + struct headset_data *hs; + unsigned int id; + + switch (client->type) { + case TYPE_SINK: + case TYPE_SOURCE: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (!a2dp->sep) { + error("seid %d not opened", client->seid); + goto failed; + } + + id = a2dp_config(a2dp->session, a2dp->sep, a2dp_config_complete, + client->caps, client); + client->cancel = a2dp_cancel; + break; + + case TYPE_HEADSET: + hs = &client->d.hs; + + if (!hs->locked) { + error("seid %d not opened", client->seid); + goto failed; + } + + id = headset_config_stream(dev, TRUE, headset_setup_complete, + client); + client->cancel = headset_cancel_stream; + break; + case TYPE_GATEWAY: + if (gateway_config_stream(dev, gateway_setup_complete, client) >= 0) { + client->cancel = gateway_cancel_stream; + id = 1; + } else + id = 0; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("config failed"); + goto failed; + } + + client->req_id = id; + g_slist_free(client->caps); + client->caps = NULL; + + return; + +failed: + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); +} + +static void start_resume(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp = NULL; + struct headset_data *hs; + unsigned int id; + struct avdtp *session = NULL; + + switch (client->type) { + case TYPE_SINK: + case TYPE_SOURCE: + a2dp = &client->d.a2dp; + + if (!a2dp->sep) { + error("seid not opened"); + goto failed; + } + + if (!a2dp->session) { + session = avdtp_get(&dev->src, &dev->dst); + if (!session) { + error("Unable to get a session"); + goto failed; + } + a2dp->session = session; + } + + id = a2dp_resume(a2dp->session, a2dp->sep, a2dp_resume_complete, + client); + client->cancel = a2dp_cancel; + + break; + + case TYPE_HEADSET: + hs = &client->d.hs; + + if (!hs->locked) { + error("seid not opened"); + goto failed; + } + + id = headset_request_stream(dev, headset_resume_complete, + client); + client->cancel = headset_cancel_stream; + break; + + case TYPE_GATEWAY: + if (gateway_request_stream(dev, gateway_resume_complete, client)) + id = 1; + else + id = 0; + client->cancel = gateway_cancel_stream; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("start_resume: resume failed"); + goto failed; + } + + client->req_id = id; + + return; + +failed: + if (session) { + avdtp_unref(session); + a2dp->session = NULL; + } + + unix_ipc_error(client, BT_START_STREAM, EIO); +} + +static void start_suspend(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp = NULL; + struct headset_data *hs; + unsigned int id; + struct avdtp *session = NULL; + + switch (client->type) { + case TYPE_SINK: + case TYPE_SOURCE: + a2dp = &client->d.a2dp; + + if (!a2dp->sep) { + error("seid not opened"); + goto failed; + } + + if (!a2dp->session) { + session = avdtp_get(&dev->src, &dev->dst); + if (!session) { + error("Unable to get a session"); + goto failed; + } + a2dp->session = session; + } + + if (!a2dp->sep) { + error("Unable to get a sep"); + goto failed; + } + + id = a2dp_suspend(a2dp->session, a2dp->sep, + a2dp_suspend_complete, client); + client->cancel = a2dp_cancel; + break; + + case TYPE_HEADSET: + hs = &client->d.hs; + + if (!hs->locked) { + error("seid not opened"); + goto failed; + } + + id = headset_suspend_stream(dev, headset_suspend_complete, + client); + client->cancel = headset_cancel_stream; + break; + + case TYPE_GATEWAY: + gateway_suspend_stream(dev); + client->cancel = gateway_cancel_stream; + headset_suspend_complete(dev, client); + id = 1; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("suspend failed"); + goto failed; + } + + return; + +failed: + if (session) { + avdtp_unref(session); + a2dp->session = NULL; + } + + unix_ipc_error(client, BT_STOP_STREAM, EIO); +} + +static void close_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_close_rsp *rsp = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_CLOSE; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + return; +} + +static void start_close(struct audio_device *dev, struct unix_client *client, + gboolean reply) +{ + struct a2dp_data *a2dp; + struct headset_data *hs; + + if (!client->dev) + goto failed; + + switch (client->type) { + case TYPE_HEADSET: + hs = &client->d.hs; + + if (client->dev && hs->locked) { + headset_unlock(client->dev, client->lock); + hs->locked = FALSE; + } + break; + case TYPE_GATEWAY: + break; + case TYPE_SOURCE: + case TYPE_SINK: + a2dp = &client->d.a2dp; + + if (client->cb_id > 0) { + avdtp_stream_remove_cb(a2dp->session, a2dp->stream, + client->cb_id); + client->cb_id = 0; + } + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + if (a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + a2dp->stream = NULL; + break; + default: + error("No known services for device"); + goto failed; + } + + if (!reply) + return; + + close_complete(dev, client); + client->dev = NULL; + + return; + +failed: + if (reply) + unix_ipc_error(client, BT_STOP_STREAM, EINVAL); +} + +static void handle_getcapabilities_req(struct unix_client *client, + struct bt_get_capabilities_req *req) +{ + struct audio_device *dev; + bdaddr_t src, dst; + int err = EIO; + const char *interface; + + if (!check_nul(req->source) || !check_nul(req->destination) || + !check_nul(req->object)) { + err = EINVAL; + goto failed; + } + + str2ba(req->source, &src); + str2ba(req->destination, &dst); + + if (!manager_find_device(req->object, &src, &dst, NULL, FALSE)) + goto failed; + + if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO) + interface = AUDIO_HEADSET_INTERFACE; + else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + interface = AUDIO_SINK_INTERFACE; + else + interface = client->interface; + + dev = manager_find_device(req->object, &src, &dst, interface, TRUE); + if (!dev && (req->flags & BT_FLAG_AUTOCONNECT)) + dev = manager_find_device(req->object, &src, &dst, + interface, FALSE); + + if (!dev) { + if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO) + interface = AUDIO_GATEWAY_INTERFACE; + else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + interface = AUDIO_SOURCE_INTERFACE; + else + interface = NULL; + dev = manager_find_device(req->object, &src, &dst, + interface, TRUE); + if (!dev && (req->flags & BT_FLAG_AUTOCONNECT)) + dev = manager_find_device(req->object, &src, &dst, + interface, FALSE); + } + + if (!dev) { + error("Unable to find a matching device"); + goto failed; + } + + client->type = select_service(dev, interface); + if (client->type == TYPE_NONE) { + error("No matching service found"); + goto failed; + } + + if (g_strcmp0(interface, client->interface) != 0) { + g_free(client->interface); + client->interface = g_strdup(interface); + } + + client->seid = req->seid; + + start_discovery(dev, client); + + return; + +failed: + unix_ipc_error(client, BT_GET_CAPABILITIES, err); +} + +static int handle_sco_open(struct unix_client *client, struct bt_open_req *req) +{ + if (!client->interface) + client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); + else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) && + !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE)) + return -EIO; + + DBG("open sco - object=%s source=%s destination=%s lock=%s%s", + strcmp(req->object, "") ? req->object : "ANY", + strcmp(req->source, "") ? req->source : "ANY", + strcmp(req->destination, "") ? req->destination : "ANY", + req->lock & BT_READ_LOCK ? "read" : "", + req->lock & BT_WRITE_LOCK ? "write" : ""); + + return 0; +} + +static int handle_a2dp_open(struct unix_client *client, struct bt_open_req *req) +{ + if (!client->interface) + /* FIXME: are we treating a sink or a source? */ + client->interface = g_strdup(AUDIO_SINK_INTERFACE); + else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE) && + !g_str_equal(client->interface, AUDIO_SOURCE_INTERFACE)) + return -EIO; + + DBG("open a2dp - object=%s source=%s destination=%s lock=%s%s", + strcmp(req->object, "") ? req->object : "ANY", + strcmp(req->source, "") ? req->source : "ANY", + strcmp(req->destination, "") ? req->destination : "ANY", + req->lock & BT_READ_LOCK ? "read" : "", + req->lock & BT_WRITE_LOCK ? "write" : ""); + + return 0; +} + +static void handle_open_req(struct unix_client *client, struct bt_open_req *req) +{ + struct audio_device *dev; + bdaddr_t src, dst; + int err = 0; + + if (!check_nul(req->source) || !check_nul(req->destination) || + !check_nul(req->object)) { + err = EINVAL; + goto failed; + } + + str2ba(req->source, &src); + str2ba(req->destination, &dst); + + if (req->seid > BT_A2DP_SEID_RANGE) { + err = handle_sco_open(client, req); + if (err < 0) { + err = -err; + goto failed; + } + } else { + err = handle_a2dp_open(client, req); + if (err < 0) { + err = -err; + goto failed; + } + } + + if (!manager_find_device(req->object, &src, &dst, NULL, FALSE)) + goto failed; + + dev = manager_find_device(req->object, &src, &dst, client->interface, + TRUE); + if (!dev) + dev = manager_find_device(req->object, &src, &dst, + client->interface, FALSE); + + if (!dev) + goto failed; + + client->seid = req->seid; + client->lock = req->lock; + + start_open(dev, client); + + return; + +failed: + unix_ipc_error(client, BT_OPEN, err ? : EIO); +} + +static int handle_sco_transport(struct unix_client *client, + struct bt_set_configuration_req *req) +{ + struct audio_device *dev = client->dev; + + if (!client->interface) { + if (dev->headset) + client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); + else if (dev->gateway) + client->interface = g_strdup(AUDIO_GATEWAY_INTERFACE); + else + return -EIO; + } else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) && + !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE)) + return -EIO; + + return 0; +} + +static int handle_a2dp_transport(struct unix_client *client, + struct bt_set_configuration_req *req) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + struct mpeg_codec_cap mpeg_cap; + + if (!client->interface) + /* FIXME: are we treating a sink or a source? */ + client->interface = g_strdup(AUDIO_SINK_INTERFACE); + else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE) && + !g_str_equal(client->interface, AUDIO_SOURCE_INTERFACE)) + return -EIO; + + if (client->caps) { + g_slist_foreach(client->caps, (GFunc) g_free, NULL); + g_slist_free(client->caps); + client->caps = NULL; + } + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + client->caps = g_slist_append(client->caps, media_transport); + + if (req->codec.type == BT_A2DP_MPEG12_SINK || + req->codec.type == BT_A2DP_MPEG12_SOURCE) { + mpeg_capabilities_t *mpeg = (void *) &req->codec; + + memset(&mpeg_cap, 0, sizeof(mpeg_cap)); + + mpeg_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + mpeg_cap.cap.media_codec_type = A2DP_CODEC_MPEG12; + mpeg_cap.channel_mode = mpeg->channel_mode; + mpeg_cap.crc = mpeg->crc; + mpeg_cap.layer = mpeg->layer; + mpeg_cap.frequency = mpeg->frequency; + mpeg_cap.mpf = mpeg->mpf; + mpeg_cap.bitrate = mpeg->bitrate; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap, + sizeof(mpeg_cap)); + + print_mpeg12(&mpeg_cap); + } else if (req->codec.type == BT_A2DP_SBC_SINK || + req->codec.type == BT_A2DP_SBC_SOURCE) { + sbc_capabilities_t *sbc = (void *) &req->codec; + + memset(&sbc_cap, 0, sizeof(sbc_cap)); + + sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC; + sbc_cap.channel_mode = sbc->channel_mode; + sbc_cap.frequency = sbc->frequency; + sbc_cap.allocation_method = sbc->allocation_method; + sbc_cap.subbands = sbc->subbands; + sbc_cap.block_length = sbc->block_length; + sbc_cap.min_bitpool = sbc->min_bitpool; + sbc_cap.max_bitpool = sbc->max_bitpool; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + print_sbc(&sbc_cap); + } else + return -EINVAL; + + client->caps = g_slist_append(client->caps, media_codec); + + return 0; +} + +static void handle_setconfiguration_req(struct unix_client *client, + struct bt_set_configuration_req *req) +{ + int err = 0; + + if (req->codec.seid != client->seid) { + error("Unable to set configuration: seid %d not opened", + req->codec.seid); + goto failed; + } + + if (!client->dev) + goto failed; + + if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_SCO) { + err = handle_sco_transport(client, req); + if (err < 0) { + err = -err; + goto failed; + } + } else if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + err = handle_a2dp_transport(client, req); + if (err < 0) { + err = -err; + goto failed; + } + } + + start_config(client->dev, client); + + return; + +failed: + unix_ipc_error(client, BT_SET_CONFIGURATION, err ? : EIO); +} + +static void handle_streamstart_req(struct unix_client *client, + struct bt_start_stream_req *req) +{ + if (!client->dev) + goto failed; + + start_resume(client->dev, client); + + return; + +failed: + unix_ipc_error(client, BT_START_STREAM, EIO); +} + +static void handle_streamstop_req(struct unix_client *client, + struct bt_stop_stream_req *req) +{ + if (!client->dev) + goto failed; + + start_suspend(client->dev, client); + + return; + +failed: + unix_ipc_error(client, BT_STOP_STREAM, EIO); +} + +static void handle_close_req(struct unix_client *client, + struct bt_close_req *req) +{ + if (!client->dev) + goto failed; + + start_close(client->dev, client, TRUE); + + return; + +failed: + unix_ipc_error(client, BT_CLOSE, EIO); +} + +static void handle_control_req(struct unix_client *client, + struct bt_control_req *req) +{ + /* FIXME: really implement that */ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_CONTROL; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); +} + +static void handle_delay_report_req(struct unix_client *client, + struct bt_delay_report_req *req) +{ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + struct a2dp_data *a2dp; + int err; + + if (!client->dev) { + err = -ENODEV; + goto failed; + } + + switch (client->type) { + case TYPE_HEADSET: + case TYPE_GATEWAY: + err = -EINVAL; + goto failed; + case TYPE_SOURCE: + case TYPE_SINK: + a2dp = &client->d.a2dp; + if (a2dp->session && a2dp->stream) { + err = avdtp_delay_report(a2dp->session, a2dp->stream, + req->delay); + if (err < 0) + goto failed; + } else { + err = -EINVAL; + goto failed; + } + break; + default: + error("No known services for device"); + err = -EINVAL; + goto failed; + } + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_DELAY_REPORT; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + unix_ipc_error(client, BT_DELAY_REPORT, -err); +} + +static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + bt_audio_msg_header_t *msghdr = (void *) buf; + struct unix_client *client = data; + int len; + const char *type, *name; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + DBG("Unix client disconnected (fd=%d)", client->sock); + + goto failed; + } + + memset(buf, 0, sizeof(buf)); + + len = recv(client->sock, buf, sizeof(buf), 0); + if (len < 0) { + error("recv: %s (%d)", strerror(errno), errno); + goto failed; + } + + type = bt_audio_strtype(msghdr->type); + name = bt_audio_strname(msghdr->name); + + DBG("Audio API: %s <- %s", type, name); + + if (msghdr->length != len) { + error("Invalid message: length mismatch"); + goto failed; + } + + switch (msghdr->name) { + case BT_GET_CAPABILITIES: + handle_getcapabilities_req(client, + (struct bt_get_capabilities_req *) msghdr); + break; + case BT_OPEN: + handle_open_req(client, + (struct bt_open_req *) msghdr); + break; + case BT_SET_CONFIGURATION: + handle_setconfiguration_req(client, + (struct bt_set_configuration_req *) msghdr); + break; + case BT_START_STREAM: + handle_streamstart_req(client, + (struct bt_start_stream_req *) msghdr); + break; + case BT_STOP_STREAM: + handle_streamstop_req(client, + (struct bt_stop_stream_req *) msghdr); + break; + case BT_CLOSE: + handle_close_req(client, + (struct bt_close_req *) msghdr); + break; + case BT_CONTROL: + handle_control_req(client, + (struct bt_control_req *) msghdr); + break; + case BT_DELAY_REPORT: + handle_delay_report_req(client, + (struct bt_delay_report_req *) msghdr); + break; + default: + error("Audio API: received unexpected message name %d", + msghdr->name); + } + + return TRUE; + +failed: + clients = g_slist_remove(clients, client); + start_close(client->dev, client, FALSE); + client_free(client); + return FALSE; +} + +static gboolean server_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + struct sockaddr_un addr; + socklen_t addrlen; + int sk, cli_sk; + struct unix_client *client; + GIOChannel *io; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + g_io_channel_shutdown(chan, TRUE, NULL); + return FALSE; + } + + sk = g_io_channel_unix_get_fd(chan); + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + cli_sk = accept(sk, (struct sockaddr *) &addr, &addrlen); + if (cli_sk < 0) { + error("accept: %s (%d)", strerror(errno), errno); + return TRUE; + } + + DBG("Accepted new client connection on unix socket (fd=%d)", cli_sk); + set_nonblocking(cli_sk); + + client = g_new0(struct unix_client, 1); + client->sock = cli_sk; + clients = g_slist_append(clients, client); + + io = g_io_channel_unix_new(cli_sk); + g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + client_cb, client); + g_io_channel_unref(io); + + return TRUE; +} + +void unix_device_removed(struct audio_device *dev) +{ + GSList *l; + + DBG("unix_device_removed(%p)", dev); + + l = clients; + while (l) { + struct unix_client *client = l->data; + + l = l->next; + + if (client->dev == dev) { + clients = g_slist_remove(clients, client); + start_close(client->dev, client, FALSE); + client_free(client); + } + } +} + +void unix_delay_report(struct audio_device *dev, uint8_t seid, uint16_t delay) +{ + GSList *l; + struct bt_delay_report_ind ind; + + DBG("unix_delay_report(%p): %u.%ums", dev, delay / 10, delay % 10); + + memset(&ind, 0, sizeof(ind)); + ind.h.type = BT_INDICATION; + ind.h.name = BT_DELAY_REPORT; + ind.h.length = sizeof(ind); + ind.delay = delay; + + for (l = clients; l != NULL; l = g_slist_next(l)) { + struct unix_client *client = l->data; + + if (client->dev != dev || client->seid != seid) + continue; + + unix_ipc_sendmsg(client, (void *) &ind); + } +} + +int unix_init(void) +{ + GIOChannel *io; + struct sockaddr_un addr = { + AF_UNIX, BT_IPC_SOCKET_NAME + }; + + int sk, err; + + sk = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sk < 0) { + err = errno; + error("Can't create unix socket: %s (%d)", strerror(err), err); + return -err; + } + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + error("Can't bind unix socket: %s (%d)", strerror(errno), + errno); + close(sk); + return -1; + } + + set_nonblocking(sk); + + if (listen(sk, 1) < 0) { + error("Can't listen on unix socket: %s (%d)", + strerror(errno), errno); + close(sk); + return -1; + } + + unix_sock = sk; + + io = g_io_channel_unix_new(sk); + g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + server_cb, NULL); + g_io_channel_unref(io); + + DBG("Unix socket created: %d", sk); + + return 0; +} + +void unix_exit(void) +{ + g_slist_foreach(clients, (GFunc) client_free, NULL); + g_slist_free(clients); + if (unix_sock >= 0) { + close(unix_sock); + unix_sock = -1; + } +} diff --git a/audio/unix.h b/audio/unix.h new file mode 100644 index 0000000..74ca16d --- /dev/null +++ b/audio/unix.h @@ -0,0 +1,30 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void unix_device_removed(struct audio_device *dev); + +void unix_delay_report(struct audio_device *dev, uint8_t seid, uint16_t delay); + +int unix_init(void); +void unix_exit(void); diff --git a/bluez.pc.in b/bluez.pc.in new file mode 100644 index 0000000..3d6e596 --- /dev/null +++ b/bluez.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: BlueZ +Description: Bluetooth protocol stack for Linux +Version: @VERSION@ +Libs: -L${libdir} -lbluetooth +Cflags: -I${includedir} diff --git a/btio/btio.c b/btio/btio.c new file mode 100644 index 0000000..3f5b69a --- /dev/null +++ b/btio/btio.c @@ -0,0 +1,1319 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009-2010 Marcel Holtmann + * Copyright (C) 2009-2010 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "btio.h" + +#define ERROR_FAILED(gerr, str, err) \ + g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_FAILED, \ + str ": %s (%d)", strerror(err), err) + +#define DEFAULT_DEFER_TIMEOUT 30 + +struct set_opts { + bdaddr_t src; + bdaddr_t dst; + int defer; + int sec_level; + uint8_t channel; + uint16_t psm; + uint16_t cid; + uint16_t mtu; + uint16_t imtu; + uint16_t omtu; + int master; + uint8_t mode; +}; + +struct connect { + BtIOConnect connect; + gpointer user_data; + GDestroyNotify destroy; +}; + +struct accept { + BtIOConnect connect; + gpointer user_data; + GDestroyNotify destroy; +}; + +struct server { + BtIOConnect connect; + BtIOConfirm confirm; + gpointer user_data; + GDestroyNotify destroy; +}; + +static void server_remove(struct server *server) +{ + if (server->destroy) + server->destroy(server->user_data); + g_free(server); +} + +static void connect_remove(struct connect *conn) +{ + if (conn->destroy) + conn->destroy(conn->user_data); + g_free(conn); +} + +static void accept_remove(struct accept *accept) +{ + if (accept->destroy) + accept->destroy(accept->user_data); + g_free(accept); +} + +static gboolean check_nval(GIOChannel *io) +{ + struct pollfd fds; + + memset(&fds, 0, sizeof(fds)); + fds.fd = g_io_channel_unix_get_fd(io); + fds.events = POLLNVAL; + + if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLNVAL)) + return TRUE; + + return FALSE; +} + +static gboolean accept_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct accept *accept = user_data; + GError *err = NULL; + + /* If the user aborted this accept attempt */ + if ((cond & G_IO_NVAL) || check_nval(io)) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) + g_set_error(&err, BT_IO_ERROR, BT_IO_ERROR_DISCONNECTED, + "HUP or ERR on socket"); + + accept->connect(io, err, accept->user_data); + + g_clear_error(&err); + + return FALSE; +} + +static gboolean connect_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct connect *conn = user_data; + GError *gerr = NULL; + + /* If the user aborted this connect attempt */ + if ((cond & G_IO_NVAL) || check_nval(io)) + return FALSE; + + if (cond & G_IO_OUT) { + int err = 0, sock = g_io_channel_unix_get_fd(io); + socklen_t len = sizeof(err); + + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len) < 0) + err = errno; + + if (err) + g_set_error(&gerr, BT_IO_ERROR, + BT_IO_ERROR_CONNECT_FAILED, "%s (%d)", + strerror(err), err); + } else if (cond & (G_IO_HUP | G_IO_ERR)) + g_set_error(&gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED, + "HUP or ERR on socket"); + + conn->connect(io, gerr, conn->user_data); + + if (gerr) + g_error_free(gerr); + + return FALSE; +} + +static gboolean server_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct server *server = user_data; + int srv_sock, cli_sock; + GIOChannel *cli_io; + + /* If the user closed the server */ + if ((cond & G_IO_NVAL) || check_nval(io)) + return FALSE; + + srv_sock = g_io_channel_unix_get_fd(io); + + cli_sock = accept(srv_sock, NULL, NULL); + if (cli_sock < 0) + return TRUE; + + cli_io = g_io_channel_unix_new(cli_sock); + + g_io_channel_set_close_on_unref(cli_io, TRUE); + g_io_channel_set_flags(cli_io, G_IO_FLAG_NONBLOCK, NULL); + + if (server->confirm) + server->confirm(cli_io, server->user_data); + else + server->connect(cli_io, NULL, server->user_data); + + g_io_channel_unref(cli_io); + + return TRUE; +} + +static void server_add(GIOChannel *io, BtIOConnect connect, + BtIOConfirm confirm, gpointer user_data, + GDestroyNotify destroy) +{ + struct server *server; + GIOCondition cond; + + server = g_new0(struct server, 1); + server->connect = connect; + server->confirm = confirm; + server->user_data = user_data; + server->destroy = destroy; + + cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, server_cb, server, + (GDestroyNotify) server_remove); +} + +static void connect_add(GIOChannel *io, BtIOConnect connect, + gpointer user_data, GDestroyNotify destroy) +{ + struct connect *conn; + GIOCondition cond; + + conn = g_new0(struct connect, 1); + conn->connect = connect; + conn->user_data = user_data; + conn->destroy = destroy; + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, conn, + (GDestroyNotify) connect_remove); +} + +static void accept_add(GIOChannel *io, BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy) +{ + struct accept *accept; + GIOCondition cond; + + accept = g_new0(struct accept, 1); + accept->connect = connect; + accept->user_data = user_data; + accept->destroy = destroy; + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, accept_cb, accept, + (GDestroyNotify) accept_remove); +} + +static int l2cap_bind(int sock, const bdaddr_t *src, uint16_t psm, + uint16_t cid, GError **err) +{ + struct sockaddr_l2 addr; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (cid) + addr.l2_cid = htobs(cid); + else + addr.l2_psm = htobs(psm); + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + ERROR_FAILED(err, "l2cap_bind", errno); + return -1; + } + + return 0; +} + +static int l2cap_connect(int sock, const bdaddr_t *dst, + uint16_t psm, uint16_t cid) +{ + int err; + struct sockaddr_l2 addr; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + if (cid) + addr.l2_cid = htobs(cid); + else + addr.l2_psm = htobs(psm); + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return err; + + return 0; +} + +static int l2cap_set_master(int sock, int master) +{ + int flags; + socklen_t len; + + len = sizeof(flags); + if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len) < 0) + return -errno; + + if (master) { + if (flags & L2CAP_LM_MASTER) + return 0; + flags |= L2CAP_LM_MASTER; + } else { + if (!(flags & L2CAP_LM_MASTER)) + return 0; + flags &= ~L2CAP_LM_MASTER; + } + + if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, sizeof(flags)) < 0) + return -errno; + + return 0; +} + +static int rfcomm_set_master(int sock, int master) +{ + int flags; + socklen_t len; + + len = sizeof(flags); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, &len) < 0) + return -errno; + + if (master) { + if (flags & RFCOMM_LM_MASTER) + return 0; + flags |= RFCOMM_LM_MASTER; + } else { + if (!(flags & RFCOMM_LM_MASTER)) + return 0; + flags &= ~RFCOMM_LM_MASTER; + } + + if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, sizeof(flags)) < 0) + return -errno; + + return 0; +} + +static int l2cap_set_lm(int sock, int level) +{ + int lm_map[] = { + 0, + L2CAP_LM_AUTH, + L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT, + L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT | L2CAP_LM_SECURE, + }, opt = lm_map[level]; + + if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) + return -errno; + + return 0; +} + +static int rfcomm_set_lm(int sock, int level) +{ + int lm_map[] = { + 0, + RFCOMM_LM_AUTH, + RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT, + RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT | RFCOMM_LM_SECURE, + }, opt = lm_map[level]; + + if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0) + return -errno; + + return 0; +} + +static gboolean set_sec_level(int sock, BtIOType type, int level, GError **err) +{ + struct bt_security sec; + int ret; + + if (level < BT_SECURITY_LOW || level > BT_SECURITY_HIGH) { + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Valid security level range is %d-%d", + BT_SECURITY_LOW, BT_SECURITY_HIGH); + return FALSE; + } + + memset(&sec, 0, sizeof(sec)); + sec.level = level; + + if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, + sizeof(sec)) == 0) + return TRUE; + + if (errno != ENOPROTOOPT) { + ERROR_FAILED(err, "setsockopt(BT_SECURITY)", errno); + return FALSE; + } + + if (type == BT_IO_L2CAP) + ret = l2cap_set_lm(sock, level); + else + ret = rfcomm_set_lm(sock, level); + + if (ret < 0) { + ERROR_FAILED(err, "setsockopt(LM)", -ret); + return FALSE; + } + + return TRUE; +} + +static int l2cap_get_lm(int sock, int *sec_level) +{ + int opt; + socklen_t len; + + len = sizeof(opt); + if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, &len) < 0) + return -errno; + + *sec_level = 0; + + if (opt & L2CAP_LM_AUTH) + *sec_level = BT_SECURITY_LOW; + if (opt & L2CAP_LM_ENCRYPT) + *sec_level = BT_SECURITY_MEDIUM; + if (opt & L2CAP_LM_SECURE) + *sec_level = BT_SECURITY_HIGH; + + return 0; +} + +static int rfcomm_get_lm(int sock, int *sec_level) +{ + int opt; + socklen_t len; + + len = sizeof(opt); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, &len) < 0) + return -errno; + + *sec_level = 0; + + if (opt & RFCOMM_LM_AUTH) + *sec_level = BT_SECURITY_LOW; + if (opt & RFCOMM_LM_ENCRYPT) + *sec_level = BT_SECURITY_MEDIUM; + if (opt & RFCOMM_LM_SECURE) + *sec_level = BT_SECURITY_HIGH; + + return 0; +} + +static gboolean get_sec_level(int sock, BtIOType type, int *level, + GError **err) +{ + struct bt_security sec; + socklen_t len; + int ret; + + memset(&sec, 0, sizeof(sec)); + len = sizeof(sec); + if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) == 0) { + *level = sec.level; + return TRUE; + } + + if (errno != ENOPROTOOPT) { + ERROR_FAILED(err, "getsockopt(BT_SECURITY)", errno); + return FALSE; + } + + if (type == BT_IO_L2CAP) + ret = l2cap_get_lm(sock, level); + else + ret = rfcomm_get_lm(sock, level); + + if (ret < 0) { + ERROR_FAILED(err, "getsockopt(LM)", -ret); + return FALSE; + } + + return TRUE; +} + +static gboolean l2cap_set(int sock, int sec_level, uint16_t imtu, uint16_t omtu, + uint8_t mode, int master, GError **err) +{ + if (imtu || omtu || mode) { + struct l2cap_options l2o; + socklen_t len; + + memset(&l2o, 0, sizeof(l2o)); + len = sizeof(l2o); + if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, + &len) < 0) { + ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno); + return FALSE; + } + + if (imtu) + l2o.imtu = imtu; + if (omtu) + l2o.omtu = omtu; + if (mode) + l2o.mode = mode; + + if (setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, + sizeof(l2o)) < 0) { + ERROR_FAILED(err, "setsockopt(L2CAP_OPTIONS)", errno); + return FALSE; + } + } + + if (master >= 0 && l2cap_set_master(sock, master) < 0) { + ERROR_FAILED(err, "l2cap_set_master", errno); + return FALSE; + } + + if (sec_level && !set_sec_level(sock, BT_IO_L2CAP, sec_level, err)) + return FALSE; + + return TRUE; +} + +static int rfcomm_bind(int sock, + const bdaddr_t *src, uint8_t channel, GError **err) +{ + struct sockaddr_rc addr; + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + addr.rc_channel = channel; + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + ERROR_FAILED(err, "rfcomm_bind", errno); + return -1; + } + + return 0; +} + +static int rfcomm_connect(int sock, const bdaddr_t *dst, uint8_t channel) +{ + int err; + struct sockaddr_rc addr; + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, dst); + addr.rc_channel = channel; + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return err; + + return 0; +} + +static gboolean rfcomm_set(int sock, int sec_level, int master, GError **err) +{ + if (sec_level && !set_sec_level(sock, BT_IO_RFCOMM, sec_level, err)) + return FALSE; + + if (master >= 0 && rfcomm_set_master(sock, master) < 0) { + ERROR_FAILED(err, "rfcomm_set_master", errno); + return FALSE; + } + + return TRUE; +} + +static int sco_bind(int sock, const bdaddr_t *src, GError **err) +{ + struct sockaddr_sco addr; + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, src); + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + ERROR_FAILED(err, "sco_bind", errno); + return -1; + } + + return 0; +} + +static int sco_connect(int sock, const bdaddr_t *dst) +{ + struct sockaddr_sco addr; + int err; + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, dst); + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return err; + + return 0; +} + +static gboolean sco_set(int sock, uint16_t mtu, GError **err) +{ + struct sco_options sco_opt; + socklen_t len; + + if (!mtu) + return TRUE; + + len = sizeof(sco_opt); + memset(&sco_opt, 0, len); + if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) { + ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno); + return FALSE; + } + + sco_opt.mtu = mtu; + if (setsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, + sizeof(sco_opt)) < 0) { + ERROR_FAILED(err, "setsockopt(SCO_OPTIONS)", errno); + return FALSE; + } + + return TRUE; +} + +static gboolean parse_set_opts(struct set_opts *opts, GError **err, + BtIOOption opt1, va_list args) +{ + BtIOOption opt = opt1; + const char *str; + + memset(opts, 0, sizeof(*opts)); + + /* Set defaults */ + opts->defer = DEFAULT_DEFER_TIMEOUT; + opts->master = -1; + opts->sec_level = BT_IO_SEC_MEDIUM; + opts->mode = L2CAP_MODE_BASIC; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + str = va_arg(args, const char *); + if (strncasecmp(str, "hci", 3) == 0) + hci_devba(atoi(str + 3), &opts->src); + else + str2ba(str, &opts->src); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(&opts->src, va_arg(args, const bdaddr_t *)); + break; + case BT_IO_OPT_DEST: + str2ba(va_arg(args, const char *), &opts->dst); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(&opts->dst, va_arg(args, const bdaddr_t *)); + break; + case BT_IO_OPT_DEFER_TIMEOUT: + opts->defer = va_arg(args, int); + break; + case BT_IO_OPT_SEC_LEVEL: + opts->sec_level = va_arg(args, int); + break; + case BT_IO_OPT_CHANNEL: + opts->channel = va_arg(args, int); + break; + case BT_IO_OPT_PSM: + opts->psm = va_arg(args, int); + break; + case BT_IO_OPT_CID: + opts->cid = va_arg(args, int); + break; + case BT_IO_OPT_MTU: + opts->mtu = va_arg(args, int); + opts->imtu = opts->mtu; + opts->omtu = opts->mtu; + break; + case BT_IO_OPT_OMTU: + opts->omtu = va_arg(args, int); + if (!opts->mtu) + opts->mtu = opts->omtu; + break; + case BT_IO_OPT_IMTU: + opts->imtu = va_arg(args, int); + if (!opts->mtu) + opts->mtu = opts->imtu; + break; + case BT_IO_OPT_MASTER: + opts->master = va_arg(args, gboolean); + break; + case BT_IO_OPT_MODE: + opts->mode = va_arg(args, int); + break; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static gboolean get_peers(int sock, struct sockaddr *src, struct sockaddr *dst, + socklen_t len, GError **err) +{ + socklen_t olen; + + memset(src, 0, len); + olen = len; + if (getsockname(sock, src, &olen) < 0) { + ERROR_FAILED(err, "getsockname", errno); + return FALSE; + } + + memset(dst, 0, len); + olen = len; + if (getpeername(sock, dst, &olen) < 0) { + ERROR_FAILED(err, "getpeername", errno); + return FALSE; + } + + return TRUE; +} + +static int l2cap_get_info(int sock, uint16_t *handle, uint8_t *dev_class) +{ + struct l2cap_conninfo info; + socklen_t len; + + len = sizeof(info); + if (getsockopt(sock, SOL_L2CAP, L2CAP_CONNINFO, &info, &len) < 0) + return -errno; + + if (handle) + *handle = info.hci_handle; + + if (dev_class) + memcpy(dev_class, info.dev_class, 3); + + return 0; +} + +static gboolean l2cap_get(int sock, GError **err, BtIOOption opt1, + va_list args) +{ + BtIOOption opt = opt1; + struct sockaddr_l2 src, dst; + struct l2cap_options l2o; + int flags; + uint8_t dev_class[3]; + uint16_t handle; + socklen_t len; + + len = sizeof(l2o); + memset(&l2o, 0, len); + if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) { + ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno); + return FALSE; + } + + if (!get_peers(sock, (struct sockaddr *) &src, + (struct sockaddr *) &dst, sizeof(src), err)) + return FALSE; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + ba2str(&src.l2_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &src.l2_bdaddr); + break; + case BT_IO_OPT_DEST: + ba2str(&dst.l2_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &dst.l2_bdaddr); + break; + case BT_IO_OPT_DEFER_TIMEOUT: + len = sizeof(int); + if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, + va_arg(args, int *), &len) < 0) { + ERROR_FAILED(err, "getsockopt(DEFER_SETUP)", + errno); + return FALSE; + } + break; + case BT_IO_OPT_SEC_LEVEL: + if (!get_sec_level(sock, BT_IO_L2CAP, + va_arg(args, int *), err)) + return FALSE; + break; + case BT_IO_OPT_PSM: + *(va_arg(args, uint16_t *)) = src.l2_psm ? + src.l2_psm : dst.l2_psm; + break; + case BT_IO_OPT_CID: + *(va_arg(args, uint16_t *)) = src.l2_cid ? + src.l2_cid : dst.l2_cid; + break; + case BT_IO_OPT_OMTU: + *(va_arg(args, uint16_t *)) = l2o.omtu; + break; + case BT_IO_OPT_IMTU: + *(va_arg(args, uint16_t *)) = l2o.imtu; + break; + case BT_IO_OPT_MASTER: + len = sizeof(flags); + if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, + &len) < 0) { + ERROR_FAILED(err, "getsockopt(L2CAP_LM)", + errno); + return FALSE; + } + *(va_arg(args, gboolean *)) = + (flags & L2CAP_LM_MASTER) ? TRUE : FALSE; + break; + case BT_IO_OPT_HANDLE: + if (l2cap_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "L2CAP_CONNINFO", errno); + return FALSE; + } + *(va_arg(args, uint16_t *)) = handle; + break; + case BT_IO_OPT_CLASS: + if (l2cap_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "L2CAP_CONNINFO", errno); + return FALSE; + } + memcpy(va_arg(args, uint8_t *), dev_class, 3); + break; + case BT_IO_OPT_MODE: + *(va_arg(args, uint8_t *)) = l2o.mode; + break; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static int rfcomm_get_info(int sock, uint16_t *handle, uint8_t *dev_class) +{ + struct rfcomm_conninfo info; + socklen_t len; + + len = sizeof(info); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_CONNINFO, &info, &len) < 0) + return -errno; + + if (handle) + *handle = info.hci_handle; + + if (dev_class) + memcpy(dev_class, info.dev_class, 3); + + return 0; +} + +static gboolean rfcomm_get(int sock, GError **err, BtIOOption opt1, + va_list args) +{ + BtIOOption opt = opt1; + struct sockaddr_rc src, dst; + int flags; + socklen_t len; + uint8_t dev_class[3]; + uint16_t handle; + + if (!get_peers(sock, (struct sockaddr *) &src, + (struct sockaddr *) &dst, sizeof(src), err)) + return FALSE; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + ba2str(&src.rc_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &src.rc_bdaddr); + break; + case BT_IO_OPT_DEST: + ba2str(&dst.rc_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &dst.rc_bdaddr); + break; + case BT_IO_OPT_DEFER_TIMEOUT: + len = sizeof(int); + if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, + va_arg(args, int *), &len) < 0) { + ERROR_FAILED(err, "getsockopt(DEFER_SETUP)", + errno); + return FALSE; + } + break; + case BT_IO_OPT_SEC_LEVEL: + if (!get_sec_level(sock, BT_IO_RFCOMM, + va_arg(args, int *), err)) + return FALSE; + break; + case BT_IO_OPT_CHANNEL: + *(va_arg(args, uint8_t *)) = src.rc_channel ? + src.rc_channel : dst.rc_channel; + break; + case BT_IO_OPT_SOURCE_CHANNEL: + *(va_arg(args, uint8_t *)) = src.rc_channel; + break; + case BT_IO_OPT_DEST_CHANNEL: + *(va_arg(args, uint8_t *)) = dst.rc_channel; + break; + case BT_IO_OPT_MASTER: + len = sizeof(flags); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, + &len) < 0) { + ERROR_FAILED(err, "getsockopt(RFCOMM_LM)", + errno); + return FALSE; + } + *(va_arg(args, gboolean *)) = + (flags & RFCOMM_LM_MASTER) ? TRUE : FALSE; + break; + case BT_IO_OPT_HANDLE: + if (rfcomm_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "RFCOMM_CONNINFO", errno); + return FALSE; + } + *(va_arg(args, uint16_t *)) = handle; + break; + case BT_IO_OPT_CLASS: + if (rfcomm_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "RFCOMM_CONNINFO", errno); + return FALSE; + } + memcpy(va_arg(args, uint8_t *), dev_class, 3); + break; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static int sco_get_info(int sock, uint16_t *handle, uint8_t *dev_class) +{ + struct sco_conninfo info; + socklen_t len; + + len = sizeof(info); + if (getsockopt(sock, SOL_SCO, SCO_CONNINFO, &info, &len) < 0) + return -errno; + + if (handle) + *handle = info.hci_handle; + + if (dev_class) + memcpy(dev_class, info.dev_class, 3); + + return 0; +} + +static gboolean sco_get(int sock, GError **err, BtIOOption opt1, va_list args) +{ + BtIOOption opt = opt1; + struct sockaddr_sco src, dst; + struct sco_options sco_opt; + socklen_t len; + uint8_t dev_class[3]; + uint16_t handle; + + len = sizeof(sco_opt); + memset(&sco_opt, 0, len); + if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) { + ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno); + return FALSE; + } + + if (!get_peers(sock, (struct sockaddr *) &src, + (struct sockaddr *) &dst, sizeof(src), err)) + return FALSE; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + ba2str(&src.sco_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &src.sco_bdaddr); + break; + case BT_IO_OPT_DEST: + ba2str(&dst.sco_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &dst.sco_bdaddr); + break; + case BT_IO_OPT_MTU: + case BT_IO_OPT_IMTU: + case BT_IO_OPT_OMTU: + *(va_arg(args, uint16_t *)) = sco_opt.mtu; + break; + case BT_IO_OPT_HANDLE: + if (sco_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "SCO_CONNINFO", errno); + return FALSE; + } + *(va_arg(args, uint16_t *)) = handle; + break; + case BT_IO_OPT_CLASS: + if (sco_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "SCO_CONNINFO", errno); + return FALSE; + } + memcpy(va_arg(args, uint8_t *), dev_class, 3); + break; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static gboolean get_valist(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, va_list args) +{ + int sock; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2RAW: + case BT_IO_L2CAP: + return l2cap_get(sock, err, opt1, args); + case BT_IO_RFCOMM: + return rfcomm_get(sock, err, opt1, args); + case BT_IO_SCO: + return sco_get(sock, err, opt1, args); + } + + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown BtIO type %d", type); + return FALSE; +} + +gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy, GError **err) +{ + int sock; + char c; + struct pollfd pfd; + + sock = g_io_channel_unix_get_fd(io); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = sock; + pfd.events = POLLOUT; + + if (poll(&pfd, 1, 0) < 0) { + ERROR_FAILED(err, "poll", errno); + return FALSE; + } + + if (!(pfd.revents & POLLOUT)) { + int ret; + ret = read(sock, &c, 1); + } + + accept_add(io, connect, user_data, destroy); + + return TRUE; +} + +gboolean bt_io_set(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, ...) +{ + va_list args; + gboolean ret; + struct set_opts opts; + int sock; + + va_start(args, opt1); + ret = parse_set_opts(&opts, err, opt1, args); + va_end(args); + + if (!ret) + return ret; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2RAW: + case BT_IO_L2CAP: + return l2cap_set(sock, opts.sec_level, opts.imtu, opts.omtu, + opts.mode, opts.master, err); + case BT_IO_RFCOMM: + return rfcomm_set(sock, opts.sec_level, opts.master, err); + case BT_IO_SCO: + return sco_set(sock, opts.mtu, err); + } + + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown BtIO type %d", type); + return FALSE; +} + +gboolean bt_io_get(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, ...) +{ + va_list args; + gboolean ret; + + va_start(args, opt1); + ret = get_valist(io, type, err, opt1, args); + va_end(args); + + return ret; +} + +static GIOChannel *create_io(BtIOType type, gboolean server, + struct set_opts *opts, GError **err) +{ + int sock; + GIOChannel *io; + + switch (type) { + case BT_IO_L2RAW: + sock = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); + if (sock < 0) { + ERROR_FAILED(err, "socket(RAW, L2CAP)", errno); + return NULL; + } + if (l2cap_bind(sock, &opts->src, server ? opts->psm : 0, + opts->cid, err) < 0) + goto failed; + if (!l2cap_set(sock, opts->sec_level, 0, 0, 0, -1, err)) + goto failed; + break; + case BT_IO_L2CAP: + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sock < 0) { + ERROR_FAILED(err, "socket(SEQPACKET, L2CAP)", errno); + return NULL; + } + if (l2cap_bind(sock, &opts->src, server ? opts->psm : 0, + opts->cid, err) < 0) + goto failed; + if (!l2cap_set(sock, opts->sec_level, opts->imtu, opts->omtu, + opts->mode, opts->master, err)) + goto failed; + break; + case BT_IO_RFCOMM: + sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sock < 0) { + ERROR_FAILED(err, "socket(STREAM, RFCOMM)", errno); + return NULL; + } + if (rfcomm_bind(sock, &opts->src, + server ? opts->channel : 0, err) < 0) + goto failed; + if (!rfcomm_set(sock, opts->sec_level, opts->master, err)) + goto failed; + break; + case BT_IO_SCO: + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + if (sock < 0) { + ERROR_FAILED(err, "socket(SEQPACKET, SCO)", errno); + return NULL; + } + if (sco_bind(sock, &opts->src, err) < 0) + goto failed; + if (!sco_set(sock, opts->mtu, err)) + goto failed; + break; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown BtIO type %d", type); + return NULL; + } + + io = g_io_channel_unix_new(sock); + + g_io_channel_set_close_on_unref(io, TRUE); + g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); + + return io; + +failed: + close(sock); + + return NULL; +} + +GIOChannel *bt_io_connect(BtIOType type, BtIOConnect connect, + gpointer user_data, GDestroyNotify destroy, + GError **gerr, BtIOOption opt1, ...) +{ + GIOChannel *io; + va_list args; + struct set_opts opts; + int err, sock; + gboolean ret; + + va_start(args, opt1); + ret = parse_set_opts(&opts, gerr, opt1, args); + va_end(args); + + if (ret == FALSE) + return NULL; + + io = create_io(type, FALSE, &opts, gerr); + if (io == NULL) + return NULL; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2RAW: + err = l2cap_connect(sock, &opts.dst, 0, opts.cid); + break; + case BT_IO_L2CAP: + err = l2cap_connect(sock, &opts.dst, opts.psm, opts.cid); + break; + case BT_IO_RFCOMM: + err = rfcomm_connect(sock, &opts.dst, opts.channel); + break; + case BT_IO_SCO: + err = sco_connect(sock, &opts.dst); + break; + default: + g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown BtIO type %d", type); + return NULL; + } + + if (err < 0) { + g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED, + "connect: %s (%d)", strerror(-err), -err); + g_io_channel_unref(io); + return NULL; + } + + connect_add(io, connect, user_data, destroy); + + return io; +} + +GIOChannel *bt_io_listen(BtIOType type, BtIOConnect connect, + BtIOConfirm confirm, gpointer user_data, + GDestroyNotify destroy, GError **err, + BtIOOption opt1, ...) +{ + GIOChannel *io; + va_list args; + struct set_opts opts; + int sock; + gboolean ret; + + if (type == BT_IO_L2RAW) { + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Server L2CAP RAW sockets not supported"); + return NULL; + } + + va_start(args, opt1); + ret = parse_set_opts(&opts, err, opt1, args); + va_end(args); + + if (ret == FALSE) + return NULL; + + io = create_io(type, TRUE, &opts, err); + if (io == NULL) + return NULL; + + sock = g_io_channel_unix_get_fd(io); + + if (confirm) + setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &opts.defer, + sizeof(opts.defer)); + + if (listen(sock, 5) < 0) { + ERROR_FAILED(err, "listen", errno); + g_io_channel_unref(io); + return NULL; + } + + server_add(io, connect, confirm, user_data, destroy); + + return io; +} + +GQuark bt_io_error_quark(void) +{ + return g_quark_from_static_string("bt-io-error-quark"); +} diff --git a/btio/btio.h b/btio/btio.h new file mode 100644 index 0000000..53e8eaa --- /dev/null +++ b/btio/btio.h @@ -0,0 +1,98 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009-2010 Marcel Holtmann + * Copyright (C) 2009-2010 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef BT_IO_H +#define BT_IO_H + +#include + +typedef enum { + BT_IO_ERROR_DISCONNECTED, + BT_IO_ERROR_CONNECT_FAILED, + BT_IO_ERROR_FAILED, + BT_IO_ERROR_INVALID_ARGS, +} BtIOError; + +#define BT_IO_ERROR bt_io_error_quark() + +GQuark bt_io_error_quark(void); + +typedef enum { + BT_IO_L2RAW, + BT_IO_L2CAP, + BT_IO_RFCOMM, + BT_IO_SCO, +} BtIOType; + +typedef enum { + BT_IO_OPT_INVALID = 0, + BT_IO_OPT_SOURCE, + BT_IO_OPT_SOURCE_BDADDR, + BT_IO_OPT_DEST, + BT_IO_OPT_DEST_BDADDR, + BT_IO_OPT_DEFER_TIMEOUT, + BT_IO_OPT_SEC_LEVEL, + BT_IO_OPT_CHANNEL, + BT_IO_OPT_SOURCE_CHANNEL, + BT_IO_OPT_DEST_CHANNEL, + BT_IO_OPT_PSM, + BT_IO_OPT_CID, + BT_IO_OPT_MTU, + BT_IO_OPT_OMTU, + BT_IO_OPT_IMTU, + BT_IO_OPT_MASTER, + BT_IO_OPT_HANDLE, + BT_IO_OPT_CLASS, + BT_IO_OPT_MODE, +} BtIOOption; + +typedef enum { + BT_IO_SEC_SDP = 0, + BT_IO_SEC_LOW, + BT_IO_SEC_MEDIUM, + BT_IO_SEC_HIGH, +} BtIOSecLevel; + +typedef void (*BtIOConfirm)(GIOChannel *io, gpointer user_data); + +typedef void (*BtIOConnect)(GIOChannel *io, GError *err, gpointer user_data); + +gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy, GError **err); + +gboolean bt_io_set(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, ...); + +gboolean bt_io_get(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, ...); + +GIOChannel *bt_io_connect(BtIOType type, BtIOConnect connect, + gpointer user_data, GDestroyNotify destroy, + GError **err, BtIOOption opt1, ...); + +GIOChannel *bt_io_listen(BtIOType type, BtIOConnect connect, + BtIOConfirm confirm, gpointer user_data, + GDestroyNotify destroy, GError **err, + BtIOOption opt1, ...); + +#endif diff --git a/compat/bnep.c b/compat/bnep.c new file mode 100644 index 0000000..281350b --- /dev/null +++ b/compat/bnep.c @@ -0,0 +1,339 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "pand.h" + +static int ctl; + +/* Compatibility with old ioctls */ +#define OLD_BNEPCONADD 1 +#define OLD_BNEPCONDEL 2 +#define OLD_BNEPGETCONLIST 3 +#define OLD_BNEPGETCONINFO 4 + +static unsigned long bnepconnadd; +static unsigned long bnepconndel; +static unsigned long bnepgetconnlist; +static unsigned long bnepgetconninfo; + +static struct { + char *str; + uint16_t uuid; +} __svc[] = { + { "PANU", BNEP_SVC_PANU }, + { "NAP", BNEP_SVC_NAP }, + { "GN", BNEP_SVC_GN }, + { NULL } +}; + +int bnep_str2svc(char *svc, uint16_t *uuid) +{ + int i; + for (i = 0; __svc[i].str; i++) + if (!strcasecmp(svc, __svc[i].str)) { + *uuid = __svc[i].uuid; + return 0; + } + return -1; +} + +char *bnep_svc2str(uint16_t uuid) +{ + int i; + for (i = 0; __svc[i].str; i++) + if (__svc[i].uuid == uuid) + return __svc[i].str; + return NULL; +} + +int bnep_init(void) +{ + ctl = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_BNEP); + if (ctl < 0) { + perror("Failed to open control socket"); + return 1; + } + + /* Temporary ioctl compatibility hack */ + { + struct bnep_connlist_req req; + struct bnep_conninfo ci[1]; + + req.cnum = 1; + req.ci = ci; + + if (!ioctl(ctl, BNEPGETCONNLIST, &req)) { + /* New ioctls */ + bnepconnadd = BNEPCONNADD; + bnepconndel = BNEPCONNDEL; + bnepgetconnlist = BNEPGETCONNLIST; + bnepgetconninfo = BNEPGETCONNINFO; + } else { + /* Old ioctls */ + bnepconnadd = OLD_BNEPCONADD; + bnepconndel = OLD_BNEPCONDEL; + bnepgetconnlist = OLD_BNEPGETCONLIST; + bnepgetconninfo = OLD_BNEPGETCONINFO; + } + } + + return 0; +} + +int bnep_cleanup(void) +{ + close(ctl); + return 0; +} + +int bnep_show_connections(void) +{ + struct bnep_connlist_req req; + struct bnep_conninfo ci[48]; + unsigned int i; + + req.cnum = 48; + req.ci = ci; + if (ioctl(ctl, bnepgetconnlist, &req)) { + perror("Failed to get connection list"); + return -1; + } + + for (i = 0; i < req.cnum; i++) { + char addr[18]; + ba2str((bdaddr_t *) ci[i].dst, addr); + printf("%s %s %s\n", ci[i].device, + addr, bnep_svc2str(ci[i].role)); + } + return 0; +} + +int bnep_kill_connection(uint8_t *dst) +{ + struct bnep_conndel_req req; + + memcpy(req.dst, dst, ETH_ALEN); + req.flags = 0; + if (ioctl(ctl, bnepconndel, &req)) { + perror("Failed to kill connection"); + return -1; + } + return 0; +} + +int bnep_kill_all_connections(void) +{ + struct bnep_connlist_req req; + struct bnep_conninfo ci[48]; + unsigned int i; + + req.cnum = 48; + req.ci = ci; + if (ioctl(ctl, bnepgetconnlist, &req)) { + perror("Failed to get connection list"); + return -1; + } + + for (i = 0; i < req.cnum; i++) { + struct bnep_conndel_req req; + memcpy(req.dst, ci[i].dst, ETH_ALEN); + req.flags = 0; + ioctl(ctl, bnepconndel, &req); + } + return 0; +} + +static int bnep_connadd(int sk, uint16_t role, char *dev) +{ + struct bnep_connadd_req req; + + strncpy(req.device, dev, 16); + req.device[15] = '\0'; + req.sock = sk; + req.role = role; + if (ioctl(ctl, bnepconnadd, &req)) + return -1; + strncpy(dev, req.device, 16); + return 0; +} + +struct __service_16 { + uint16_t dst; + uint16_t src; +} __attribute__ ((packed)); + +struct __service_32 { + uint16_t unused1; + uint16_t dst; + uint16_t unused2; + uint16_t src; +} __attribute__ ((packed)); + +struct __service_128 { + uint16_t unused1; + uint16_t dst; + uint16_t unused2[8]; + uint16_t src; + uint16_t unused3[7]; +} __attribute__ ((packed)); + +int bnep_accept_connection(int sk, uint16_t role, char *dev) +{ + struct bnep_setup_conn_req *req; + struct bnep_control_rsp *rsp; + unsigned char pkt[BNEP_MTU]; + ssize_t r; + + r = recv(sk, pkt, BNEP_MTU, 0); + if (r <= 0) + return -1; + + errno = EPROTO; + + if ((size_t) r < sizeof(*req)) + return -1; + + req = (void *) pkt; + + /* Highest known Control command ID + * is BNEP_FILTER_MULT_ADDR_RSP = 0x06 */ + if (req->type == BNEP_CONTROL && + req->ctrl > BNEP_FILTER_MULT_ADDR_RSP) { + uint8_t pkt[3]; + + pkt[0] = BNEP_CONTROL; + pkt[1] = BNEP_CMD_NOT_UNDERSTOOD; + pkt[2] = req->ctrl; + + send(sk, pkt, sizeof(pkt), 0); + + return -1; + } + + if (req->type != BNEP_CONTROL || req->ctrl != BNEP_SETUP_CONN_REQ) + return -1; + + /* FIXME: Check role UUIDs */ + + rsp = (void *) pkt; + rsp->type = BNEP_CONTROL; + rsp->ctrl = BNEP_SETUP_CONN_RSP; + rsp->resp = htons(BNEP_SUCCESS); + if (send(sk, rsp, sizeof(*rsp), 0) < 0) + return -1; + + return bnep_connadd(sk, role, dev); +} + +/* Create BNEP connection + * sk - Connect L2CAP socket + * role - Local role + * service - Remote service + * dev - Network device (contains actual dev name on return) + */ +int bnep_create_connection(int sk, uint16_t role, uint16_t svc, char *dev) +{ + struct bnep_setup_conn_req *req; + struct bnep_control_rsp *rsp; + struct __service_16 *s; + struct timeval timeo; + unsigned char pkt[BNEP_MTU]; + ssize_t r; + + /* Send request */ + req = (void *) pkt; + req->type = BNEP_CONTROL; + req->ctrl = BNEP_SETUP_CONN_REQ; + req->uuid_size = 2; /* 16bit UUID */ + + s = (void *) req->service; + s->dst = htons(svc); + s->src = htons(role); + + memset(&timeo, 0, sizeof(timeo)); + timeo.tv_sec = 30; + + setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); + + if (send(sk, pkt, sizeof(*req) + sizeof(*s), 0) < 0) + return -1; + +receive: + /* Get response */ + r = recv(sk, pkt, BNEP_MTU, 0); + if (r <= 0) + return -1; + + memset(&timeo, 0, sizeof(timeo)); + timeo.tv_sec = 0; + + setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); + + errno = EPROTO; + + if ((size_t) r < sizeof(*rsp)) + return -1; + + rsp = (void *) pkt; + if (rsp->type != BNEP_CONTROL) + return -1; + + if (rsp->ctrl != BNEP_SETUP_CONN_RSP) + goto receive; + + r = ntohs(rsp->resp); + + switch (r) { + case BNEP_SUCCESS: + break; + + case BNEP_CONN_INVALID_DST: + case BNEP_CONN_INVALID_SRC: + case BNEP_CONN_INVALID_SVC: + errno = EPROTO; + return -1; + + case BNEP_CONN_NOT_ALLOWED: + errno = EACCES; + return -1; + } + + return bnep_connadd(sk, role, dev); +} diff --git a/compat/dun.c b/compat/dun.c new file mode 100644 index 0000000..59f036f --- /dev/null +++ b/compat/dun.c @@ -0,0 +1,334 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "dund.h" +#include "lib.h" + +#define PROC_BASE "/proc" + +static int for_each_port(int (*func)(struct rfcomm_dev_info *, unsigned long), unsigned long arg) +{ + struct rfcomm_dev_list_req *dl; + struct rfcomm_dev_info *di; + long r = 0; + int sk, i; + + sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM); + if (sk < 0 ) { + perror("Can't open RFCOMM control socket"); + exit(1); + } + + dl = malloc(sizeof(*dl) + RFCOMM_MAX_DEV * sizeof(*di)); + if (!dl) { + perror("Can't allocate request memory"); + close(sk); + exit(1); + } + + dl->dev_num = RFCOMM_MAX_DEV; + di = dl->dev_info; + + if (ioctl(sk, RFCOMMGETDEVLIST, (void *) dl) < 0) { + perror("Can't get device list"); + exit(1); + } + + for (i = 0; i < dl->dev_num; i++) { + r = func(di + i, arg); + if (r) break; + } + + close(sk); + free(dl); + return r; +} + +static int uses_rfcomm(char *path, char *dev) +{ + struct dirent *de; + DIR *dir; + + dir = opendir(path); + if (!dir) + return 0; + + if (chdir(path) < 0) + return 0; + + while ((de = readdir(dir)) != NULL) { + char link[PATH_MAX + 1]; + int len = readlink(de->d_name, link, sizeof(link)); + if (len > 0) { + link[len] = 0; + if (strstr(link, dev)) { + closedir(dir); + return 1; + } + } + } + + closedir(dir); + + return 0; +} + +static int find_pppd(int id, pid_t *pid) +{ + struct dirent *de; + char path[PATH_MAX + 1]; + char dev[10]; + int empty = 1; + DIR *dir; + + dir = opendir(PROC_BASE); + if (!dir) { + perror(PROC_BASE); + return -1; + } + + sprintf(dev, "rfcomm%d", id); + + *pid = 0; + while ((de = readdir(dir)) != NULL) { + empty = 0; + if (isdigit(de->d_name[0])) { + sprintf(path, "%s/%s/fd", PROC_BASE, de->d_name); + if (uses_rfcomm(path, dev)) { + *pid = atoi(de->d_name); + break; + } + } + } + closedir(dir); + + if (empty) + fprintf(stderr, "%s is empty (not mounted ?)\n", PROC_BASE); + + return *pid != 0; +} + +static int dun_exec(char *tty, char *prog, char **args) +{ + int pid = fork(); + int fd; + + switch (pid) { + case -1: + return -1; + + case 0: + break; + + default: + return pid; + } + + setsid(); + + /* Close all FDs */ + for (fd = 3; fd < 20; fd++) + close(fd); + + execvp(prog, args); + + syslog(LOG_ERR, "Error while executing %s", prog); + + exit(1); +} + +static int dun_create_tty(int sk, char *tty, int size) +{ + struct sockaddr_rc sa; + struct stat st; + socklen_t alen; + int id, try = 30; + + struct rfcomm_dev_req req = { + flags: (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP), + dev_id: -1 + }; + + alen = sizeof(sa); + if (getpeername(sk, (struct sockaddr *) &sa, &alen) < 0) + return -1; + bacpy(&req.dst, &sa.rc_bdaddr); + + alen = sizeof(sa); + if (getsockname(sk, (struct sockaddr *) &sa, &alen) < 0) + return -1; + bacpy(&req.src, &sa.rc_bdaddr); + req.channel = sa.rc_channel; + + id = ioctl(sk, RFCOMMCREATEDEV, &req); + if (id < 0) + return id; + + snprintf(tty, size, "/dev/rfcomm%d", id); + while (stat(tty, &st) < 0) { + snprintf(tty, size, "/dev/bluetooth/rfcomm/%d", id); + if (stat(tty, &st) < 0) { + snprintf(tty, size, "/dev/rfcomm%d", id); + if (try--) { + usleep(100 * 1000); + continue; + } + + memset(&req, 0, sizeof(req)); + req.dev_id = id; + ioctl(sk, RFCOMMRELEASEDEV, &req); + + return -1; + } + } + + return id; +} + +int dun_init(void) +{ + return 0; +} + +int dun_cleanup(void) +{ + return 0; +} + +static int show_conn(struct rfcomm_dev_info *di, unsigned long arg) +{ + pid_t pid; + + if (di->state == BT_CONNECTED && + (di->flags & (1<flags & (1<flags & (1<id, &pid)) { + char dst[18]; + ba2str(&di->dst, dst); + + printf("rfcomm%d: %s channel %d pppd pid %d\n", + di->id, dst, di->channel, pid); + } + } + return 0; +} + +static int kill_conn(struct rfcomm_dev_info *di, unsigned long arg) +{ + bdaddr_t *dst = (bdaddr_t *) arg; + pid_t pid; + + if (di->state == BT_CONNECTED && + (di->flags & (1<flags & (1<flags & (1<dst, dst)) + return 0; + + if (find_pppd(di->id, &pid)) { + if (kill(pid, SIGINT) < 0) + perror("Kill"); + + if (!dst) + return 0; + return 1; + } + } + return 0; +} + +int dun_show_connections(void) +{ + for_each_port(show_conn, 0); + return 0; +} + +int dun_kill_connection(uint8_t *dst) +{ + for_each_port(kill_conn, (unsigned long) dst); + return 0; +} + +int dun_kill_all_connections(void) +{ + for_each_port(kill_conn, 0); + return 0; +} + +int dun_open_connection(int sk, char *pppd, char **args, int wait) +{ + char tty[100]; + int pid; + + if (dun_create_tty(sk, tty, sizeof(tty) - 1) < 0) { + syslog(LOG_ERR, "RFCOMM TTY creation failed. %s(%d)", strerror(errno), errno); + return -1; + } + + args[0] = "pppd"; + args[1] = tty; + args[2] = "nodetach"; + + pid = dun_exec(tty, pppd, args); + if (pid < 0) { + syslog(LOG_ERR, "Exec failed. %s(%d)", strerror(errno), errno); + return -1; + } + + if (wait) { + int status; + waitpid(pid, &status, 0); + /* FIXME: Check for waitpid errors */ + } + + return 0; +} diff --git a/compat/dund.1 b/compat/dund.1 new file mode 100644 index 0000000..09fb7f7 --- /dev/null +++ b/compat/dund.1 @@ -0,0 +1,72 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.29. +.TH BlueZ "1" "February 2003" "DUN daemon" "User Commands" +.SH NAME +dund \- BlueZ Bluetooth dial-up networking daemon +.SH DESCRIPTION +DUN daemon +.SH SYNOPSIS +dund [pppd options] +.SH OPTIONS +.TP +\fB\-\-show\fR \fB\-\-list\fR \fB\-l\fR +Show active DUN connections +.TP +\fB\-\-listen\fR \fB\-s\fR +Listen for DUN connections +.TP +\fB\-\-dialup\fR \fB\-u\fR +Listen for dialup/telephone connections +.TP +\fB\-\-connect\fR \fB\-c\fR +Create DUN connection +.TP +\fB\-\-mrouter\fR \fB\-m\fR +Create mRouter connection +.TP +\fB\-\-search\fR \fB\-Q[duration]\fR +Search and connect +.TP +\fB\-\-kill\fR \fB\-k\fR +Kill DUN connection +.TP +\fB\-\-killall\fR \fB\-K\fR +Kill all DUN connections +.TP +\fB\-\-channel\fR \fB\-C\fR +RFCOMM channel +.TP +\fB\-\-device\fR \fB\-i\fR +Source bdaddr +.TP +\fB\-\-nosdp\fR \fB\-D\fR +Disable SDP +.TP +\fB\-\-auth\fR \fB\-A\fR +Enable authentification +.TP +\fB\-\-encrypt\fR \fB\-E\fR +Enable encryption +.TP +\fB\-\-secure\fR \fB\-S\fR +Secure connection +.TP +\fB\-\-master\fR \fB\-M\fR +Become the master of a piconet +.TP +\fB\-\-nodetach\fR \fB\-n\fR +Do not become a daemon +.TP +\fB\-\-persist\fR \fB\-p[interval]\fR +Persist mode +.TP +\fB\-\-pppd\fR \fB\-d\fR +Location of the PPP daemon (pppd) +.TP +\fB\-\-msdun\fR \fB\-X\fR [timeo] +Enable Microsoft dialup networking support +.TP +\fB\-\-activesync\fR \fB\-a\fR +Enable Microsoft ActiveSync networking +.TP +\fB\-\-cache\fR \fB\-C\fR [valid] +Enable address cache diff --git a/compat/dund.c b/compat/dund.c new file mode 100644 index 0000000..af1b536 --- /dev/null +++ b/compat/dund.c @@ -0,0 +1,645 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "sdp.h" +#include "dund.h" +#include "lib.h" + +volatile sig_atomic_t __io_canceled; + +/* MS dialup networking support (i.e. CLIENT / CLIENTSERVER thing) */ +static int msdun = 0; + +static char *pppd = "/usr/sbin/pppd"; +static char *pppd_opts[DUN_MAX_PPP_OPTS] = + { + /* First 3 are reserved */ + "", "", "", + "noauth", + "noipdefault", + NULL + }; + +static int detach = 1; +static int persist; +static int use_sdp = 1; +static int auth; +static int encrypt; +static int secure; +static int master; +static int type = LANACCESS; +static int search_duration = 10; +static uint use_cache; + +static int channel; + +static struct { + uint valid; + char dst[40]; + bdaddr_t bdaddr; + int channel; +} cache; + +static bdaddr_t src_addr = *BDADDR_ANY; +static int src_dev = -1; + +volatile int terminate; + +enum { + NONE, + SHOW, + LISTEN, + CONNECT, + KILL +} modes; + +static int create_connection(char *dst, bdaddr_t *bdaddr, int mrouter); + +static int do_listen(void) +{ + struct sockaddr_rc sa; + int sk, lm; + + if (type == MROUTER) { + if (!cache.valid) + return -1; + + if (create_connection(cache.dst, &cache.bdaddr, type) < 0) { + syslog(LOG_ERR, "Cannot connect to mRouter device. %s(%d)", + strerror(errno), errno); + return -1; + } + } + + if (!channel) + channel = DUN_DEFAULT_CHANNEL; + + if (use_sdp) + dun_sdp_register(&src_addr, channel, type); + + if (type == MROUTER) + syslog(LOG_INFO, "Waiting for mRouter callback on channel %d", channel); + + /* Create RFCOMM socket */ + sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + syslog(LOG_ERR, "Cannot create RFCOMM socket. %s(%d)", + strerror(errno), errno); + return -1; + } + + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = channel; + sa.rc_bdaddr = src_addr; + + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) { + syslog(LOG_ERR, "Bind failed. %s(%d)", strerror(errno), errno); + return -1; + } + + /* Set link mode */ + lm = 0; + if (master) + lm |= RFCOMM_LM_MASTER; + if (auth) + lm |= RFCOMM_LM_AUTH; + if (encrypt) + lm |= RFCOMM_LM_ENCRYPT; + if (secure) + lm |= RFCOMM_LM_SECURE; + + if (lm && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) { + syslog(LOG_ERR, "Failed to set link mode. %s(%d)", strerror(errno), errno); + return -1; + } + + listen(sk, 10); + + while (!terminate) { + socklen_t alen = sizeof(sa); + int nsk; + char ba[40]; + char ch[10]; + + nsk = accept(sk, (struct sockaddr *) &sa, &alen); + if (nsk < 0) { + syslog(LOG_ERR, "Accept failed. %s(%d)", strerror(errno), errno); + continue; + } + + switch (fork()) { + case 0: + break; + case -1: + syslog(LOG_ERR, "Fork failed. %s(%d)", strerror(errno), errno); + default: + close(nsk); + if (type == MROUTER) { + close(sk); + terminate = 1; + } + continue; + } + + close(sk); + + if (msdun && ms_dun(nsk, 1, msdun) < 0) { + syslog(LOG_ERR, "MSDUN failed. %s(%d)", strerror(errno), errno); + exit(0); + } + + ba2str(&sa.rc_bdaddr, ba); + snprintf(ch, sizeof(ch), "%d", channel); + + /* Setup environment */ + setenv("DUN_BDADDR", ba, 1); + setenv("DUN_CHANNEL", ch, 1); + + if (!dun_open_connection(nsk, pppd, pppd_opts, 0)) + syslog(LOG_INFO, "New connection from %s", ba); + + close(nsk); + exit(0); + } + + if (use_sdp) + dun_sdp_unregister(); + return 0; +} + +/* Connect and initiate RFCOMM session + * Returns: + * -1 - critical error (exit persist mode) + * 1 - non critical error + * 0 - success + */ +static int create_connection(char *dst, bdaddr_t *bdaddr, int mrouter) +{ + struct sockaddr_rc sa; + int sk, err = 0, ch; + + if (use_cache && cache.valid && cache.channel) { + /* Use cached channel */ + ch = cache.channel; + + } else if (!channel) { + syslog(LOG_INFO, "Searching for %s on %s", mrouter ? "SP" : "LAP", dst); + + if (dun_sdp_search(&src_addr, bdaddr, &ch, mrouter) <= 0) + return 0; + } else + ch = channel; + + syslog(LOG_INFO, "Connecting to %s channel %d", dst, ch); + + sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + syslog(LOG_ERR, "Cannot create RFCOMM socket. %s(%d)", + strerror(errno), errno); + return -1; + } + + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = 0; + sa.rc_bdaddr = src_addr; + + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) + syslog(LOG_ERR, "Bind failed. %s(%d)", + strerror(errno), errno); + + sa.rc_channel = ch; + sa.rc_bdaddr = *bdaddr; + + if (!connect(sk, (struct sockaddr *) &sa, sizeof(sa)) ) { + if (mrouter) { + sleep(1); + close(sk); + return 0; + } + + syslog(LOG_INFO, "Connection established"); + + if (msdun && ms_dun(sk, 0, msdun) < 0) { + syslog(LOG_ERR, "MSDUN failed. %s(%d)", strerror(errno), errno); + err = 1; + goto out; + } + + if (!dun_open_connection(sk, pppd, pppd_opts, (persist > 0))) + err = 0; + else + err = 1; + } else { + syslog(LOG_ERR, "Connect to %s failed. %s(%d)", + dst, strerror(errno), errno); + err = 1; + } + +out: + if (use_cache) { + if (!err) { + /* Succesesful connection, validate cache */ + strcpy(cache.dst, dst); + bacpy(&cache.bdaddr, bdaddr); + cache.channel = ch; + cache.valid = use_cache; + } else { + cache.channel = 0; + cache.valid--; + } + } + + close(sk); + return err; +} + +/* Search and connect + * Returns: + * -1 - critical error (exit persist mode) + * 1 - non critical error + * 0 - success + */ +static int do_connect(void) +{ + inquiry_info *ii; + int reconnect = 0; + int i, n, r = 0; + + do { + if (reconnect) + sleep(persist); + reconnect = 1; + + if (cache.valid) { + /* Use cached bdaddr */ + r = create_connection(cache.dst, &cache.bdaddr, 0); + if (r < 0) { + terminate = 1; + break; + } + continue; + } + + syslog(LOG_INFO, "Inquiring"); + + /* FIXME: Should we use non general LAP here ? */ + + ii = NULL; + n = hci_inquiry(src_dev, search_duration, 0, NULL, &ii, 0); + if (n < 0) { + syslog(LOG_ERR, "Inquiry failed. %s(%d)", strerror(errno), errno); + continue; + } + + for (i = 0; i < n; i++) { + char dst[40]; + ba2str(&ii[i].bdaddr, dst); + + r = create_connection(dst, &ii[i].bdaddr, 0); + if (r < 0) { + terminate = 1; + break; + } + } + bt_free(ii); + } while (!terminate && persist); + + return r; +} + +static void do_show(void) +{ + dun_show_connections(); +} + +static void do_kill(char *dst) +{ + if (dst) { + bdaddr_t ba; + str2ba(dst, &ba); + dun_kill_connection((void *) &ba); + } else + dun_kill_all_connections(); +} + +static void sig_hup(int sig) +{ + return; +} + +static void sig_term(int sig) +{ + io_cancel(); + terminate = 1; +} + +static struct option main_lopts[] = { + { "help", 0, 0, 'h' }, + { "listen", 0, 0, 's' }, + { "connect", 1, 0, 'c' }, + { "search", 2, 0, 'Q' }, + { "kill", 1, 0, 'k' }, + { "killall", 0, 0, 'K' }, + { "channel", 1, 0, 'P' }, + { "device", 1, 0, 'i' }, + { "nosdp", 0, 0, 'D' }, + { "list", 0, 0, 'l' }, + { "show", 0, 0, 'l' }, + { "nodetach", 0, 0, 'n' }, + { "persist", 2, 0, 'p' }, + { "auth", 0, 0, 'A' }, + { "encrypt", 0, 0, 'E' }, + { "secure", 0, 0, 'S' }, + { "master", 0, 0, 'M' }, + { "cache", 0, 0, 'C' }, + { "pppd", 1, 0, 'd' }, + { "msdun", 2, 0, 'X' }, + { "activesync", 0, 0, 'a' }, + { "mrouter", 1, 0, 'm' }, + { "dialup", 0, 0, 'u' }, + { 0, 0, 0, 0 } +}; + +static const char *main_sopts = "hsc:k:Kr:i:lnp::DQ::AESMP:C::P:Xam:u"; + +static const char *main_help = + "Bluetooth LAP (LAN Access over PPP) daemon version %s\n" + "Usage:\n" + "\tdund [pppd options]\n" + "Options:\n" + "\t--show --list -l Show active LAP connections\n" + "\t--listen -s Listen for LAP connections\n" + "\t--dialup -u Pretend to be a dialup/telephone\n" + "\t--connect -c Create LAP connection\n" + "\t--mrouter -m Create mRouter connection\n" + "\t--search -Q[duration] Search and connect\n" + "\t--kill -k Kill LAP connection\n" + "\t--killall -K Kill all LAP connections\n" + "\t--channel -P RFCOMM channel\n" + "\t--device -i Source bdaddr\n" + "\t--nosdp -D Disable SDP\n" + "\t--auth -A Enable authentication\n" + "\t--encrypt -E Enable encryption\n" + "\t--secure -S Secure connection\n" + "\t--master -M Become the master of a piconet\n" + "\t--nodetach -n Do not become a daemon\n" + "\t--persist -p[interval] Persist mode\n" + "\t--pppd -d Location of the PPP daemon (pppd)\n" + "\t--msdun -X[timeo] Enable Microsoft dialup networking support\n" + "\t--activesync -a Enable Microsoft ActiveSync networking\n" + "\t--cache -C[valid] Enable address cache\n"; + +int main(int argc, char *argv[]) +{ + char *dst = NULL, *src = NULL; + struct sigaction sa; + int mode = NONE; + int opt; + + while ((opt=getopt_long(argc, argv, main_sopts, main_lopts, NULL)) != -1) { + switch(opt) { + case 'l': + mode = SHOW; + detach = 0; + break; + + case 's': + mode = LISTEN; + type = LANACCESS; + break; + + case 'c': + mode = CONNECT; + dst = strdup(optarg); + break; + + case 'Q': + mode = CONNECT; + dst = NULL; + if (optarg) + search_duration = atoi(optarg); + break; + + case 'k': + mode = KILL; + detach = 0; + dst = strdup(optarg); + break; + + case 'K': + mode = KILL; + detach = 0; + dst = NULL; + break; + + case 'P': + channel = atoi(optarg); + break; + + case 'i': + src = strdup(optarg); + break; + + case 'D': + use_sdp = 0; + break; + + case 'A': + auth = 1; + break; + + case 'E': + encrypt = 1; + break; + + case 'S': + secure = 1; + break; + + case 'M': + master = 1; + break; + + case 'n': + detach = 0; + break; + + case 'p': + if (optarg) + persist = atoi(optarg); + else + persist = 5; + break; + + case 'C': + if (optarg) + use_cache = atoi(optarg); + else + use_cache = 2; + break; + + case 'd': + pppd = strdup(optarg); + break; + + case 'X': + if (optarg) + msdun = atoi(optarg); + else + msdun = 10; + break; + + case 'a': + msdun = 10; + type = ACTIVESYNC; + break; + + case 'm': + mode = LISTEN; + dst = strdup(optarg); + type = MROUTER; + break; + + case 'u': + mode = LISTEN; + type = DIALUP; + break; + + case 'h': + default: + printf(main_help, VERSION); + exit(0); + } + } + + argc -= optind; + argv += optind; + + /* The rest is pppd options */ + if (argc > 0) { + for (opt = 3; argc && opt < DUN_MAX_PPP_OPTS - 1; + argc--, opt++) + pppd_opts[opt] = *argv++; + pppd_opts[opt] = NULL; + } + + io_init(); + + if (dun_init()) { + free(dst); + return -1; + } + + /* Check non daemon modes first */ + switch (mode) { + case SHOW: + do_show(); + free(dst); + return 0; + + case KILL: + do_kill(dst); + free(dst); + return 0; + + case NONE: + printf(main_help, VERSION); + free(dst); + return 0; + } + + /* Initialize signals */ + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + if (detach && daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + + openlog("dund", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON); + syslog(LOG_INFO, "Bluetooth DUN daemon version %s", VERSION); + + if (src) { + src_dev = hci_devid(src); + if (src_dev < 0 || hci_devba(src_dev, &src_addr) < 0) { + syslog(LOG_ERR, "Invalid source. %s(%d)", strerror(errno), errno); + free(dst); + return -1; + } + } + + if (dst) { + strncpy(cache.dst, dst, sizeof(cache.dst) - 1); + str2ba(dst, &cache.bdaddr); + + /* Disable cache invalidation */ + use_cache = cache.valid = ~0; + } + + switch (mode) { + case CONNECT: + do_connect(); + break; + + case LISTEN: + do_listen(); + break; + } + + free(dst); + return 0; +} diff --git a/compat/dund.h b/compat/dund.h new file mode 100644 index 0000000..e3a4ef6 --- /dev/null +++ b/compat/dund.h @@ -0,0 +1,40 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define DUN_CONFIG_DIR "/etc/bluetooth/dun" + +#define DUN_DEFAULT_CHANNEL 1 + +#define DUN_MAX_PPP_OPTS 40 + +int dun_init(void); +int dun_cleanup(void); + +int dun_show_connections(void); +int dun_kill_connection(uint8_t *dst); +int dun_kill_all_connections(void); + +int dun_open_connection(int sk, char *pppd, char **pppd_opts, int wait); + +int ms_dun(int fd, int server, int timeo); diff --git a/compat/fakehid.c b/compat/fakehid.c new file mode 100644 index 0000000..b996d10 --- /dev/null +++ b/compat/fakehid.c @@ -0,0 +1,669 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "hidd.h" +#include "uinput.h" + +#include + +#ifdef NEED_PPOLL +#include "ppoll.h" +#endif + +static volatile sig_atomic_t __io_canceled = 0; + +static void sig_hup(int sig) +{ +} + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static void send_event(int fd, uint16_t type, uint16_t code, int32_t value) +{ + struct uinput_event event; + int len; + + if (fd <= fileno(stderr)) + return; + + memset(&event, 0, sizeof(event)); + event.type = type; + event.code = code; + event.value = value; + + len = write(fd, &event, sizeof(event)); +} + +static int uinput_create(char *name, int keyboard, int mouse) +{ + struct uinput_dev dev; + int fd, aux; + + fd = open("/dev/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/input/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/misc/uinput", O_RDWR); + if (fd < 0) { + fprintf(stderr, "Can't open input device: %s (%d)\n", + strerror(errno), errno); + return -1; + } + } + } + + memset(&dev, 0, sizeof(dev)); + + if (name) + strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1); + + dev.id.bustype = BUS_BLUETOOTH; + dev.id.vendor = 0x0000; + dev.id.product = 0x0000; + dev.id.version = 0x0000; + + if (write(fd, &dev, sizeof(dev)) < 0) { + fprintf(stderr, "Can't write device information: %s (%d)\n", + strerror(errno), errno); + close(fd); + return -1; + } + + if (mouse) { + ioctl(fd, UI_SET_EVBIT, EV_REL); + + for (aux = REL_X; aux <= REL_MISC; aux++) + ioctl(fd, UI_SET_RELBIT, aux); + } + + if (keyboard) { + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_EVBIT, EV_LED); + ioctl(fd, UI_SET_EVBIT, EV_REP); + + for (aux = KEY_RESERVED; aux <= KEY_UNKNOWN; aux++) + ioctl(fd, UI_SET_KEYBIT, aux); + + //for (aux = LED_NUML; aux <= LED_MISC; aux++) + // ioctl(fd, UI_SET_LEDBIT, aux); + } + + if (mouse) { + ioctl(fd, UI_SET_EVBIT, EV_KEY); + + for (aux = BTN_LEFT; aux <= BTN_BACK; aux++) + ioctl(fd, UI_SET_KEYBIT, aux); + } + + ioctl(fd, UI_DEV_CREATE); + + return fd; +} + +static int rfcomm_connect(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + struct sockaddr_rc addr; + int sk; + + sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + fprintf(stderr, "Can't create socket: %s (%d)\n", + strerror(errno), errno); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Can't bind socket: %s (%d)\n", + strerror(errno), errno); + close(sk); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, dst); + addr.rc_channel = channel; + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Can't connect: %s (%d)\n", + strerror(errno), errno); + close(sk); + return -1; + } + + return sk; +} + +static void func(int fd) +{ +} + +static void back(int fd) +{ +} + +static void next(int fd) +{ +} + +static void button(int fd, unsigned int button, int is_press) +{ + switch (button) { + case 1: + send_event(fd, EV_KEY, BTN_LEFT, is_press); + break; + case 3: + send_event(fd, EV_KEY, BTN_RIGHT, is_press); + break; + } + + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static void move(int fd, unsigned int direction) +{ + double angle; + int32_t x, y; + + angle = (direction * 22.5) * 3.1415926 / 180; + x = (int) (sin(angle) * 8); + y = (int) (cos(angle) * -8); + + send_event(fd, EV_REL, REL_X, x); + send_event(fd, EV_REL, REL_Y, y); + + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static inline void epox_decode(int fd, unsigned char event) +{ + switch (event) { + case 48: + func(fd); break; + case 55: + back(fd); break; + case 56: + next(fd); break; + case 53: + button(fd, 1, 1); break; + case 121: + button(fd, 1, 0); break; + case 113: + break; + case 54: + button(fd, 3, 1); break; + case 120: + button(fd, 3, 0); break; + case 112: + break; + case 51: + move(fd, 0); break; + case 97: + move(fd, 1); break; + case 65: + move(fd, 2); break; + case 98: + move(fd, 3); break; + case 50: + move(fd, 4); break; + case 99: + move(fd, 5); break; + case 67: + move(fd, 6); break; + case 101: + move(fd, 7); break; + case 52: + move(fd, 8); break; + case 100: + move(fd, 9); break; + case 66: + move(fd, 10); break; + case 102: + move(fd, 11); break; + case 49: + move(fd, 12); break; + case 103: + move(fd, 13); break; + case 57: + move(fd, 14); break; + case 104: + move(fd, 15); break; + case 69: + break; + default: + printf("Unknown event code %d\n", event); + break; + } +} + +int epox_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + unsigned char buf[16]; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char addr[18]; + int i, fd, sk, len; + + sk = rfcomm_connect(src, dst, channel); + if (sk < 0) + return -1; + + fd = uinput_create("Bluetooth Presenter", 0, 1); + if (fd < 0) { + close(sk); + return -1; + } + + ba2str(dst, addr); + + printf("Connected to %s on channel %d\n", addr, channel); + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) < 1) + continue; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + break; + + for (i = 0; i < len; i++) + epox_decode(fd, buf[i]); + } + + printf("Disconnected\n"); + + ioctl(fd, UI_DEV_DESTROY); + + close(fd); + close(sk); + + return 0; +} + +int headset_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + printf("Not implemented\n"); + return -1; +} + +/* The strange meta key close to Ctrl has been assigned to Esc, + Fn key to CtrlR and the left space to Alt*/ + +static unsigned char jthree_keycodes[63] = { + KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, + KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, + KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, + KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, + KEY_LEFTALT, KEY_TAB, KEY_CAPSLOCK, KEY_ESC, + KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, + KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE, + KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_ENTER, + KEY_N, KEY_M, KEY_COMMA, KEY_DOT, KEY_SLASH, KEY_UP, + KEY_SPACE, KEY_COMPOSE, KEY_LEFT, KEY_DOWN, KEY_RIGHT, + KEY_LEFTCTRL, KEY_RIGHTSHIFT, KEY_LEFTSHIFT, KEY_DELETE, KEY_RIGHTCTRL, KEY_RIGHTALT, +}; + +static inline void jthree_decode(int fd, unsigned char event) +{ + if (event > 63) + send_event(fd, EV_KEY, jthree_keycodes[event & 0x3f], 0); + else + send_event(fd, EV_KEY, jthree_keycodes[event - 1], 1); +} + +int jthree_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + unsigned char buf[16]; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char addr[18]; + int i, fd, sk, len; + + sk = rfcomm_connect(src, dst, channel); + if (sk < 0) + return -1; + + fd = uinput_create("J-Three Keyboard", 1, 0); + if (fd < 0) { + close(sk); + return -1; + } + + ba2str(dst, addr); + + printf("Connected to %s on channel %d\n", addr, channel); + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) < 1) + continue; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + break; + + for (i = 0; i < len; i++) + jthree_decode(fd, buf[i]); + } + + printf("Disconnected\n"); + + ioctl(fd, UI_DEV_DESTROY); + + close(fd); + close(sk); + + return 0; +} + +static const int celluon_xlate_num[10] = { + KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 +}; + +static const int celluon_xlate_char[26] = { + KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, + KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, + KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z +}; + +static int celluon_xlate(int c) +{ + if (c >= '0' && c <= '9') + return celluon_xlate_num[c - '0']; + + if (c >= 'A' && c <= 'Z') + return celluon_xlate_char[c - 'A']; + + switch (c) { + case 0x08: + return KEY_BACKSPACE; + case 0x09: + return KEY_TAB; + case 0x0d: + return KEY_ENTER; + case 0x11: + return KEY_LEFTCTRL; + case 0x14: + return KEY_CAPSLOCK; + case 0x20: + return KEY_SPACE; + case 0x25: + return KEY_LEFT; + case 0x26: + return KEY_UP; + case 0x27: + return KEY_RIGHT; + case 0x28: + return KEY_DOWN; + case 0x2e: + return KEY_DELETE; + case 0x5b: + return KEY_MENU; + case 0xa1: + return KEY_RIGHTSHIFT; + case 0xa0: + return KEY_LEFTSHIFT; + case 0xba: + return KEY_SEMICOLON; + case 0xbd: + return KEY_MINUS; + case 0xbc: + return KEY_COMMA; + case 0xbb: + return KEY_EQUAL; + case 0xbe: + return KEY_DOT; + case 0xbf: + return KEY_SLASH; + case 0xc0: + return KEY_GRAVE; + case 0xdb: + return KEY_LEFTBRACE; + case 0xdc: + return KEY_BACKSLASH; + case 0xdd: + return KEY_RIGHTBRACE; + case 0xde: + return KEY_APOSTROPHE; + case 0xff03: + return KEY_HOMEPAGE; + case 0xff04: + return KEY_TIME; + case 0xff06: + return KEY_OPEN; + case 0xff07: + return KEY_LIST; + case 0xff08: + return KEY_MAIL; + case 0xff30: + return KEY_CALC; + case 0xff1a: /* Map FN to ALT */ + return KEY_LEFTALT; + case 0xff2f: + return KEY_INFO; + default: + printf("Unknown key %x\n", c); + return c; + } +} + +struct celluon_state { + int len; /* Expected length of current packet */ + int count; /* Number of bytes received */ + int action; + int key; +}; + +static void celluon_decode(int fd, struct celluon_state *s, uint8_t c) +{ + if (s->count < 2 && c != 0xa5) { + /* Lost Sync */ + s->count = 0; + return; + } + + switch (s->count) { + case 0: + /* New packet - Reset state */ + s->len = 30; + s->key = 0; + break; + case 1: + break; + case 6: + s->action = c; + break; + case 28: + s->key = c; + if (c == 0xff) + s->len = 31; + break; + case 29: + case 30: + if (s->count == s->len - 1) { + /* TODO: Verify checksum */ + if (s->action < 2) { + send_event(fd, EV_KEY, celluon_xlate(s->key), + s->action); + } + s->count = -1; + } else { + s->key = (s->key << 8) | c; + } + break; + } + + s->count++; + + return; +} + +int celluon_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + unsigned char buf[16]; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char addr[18]; + int i, fd, sk, len; + struct celluon_state s; + + sk = rfcomm_connect(src, dst, channel); + if (sk < 0) + return -1; + + fd = uinput_create("Celluon Keyboard", 1, 0); + if (fd < 0) { + close(sk); + return -1; + } + + ba2str(dst, addr); + + printf("Connected to %s on channel %d\n", addr, channel); + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + memset(&s, 0, sizeof(s)); + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) < 1) + continue; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + break; + + for (i = 0; i < len; i++) + celluon_decode(fd, &s, buf[i]); + } + + printf("Disconnected\n"); + + ioctl(fd, UI_DEV_DESTROY); + + close(fd); + close(sk); + + return 0; +} diff --git a/compat/hidd.1 b/compat/hidd.1 new file mode 100644 index 0000000..b186ac2 --- /dev/null +++ b/compat/hidd.1 @@ -0,0 +1,41 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.33. +.TH HIDD "1" "May 2004" "hidd - Bluetooth HID daemon" "User Commands" +.SH NAME +hidd \- Bluetooth HID daemon +.SH DESCRIPTION +hidd - Bluetooth HID daemon +.SS "Usage:" +.IP +hidd [options] [commands] +.SH OPTIONS +.TP +\fB\-i\fR +Local HCI device or BD Address +.TP +\fB\-t\fR +Set idle timeout (in minutes) +.TP +\fB\-n\fR, \fB\-\-nodaemon\fR +Don't fork daemon to background +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help +.SS "Commands:" +.TP +\fB\-\-server\fR +Start HID server +.TP +\fB\-\-search\fR +Search for HID devices +.TP +\fB\-\-connect\fR +Connect remote HID device +.TP +\fB\-\-kill\fR +Terminate HID connection +.TP +\fB\-\-killall\fR +Terminate all connections +.TP +\fB\-\-show\fR +List current HID connections diff --git a/compat/hidd.c b/compat/hidd.c new file mode 100644 index 0000000..88944cf --- /dev/null +++ b/compat/hidd.c @@ -0,0 +1,862 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sdp.h" +#include "hidd.h" + +#ifdef NEED_PPOLL +#include "ppoll.h" +#endif + +enum { + NONE, + SHOW, + SERVER, + SEARCH, + CONNECT, + KILL +}; + +static volatile sig_atomic_t __io_canceled = 0; + +static void sig_hup(int sig) +{ +} + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static int l2cap_connect(bdaddr_t *src, bdaddr_t *dst, unsigned short psm) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + int sk; + + if ((sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + memset(&opts, 0, sizeof(opts)); + opts.imtu = HIDP_DEFAULT_MTU; + opts.omtu = HIDP_DEFAULT_MTU; + opts.flush_to = 0xffff; + + setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)); + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(psm); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + return sk; +} + +static int l2cap_listen(const bdaddr_t *bdaddr, unsigned short psm, int lm, int backlog) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + int sk; + + if ((sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, bdaddr); + addr.l2_psm = htobs(psm); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + setsockopt(sk, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm)); + + memset(&opts, 0, sizeof(opts)); + opts.imtu = HIDP_DEFAULT_MTU; + opts.omtu = HIDP_DEFAULT_MTU; + opts.flush_to = 0xffff; + + setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)); + + if (listen(sk, backlog) < 0) { + close(sk); + return -1; + } + + return sk; +} + +static int l2cap_accept(int sk, bdaddr_t *bdaddr) +{ + struct sockaddr_l2 addr; + socklen_t addrlen; + int nsk; + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if ((nsk = accept(sk, (struct sockaddr *) &addr, &addrlen)) < 0) + return -1; + + if (bdaddr) + bacpy(bdaddr, &addr.l2_bdaddr); + + return nsk; +} + +static int request_authentication(bdaddr_t *src, bdaddr_t *dst) +{ + struct hci_conn_info_req *cr; + char addr[18]; + int err, dd, dev_id; + + ba2str(src, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) + return dev_id; + + dd = hci_open_dev(dev_id); + if (dd < 0) + return dd; + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) + return -ENOMEM; + + bacpy(&cr->bdaddr, dst); + cr->type = ACL_LINK; + err = ioctl(dd, HCIGETCONNINFO, (unsigned long) cr); + if (err < 0) { + free(cr); + hci_close_dev(dd); + return err; + } + + err = hci_authenticate_link(dd, htobs(cr->conn_info->handle), 25000); + + free(cr); + hci_close_dev(dd); + + return err; +} + +static int request_encryption(bdaddr_t *src, bdaddr_t *dst) +{ + struct hci_conn_info_req *cr; + char addr[18]; + int err, dd, dev_id; + + ba2str(src, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) + return dev_id; + + dd = hci_open_dev(dev_id); + if (dd < 0) + return dd; + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) + return -ENOMEM; + + bacpy(&cr->bdaddr, dst); + cr->type = ACL_LINK; + err = ioctl(dd, HCIGETCONNINFO, (unsigned long) cr); + if (err < 0) { + free(cr); + hci_close_dev(dd); + return err; + } + + err = hci_encrypt_link(dd, htobs(cr->conn_info->handle), 1, 25000); + + free(cr); + hci_close_dev(dd); + + return err; +} + +static void enable_sixaxis(int csk) +{ + const unsigned char buf[] = { + 0x53 /*HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE*/, + 0xf4, 0x42, 0x03, 0x00, 0x00 }; + int err; + + err = write(csk, buf, sizeof(buf)); +} + +static int create_device(int ctl, int csk, int isk, uint8_t subclass, int nosdp, int nocheck, int bootonly, int encrypt, int timeout) +{ + struct hidp_connadd_req req; + struct sockaddr_l2 addr; + socklen_t addrlen; + bdaddr_t src, dst; + char bda[18]; + int err; + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if (getsockname(csk, (struct sockaddr *) &addr, &addrlen) < 0) + return -1; + + bacpy(&src, &addr.l2_bdaddr); + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if (getpeername(csk, (struct sockaddr *) &addr, &addrlen) < 0) + return -1; + + bacpy(&dst, &addr.l2_bdaddr); + + memset(&req, 0, sizeof(req)); + req.ctrl_sock = csk; + req.intr_sock = isk; + req.flags = 0; + req.idle_to = timeout * 60; + + err = get_stored_device_info(&src, &dst, &req); + if (!err) + goto create; + + if (!nocheck) { + ba2str(&dst, bda); + syslog(LOG_ERR, "Rejected connection from unknown device %s", bda); + /* Return no error to avoid run_server() complaining too */ + return 0; + } + + if (!nosdp) { + err = get_sdp_device_info(&src, &dst, &req); + if (err < 0) + goto error; + } else { + struct l2cap_conninfo conn; + socklen_t size; + uint8_t class[3]; + + memset(&conn, 0, sizeof(conn)); + size = sizeof(conn); + if (getsockopt(csk, SOL_L2CAP, L2CAP_CONNINFO, &conn, &size) < 0) + memset(class, 0, 3); + else + memcpy(class, conn.dev_class, 3); + + if (class[1] == 0x25 && (class[2] == 0x00 || class[2] == 0x01)) + req.subclass = class[0]; + else + req.subclass = 0xc0; + } + +create: + if (subclass != 0x00) + req.subclass = subclass; + + ba2str(&dst, bda); + syslog(LOG_INFO, "New HID device %s (%s)", bda, req.name); + + if (encrypt && (req.subclass & 0x40)) { + err = request_authentication(&src, &dst); + if (err < 0) { + syslog(LOG_ERR, "Authentication for %s failed", bda); + goto error; + } + + err = request_encryption(&src, &dst); + if (err < 0) + syslog(LOG_ERR, "Encryption for %s failed", bda); + } + + if (bootonly) { + req.rd_size = 0; + req.flags |= (1 << HIDP_BOOT_PROTOCOL_MODE); + } + + if (req.vendor == 0x054c && req.product == 0x0268) + enable_sixaxis(csk); + + err = ioctl(ctl, HIDPCONNADD, &req); + +error: + if (req.rd_data) + free(req.rd_data); + + return err; +} + +static void run_server(int ctl, int csk, int isk, uint8_t subclass, int nosdp, int nocheck, int bootonly, int encrypt, int timeout) +{ + struct pollfd p[2]; + sigset_t sigs; + short events; + int err, ncsk, nisk; + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p[0].fd = csk; + p[0].events = POLLIN | POLLERR | POLLHUP; + + p[1].fd = isk; + p[1].events = POLLIN | POLLERR | POLLHUP; + + while (!__io_canceled) { + p[0].revents = 0; + p[1].revents = 0; + + if (ppoll(p, 2, NULL, &sigs) < 1) + continue; + + events = p[0].revents | p[1].revents; + + if (events & POLLIN) { + ncsk = l2cap_accept(csk, NULL); + nisk = l2cap_accept(isk, NULL); + + err = create_device(ctl, ncsk, nisk, subclass, nosdp, nocheck, bootonly, encrypt, timeout); + if (err < 0) + syslog(LOG_ERR, "HID create error %d (%s)", + errno, strerror(errno)); + + close(nisk); + sleep(1); + close(ncsk); + } + } +} + +static char *hidp_state[] = { + "unknown", + "connected", + "open", + "bound", + "listening", + "connecting", + "connecting", + "config", + "disconnecting", + "closed" +}; + +static char *hidp_flagstostr(uint32_t flags) +{ + static char str[100]; + str[0] = 0; + + strcat(str, "["); + + if (flags & (1 << HIDP_BOOT_PROTOCOL_MODE)) + strcat(str, "boot-protocol"); + + strcat(str, "]"); + + return str; +} + +static void do_show(int ctl) +{ + struct hidp_connlist_req req; + struct hidp_conninfo ci[16]; + char addr[18]; + unsigned int i; + + req.cnum = 16; + req.ci = ci; + + if (ioctl(ctl, HIDPGETCONNLIST, &req) < 0) { + perror("Can't get connection list"); + close(ctl); + exit(1); + } + + for (i = 0; i < req.cnum; i++) { + ba2str(&ci[i].bdaddr, addr); + printf("%s %s [%04x:%04x] %s %s\n", addr, ci[i].name, + ci[i].vendor, ci[i].product, hidp_state[ci[i].state], + ci[i].flags ? hidp_flagstostr(ci[i].flags) : ""); + } +} + +static void do_connect(int ctl, bdaddr_t *src, bdaddr_t *dst, uint8_t subclass, int fakehid, int bootonly, int encrypt, int timeout) +{ + struct hidp_connadd_req req; + uint16_t uuid = HID_SVCLASS_ID; + uint8_t channel = 0; + char name[256]; + int csk, isk, err; + + memset(&req, 0, sizeof(req)); + name[0] = '\0'; + + err = get_sdp_device_info(src, dst, &req); + if (err < 0 && fakehid) + err = get_alternate_device_info(src, dst, + &uuid, &channel, name, sizeof(name) - 1); + + if (err < 0) { + perror("Can't get device information"); + close(ctl); + exit(1); + } + + switch (uuid) { + case HID_SVCLASS_ID: + goto connect; + + case SERIAL_PORT_SVCLASS_ID: + if (subclass == 0x40 || !strcmp(name, "Cable Replacement")) { + if (epox_presenter(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + if (subclass == 0x1f || !strcmp(name, "SPP slave")) { + if (jthree_keyboard(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + if (subclass == 0x02 || !strcmp(name, "Serial Port")) { + if (celluon_keyboard(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + break; + + case HEADSET_SVCLASS_ID: + case HANDSFREE_SVCLASS_ID: + if (headset_presenter(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + + return; + +connect: + csk = l2cap_connect(src, dst, L2CAP_PSM_HIDP_CTRL); + if (csk < 0) { + perror("Can't create HID control channel"); + close(ctl); + exit(1); + } + + isk = l2cap_connect(src, dst, L2CAP_PSM_HIDP_INTR); + if (isk < 0) { + perror("Can't create HID interrupt channel"); + close(csk); + close(ctl); + exit(1); + } + + err = create_device(ctl, csk, isk, subclass, 1, 1, bootonly, encrypt, timeout); + if (err < 0) { + fprintf(stderr, "HID create error %d (%s)\n", + errno, strerror(errno)); + close(isk); + sleep(1); + close(csk); + close(ctl); + exit(1); + } +} + +static void do_search(int ctl, bdaddr_t *bdaddr, uint8_t subclass, int fakehid, int bootonly, int encrypt, int timeout) +{ + inquiry_info *info = NULL; + bdaddr_t src, dst; + int i, dev_id, num_rsp, length, flags; + char addr[18]; + uint8_t class[3]; + + ba2str(bdaddr, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) { + dev_id = hci_get_route(NULL); + hci_devba(dev_id, &src); + } else + bacpy(&src, bdaddr); + + length = 8; /* ~10 seconds */ + num_rsp = 0; + flags = IREQ_CACHE_FLUSH; + + printf("Searching ...\n"); + + num_rsp = hci_inquiry(dev_id, length, num_rsp, NULL, &info, flags); + + for (i = 0; i < num_rsp; i++) { + memcpy(class, (info+i)->dev_class, 3); + if (class[1] == 0x25 && (class[2] == 0x00 || class[2] == 0x01)) { + bacpy(&dst, &(info+i)->bdaddr); + ba2str(&dst, addr); + + printf("\tConnecting to device %s\n", addr); + do_connect(ctl, &src, &dst, subclass, fakehid, bootonly, encrypt, timeout); + } + } + + if (!fakehid) + goto done; + + for (i = 0; i < num_rsp; i++) { + memcpy(class, (info+i)->dev_class, 3); + if ((class[0] == 0x00 && class[2] == 0x00 && + (class[1] == 0x40 || class[1] == 0x1f)) || + (class[0] == 0x10 && class[1] == 0x02 && class[2] == 0x40)) { + bacpy(&dst, &(info+i)->bdaddr); + ba2str(&dst, addr); + + printf("\tConnecting to device %s\n", addr); + do_connect(ctl, &src, &dst, subclass, 1, bootonly, 0, timeout); + } + } + +done: + bt_free(info); + + if (!num_rsp) { + fprintf(stderr, "\tNo devices in range or visible\n"); + close(ctl); + exit(1); + } +} + +static void do_kill(int ctl, bdaddr_t *bdaddr, uint32_t flags) +{ + struct hidp_conndel_req req; + struct hidp_connlist_req cl; + struct hidp_conninfo ci[16]; + unsigned int i; + + if (!bacmp(bdaddr, BDADDR_ALL)) { + cl.cnum = 16; + cl.ci = ci; + + if (ioctl(ctl, HIDPGETCONNLIST, &cl) < 0) { + perror("Can't get connection list"); + close(ctl); + exit(1); + } + + for (i = 0; i < cl.cnum; i++) { + bacpy(&req.bdaddr, &ci[i].bdaddr); + req.flags = flags; + + if (ioctl(ctl, HIDPCONNDEL, &req) < 0) { + perror("Can't release connection"); + close(ctl); + exit(1); + } + } + + } else { + bacpy(&req.bdaddr, bdaddr); + req.flags = flags; + + if (ioctl(ctl, HIDPCONNDEL, &req) < 0) { + perror("Can't release connection"); + close(ctl); + exit(1); + } + } +} + +static void usage(void) +{ + printf("hidd - Bluetooth HID daemon version %s\n\n", VERSION); + + printf("Usage:\n" + "\thidd [options] [commands]\n" + "\n"); + + printf("Options:\n" + "\t-i Local HCI device or BD Address\n" + "\t-t Set idle timeout (in minutes)\n" + "\t-b Overwrite the boot mode subclass\n" + "\t-n, --nodaemon Don't fork daemon to background\n" + "\t-h, --help Display help\n" + "\n"); + + printf("Commands:\n" + "\t--server Start HID server\n" + "\t--search Search for HID devices\n" + "\t--connect Connect remote HID device\n" + "\t--unplug Unplug the HID connection\n" + "\t--kill Terminate HID connection\n" + "\t--killall Terminate all connections\n" + "\t--show List current HID connections\n" + "\n"); +} + +static struct option main_options[] = { + { "help", 0, 0, 'h' }, + { "nodaemon", 0, 0, 'n' }, + { "subclass", 1, 0, 'b' }, + { "timeout", 1, 0, 't' }, + { "device", 1, 0, 'i' }, + { "master", 0, 0, 'M' }, + { "encrypt", 0, 0, 'E' }, + { "nosdp", 0, 0, 'D' }, + { "nocheck", 0, 0, 'Z' }, + { "bootonly", 0, 0, 'B' }, + { "hidonly", 0, 0, 'H' }, + { "show", 0, 0, 'l' }, + { "list", 0, 0, 'l' }, + { "server", 0, 0, 'd' }, + { "listen", 0, 0, 'd' }, + { "search", 0, 0, 's' }, + { "create", 1, 0, 'c' }, + { "connect", 1, 0, 'c' }, + { "disconnect", 1, 0, 'k' }, + { "terminate", 1, 0, 'k' }, + { "release", 1, 0, 'k' }, + { "kill", 1, 0, 'k' }, + { "killall", 0, 0, 'K' }, + { "unplug", 1, 0, 'u' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + bdaddr_t bdaddr, dev; + uint32_t flags = 0; + uint8_t subclass = 0x00; + char addr[18]; + int log_option = LOG_NDELAY | LOG_PID; + int opt, ctl, csk, isk; + int mode = SHOW, detach = 1, nosdp = 0, nocheck = 0, bootonly = 0; + int fakehid = 1, encrypt = 0, timeout = 30, lm = 0; + + bacpy(&bdaddr, BDADDR_ANY); + + while ((opt = getopt_long(argc, argv, "+i:nt:b:MEDZBHldsc:k:Ku:h", main_options, NULL)) != -1) { + switch(opt) { + case 'i': + if (!strncasecmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &bdaddr); + else + str2ba(optarg, &bdaddr); + break; + case 'n': + detach = 0; + break; + case 't': + timeout = atoi(optarg); + break; + case 'b': + if (!strncasecmp(optarg, "0x", 2)) + subclass = (uint8_t) strtol(optarg, NULL, 16); + else + subclass = atoi(optarg); + break; + case 'M': + lm |= L2CAP_LM_MASTER; + break; + case 'E': + encrypt = 1; + break; + case 'D': + nosdp = 1; + break; + case 'Z': + nocheck = 1; + break; + case 'B': + bootonly = 1; + break; + case 'H': + fakehid = 0; + break; + case 'l': + mode = SHOW; + break; + case 'd': + mode = SERVER; + break; + case 's': + mode = SEARCH; + break; + case 'c': + str2ba(optarg, &dev); + mode = CONNECT; + break; + case 'k': + str2ba(optarg, &dev); + mode = KILL; + break; + case 'K': + bacpy(&dev, BDADDR_ALL); + mode = KILL; + break; + case 'u': + str2ba(optarg, &dev); + flags = (1 << HIDP_VIRTUAL_CABLE_UNPLUG); + mode = KILL; + break; + case 'h': + usage(); + exit(0); + default: + exit(0); + } + } + + ba2str(&bdaddr, addr); + + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP); + if (ctl < 0) { + perror("Can't open HIDP control socket"); + exit(1); + } + + switch (mode) { + case SERVER: + csk = l2cap_listen(&bdaddr, L2CAP_PSM_HIDP_CTRL, lm, 10); + if (csk < 0) { + perror("Can't listen on HID control channel"); + close(ctl); + exit(1); + } + + isk = l2cap_listen(&bdaddr, L2CAP_PSM_HIDP_INTR, lm, 10); + if (isk < 0) { + perror("Can't listen on HID interrupt channel"); + close(ctl); + close(csk); + exit(1); + } + break; + + case SEARCH: + do_search(ctl, &bdaddr, subclass, fakehid, bootonly, encrypt, timeout); + close(ctl); + exit(0); + + case CONNECT: + do_connect(ctl, &bdaddr, &dev, subclass, fakehid, bootonly, encrypt, timeout); + close(ctl); + exit(0); + + case KILL: + do_kill(ctl, &dev, flags); + close(ctl); + exit(0); + + default: + do_show(ctl); + close(ctl); + exit(0); + } + + if (detach) { + if (daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + } else + log_option |= LOG_PERROR; + + openlog("hidd", log_option, LOG_DAEMON); + + if (bacmp(&bdaddr, BDADDR_ANY)) + syslog(LOG_INFO, "Bluetooth HID daemon (%s)", addr); + else + syslog(LOG_INFO, "Bluetooth HID daemon"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + run_server(ctl, csk, isk, subclass, nosdp, nocheck, bootonly, encrypt, timeout); + + syslog(LOG_INFO, "Exit"); + + close(csk); + close(isk); + close(ctl); + + return 0; +} diff --git a/compat/hidd.h b/compat/hidd.h new file mode 100644 index 0000000..0536967 --- /dev/null +++ b/compat/hidd.h @@ -0,0 +1,30 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define L2CAP_PSM_HIDP_CTRL 0x11 +#define L2CAP_PSM_HIDP_INTR 0x13 + +int epox_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); +int headset_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); +int jthree_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); +int celluon_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); diff --git a/compat/lib.h b/compat/lib.h new file mode 100644 index 0000000..3b3aeb5 --- /dev/null +++ b/compat/lib.h @@ -0,0 +1,86 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include + +#ifndef min +#define min(a,b) ( (a)<(b) ? (a):(b) ) +#endif + +/* IO cancelation */ +extern volatile sig_atomic_t __io_canceled; + +static inline void io_init(void) +{ + __io_canceled = 0; +} + +static inline void io_cancel(void) +{ + __io_canceled = 1; +} + +/* Read exactly len bytes (Signal safe)*/ +static inline int read_n(int fd, char *buf, int len) +{ + register int t = 0, w; + + while (!__io_canceled && len > 0) { + if ((w = read(fd, buf, len)) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!w) + return 0; + len -= w; + buf += w; + t += w; + } + + return t; +} + +/* Write exactly len bytes (Signal safe)*/ +static inline int write_n(int fd, char *buf, int len) +{ + register int t = 0, w; + + while (!__io_canceled && len > 0) { + if ((w = write(fd, buf, len)) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!w) + return 0; + len -= w; + buf += w; + t += w; + } + + return t; +} diff --git a/compat/msdun.c b/compat/msdun.c new file mode 100644 index 0000000..ae88c0c --- /dev/null +++ b/compat/msdun.c @@ -0,0 +1,153 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib.h" +#include "dund.h" + +#define MS_PPP 2 +#define MS_SUCCESS 1 +#define MS_FAILED -1 +#define MS_TIMEOUT -2 + +static sigjmp_buf jmp; +static int retry; +static int timeout; + +static void sig_alarm(int sig) +{ + siglongjmp(jmp, MS_TIMEOUT); +} + +static int w4_str(int fd, char *str) +{ + char buf[40]; + unsigned len = 0; + int r; + + while (1) { + r = read(fd, buf + len, sizeof(buf) - len - 1); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + break; + } + if (!r) + break; + + len += r; + + if (len < strlen(str)) + continue; + buf[len] = 0; + + if (strstr(buf, str)) + return MS_SUCCESS; + + /* Detect PPP */ + if (strchr(buf, '~')) + return MS_PPP; + } + return MS_FAILED; +} + +static int ms_server(int fd) +{ + switch (w4_str(fd, "CLIENT")) { + case MS_SUCCESS: + write_n(fd, "CLIENTSERVER", 12); + case MS_PPP: + return MS_SUCCESS; + default: + return MS_FAILED; + } +} + +static int ms_client(int fd) +{ + write_n(fd, "CLIENT", 6); + return w4_str(fd, "CLIENTSERVER"); +} + +int ms_dun(int fd, int server, int timeo) +{ + sig_t osig; + + retry = 4; + timeout = timeo; + + if (!server) + timeout /= retry; + + osig = signal(SIGALRM, sig_alarm); + + while (1) { + int r = sigsetjmp(jmp, 1); + if (r) { + if (r == MS_TIMEOUT && !server && --retry) + continue; + + alarm(0); + signal(SIGALRM, osig); + + switch (r) { + case MS_SUCCESS: + case MS_PPP: + errno = 0; + return 0; + + case MS_FAILED: + errno = EPROTO; + break; + + case MS_TIMEOUT: + errno = ETIMEDOUT; + break; + } + return -1; + } + + alarm(timeout); + + if (server) + r = ms_server(fd); + else + r = ms_client(fd); + + siglongjmp(jmp, r); + } +} diff --git a/compat/pand.1 b/compat/pand.1 new file mode 100644 index 0000000..4603b8b --- /dev/null +++ b/compat/pand.1 @@ -0,0 +1,77 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.29. +.TH BlueZ "1" "February 2003" "PAN daemon" "User Commands" +.SH NAME +pand \- BlueZ Bluetooth PAN daemon +.SH DESCRIPTION +The pand PAN daemon allows your computer to connect to ethernet +networks using Bluetooth. +.SH SYNPOSIS +pand +.SH OPTIONS +.TP +\fB\-\-show\fR \fB\-\-list\fR \fB\-l\fR +Show active PAN connections +.TP +\fB\-\-listen\fR \fB\-s\fR +Listen for PAN connections +.TP +\fB\-\-connect\fR \fB\-c\fR +Create PAN connection +.TP +\fB\-\-search\fR \fB\-Q[duration]\fR +Search and connect +.TP +\fB\-\-kill\fR \fB\-k\fR +Kill PAN connection +.TP +\fB\-\-killall\fR \fB\-K\fR +Kill all PAN connections +.TP +\fB\-\-role\fR \fB\-r\fR +Local PAN role (PANU, NAP, GN) +.TP +\fB\-\-service\fR \fB\-d\fR +Remote PAN service (PANU, NAP, GN) +.TP +\fB\-\-ethernet\fR \fB\-e\fR +Network interface name +.TP +\fB\-\-device\fR \fB\-i\fR +Source bdaddr +.TP +\fB\-\-nosdp\fR \fB\-D\fR +Disable SDP +.TP +\fB\-\-encrypt\fR \fB\-E\fR +Enable encryption +.TP +\fB\-\-secure\fR \fB\-S\fR +Secure connection +.TP +\fB\-\-master\fR \fB\-M\fR +Become the master of a piconet +.TP +\fB\-\-nodetach\fR \fB\-n\fR +Do not become a daemon +.TP +\fB\-\-persist\fR \fB\-p[interval]\fR +Persist mode +.TP +\fB\-\-cache\fR \fB\-C[valid]\fR +Cache addresses +.TP +\fB\-\-pidfile\fR \fB\-P \fR +Create PID file +.TP +\fB\-\-devup\fR \fB\-u