diff --git a/temp/pulseaudio/0001-bluez5-util-move-pa_bluetooth_discovery-to-header.patch b/temp/pulseaudio/0001-bluez5-util-move-pa_bluetooth_discovery-to-header.patch new file mode 100644 index 000000000..f52a85090 --- /dev/null +++ b/temp/pulseaudio/0001-bluez5-util-move-pa_bluetooth_discovery-to-header.patch @@ -0,0 +1,110 @@ +From 355c4cc3f70abee505d2aad0a37d453a44ead1c2 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Wed, 6 Apr 2022 08:15:13 +0200 +Subject: [PATCH 01/26] bluez5-util: move pa_bluetooth_discovery to header + +--- + src/modules/bluetooth/bluez5-util.c | 23 ----------------------- + src/modules/bluetooth/bluez5-util.h | 24 ++++++++++++++++++++++++ + src/modules/meson.build | 2 +- + 3 files changed, 25 insertions(+), 24 deletions(-) + +diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c +index 83f2932e9..2f8d6faf1 100644 +--- a/src/modules/bluetooth/bluez5-util.c ++++ b/src/modules/bluetooth/bluez5-util.c +@@ -31,7 +31,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -128,28 +127,6 @@ static uint16_t volume_to_a2dp_gain(pa_volume_t volume) { + return gain; + } + +-struct pa_bluetooth_discovery { +- PA_REFCNT_DECLARE; +- +- pa_core *core; +- pa_dbus_connection *connection; +- bool filter_added; +- bool matches_added; +- bool objects_listed; +- pa_hook hooks[PA_BLUETOOTH_HOOK_MAX]; +- pa_hashmap *adapters; +- pa_hashmap *devices; +- pa_hashmap *transports; +- pa_bluetooth_profile_status_t profiles_status[PA_BLUETOOTH_PROFILE_COUNT]; +- +- int headset_backend; +- pa_bluetooth_backend *ofono_backend, *native_backend; +- PA_LLIST_HEAD(pa_dbus_pending, pending); +- bool enable_native_hsp_hs; +- bool enable_native_hfp_hf; +- bool enable_msbc; +-}; +- + static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, + DBusPendingCallNotifyFunction func, void *call_data) { + pa_dbus_pending *p; +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index 86eb6301e..f899d9d0c 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -22,6 +22,7 @@ + ***/ + + #include ++#include + + #include "a2dp-codec-util.h" + +@@ -134,6 +135,29 @@ struct pa_bluetooth_transport { + void *userdata; + }; + ++struct pa_bluetooth_discovery { ++ PA_REFCNT_DECLARE; ++ ++ pa_core *core; ++ pa_dbus_connection *connection; ++ bool filter_added; ++ bool matches_added; ++ bool objects_listed; ++ pa_hook hooks[PA_BLUETOOTH_HOOK_MAX]; ++ pa_hashmap *adapters; ++ pa_hashmap *devices; ++ pa_hashmap *transports; ++ pa_bluetooth_profile_status_t profiles_status[PA_BLUETOOTH_PROFILE_COUNT]; ++ ++ int headset_backend; ++ pa_bluetooth_backend *ofono_backend, *native_backend; ++ PA_LLIST_HEAD(pa_dbus_pending, pending); ++ bool enable_native_hsp_hs; ++ bool enable_native_hfp_hf; ++ bool enable_msbc; ++}; ++ ++ + struct pa_bluetooth_device { + pa_bluetooth_discovery *discovery; + pa_bluetooth_adapter *adapter; +diff --git a/src/modules/meson.build b/src/modules/meson.build +index 1ae172971..08c754948 100644 +--- a/src/modules/meson.build ++++ b/src/modules/meson.build +@@ -123,7 +123,7 @@ if cdata.has('HAVE_BLUEZ_5') + all_modules += [ + [ 'module-bluetooth-discover', 'bluetooth/module-bluetooth-discover.c' ], + [ 'module-bluetooth-policy', 'bluetooth/module-bluetooth-policy.c', [], [], [dbus_dep] ], +- [ 'module-bluez5-device', 'bluetooth/module-bluez5-device.c', [], [], [], libbluez5_util ], ++ [ 'module-bluez5-device', 'bluetooth/module-bluez5-device.c', [], [], [dbus_dep], libbluez5_util ], + [ 'module-bluez5-discover', 'bluetooth/module-bluez5-discover.c', [], [], [dbus_dep], libbluez5_util ], + ] + endif +-- +2.35.1 + diff --git a/temp/pulseaudio/0002-bluetooth-add-AT-BIA-support.patch b/temp/pulseaudio/0002-bluetooth-add-AT-BIA-support.patch new file mode 100644 index 000000000..f8869ef8d --- /dev/null +++ b/temp/pulseaudio/0002-bluetooth-add-AT-BIA-support.patch @@ -0,0 +1,194 @@ +From 5aebba47f8215d5c580602a34082fc27081d963d Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Tue, 5 Apr 2022 20:10:05 +0200 +Subject: [PATCH 02/26] bluetooth: add AT+BIA support + +AT+BIA is used to enable/disable CIND indicators by Bluetooth HFP spec. +By default, all indicators are enabled on connection. +AT+BIA will configure which indicators should be disabled then, +the disabled indicators may be enabled later on again with AT+BIA. +When the connection is lost and recovered, all indicators are enabled +again. The HF will reconfigure the indicators again with an AT+BIA +command. +--- + src/modules/bluetooth/backend-native.c | 93 ++++++++++++++++++++++++-- + 1 file changed, 87 insertions(+), 6 deletions(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index f6b85e90d..d26edc1c9 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -39,6 +39,11 @@ + #include "bluez5-util.h" + #include "bt-codec-msbc.h" + ++#define MANDATORY_CALL_INDICATORS \ ++ "(\"call\",(0-1))," \ ++ "(\"callsetup\",(0-3))," \ ++ "(\"callheld\",(0-2))" \ ++ + struct pa_bluetooth_backend { + pa_core *core; + pa_dbus_connection *connection; +@@ -47,6 +52,8 @@ struct pa_bluetooth_backend { + bool enable_shared_profiles; + bool enable_hsp_hs; + bool enable_hfp_hf; ++ bool cmer_indicator_reporting_enabled; ++ uint32_t cind_enabled_indicators; + + PA_LLIST_HEAD(pa_dbus_pending, pending); + }; +@@ -97,6 +104,20 @@ enum hfp_ag_features { + HFP_AG_INDICATORS = 10, + }; + ++/* ++ * Always keep this struct in sync with indicator discovery of AT+CIND=? ++ * These indicators are used in bitflags and intentionally start at 1 ++ * since AT+CIND indicators start at index 1. ++ */ ++typedef enum pa_bluetooth_ag_to_hf_indicators { ++ CIND_CALL_INDICATOR = 1, ++ CIND_CALL_SETUP_INDICATOR = 2, ++ CIND_CALL_HELD_INDICATOR = 3, ++ CIND_SERVICE_INDICATOR = 4, ++ CIND_BATT_CHG_INDICATOR = 5, ++ CIND_INDICATOR_MAX = 6 ++} pa_bluetooth_ag_to_hf_indicators_t; ++ + /* gateway features we support, which is as little as we can get away with */ + static uint32_t hfp_features = + /* HFP 1.6 requires this */ +@@ -588,8 +609,9 @@ static pa_volume_t set_source_volume(pa_bluetooth_transport *t, pa_volume_t volu + + static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf) + { ++ struct pa_bluetooth_discovery *discovery = t->device->discovery; + struct hfp_config *c = t->config; +- int indicator, val; ++ int indicator, mode, val; + char str[5]; + const char *r; + size_t len; +@@ -607,6 +629,34 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + c->supports_indicators = !!(1 << HFP_HF_INDICATORS); + c->state = 1; + ++ return true; ++ } else if (sscanf(buf, "AT+BIA=%s", str) == 1) { ++ /* Indicators start with index 1 and follow the order of the AT+CIND=? response */ ++ indicator = 1; ++ ++ while ((r = pa_split_in_place(str, ",", &len, &state))) { ++ /* Ignore updates to mandantory indicators which are always ON */ ++ if (indicator == CIND_CALL_INDICATOR ++ || indicator == CIND_CALL_SETUP_INDICATOR ++ || indicator == CIND_CALL_HELD_INDICATOR) ++ continue; ++ ++ /* Indicators may have no value and should be skipped */ ++ if (len == 0) ++ continue; ++ ++ if (len == 1 && r[0] == '1') ++ discovery->native_backend->cind_enabled_indicators |= (1 << indicator); ++ else if (len == 1 && r[0] == '0') ++ discovery->native_backend->cind_enabled_indicators &= ~(1 << indicator); ++ else { ++ pa_log_error("Unable to parse indicator of AT+BIA command: %s", buf); ++ rfcomm_write_response(fd, "ERROR"); ++ return false; ++ } ++ ++ indicator++; ++ } + return true; + } else if (sscanf(buf, "AT+BAC=%3s", str) == 1) { + c->support_msbc = false; +@@ -637,10 +687,8 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + rfcomm_write_response(fd, "+CIND: " + /* many indicators can be supported, only call and + * callheld are mandatory, so that's all we reply */ +- "(\"service\",(0-1))," +- "(\"call\",(0-1))," +- "(\"callsetup\",(0-3))," +- "(\"callheld\",(0-2))"); ++ MANDATORY_CALL_INDICATORS ",", ++ "(\"service\",(0-1))"); + c->state = 2; + + return true; +@@ -650,7 +698,24 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + + return true; + } else if ((c->state == 2 || c->state == 3) && pa_startswith(buf, "AT+CMER=")) { +- rfcomm_write_response(fd, "OK"); ++ if (sscanf(buf, "AT+CMER=%d,%*d,%*d,%d", &mode, &val) == 2) { ++ /* Bluetooth HFP spec only defines mode == 3 */ ++ if (mode != 3) { ++ pa_log_warn("Unexpected mode for AT+CMER: %d", mode); ++ } ++ ++ /* Configure CMER event reporting */ ++ discovery->native_backend->cmer_indicator_reporting_enabled = !!val; ++ ++ pa_log_debug("Event indications enabled? %s", pa_yes_no(val)); ++ ++ rfcomm_write_response(fd, "OK"); ++ } ++ else { ++ pa_log_error("Unable to parse AT+CMER command: %s", buf); ++ rfcomm_write_response(fd, "ERROR"); ++ return false; ++ } + + if (c->support_codec_negotiation) { + if (c->support_msbc && pa_bluetooth_discovery_get_enable_msbc(t->device->discovery)) { +@@ -740,6 +805,8 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + + static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_bluetooth_transport *t = userdata; ++ pa_bluetooth_discovery *discovery = t->device->discovery; ++ int i; + + pa_assert(io); + pa_assert(t); +@@ -860,6 +927,11 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i + return; + + fail: ++ /* Service Connection lost, reset indicators and event reporting to default values */ ++ discovery->native_backend->cmer_indicator_reporting_enabled = false; ++ for (i = 1; i < CIND_INDICATOR_MAX; i++) ++ discovery->native_backend->cind_enabled_indicators |= (1 << i); ++ + pa_bluetooth_transport_unlink(t); + pa_bluetooth_transport_free(t); + } +@@ -1188,6 +1260,7 @@ void pa_bluetooth_native_backend_enable_shared_profiles(pa_bluetooth_backend *na + pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_shared_profiles) { + pa_bluetooth_backend *backend; + DBusError err; ++ int i; + + pa_log_debug("Bluetooth Headset Backend API support using the native backend"); + +@@ -1220,6 +1293,14 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d + if (backend->enable_shared_profiles) + native_backend_apply_profile_registration_change(backend, true); + ++ /* All CIND indicators are enabled by default until overriden by AT+BIA */ ++ for (i = 1; i < CIND_INDICATOR_MAX; i++) ++ backend->cind_enabled_indicators |= (1 << i); ++ ++ /* While all CIND indicators are enabled, event reporting is not enabled by default */ ++ backend->cmer_indicator_reporting_enabled = false; ++ ++ + return backend; + } + +-- +2.35.1 + diff --git a/temp/pulseaudio/0003-bluetooth-add-UPower-backend.patch b/temp/pulseaudio/0003-bluetooth-add-UPower-backend.patch new file mode 100644 index 000000000..48f83da0f --- /dev/null +++ b/temp/pulseaudio/0003-bluetooth-add-UPower-backend.patch @@ -0,0 +1,393 @@ +From 0c8451cae9c585793fa98fb99864d5228127bbf9 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Tue, 5 Apr 2022 20:26:09 +0200 +Subject: [PATCH 03/26] bluetooth: add UPower backend + +UPower provides information about the power supply and battery +level of the host. Add a backend to retrieve the host battery level. +--- + src/modules/bluetooth/meson.build | 4 +- + src/modules/bluetooth/upower.c | 300 ++++++++++++++++++++++++++++++ + src/modules/bluetooth/upower.h | 41 ++++ + 3 files changed, 344 insertions(+), 1 deletion(-) + create mode 100644 src/modules/bluetooth/upower.c + create mode 100644 src/modules/bluetooth/upower.h + +diff --git a/src/modules/bluetooth/meson.build b/src/modules/bluetooth/meson.build +index ca77ee6aa..0703d5086 100644 +--- a/src/modules/bluetooth/meson.build ++++ b/src/modules/bluetooth/meson.build +@@ -16,6 +16,8 @@ libbluez5_util_headers = [ + + if get_option('bluez5-native-headset') + libbluez5_util_sources += [ 'backend-native.c' ] ++ libbluez5_util_sources += [ 'upower.c' ] ++ libbluez5_util_headers += [ 'upower.h' ] + endif + + if get_option('bluez5-ofono-headset') +@@ -35,7 +37,7 @@ libbluez5_util = shared_library('bluez5-util', + c_args : [pa_c_args, server_c_args], + link_args : [nodelete_link_args], + include_directories : [configinc, topinc], +- dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, bluez_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep], ++ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, bluez_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep, libm_dep], + install : true, + install_rpath : privlibdir, + install_dir : modlibexecdir, +diff --git a/src/modules/bluetooth/upower.c b/src/modules/bluetooth/upower.c +new file mode 100644 +index 000000000..b5cb89bfe +--- /dev/null ++++ b/src/modules/bluetooth/upower.c +@@ -0,0 +1,300 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyrigth 2022 Dylan Van Assche ++ ++ PulseAudio 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. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with PulseAudio; if not, see . ++***/ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "upower.h" ++ ++static pa_dbus_pending* send_and_add_to_pending(pa_upower_backend *backend, DBusMessage *m, ++ DBusPendingCallNotifyFunction func, void *call_data) { ++ ++ pa_dbus_pending *p; ++ DBusPendingCall *call; ++ ++ pa_assert(backend); ++ pa_assert(m); ++ ++ pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(backend->connection), m, &call, -1)); ++ ++ p = pa_dbus_pending_new(pa_dbus_connection_get(backend->connection), m, call, backend, call_data); ++ PA_LLIST_PREPEND(pa_dbus_pending, backend->pending, p); ++ dbus_pending_call_set_notify(call, func, p, NULL); ++ ++ return p; ++} ++ ++static void parse_percentage(pa_upower_backend *b, DBusMessageIter *i) { ++ double percentage; ++ unsigned int battery_level; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_DOUBLE); ++ ++ dbus_message_iter_get_basic(i, &percentage); ++ battery_level = (unsigned int) round(percentage / 20.0); ++ ++ if (battery_level != b->battery_level) { ++ b->battery_level = battery_level; ++ pa_log_debug("AG battery level updated (%d/5)", b->battery_level); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), b); ++ } ++} ++ ++static void get_percentage_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_upower_backend *b; ++ DBusMessage *r; ++ DBusMessageIter arg_i, variant_i; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("UPower D-Bus Display Device not available"); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("Get() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ goto finish; ++ } ++ ++ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "v")) { ++ pa_log_error("Invalid reply signature for Get()"); ++ goto finish; ++ } ++ ++ dbus_message_iter_recurse(&arg_i, &variant_i); ++ parse_percentage(b, &variant_i); ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++} ++ ++static const char *check_variant_property(DBusMessageIter *i) { ++ const char *key; ++ ++ pa_assert(i); ++ ++ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { ++ pa_log_error("Property name not a string."); ++ return NULL; ++ } ++ ++ dbus_message_iter_get_basic(i, &key); ++ ++ if (!dbus_message_iter_next(i)) { ++ pa_log_error("Property value missing"); ++ return NULL; ++ } ++ ++ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) { ++ pa_log_error("Property value not a variant."); ++ return NULL; ++ } ++ ++ return key; ++} ++ ++static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *data) { ++ DBusError err; ++ DBusMessage *m2; ++ static const char* upower_device_interface = UPOWER_SERVICE UPOWER_DEVICE_INTERFACE; ++ static const char* percentage_property = "Percentage"; ++ pa_upower_backend *b = data; ++ const char *path, *interface, *member; ++ ++ pa_assert(bus); ++ pa_assert(m); ++ pa_assert(b); ++ ++ dbus_error_init(&err); ++ ++ path = dbus_message_get_path(m); ++ interface = dbus_message_get_interface(m); ++ member = dbus_message_get_member(m); ++ ++ pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); ++ ++ /* UPower D-Bus status change */ ++ if (dbus_message_is_signal(m, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { ++ const char *name, *old_owner, *new_owner; ++ ++ if (!dbus_message_get_args(m, &err, ++ DBUS_TYPE_STRING, &name, ++ DBUS_TYPE_STRING, &old_owner, ++ DBUS_TYPE_STRING, &new_owner, ++ DBUS_TYPE_INVALID)) { ++ pa_log_error("Failed to parse " DBUS_INTERFACE_DBUS ".NameOwnerChanged: %s", err.message); ++ goto fail; ++ } ++ ++ if (pa_streq(name, UPOWER_SERVICE)) { ++ ++ /* UPower disappeared from D-Bus */ ++ if (old_owner && *old_owner) { ++ pa_log_debug("UPower disappeared from D-Bus"); ++ b->battery_level = 0; ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), b); ++ } ++ ++ /* UPower appeared on D-Bus */ ++ if (new_owner && *new_owner) { ++ pa_log_debug("UPower appeared on D-Bus"); ++ ++ /* Update battery level */ ++ pa_assert_se(m2 = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get")); ++ pa_assert_se(dbus_message_append_args(m2, ++ DBUS_TYPE_STRING, &upower_device_interface, ++ DBUS_TYPE_STRING, &percentage_property, ++ DBUS_TYPE_INVALID)); ++ send_and_add_to_pending(b, m2, get_percentage_reply, NULL); ++ } ++ } ++ ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ /* UPower battery level property updates */ ++ } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged")) { ++ DBusMessageIter arg_i, element_i; ++ ++ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "sa{sv}as")) { ++ pa_log_error("Invalid signature found in PropertiesChanged"); ++ goto fail; ++ } ++ ++ /* Skip interface name */ ++ pa_assert_se(dbus_message_iter_next(&arg_i)); ++ pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY); ++ ++ dbus_message_iter_recurse(&arg_i, &element_i); ++ ++ /* Parse UPower property updates */ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { ++ DBusMessageIter dict_i, variant_i; ++ const char *key; ++ ++ dbus_message_iter_recurse(&element_i, &dict_i); ++ ++ /* Retrieve property name */ ++ key = check_variant_property(&dict_i); ++ if (key == NULL) { ++ pa_log_error("Received invalid property!"); ++ break; ++ } ++ ++ dbus_message_iter_recurse(&dict_i, &variant_i); ++ ++ if(pa_streq(path, UPOWER_DISPLAY_DEVICE_OBJECT)) { ++ pa_log_debug("UPower Device property updated: %s", key); ++ ++ if(pa_streq(key, "Percentage")) ++ parse_percentage(b, &variant_i); ++ } ++ ++ dbus_message_iter_next(&element_i); ++ } ++ } ++ ++fail: ++ dbus_error_free(&err); ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++unsigned int pa_upower_get_battery_level(pa_upower_backend *backend) { ++ return backend->battery_level; ++} ++ ++pa_upower_backend *pa_upower_backend_new(pa_core *c, pa_bluetooth_discovery *d) { ++ pa_upower_backend *backend; ++ DBusError err; ++ DBusMessage *m; ++ static const char* upower_device_interface = UPOWER_SERVICE UPOWER_DEVICE_INTERFACE; ++ static const char* percentage_property = "Percentage"; ++ ++ pa_log_debug("Native backend enabled UPower battery status reporting"); ++ ++ backend = pa_xnew0(pa_upower_backend, 1); ++ backend->core = c; ++ backend->discovery = d; ++ ++ /* Get DBus connection */ ++ dbus_error_init(&err); ++ if (!(backend->connection = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &err))) { ++ pa_log("Failed to get D-Bus connection: %s", err.message); ++ dbus_error_free(&err); ++ pa_xfree(backend); ++ return NULL; ++ } ++ ++ /* Add filter callback for DBus connection */ ++ if (!dbus_connection_add_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend, NULL)) { ++ pa_log_error("Failed to add filter function"); ++ pa_dbus_connection_unref(backend->connection); ++ pa_xfree(backend); ++ return NULL; ++ } ++ ++ /* Register for battery level changes from UPower */ ++ if (pa_dbus_add_matches(pa_dbus_connection_get(backend->connection), &err, ++ "type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='NameOwnerChanged'," ++ "arg0='" UPOWER_SERVICE "'", ++ "type='signal',sender='" UPOWER_SERVICE "',interface='" DBUS_INTERFACE_PROPERTIES "',member='PropertiesChanged'", ++ NULL) < 0) { ++ pa_log("Failed to add UPower D-Bus matches: %s", err.message); ++ dbus_connection_remove_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend); ++ pa_dbus_connection_unref(backend->connection); ++ pa_xfree(backend); ++ return NULL; ++ } ++ ++ /* Initialize battery level by requesting it from UPower */ ++ pa_assert_se(m = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get")); ++ pa_assert_se(dbus_message_append_args(m, ++ DBUS_TYPE_STRING, &upower_device_interface, ++ DBUS_TYPE_STRING, &percentage_property, ++ DBUS_TYPE_INVALID)); ++ send_and_add_to_pending(backend, m, get_percentage_reply, NULL); ++ ++ return backend; ++} ++ ++void pa_upower_backend_free(pa_upower_backend *backend) { ++ pa_assert(backend); ++ ++ pa_dbus_free_pending_list(&backend->pending); ++ ++ pa_dbus_connection_unref(backend->connection); ++ ++ pa_xfree(backend); ++} ++ +diff --git a/src/modules/bluetooth/upower.h b/src/modules/bluetooth/upower.h +new file mode 100644 +index 000000000..3e9ee9291 +--- /dev/null ++++ b/src/modules/bluetooth/upower.h +@@ -0,0 +1,41 @@ ++#pragma once ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyrigth 2022 Dylan Van Assche ++ ++ PulseAudio 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. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with PulseAudio; if not, see . ++***/ ++ ++#include "bluez5-util.h" ++ ++#define UPOWER_SERVICE "org.freedesktop.UPower" ++#define UPOWER_DEVICE_INTERFACE ".Device" ++#define UPOWER_DISPLAY_DEVICE_OBJECT "/org/freedesktop/UPower/devices/DisplayDevice" ++ ++struct pa_upower_backend { ++ pa_core *core; ++ pa_dbus_connection *connection; ++ pa_bluetooth_discovery *discovery; ++ unsigned int battery_level; ++ ++ PA_LLIST_HEAD(pa_dbus_pending, pending); ++}; ++ ++pa_upower_backend *pa_upower_backend_new(pa_core *c, pa_bluetooth_discovery *d); ++void pa_upower_backend_free(pa_upower_backend *backend); ++void pa_upower_register_transport(pa_upower_backend *backend, pa_bluetooth_transport *t); ++void pa_upower_unregister_transport(pa_upower_backend *backend); ++unsigned int pa_upower_get_battery_level(pa_upower_backend *backend); +-- +2.35.1 + diff --git a/temp/pulseaudio/0004-bluetooth-hook-up-UPower-backend.patch b/temp/pulseaudio/0004-bluetooth-hook-up-UPower-backend.patch new file mode 100644 index 000000000..5f74a0fa6 --- /dev/null +++ b/temp/pulseaudio/0004-bluetooth-hook-up-UPower-backend.patch @@ -0,0 +1,197 @@ +From 78e255eefb417275e73f8ed96ce08bf7c3a1dff9 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Tue, 5 Apr 2022 20:39:11 +0200 +Subject: [PATCH 04/26] bluetooth: hook up UPower backend + +Hook up the UPower backend to backend-native to report +the host battery level to the HF. +--- + src/modules/bluetooth/backend-native.c | 100 +++++++++++++++++++++++-- + src/modules/bluetooth/bluez5-util.h | 2 + + 2 files changed, 95 insertions(+), 7 deletions(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index d26edc1c9..a490d6efd 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -38,6 +38,7 @@ + + #include "bluez5-util.h" + #include "bt-codec-msbc.h" ++#include "upower.h" + + #define MANDATORY_CALL_INDICATORS \ + "(\"call\",(0-1))," \ +@@ -49,6 +50,8 @@ struct pa_bluetooth_backend { + pa_dbus_connection *connection; + pa_bluetooth_discovery *discovery; + pa_hook_slot *adapter_uuids_changed_slot; ++ pa_hook_slot *host_battery_level_changed_slot; ++ pa_upower_backend *upower; + bool enable_shared_profiles; + bool enable_hsp_hs; + bool enable_hfp_hf; +@@ -683,17 +686,27 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + + return true; + } else if (c->state == 1 && pa_startswith(buf, "AT+CIND=?")) { +- /* we declare minimal no indicators */ +- rfcomm_write_response(fd, "+CIND: " +- /* many indicators can be supported, only call and +- * callheld are mandatory, so that's all we reply */ +- MANDATORY_CALL_INDICATORS ",", +- "(\"service\",(0-1))"); ++ /* UPower backend available, declare support for more indicators */ ++ if (discovery->native_backend->upower) { ++ rfcomm_write_response(fd, "+CIND: " ++ MANDATORY_CALL_INDICATORS "," ++ "(\"service\",(0-1))," ++ "(\"battchg\",(0-5))"); ++ ++ /* Minimal indicators supported without any additional backend */ ++ } else { ++ rfcomm_write_response(fd, "+CIND: " ++ MANDATORY_CALL_INDICATORS "," ++ "(\"service\",(0-1))"); ++ } + c->state = 2; + + return true; + } else if (c->state == 2 && pa_startswith(buf, "AT+CIND?")) { +- rfcomm_write_response(fd, "+CIND: 0,0,0,0"); ++ if (discovery->native_backend->upower) ++ rfcomm_write_response(fd, "+CIND: 0,0,0,0,%u", pa_upower_get_battery_level(discovery->native_backend->upower)); ++ else ++ rfcomm_write_response(fd, "+CIND: 0,0,0,0"); + c->state = 3; + + return true; +@@ -803,6 +816,67 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + return true; + } + ++static int get_rfcomm_fd (pa_bluetooth_discovery *discovery) { ++ struct pa_bluetooth_transport *t; ++ struct transport_data *trd = NULL; ++ pa_bluetooth_profile_t profiles[] = { PA_BLUETOOTH_PROFILE_HSP_HS, PA_BLUETOOTH_PROFILE_HSP_AG, PA_BLUETOOTH_PROFILE_HFP_HF }; ++ int i; ++ bool found_transport = false; ++ void *state = NULL; ++ ++ /* Find RFCOMM transport by checking if a HSP or HFP profile transport is available */ ++ while ((t = pa_hashmap_iterate(discovery->transports, &state, NULL))) { ++ /* Skip non-connected transports */ ++ if (!t || t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) { ++ pa_log_debug("Profile %d disconnected or unavailable", i); ++ continue; ++ } ++ ++ /* Break when an RFCOMM capable transport profile is available */ ++ for (i = 0; i < (sizeof profiles / sizeof *profiles); i++) { ++ if (t->profile == profiles[i]) { ++ trd = t->userdata; ++ found_transport = true; ++ break; ++ } ++ } ++ ++ if (found_transport) ++ break; ++ } ++ ++ /* Skip if RFCOMM channel is not available yet */ ++ if (!trd) { ++ pa_log_info("RFCOMM not available yet, skipping notification"); ++ return -1; ++ } ++ ++ return trd->rfcomm_fd; ++} ++ ++static pa_hook_result_t host_battery_level_changed_cb(pa_bluetooth_discovery *y, const pa_upower_backend *u, pa_bluetooth_backend *b) { ++ int rfcomm_fd; ++ ++ pa_assert(y); ++ pa_assert(u); ++ pa_assert(b); ++ ++ /* Get RFCOMM channel if available */ ++ rfcomm_fd = get_rfcomm_fd (y); ++ if (rfcomm_fd < 0) ++ return PA_HOOK_OK; ++ ++ /* Notify HF about AG battery level change over RFCOMM */ ++ if (b->cmer_indicator_reporting_enabled && (b->cind_enabled_indicators & (1 << CIND_BATT_CHG_INDICATOR))) { ++ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_BATT_CHG_INDICATOR, u->battery_level); ++ pa_log_debug("HG notified of AG's battery level change"); ++ /* Skip notification if indicator is disabled or event reporting is completely disabled */ ++ } else ++ pa_log_debug("Battery level change indicator disabled, skipping notification"); ++ ++ return PA_HOOK_OK; ++} ++ + static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_bluetooth_transport *t = userdata; + pa_bluetooth_discovery *discovery = t->device->discovery; +@@ -1284,6 +1358,10 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d + pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_ADAPTER_UUIDS_CHANGED), PA_HOOK_NORMAL, + (pa_hook_cb_t) adapter_uuids_changed_cb, backend); + ++ backend->host_battery_level_changed_slot = ++ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), PA_HOOK_NORMAL, ++ (pa_hook_cb_t) host_battery_level_changed_cb, backend); ++ + if (!backend->enable_hsp_hs && !backend->enable_hfp_hf) + pa_log_warn("Both HSP HS and HFP HF bluetooth profiles disabled in native backend. Native backend will not register for headset connections."); + +@@ -1293,6 +1371,8 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d + if (backend->enable_shared_profiles) + native_backend_apply_profile_registration_change(backend, true); + ++ backend->upower = pa_upower_backend_new(c, y); ++ + /* All CIND indicators are enabled by default until overriden by AT+BIA */ + for (i = 1; i < CIND_INDICATOR_MAX; i++) + backend->cind_enabled_indicators |= (1 << i); +@@ -1312,12 +1392,18 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) { + if (backend->adapter_uuids_changed_slot) + pa_hook_slot_free(backend->adapter_uuids_changed_slot); + ++ if (backend->host_battery_level_changed_slot) ++ pa_hook_slot_free(backend->host_battery_level_changed_slot); ++ + if (backend->enable_shared_profiles) + native_backend_apply_profile_registration_change(backend, false); + + if (backend->enable_hsp_hs) + profile_done(backend, PA_BLUETOOTH_PROFILE_HSP_HS); + ++ if (backend->upower) ++ pa_upower_backend_free(backend->upower); ++ + pa_dbus_connection_unref(backend->connection); + + pa_xfree(backend); +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index f899d9d0c..a24a0b823 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -63,12 +63,14 @@ typedef struct pa_bluetooth_device pa_bluetooth_device; + typedef struct pa_bluetooth_adapter pa_bluetooth_adapter; + typedef struct pa_bluetooth_discovery pa_bluetooth_discovery; + typedef struct pa_bluetooth_backend pa_bluetooth_backend; ++typedef struct pa_upower_backend pa_upower_backend; + + typedef enum pa_bluetooth_hook { + PA_BLUETOOTH_HOOK_ADAPTER_UUIDS_CHANGED, /* Call data: pa_bluetooth_adapter */ + PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */ + PA_BLUETOOTH_HOOK_DEVICE_UNLINK, /* Call data: pa_bluetooth_device */ + PA_BLUETOOTH_HOOK_DEVICE_BATTERY_LEVEL_CHANGED, /* Call data: pa_bluetooth_device */ ++ PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED, /* Call data: pa_upower_backend */ + PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */ + PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */ + PA_BLUETOOTH_HOOK_TRANSPORT_SINK_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */ +-- +2.35.1 + diff --git a/temp/pulseaudio/0005-bluetooth-add-ModemManager-backend.patch b/temp/pulseaudio/0005-bluetooth-add-ModemManager-backend.patch new file mode 100644 index 000000000..94fcd077c --- /dev/null +++ b/temp/pulseaudio/0005-bluetooth-add-ModemManager-backend.patch @@ -0,0 +1,1712 @@ +From 67fc83f344dc0a308aad5619621edff3307f54d6 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Thu, 7 Apr 2022 20:30:24 +0200 +Subject: [PATCH 05/26] bluetooth: add ModemManager backend + +ModemManager is an alternative to oFono for interacting with +modems. Add a ModemManager backend to better support Bluetooth HFP 1.6 +with cellular support such as making and rejecting calls, retrieving +cellular status and in-band ringing. +--- + .gitlab-ci.yml | 3 +- + meson.build | 2 + + src/modules/bluetooth/bluez5-util.h | 7 + + src/modules/bluetooth/meson.build | 4 +- + src/modules/bluetooth/modemmanager.c | 1498 ++++++++++++++++++++++++++ + src/modules/bluetooth/modemmanager.h | 98 ++ + 6 files changed, 1610 insertions(+), 2 deletions(-) + create mode 100644 src/modules/bluetooth/modemmanager.c + create mode 100644 src/modules/bluetooth/modemmanager.h + +diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml +index e6b36da87..f2e20dd64 100644 +--- a/.gitlab-ci.yml ++++ b/.gitlab-ci.yml +@@ -19,7 +19,7 @@ variables: + # CI runs, for example when adding new packages to FDO_DISTRIBUTION_PACKAGES. + # The tag is an arbitrary string that identifies the exact container + # contents. +- FDO_DISTRIBUTION_TAG: '2021-11-03-00' ++ FDO_DISTRIBUTION_TAG: '2022-04-20-00' + FDO_DISTRIBUTION_VERSION: '20.04' + FDO_UPSTREAM_REPO: 'pulseaudio/pulseaudio' + UBUNTU_IMAGE: "$CI_REGISTRY_IMAGE/ubuntu/$FDO_DISTRIBUTION_VERSION:$FDO_DISTRIBUTION_TAG" +@@ -81,6 +81,7 @@ build-container: + libxml2-utils + libxtst-dev + m4 ++ modemmanager-dev + ninja-build + pkg-config + python3-setuptools +diff --git a/meson.build b/meson.build +index 3ae7ac69a..063390c7f 100644 +--- a/meson.build ++++ b/meson.build +@@ -773,6 +773,8 @@ if get_option('daemon') + endif + endif + ++ modemmanager_dep = dependency('ModemManager', version : '>= 1.10.0', required : get_option('bluez5-native-headset')) ++ + jack_dep = dependency('jack', version : '>= 0.117.0', required : get_option('jack')) + if jack_dep.found() + cdata.set('HAVE_JACK', 1) +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index a24a0b823..f69176425 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -64,6 +64,7 @@ typedef struct pa_bluetooth_adapter pa_bluetooth_adapter; + typedef struct pa_bluetooth_discovery pa_bluetooth_discovery; + typedef struct pa_bluetooth_backend pa_bluetooth_backend; + typedef struct pa_upower_backend pa_upower_backend; ++typedef struct pa_modemmanager_backend pa_modemmanager_backend; + + typedef enum pa_bluetooth_hook { + PA_BLUETOOTH_HOOK_ADAPTER_UUIDS_CHANGED, /* Call data: pa_bluetooth_adapter */ +@@ -71,6 +72,12 @@ typedef enum pa_bluetooth_hook { + PA_BLUETOOTH_HOOK_DEVICE_UNLINK, /* Call data: pa_bluetooth_device */ + PA_BLUETOOTH_HOOK_DEVICE_BATTERY_LEVEL_CHANGED, /* Call data: pa_bluetooth_device */ + PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED, /* Call data: pa_upower_backend */ ++ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED, /* Call data: pa_modemmanager_transport */ ++ PA_BLUETOOTH_HOOK_HOST_SIGNAL_STRENGTH_CHANGED, /* Call data: pa_modemmanager_transport */ ++ PA_BLUETOOTH_HOOK_HOST_HAS_SERVICE_CHANGED, /* Call data: pa_modemmanager_transport */ ++ PA_BLUETOOTH_HOOK_HOST_IS_ROAMING_CHANGED, /* Call data: pa_modemmanager_transport */ ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED, /* Call data: pa_modemmanager_transport */ ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED, /* Call data: pa_modemmanager_transport */ + PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */ + PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */ + PA_BLUETOOTH_HOOK_TRANSPORT_SINK_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */ +diff --git a/src/modules/bluetooth/meson.build b/src/modules/bluetooth/meson.build +index 0703d5086..fdcc8a521 100644 +--- a/src/modules/bluetooth/meson.build ++++ b/src/modules/bluetooth/meson.build +@@ -18,6 +18,8 @@ if get_option('bluez5-native-headset') + libbluez5_util_sources += [ 'backend-native.c' ] + libbluez5_util_sources += [ 'upower.c' ] + libbluez5_util_headers += [ 'upower.h' ] ++ libbluez5_util_sources += [ 'modemmanager.c' ] ++ libbluez5_util_headers += [ 'modemmanager.h' ] + endif + + if get_option('bluez5-ofono-headset') +@@ -37,7 +39,7 @@ libbluez5_util = shared_library('bluez5-util', + c_args : [pa_c_args, server_c_args], + link_args : [nodelete_link_args], + include_directories : [configinc, topinc], +- dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, bluez_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep, libm_dep], ++ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, bluez_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep, libm_dep, modemmanager_dep], + install : true, + install_rpath : privlibdir, + install_dir : modlibexecdir, +diff --git a/src/modules/bluetooth/modemmanager.c b/src/modules/bluetooth/modemmanager.c +new file mode 100644 +index 000000000..60c3fabec +--- /dev/null ++++ b/src/modules/bluetooth/modemmanager.c +@@ -0,0 +1,1498 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2022 Dylan Van Assche ++ ++ PulseAudio 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. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with PulseAudio; if not, see . ++***/ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "modemmanager.h" ++ ++static pa_dbus_pending* send_and_add_to_pending(pa_modemmanager_backend *backend, DBusMessage *m, ++ DBusPendingCallNotifyFunction func, void *call_data) { ++ ++ pa_dbus_pending *p; ++ DBusPendingCall *call; ++ ++ pa_assert(backend); ++ pa_assert(m); ++ ++ pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(backend->connection), m, &call, -1)); ++ ++ p = pa_dbus_pending_new(pa_dbus_connection_get(backend->connection), m, call, backend, call_data); ++ PA_LLIST_PREPEND(pa_dbus_pending, backend->pending, p); ++ dbus_pending_call_set_notify(call, func, p, NULL); ++ ++ return p; ++} ++ ++static const char *check_variant_property(DBusMessageIter *i) { ++ const char *key; ++ ++ pa_assert(i); ++ ++ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { ++ pa_log_error("Property name not a string."); ++ return NULL; ++ } ++ ++ dbus_message_iter_get_basic(i, &key); ++ ++ if (!dbus_message_iter_next(i)) { ++ pa_log_error("Property value missing"); ++ return NULL; ++ } ++ ++ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) { ++ pa_log_error("Property value not a variant."); ++ return NULL; ++ } ++ ++ return key; ++} ++ ++static void create_call_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_modemmanager_backend *b; ++ DBusMessage *r; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("ModemManager D-Bus Voice not available"); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("CreateCall() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b); ++ goto finish; ++ } ++ ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), b); ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++} ++ ++static void call_hangup_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_modemmanager_backend *b; ++ DBusMessage *r; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("ModemManager D-Bus Call not available"); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("Hangup() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b); ++ goto finish; ++ } ++ ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), b); ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++} ++ ++static void call_accept_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_modemmanager_backend *b; ++ DBusMessage *r; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("ModemManager D-Bus Call not available"); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("Accept() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b); ++ goto finish; ++ } ++ ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), b); ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++} ++ ++static void send_dtmf_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_modemmanager_backend *b; ++ DBusMessage *r; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("ModemManager D-Bus Call not available"); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("SendDtmf() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b); ++ goto finish; ++ } ++ ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), b); ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++} ++ ++static void parse_state(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ MMModemState state; ++ bool has_service; ++ ++ pa_assert(i); ++ ++ /* ++ * ModemManager emits the State property as UINT32 or INT32. ++ * Cast to unsigned int if needed. ++ */ ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_UINT32 ++ || dbus_message_iter_get_arg_type(i) == DBUS_TYPE_INT32); ++ ++ if (dbus_message_iter_get_arg_type(i) == DBUS_TYPE_INT32) { ++ int s; ++ ++ dbus_message_iter_get_basic(i, &s); ++ state = (unsigned int) s; ++ } else ++ dbus_message_iter_get_basic(i, &state); ++ ++ /* All states from REGISTERED and higher indicate that the modem has service */ ++ if (state >= MM_MODEM_STATE_REGISTERED) ++ has_service = true; ++ else ++ has_service = false; ++ ++ pa_log_debug("Network has service: %s", pa_yes_no(has_service)); ++ ++ if (b->modem->network_has_service != has_service) { ++ b->modem->network_has_service = has_service; ++ pa_log_debug("AG service status updated: %s", pa_yes_no(b->modem->network_has_service)); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_HAS_SERVICE_CHANGED), b); ++ } ++} ++ ++static void parse_registration(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ MMModem3gppRegistrationState registration; ++ bool is_roaming; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_UINT32); ++ ++ dbus_message_iter_get_basic(i, ®istration); ++ ++ /* All states containing ROAMING indicate that the modem is roaming */ ++ if (registration == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING ++ || registration == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED ++ || registration == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_SMS_ONLY) { ++ is_roaming = true; ++ } else ++ is_roaming = false; ++ ++ pa_log_debug("Network is roaming: %s", pa_yes_no(is_roaming)); ++ ++ if (b->modem->network_is_roaming != is_roaming) { ++ b->modem->network_is_roaming = is_roaming; ++ pa_log_debug("AG roaming status updated: %s", pa_yes_no(b->modem->network_is_roaming)); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_IS_ROAMING_CHANGED), b); ++ } ++} ++ ++static void parse_signal_quality(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ unsigned int percentage, signal_strength; ++ DBusMessageIter structIter; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRUCT); ++ ++ dbus_message_iter_recurse(i, &structIter); ++ ++ if (dbus_message_iter_get_arg_type(&structIter) == DBUS_TYPE_UINT32) { ++ dbus_message_iter_get_basic(&structIter, &percentage); ++ signal_strength = (unsigned int) round(percentage / 20.0); ++ ++ pa_log_debug("Network signal strength: %d/100", percentage); ++ ++ if (b->modem->network_signal_strength != signal_strength) { ++ b->modem->network_signal_strength = signal_strength; ++ pa_log_debug("AG signal strength updated (%d/5)", b->modem->network_signal_strength); ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_SIGNAL_STRENGTH_CHANGED), b); ++ } ++ } ++} ++ ++static void parse_operator_name(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ char *name; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING); ++ ++ dbus_message_iter_get_basic(i, &name); ++ if (b->modem->network_operator_name) ++ pa_xfree(b->modem->network_operator_name); ++ b->modem->network_operator_name = pa_xstrdup(name); ++ ++ pa_log_debug("Network operator name: %s", b->modem->network_operator_name); ++} ++ ++static void parse_operator_code(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ char *code; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING); ++ ++ dbus_message_iter_get_basic(i, &code); ++ if (b->modem->network_operator_code) ++ pa_xfree(b->modem->network_operator_code); ++ b->modem->network_operator_code = pa_xstrdup(code); ++ ++ pa_log_debug("Network operator code: %s", b->modem->network_operator_code); ++} ++ ++static void parse_own_number(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ DBusMessageIter element_i; ++ char *number; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_ARRAY); ++ ++ dbus_message_iter_recurse(i, &element_i); ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) { ++ dbus_message_iter_get_basic(&element_i, &number); ++ if (b->modem->modem_own_number) ++ pa_xfree(b->modem->modem_own_number); ++ b->modem->modem_own_number = pa_xstrdup(number); ++ pa_log_debug("Modem own number: %s", b->modem->modem_own_number); ++ ++ /* Bluetooth HFP cannot deal with multiple SIMs and numbers */ ++ break; ++ ++ dbus_message_iter_next(&element_i); ++ } ++} ++ ++static void parse_call_number(pa_modemmanager_backend *b, DBusMessageIter *i, const char *call_object) { ++ char *number; ++ call_status_t *call_state; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING); ++ ++ dbus_message_iter_get_basic(i, &number); ++ ++ call_state = pa_hashmap_get(b->calls, call_object); ++ if (!call_state) { ++ return; ++ } ++ ++ if (call_state->number) ++ pa_xfree(call_state->number); ++ call_state->number = pa_xstrdup(number); ++ ++ pa_log_debug("Call number %s", call_state->number); ++} ++ ++static void parse_call_state(pa_modemmanager_backend *b, DBusMessageIter *i, const char *call_object) { ++ MMCallState state; ++ call_status_t *call_state; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_INT32); ++ ++ dbus_message_iter_get_basic(i, &state); ++ ++ call_state = pa_hashmap_get(b->calls, call_object); ++ if (!call_state) { ++ return; ++ } ++ ++ switch(state) { ++ case MM_CALL_STATE_UNKNOWN: ++ pa_log_warn("Call state unknown"); ++ call_state->status = PA_MODEMMANAGER_CALL_STATE_INVALID; ++ break; ++ case MM_CALL_STATE_ACTIVE: ++ pa_log_debug("Call active"); ++ call_state->status = PA_MODEMMANAGER_CALL_STATE_ACTIVE; ++ break; ++ /* FIXME: implement three-way calling */ ++ case MM_CALL_STATE_HELD: ++ pa_log_warn("Three-way calling is not implemented!"); ++ call_state->status = PA_MODEMMANAGER_CALL_STATE_INVALID; ++ break; ++ case MM_CALL_STATE_DIALING: ++ pa_log_debug("Call dailing"); ++ call_state->status = PA_MODEMMANAGER_CALL_STATE_DIALING; ++ break; ++ case MM_CALL_STATE_RINGING_OUT: ++ case MM_CALL_STATE_RINGING_IN: ++ pa_log_debug("Call ringing"); ++ call_state->status = PA_MODEMMANAGER_CALL_STATE_RINGING; ++ break; ++ /* FIXME: implement three-way calling */ ++ case MM_CALL_STATE_WAITING: ++ pa_log_warn("Three-way calling is not implemented!"); ++ call_state->status = PA_MODEMMANAGER_CALL_STATE_INVALID; ++ return; ++ case MM_CALL_STATE_TERMINATED: ++ pa_log_debug("Call terminated"); ++ call_state->status = PA_MODEMMANAGER_CALL_STATE_TERMINATED; ++ break; ++ default: ++ pa_assert_not_reached(); ++ } ++} ++ ++static void parse_call_direction(pa_modemmanager_backend *b, DBusMessageIter *i, const char *call_object) { ++ MMCallDirection direction; ++ call_status_t *call_state; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_INT32); ++ ++ dbus_message_iter_get_basic(i, &direction); ++ ++ call_state = pa_hashmap_get(b->calls, call_object); ++ if (!call_state) { ++ return; ++ } ++ ++ switch(direction) { ++ case MM_CALL_DIRECTION_UNKNOWN: ++ pa_log_warn("Unknown call direction"); ++ return; ++ case MM_CALL_DIRECTION_INCOMING: ++ call_state->is_incoming = true; ++ break; ++ case MM_CALL_DIRECTION_OUTGOING: ++ call_state->is_incoming = false; ++ break; ++ default: ++ pa_assert_not_reached(); ++ } ++ ++ pa_log_debug("Call direction updated. Is incoming? %s", pa_yes_no(call_state->is_incoming)); ++} ++ ++static void parse_manufacturer(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ char *manufacturer; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING); ++ ++ dbus_message_iter_get_basic(i, &manufacturer); ++ if (b->modem->modem_manufacturer) ++ pa_xfree(b->modem->modem_manufacturer); ++ b->modem->modem_manufacturer = pa_xstrdup(manufacturer); ++ ++ pa_log_debug("Modem manufacturer: %s", b->modem->modem_manufacturer); ++} ++ ++static void parse_model(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ char *model; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING); ++ ++ dbus_message_iter_get_basic(i, &model); ++ if (b->modem->modem_model) ++ pa_xfree(b->modem->modem_model); ++ b->modem->modem_model = pa_xstrdup(model); ++ ++ pa_log_debug("Modem model: %s", b->modem->modem_model); ++} ++ ++static void parse_revision(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ char *revision; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING); ++ ++ dbus_message_iter_get_basic(i, &revision); ++ if (b->modem->modem_revision) ++ pa_xfree(b->modem->modem_revision); ++ b->modem->modem_revision = pa_xstrdup(revision); ++ ++ pa_log_debug("Modem revision: %s", b->modem->modem_revision); ++} ++ ++static void parse_imei(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ char *imei; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING); ++ ++ dbus_message_iter_get_basic(i, &imei); ++ if (b->modem->modem_imei) ++ pa_xfree(b->modem->modem_imei); ++ b->modem->modem_imei = pa_xstrdup(imei); ++ ++ pa_log_debug("Modem IMEI: %s", b->modem->modem_imei); ++} ++ ++static void get_modem_properties_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_modemmanager_backend *b; ++ DBusMessage *r; ++ DBusMessageIter arg_i, element_i; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("ModemManager D-Bus Modem not available"); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("GetAll() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ goto finish; ++ } ++ ++ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{sv}")) { ++ pa_log_error("Invalid reply signature for GetAll()"); ++ goto finish; ++ } ++ ++ dbus_message_iter_recurse(&arg_i, &element_i); ++ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { ++ DBusMessageIter dict_i, variant_i; ++ const char *key; ++ ++ dbus_message_iter_recurse(&element_i, &dict_i); ++ ++ /* Retrieve property name */ ++ key = check_variant_property(&dict_i); ++ if (key == NULL) { ++ pa_log_error("Received invalid property!"); ++ break; ++ } ++ ++ /* Retrieve and parse property value */ ++ dbus_message_iter_recurse(&dict_i, &variant_i); ++ ++ if(pa_streq(key, MM_MODEM_PROPERTY_OWNNUMBERS)) ++ parse_own_number(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_EQUIPMENTIDENTIFIER)) ++ parse_imei(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_MANUFACTURER)) ++ parse_manufacturer(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_MODEL)) ++ parse_model(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_REVISION)) ++ parse_revision(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_SIGNALQUALITY)) ++ parse_signal_quality(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_STATE)) ++ parse_state(b, &variant_i); ++ ++ dbus_message_iter_next(&element_i); ++ } ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++} ++ ++static void get_modem_3gpp_properties_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_modemmanager_backend *b; ++ DBusMessage *r; ++ DBusMessageIter arg_i, element_i; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("ModemManager D-Bus Modem 3GPP not available"); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("GetAll() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ goto finish; ++ } ++ ++ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{sv}")) { ++ pa_log_error("Invalid reply signature for GetAll()"); ++ goto finish; ++ } ++ ++ dbus_message_iter_recurse(&arg_i, &element_i); ++ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { ++ DBusMessageIter dict_i, variant_i; ++ const char *key; ++ ++ dbus_message_iter_recurse(&element_i, &dict_i); ++ ++ /* Retrieve property name */ ++ key = check_variant_property(&dict_i); ++ if (key == NULL) { ++ pa_log_error("Received invalid property!"); ++ break; ++ } ++ ++ /* Retrieve and parse property value */ ++ dbus_message_iter_recurse(&dict_i, &variant_i); ++ ++ if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE)) ++ parse_registration(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORCODE)) ++ parse_operator_code(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORNAME)) ++ parse_operator_name(b, &variant_i); ++ ++ dbus_message_iter_next(&element_i); ++ } ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++} ++ ++static void get_call_properties_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_modemmanager_backend *b; ++ DBusMessage *r; ++ DBusMessageIter arg_i, element_i; ++ char *call_object; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(call_object = p->call_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("ModemManager D-Bus Call not available"); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("GetAll() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ goto finish; ++ } ++ ++ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{sv}")) { ++ pa_log_error("Invalid reply signature for GetAll()"); ++ goto finish; ++ } ++ ++ dbus_message_iter_recurse(&arg_i, &element_i); ++ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { ++ DBusMessageIter dict_i, variant_i; ++ const char *key; ++ ++ dbus_message_iter_recurse(&element_i, &dict_i); ++ ++ /* Retrieve property name */ ++ key = check_variant_property(&dict_i); ++ if (key == NULL) { ++ pa_log_error("Received invalid property!"); ++ break; ++ } ++ ++ /* Retrieve and parse property value */ ++ dbus_message_iter_recurse(&dict_i, &variant_i); ++ ++ if(pa_streq(key, MM_CALL_PROPERTY_NUMBER)) ++ parse_call_number(b, &variant_i, call_object); ++ else if(pa_streq(key, MM_CALL_PROPERTY_STATE)) ++ parse_call_state(b, &variant_i, call_object); ++ else if(pa_streq(key, MM_CALL_PROPERTY_DIRECTION)) ++ parse_call_direction(b, &variant_i, call_object); ++ ++ dbus_message_iter_next(&element_i); ++ } ++ ++ /* Notify change for call */ ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), b); ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++ ++ pa_xfree(call_object); ++} ++ ++static void parse_calls(pa_modemmanager_backend *b, DBusMessageIter *i) { ++ const char *mm_call_interface = MM_DBUS_INTERFACE_CALL; ++ const char *call_object; ++ DBusMessageIter element_i; ++ DBusMessage *m; ++ ++ pa_assert(i); ++ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_ARRAY); ++ ++ /* Iterate over array of dictonaries */ ++ dbus_message_iter_recurse(i, &element_i); ++ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_OBJECT_PATH) { ++ dbus_message_iter_get_basic(&element_i, &call_object); ++ ++ if (pa_hashmap_put(b->calls, pa_xstrdup(call_object), pa_xnew0(call_status_t, 1)) != 0) ++ pa_log_error("Call %s already exist", call_object); ++ ++ /* Retrieve call properties */ ++ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL)); ++ pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &mm_call_interface, DBUS_TYPE_INVALID)); ++ send_and_add_to_pending(b, m, get_call_properties_reply, pa_xstrdup(call_object)); ++ ++ // FIXME: Implement three-way calling. Only the first call is used. ++ return; ++ ++ dbus_message_iter_next(&element_i); ++ } ++} ++ ++static void get_modem_voice_properties_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_modemmanager_backend *b; ++ DBusMessage *r; ++ DBusMessageIter arg_i, element_i; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("ModemManager D-Bus Modem Voice not available"); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("GetAll() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ goto finish; ++ } ++ ++ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{sv}")) { ++ pa_log_error("Invalid reply signature for GetAll()"); ++ goto finish; ++ } ++ ++ dbus_message_iter_recurse(&arg_i, &element_i); ++ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { ++ DBusMessageIter dict_i, variant_i; ++ const char *key; ++ ++ dbus_message_iter_recurse(&element_i, &dict_i); ++ ++ /* Retrieve property name */ ++ key = check_variant_property(&dict_i); ++ if (key == NULL) { ++ pa_log_error("Received invalid property!"); ++ break; ++ } ++ ++ /* Retrieve and parse property value */ ++ dbus_message_iter_recurse(&dict_i, &variant_i); ++ ++ if(pa_streq(key, MM_MODEM_VOICE_PROPERTY_CALLS)) ++ parse_calls(b, &variant_i); ++ ++ dbus_message_iter_next(&element_i); ++ } ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++} ++ ++static void parse_interfaces_and_properties(pa_modemmanager_backend *b, DBusMessageIter *dict_i) { ++ DBusMessage *m; ++ DBusMessageIter element_i; ++ const char *path; ++ const char *mm_modem_interface = MM_DBUS_INTERFACE_MODEM; ++ const char *mm_modem_3gpp_interface = MM_DBUS_INTERFACE_MODEM_MODEM3GPP; ++ const char *mm_modem_voice_interface = MM_DBUS_INTERFACE_MODEM_VOICE; ++ ++ pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH); ++ dbus_message_iter_get_basic(dict_i, &path); ++ ++ pa_assert_se(dbus_message_iter_next(dict_i)); ++ pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_ARRAY); ++ ++ dbus_message_iter_recurse(dict_i, &element_i); ++ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { ++ DBusMessageIter dict_i; ++ const char *interface; ++ ++ dbus_message_iter_recurse(&element_i, &dict_i); ++ ++ pa_assert(dbus_message_iter_get_arg_type(&dict_i) == DBUS_TYPE_STRING); ++ dbus_message_iter_get_basic(&dict_i, &interface); ++ ++ pa_assert_se(dbus_message_iter_next(&dict_i)); ++ pa_assert(dbus_message_iter_get_arg_type(&dict_i) == DBUS_TYPE_ARRAY); ++ ++ /* ModemManager Modem inteface */ ++ if (pa_streq(interface, MM_DBUS_INTERFACE_MODEM)) { ++ pa_log_debug("Modem Interface %s found", path); ++ b->mm_modem = pa_xstrdup(path); ++ ++ /* Retrieve all modem properties */ ++ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, b->mm_modem, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL)); ++ pa_assert_se(dbus_message_append_args(m, ++ DBUS_TYPE_STRING, &mm_modem_interface, ++ DBUS_TYPE_INVALID)); ++ send_and_add_to_pending(b, m, get_modem_properties_reply, NULL); ++ /* ModemManager Modem 3GPP interface */ ++ } else if (pa_streq(interface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { ++ pa_log_debug("Modem 3GPP Interface %s found", path); ++ b->mm_modem_3gpp = pa_xstrdup(path); ++ ++ /* Retrieve all network properties */ ++ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, b->mm_modem_3gpp, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL)); ++ pa_assert_se(dbus_message_append_args(m, ++ DBUS_TYPE_STRING, &mm_modem_3gpp_interface, ++ DBUS_TYPE_INVALID)); ++ send_and_add_to_pending(b, m, get_modem_3gpp_properties_reply, NULL); ++ /* ModemManager Modem Voice interface */ ++ } else if (pa_streq(interface, MM_DBUS_INTERFACE_MODEM_VOICE)) { ++ pa_log_debug("Modem Voice Interface %s found", path); ++ b->mm_modem_voice = pa_xstrdup(path); ++ ++ /* Retrieve current call list and properties */ ++ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, b->mm_modem_voice, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL)); ++ pa_assert_se(dbus_message_append_args(m, ++ DBUS_TYPE_STRING, &mm_modem_voice_interface, ++ DBUS_TYPE_INVALID)); ++ send_and_add_to_pending(b, m, get_modem_voice_properties_reply, NULL); ++ } else ++ pa_log_debug("Unknown interface %s found, skipping", interface); ++ ++ dbus_message_iter_next(&element_i); ++ } ++ ++ return; ++} ++ ++static void invalidate_modem_properties(pa_modemmanager_backend *b) { ++ if (b->modem) { ++ if (b->modem->modem_manufacturer) { ++ pa_xfree(b->modem->modem_manufacturer); ++ b->modem->modem_manufacturer = NULL; ++ } ++ if (b->modem->modem_model) { ++ pa_xfree(b->modem->modem_model); ++ b->modem->modem_model = NULL; ++ } ++ if (b->modem->modem_revision) { ++ pa_xfree(b->modem->modem_revision); ++ b->modem->modem_revision = NULL; ++ } ++ if (b->modem->modem_imei) { ++ pa_xfree(b->modem->modem_imei); ++ b->modem->modem_imei = NULL; ++ } ++ if (b->modem->modem_own_number) { ++ pa_xfree(b->modem->modem_own_number); ++ b->modem->modem_own_number = NULL; ++ } ++ } ++} ++ ++static void invalidate_modem_3gpp_properties(pa_modemmanager_backend *b) { ++ if (b->modem) { ++ if (b->modem->network_operator_name) { ++ pa_xfree(b->modem->network_operator_name); ++ b->modem->network_operator_name = NULL; ++ } ++ if (b->modem->network_operator_code) { ++ pa_xfree(b->modem->network_operator_code); ++ b->modem->network_operator_code = NULL; ++ } ++ b->modem->network_has_service = false; ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_HAS_SERVICE_CHANGED), b); ++ b->modem->network_is_roaming = false; ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_IS_ROAMING_CHANGED), b); ++ b->modem->network_signal_strength = 0; ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_SIGNAL_STRENGTH_CHANGED), b); ++ } ++} ++ ++static void invalidate_modem_voice_properties(pa_modemmanager_backend *b) { ++ call_status_t *c = NULL; ++ void *state = NULL; ++ ++ /* Remove all call objects */ ++ if (b->calls) { ++ while ((c = pa_hashmap_iterate(b->calls, &state, NULL))) { ++ if (!c) ++ continue; ++ ++ if (c->number) ++ pa_xfree(c->number); ++ pa_hashmap_remove_and_free(b->calls, c); ++ } ++ ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), b); ++ } ++} ++ ++static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) { ++ pa_dbus_pending *p; ++ pa_modemmanager_backend *b; ++ DBusMessage *r; ++ DBusMessageIter arg_i, element_i; ++ ++ pa_assert(pending); ++ pa_assert_se(p = userdata); ++ pa_assert_se(b = p->context_data); ++ pa_assert_se(r = dbus_pending_call_steal_reply(pending)); ++ ++ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { ++ pa_log_warn("ModemManager D-Bus ObjectManager not available"); ++ goto finish; ++ } ++ ++ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { ++ pa_log_error("GetManagedObjects() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r)); ++ goto finish; ++ } ++ ++ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) { ++ pa_log_error("Invalid reply signature for GetManagedObjects()"); ++ goto finish; ++ } ++ ++ dbus_message_iter_recurse(&arg_i, &element_i); ++ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { ++ DBusMessageIter dict_i; ++ ++ dbus_message_iter_recurse(&element_i, &dict_i); ++ parse_interfaces_and_properties(b, &dict_i); ++ dbus_message_iter_next(&element_i); ++ } ++ ++finish: ++ dbus_message_unref(r); ++ ++ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p); ++ pa_dbus_pending_free(p); ++} ++ ++static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *data) { ++ DBusError err; ++ DBusMessage *m2; ++ const char *mm_call_interface = MM_DBUS_INTERFACE_CALL; ++ pa_modemmanager_backend *b = data; ++ const char *path, *interface, *member; ++ ++ pa_assert(bus); ++ pa_assert(m); ++ pa_assert(b); ++ ++ dbus_error_init(&err); ++ ++ path = dbus_message_get_path(m); ++ interface = dbus_message_get_interface(m); ++ member = dbus_message_get_member(m); ++ ++ pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); ++ ++ /* ModemManager D-Bus status change */ ++ if (dbus_message_is_signal(m, DBUS_INTERFACE_DBUS, DBUS_INTERFACE_DBUS_SIGNAL_NAMEOWNERCHANGED)) { ++ const char *name, *old_owner, *new_owner; ++ ++ if (!dbus_message_get_args(m, &err, ++ DBUS_TYPE_STRING, &name, ++ DBUS_TYPE_STRING, &old_owner, ++ DBUS_TYPE_STRING, &new_owner, ++ DBUS_TYPE_INVALID)) { ++ pa_log_error("Failed to parse " DBUS_INTERFACE_DBUS ".NameOwnerChanged: %s", err.message); ++ goto fail; ++ } ++ ++ if (pa_streq(name, MM_DBUS_SERVICE)) { ++ ++ /* ModemManager disappeared from D-Bus */ ++ if (old_owner && *old_owner) { ++ pa_log_debug("ModemManager disappeared from D-Bus"); ++ if (b->mm_bus_id) ++ pa_xfree(b->mm_bus_id); ++ b->mm_bus_id = NULL; ++ if (b->mm_modem) ++ pa_xfree(b->mm_modem); ++ b->mm_modem = NULL; ++ if (b->mm_modem_3gpp) ++ pa_xfree(b->mm_modem_3gpp); ++ b->mm_modem_3gpp = NULL; ++ if (b->mm_modem_voice) ++ pa_xfree(b->mm_modem_voice); ++ b->mm_modem_voice = NULL; ++ ++ /* Invalidate all properties since ModemManager is unavailable */ ++ invalidate_modem_properties(b); ++ invalidate_modem_3gpp_properties(b); ++ invalidate_modem_voice_properties(b); ++ } ++ ++ /* ModemManager appeared on D-Bus, properties are refreshed through the InterfacesAdded signal callback */ ++ if (new_owner && *new_owner) { ++ pa_log_debug("ModemManager appeared on D-Bus"); ++ b->mm_bus_id = pa_xstrdup(dbus_message_get_sender(m)); ++ if (b->modem) ++ pa_xfree(b->modem); ++ b->modem = pa_xnew0(modem_status_t, 1); ++ } ++ } ++ ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ } ++ /* Modem added, refresh managed objects */ ++ else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECT_MANAGER, DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESADDED)) { ++ DBusMessageIter arg_i; ++ ++ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) { ++ pa_log_error("Invalid signature found in InterfacesAdded"); ++ goto fail; ++ } ++ ++ parse_interfaces_and_properties(b, &arg_i); ++ } ++ /* Modem removed, refresh managed objects */ ++ else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECT_MANAGER, DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESREMOVED)) { ++ const char *p; ++ DBusMessageIter arg_i; ++ DBusMessageIter element_i; ++ ++ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oas")) { ++ pa_log_error("Invalid signature found in InterfacesRemoved"); ++ goto fail; ++ } ++ ++ dbus_message_iter_get_basic(&arg_i, &p); ++ ++ pa_assert_se(dbus_message_iter_next(&arg_i)); ++ pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY); ++ ++ dbus_message_iter_recurse(&arg_i, &element_i); ++ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) { ++ const char *iface; ++ ++ dbus_message_iter_get_basic(&element_i, &iface); ++ ++ /* ModemManager Modem interface removed */ ++ if (pa_streq(iface, MM_DBUS_INTERFACE_MODEM)) { ++ if (b->mm_modem) { ++ pa_xfree(b->mm_modem); ++ b->mm_modem = NULL; ++ } ++ invalidate_modem_properties(b); ++ pa_log_debug("Modem Interface removed"); ++ } ++ ++ /* ModemManager Modem 3GPP interface removed */ ++ if (pa_streq(iface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { ++ if (b->mm_modem_3gpp) { ++ pa_xfree(b->mm_modem_3gpp); ++ b->mm_modem_3gpp = NULL; ++ } ++ invalidate_modem_3gpp_properties(b); ++ pa_log_debug("Modem 3GPP Interface removed"); ++ } ++ ++ /* ModemManager Modem Voice interface removed */ ++ if (pa_streq(iface, MM_DBUS_INTERFACE_MODEM_VOICE)) { ++ if (b->mm_modem_voice) { ++ pa_xfree(b->mm_modem_voice); ++ b->mm_modem_voice = NULL; ++ } ++ invalidate_modem_voice_properties(b); ++ pa_log_debug("Modem Voice Interface removed"); ++ } ++ ++ dbus_message_iter_next(&element_i); ++ } ++ ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ } ++ /* Update properties if updated by ModemManager */ ++ else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_SIGNAL_PROPERTIESCHANGED)) { ++ DBusMessageIter arg_i, element_i; ++ ++ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "sa{sv}as")) { ++ pa_log_error("Invalid signature found in PropertiesChanged"); ++ goto fail; ++ } ++ ++ /* Skip interface name */ ++ pa_assert_se(dbus_message_iter_next(&arg_i)); ++ pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY); ++ ++ dbus_message_iter_recurse(&arg_i, &element_i); ++ ++ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { ++ DBusMessageIter dict_i, variant_i; ++ const char *key; ++ ++ dbus_message_iter_recurse(&element_i, &dict_i); ++ ++ /* Retrieve property name and parse value */ ++ key = check_variant_property(&dict_i); ++ if (key == NULL) { ++ pa_log_error("Received invalid property!"); ++ break; ++ } ++ dbus_message_iter_recurse(&dict_i, &variant_i); ++ ++ /* ++ * ModemManager Modem interface ++ * See https://www.freedesktop.org/software/ModemManager/api/latest/gdbus-org.freedesktop.ModemManager1.Modem.html ++ */ ++ if(b->mm_modem && pa_streq(path, b->mm_modem)) { ++ pa_log_debug("ModemManager Modem property updated: %s", key); ++ ++ if(pa_streq(key, MM_MODEM_PROPERTY_OWNNUMBERS)) ++ parse_own_number(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_EQUIPMENTIDENTIFIER)) ++ parse_imei(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_MANUFACTURER)) ++ parse_manufacturer(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_MODEL)) ++ parse_model(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_REVISION)) ++ parse_revision(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_SIGNALQUALITY)) ++ parse_signal_quality(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_PROPERTY_STATE)) ++ parse_state(b, &variant_i); ++ } ++ ++ /* ++ * ModemManager Modem 3GPP interface ++ * See https://www.freedesktop.org/software/ModemManager/api/latest/gdbus-org.freedesktop.ModemManager1.Modem.Modem3gpp.html ++ */ ++ if(b->mm_modem_3gpp && pa_streq(path, b->mm_modem_3gpp)) { ++ pa_log_debug("ModemManager Modem 3GPP property updated: %s", key); ++ ++ if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE)) ++ parse_registration(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORCODE)) ++ parse_operator_code(b, &variant_i); ++ else if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORNAME)) ++ parse_operator_name(b, &variant_i); ++ } ++ ++ dbus_message_iter_next(&element_i); ++ } ++ } ++ /* Call started */ ++ else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLADDED)) { ++ DBusMessageIter arg_i; ++ char *call_object; ++ ++ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "o")) { ++ pa_log_error("Invalid signature found in CallAdded"); ++ goto fail; ++ } ++ ++ /* Create call state */ ++ dbus_message_iter_get_basic(&arg_i, &call_object); ++ pa_log_debug("Call added: %s", call_object); ++ ++ if (pa_hashmap_put(b->calls, pa_xstrdup(call_object), pa_xnew0(call_status_t, 1)) != 0) ++ pa_log_error("Call %s already exist", call_object); ++ ++ /* Retrieve call properties */ ++ pa_assert_se(m2 = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL)); ++ pa_assert_se(dbus_message_append_args(m2, DBUS_TYPE_STRING, &mm_call_interface, DBUS_TYPE_INVALID)); ++ send_and_add_to_pending(b, m2, get_call_properties_reply, pa_xstrdup(call_object)); ++ } ++ /* Call rejected or ended */ ++ else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLDELETED)) { ++ DBusMessageIter arg_i; ++ char *call_object; ++ ++ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "o")) { ++ pa_log_error("Invalid signature found in CallDeleted"); ++ goto fail; ++ } ++ ++ /* Remove call state */ ++ dbus_message_iter_get_basic(&arg_i, &call_object); ++ pa_log_debug("Call removed: %s", call_object); ++ pa_hashmap_remove_and_free(b->calls, call_object); ++ ++ /* Notify about call deletion */ ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), b); ++ } ++ /* Call state updated */ ++ else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_CALL, MM_CALL_SIGNAL_STATECHANGED)) { ++ DBusMessageIter arg_i; ++ ++ pa_log_debug("Call %s: state updated", path); ++ ++ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "iiu")) { ++ pa_log_error("Invalid signature found in StateChanged"); ++ goto fail; ++ } ++ ++ /* Skip old call state */ ++ pa_assert_se(dbus_message_iter_next(&arg_i)); ++ ++ /* Parse new call state */ ++ pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_INT32); ++ parse_call_state(b, &arg_i, path); ++ ++ /* Skip call state change reason */ ++ pa_assert_se(dbus_message_iter_next(&arg_i)); ++ ++ /* Notify change for call */ ++ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, ++ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), b); ++ } ++ ++fail: ++ dbus_error_free(&err); ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++/* Accept an incoming call */ ++void pa_modemmanager_accept_call(pa_modemmanager_backend *backend, char *call) { ++ DBusMessage *m; ++ call_status_t *call_state; ++ ++ /* Check if call is incoming ringing */ ++ call_state = pa_hashmap_get(backend->calls, call); ++ if (!call_state || call_state->status != PA_MODEMMANAGER_CALL_STATE_RINGING || !call_state->is_incoming) { ++ pa_log_error("Call is not ringing and/or incoming, unable to accept call"); ++ pa_hook_fire(pa_bluetooth_discovery_hook(backend->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), backend); ++ return; ++ } ++ ++ /* Accept call */ ++ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, call, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_ACCEPT)); ++ send_and_add_to_pending(backend, m, call_accept_reply, NULL); ++} ++ ++/* End a call */ ++void pa_modemmanager_end_call(pa_modemmanager_backend *backend, char *call) { ++ DBusMessage *m; ++ call_status_t *call_state; ++ ++ /* Check if call is active or incoming ringing */ ++ call_state = pa_hashmap_get(backend->calls, call); ++ if (!call_state || (call_state->status != PA_MODEMMANAGER_CALL_STATE_ACTIVE ++ && (call_state->status != PA_MODEMMANAGER_CALL_STATE_RINGING && call_state->is_incoming))) { ++ pa_log_error("Call is not active or incoming ringing, unable to hangup call"); ++ pa_hook_fire(pa_bluetooth_discovery_hook(backend->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), backend); ++ return; ++ } ++ ++ /* End call */ ++ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, call, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_HANGUP)); ++ send_and_add_to_pending(backend, m, call_hangup_reply, NULL); ++} ++ ++/* Create a new call by dialing the provided number */ ++void pa_modemmanager_start_call(pa_modemmanager_backend *backend, char *number) { ++ DBusMessage *m; ++ DBusMessageIter i, d; ++ char *call_number; ++ ++ /* Create call for filtered number */ ++ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, backend->mm_modem, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_METHOD_CREATECALL)); ++ dbus_message_iter_init_append(m, &i); ++ dbus_message_iter_open_container(&i, 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, ++ &d); ++ call_number = pa_xstrdup(number); ++ pa_dbus_append_basic_variant_dict_entry(&d, "number", DBUS_TYPE_STRING, &call_number); ++ dbus_message_iter_close_container(&i, &d); ++ send_and_add_to_pending(backend, m, create_call_reply, NULL); ++} ++ ++/* Send a DTMF tone to a given call */ ++void pa_modemmanager_send_dtmf(pa_modemmanager_backend *backend, char *call, char *character) { ++ DBusMessage *m; ++ char *dtmf_char = pa_xstrdup(character); ++ call_status_t *call_state; ++ ++ /* Check if call is active */ ++ call_state = pa_hashmap_get(backend->calls, call); ++ if (call_state && (call_state->status != PA_MODEMMANAGER_CALL_STATE_ACTIVE)) { ++ pa_log_error("Call is not active, unable to send DTMF tone"); ++ pa_hook_fire(pa_bluetooth_discovery_hook(backend->discovery, ++ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), backend); ++ return; ++ } ++ ++ /* Let ModemManager generate DTMF tone */ ++ pa_log_debug("Generating DTMF tone: '%s'", dtmf_char); ++ m = dbus_message_new_method_call(MM_DBUS_SERVICE, call, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_SENDDTMF); ++ pa_assert_se(dbus_message_append_args(m, ++ DBUS_TYPE_STRING, &dtmf_char, ++ DBUS_TYPE_INVALID)); ++ send_and_add_to_pending(backend, m, send_dtmf_reply, NULL); ++} ++ ++/* Get network operator name */ ++char *pa_modemmanager_get_operator_name(const pa_modemmanager_backend *backend) { ++ return backend->modem->network_operator_name; ++} ++ ++/* Get network operator code */ ++char *pa_modemmanager_get_operator_code(const pa_modemmanager_backend *backend) { ++ return backend->modem->network_operator_code; ++} ++ ++/* Get service status */ ++bool pa_modemmanager_has_service(const pa_modemmanager_backend *backend) { ++ return backend->modem->network_has_service; ++} ++ ++/* Get roaming status */ ++bool pa_modemmanager_is_roaming(const pa_modemmanager_backend *backend) { ++ return backend->modem->network_is_roaming; ++} ++ ++/* Get signal strength percentage */ ++unsigned int pa_modemmanager_get_signal_strength(const pa_modemmanager_backend *backend) { ++ return backend->modem->network_signal_strength; ++} ++ ++/* Get all calls */ ++pa_hashmap *pa_modemmanager_get_calls(const pa_modemmanager_backend *backend) { ++ return backend->calls; ++} ++ ++/* Get active call */ ++char *pa_modemmanager_get_active_call_key(const pa_modemmanager_backend *backend) { ++ pa_hashmap *calls; ++ call_status_t *c; ++ const void *key; ++ void *state = NULL; ++ ++ calls = pa_modemmanager_get_calls(backend); ++ while ((c = pa_hashmap_iterate(calls, &state, &key))) { ++ if (key == NULL) ++ break; ++ /* FIXME: support three-way calling */ ++ return (char *) key; ++ } ++ ++ /* No active calls */ ++ return NULL; ++} ++ ++/* Get modem model */ ++char *pa_modemmanager_get_modem_model(const pa_modemmanager_backend *backend) { ++ return backend->modem->modem_model; ++} ++ ++/* Get modem manufacturer */ ++char *pa_modemmanager_get_modem_manufacturer(const pa_modemmanager_backend *backend) { ++ return backend->modem->modem_manufacturer; ++} ++ ++/* Get modem revision */ ++char *pa_modemmanager_get_modem_revision(const pa_modemmanager_backend *backend) { ++ return backend->modem->modem_revision; ++} ++ ++/* Get modem IMEI */ ++char *pa_modemmanager_get_modem_imei(const pa_modemmanager_backend *backend) { ++ return backend->modem->modem_imei; ++} ++ ++/* Get modem's own number */ ++char *pa_modemmanager_get_own_number(const pa_modemmanager_backend *backend) { ++ return backend->modem->modem_own_number; ++} ++ ++/* Check if modem is present */ ++bool pa_modemmanager_has_modem(const pa_modemmanager_backend *backend) { ++ if (!backend->mm_modem) ++ return false; ++ ++ if (!backend->mm_modem_3gpp) ++ return false; ++ ++ if (!backend->mm_modem_voice) ++ return false; ++ ++ if(!backend->modem) ++ return false; ++ ++ return true; ++} ++ ++pa_modemmanager_backend *pa_modemmanager_backend_new(pa_core *c, pa_bluetooth_discovery *d) { ++ pa_modemmanager_backend *backend; ++ DBusError err; ++ DBusMessage *m; ++ ++ pa_log_debug("Native backend enabled ModemManager modem & call status reporting"); ++ ++ backend = pa_xnew0(pa_modemmanager_backend, 1); ++ backend->core = c; ++ backend->discovery = d; ++ backend->calls = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ backend->modem = pa_xnew0(modem_status_t, 1); ++ ++ /* Get DBus connection */ ++ dbus_error_init(&err); ++ if (!(backend->connection = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &err))) { ++ pa_log("Failed to get D-Bus connection: %s", err.message); ++ dbus_error_free(&err); ++ pa_xfree(backend); ++ return NULL; ++ } ++ ++ /* Add filter callback for DBus connection */ ++ if (!dbus_connection_add_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend, NULL)) { ++ pa_log_error("Failed to add filter function"); ++ pa_dbus_connection_unref(backend->connection); ++ pa_xfree(backend); ++ return NULL; ++ } ++ ++ /* Register for call & modem changes from ModemManager */ ++ if (pa_dbus_add_matches(pa_dbus_connection_get(backend->connection), &err, ++ "type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='" DBUS_INTERFACE_DBUS_SIGNAL_NAMEOWNERCHANGED "'," ++ "arg0='" MM_DBUS_SERVICE "'", ++ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" DBUS_INTERFACE_OBJECT_MANAGER "',member='" DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESADDED "'", ++ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" DBUS_INTERFACE_OBJECT_MANAGER "',member='" DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESREMOVED "'", ++ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" DBUS_INTERFACE_PROPERTIES "',member='" DBUS_INTERFACE_PROPERTIES_SIGNAL_PROPERTIESCHANGED "'", ++ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLADDED "'", ++ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLDELETED "'", ++ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" MM_DBUS_INTERFACE_CALL "',member='" MM_CALL_SIGNAL_STATECHANGED "'", ++ NULL) < 0) { ++ pa_log("Failed to add ModemManager D-Bus matches: %s", err.message); ++ dbus_connection_remove_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend); ++ pa_dbus_connection_unref(backend->connection); ++ pa_xfree(backend); ++ return NULL; ++ } ++ ++ /* Initialize list of modems by requesting it from ModemManager */ ++ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, MM_DBUS_PATH, DBUS_INTERFACE_OBJECT_MANAGER, DBUS_INTERFACE_OBJECT_MANAGER_METHOD_GETMANAGEDOBJECTS)); ++ send_and_add_to_pending(backend, m, get_managed_objects_reply, NULL); ++ ++ return backend; ++} ++ ++void pa_modemmanager_backend_free(pa_modemmanager_backend *backend) { ++ pa_assert(backend); ++ ++ pa_dbus_free_pending_list(&backend->pending); ++ ++ pa_dbus_connection_unref(backend->connection); ++ ++ invalidate_modem_properties(backend); ++ invalidate_modem_3gpp_properties(backend); ++ invalidate_modem_voice_properties(backend); ++ ++ pa_xfree(backend); ++} ++ +diff --git a/src/modules/bluetooth/modemmanager.h b/src/modules/bluetooth/modemmanager.h +new file mode 100644 +index 000000000..9a1d82219 +--- /dev/null ++++ b/src/modules/bluetooth/modemmanager.h +@@ -0,0 +1,98 @@ ++#pragma once ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2022 Dylan Van Assche ++ ++ PulseAudio 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. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with PulseAudio; if not, see . ++***/ ++ ++#include "bluez5-util.h" ++#include "ModemManager.h" ++ ++#define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" ++#define DBUS_INTERFACE_OBJECT_MANAGER_METHOD_GETMANAGEDOBJECTS "GetManagedObjects" ++#define DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESADDED "InterfacesAdded" ++#define DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESREMOVED "InterfacesRemoved" ++#define DBUS_INTERFACE_DBUS_SIGNAL_NAMEOWNERCHANGED "NameOwnerChanged" ++#define DBUS_INTERFACE_PROPERTIES_METHOD_GETALL "GetAll" ++#define DBUS_INTERFACE_PROPERTIES_SIGNAL_PROPERTIESCHANGED "PropertiesChanged" ++ ++#define MAX_NUMBER_LENGTH 30 ++ ++#define STR_VALUE(x) STR(x) ++#define STR(x) #x ++ ++typedef enum pa_modemmanager_call_state { ++ PA_MODEMMANAGER_CALL_STATE_INVALID = 0, ++ PA_MODEMMANAGER_CALL_STATE_ACTIVE = 1, ++ PA_MODEMMANAGER_CALL_STATE_RINGING = 2, ++ PA_MODEMMANAGER_CALL_STATE_DIALING = 3, ++ PA_MODEMMANAGER_CALL_STATE_TERMINATED = 4 ++ /* FIXME: implement three-way calling */ ++} pa_modemmanager_call_state_t; ++ ++typedef struct call_status { ++ bool is_incoming; ++ pa_modemmanager_call_state_t status; ++ char *number; ++} call_status_t; ++ ++typedef struct modem_status { ++ char *network_operator_name; ++ char *network_operator_code; ++ unsigned int network_signal_strength; ++ bool network_has_service; ++ bool network_is_roaming; ++ char *modem_own_number; ++ char *modem_manufacturer; ++ char *modem_model; ++ char *modem_revision; ++ char *modem_imei; ++} modem_status_t; ++ ++struct pa_modemmanager_backend { ++ pa_core *core; ++ pa_dbus_connection *connection; ++ pa_bluetooth_discovery *discovery; ++ char *mm_bus_id; ++ char *mm_modem; ++ char *mm_modem_3gpp; ++ char *mm_modem_voice; ++ pa_hashmap *calls; ++ modem_status_t *modem; ++ ++ PA_LLIST_HEAD(pa_dbus_pending, pending); ++}; ++ ++pa_modemmanager_backend *pa_modemmanager_backend_new(pa_core *c, pa_bluetooth_discovery *d); ++void pa_modemmanager_backend_free(pa_modemmanager_backend *backend); ++bool pa_modemmanager_has_modem(const pa_modemmanager_backend *backend); ++void pa_modemmanager_accept_call(pa_modemmanager_backend *backend, char *call); ++void pa_modemmanager_end_call(pa_modemmanager_backend *backend, char *call); ++void pa_modemmanager_start_call(pa_modemmanager_backend *backend, char *number); ++void pa_modemmanager_send_dtmf(pa_modemmanager_backend *backend, char *call, char *character); ++pa_hashmap *pa_modemmanager_get_calls(const pa_modemmanager_backend *backend); ++char *pa_modemmanager_get_active_call_key(const pa_modemmanager_backend *backend); ++char *pa_modemmanager_get_operator_name(const pa_modemmanager_backend *backend); ++char *pa_modemmanager_get_operator_code(const pa_modemmanager_backend *backend); ++bool pa_modemmanager_has_service(const pa_modemmanager_backend *backend); ++bool pa_modemmanager_is_roaming(const pa_modemmanager_backend *backend); ++unsigned int pa_modemmanager_get_signal_strength(const pa_modemmanager_backend *backend); ++char *pa_modemmanager_get_modem_model(const pa_modemmanager_backend *backend); ++char *pa_modemmanager_get_modem_manufacturer(const pa_modemmanager_backend *backend); ++char *pa_modemmanager_get_modem_revision(const pa_modemmanager_backend *backend); ++char *pa_modemmanager_get_modem_imei(const pa_modemmanager_backend *backend); ++char *pa_modemmanager_get_own_number(const pa_modemmanager_backend *backend); +-- +2.35.1 + diff --git a/temp/pulseaudio/0006-bluetooth-only-reply-OK-for-supported-AT-cmds.patch b/temp/pulseaudio/0006-bluetooth-only-reply-OK-for-supported-AT-cmds.patch new file mode 100644 index 000000000..770ac8375 --- /dev/null +++ b/temp/pulseaudio/0006-bluetooth-only-reply-OK-for-supported-AT-cmds.patch @@ -0,0 +1,40 @@ +From 72b92267a8f906a3ecd10c571407802810e01bcc Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Wed, 13 Apr 2022 18:27:44 +0200 +Subject: [PATCH 06/26] bluetooth: only reply OK for supported AT cmds + +PulseAudio should always be honest to Bluetooth devices even when audio is connected. +--- + src/modules/bluetooth/backend-native.c | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index a490d6efd..1fadb4b14 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -2,6 +2,7 @@ + This file is part of PulseAudio. + + Copyright 2014 Wim Taymans ++ Copyright 2021-2022 Dylan Van Assche + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as +@@ -808,12 +809,8 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + return false; + } + +- /* +- * once we're fully connected, just reply OK to everything +- * it will just be the headset sending the occasional status +- * update, but we process only the ones we care about +- */ +- return true; ++ /* Unsupported commands return ERROR */ ++ return false + } + + static int get_rfcomm_fd (pa_bluetooth_discovery *discovery) { +-- +2.35.1 + diff --git a/temp/pulseaudio/0007-bluetooth-Always-reply-to-AT-CIND.patch b/temp/pulseaudio/0007-bluetooth-Always-reply-to-AT-CIND.patch new file mode 100644 index 000000000..0e777f410 --- /dev/null +++ b/temp/pulseaudio/0007-bluetooth-Always-reply-to-AT-CIND.patch @@ -0,0 +1,61 @@ +From 81045feccb25b627c2c6cfc105c27b2599573fd2 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Wed, 13 Apr 2022 18:29:08 +0200 +Subject: [PATCH 07/26] bluetooth: Always reply to AT+CIND + +Once the stateful negotiation is complete, still reply +to AT+CIND? and AT+CIND=? since we will report actual data here +such as cellular service status, roaming, signal, and battery level. +--- + src/modules/bluetooth/backend-native.c | 22 ++++++++++++++++++---- + 1 file changed, 18 insertions(+), 4 deletions(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 1fadb4b14..77792146a 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -686,7 +686,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + /* no state change */ + + return true; +- } else if (c->state == 1 && pa_startswith(buf, "AT+CIND=?")) { ++ } else if (c->state >= 1 && pa_startswith(buf, "AT+CIND=?")) { + /* UPower backend available, declare support for more indicators */ + if (discovery->native_backend->upower) { + rfcomm_write_response(fd, "+CIND: " +@@ -700,15 +700,29 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + MANDATORY_CALL_INDICATORS "," + "(\"service\",(0-1))"); + } +- c->state = 2; ++ ++ /* ++ * Only change codec negotiation state when the first AT+CIND=? command is received ++ * Bluetooth HFP 1.8 specification specifies that this AT command is issued 'at least once'. ++ */ ++ if (c->state < 2) ++ c->state = 2; + + return true; +- } else if (c->state == 2 && pa_startswith(buf, "AT+CIND?")) { ++ } else if (c->state >= 2 && pa_startswith(buf, "AT+CIND?")) { + if (discovery->native_backend->upower) + rfcomm_write_response(fd, "+CIND: 0,0,0,0,%u", pa_upower_get_battery_level(discovery->native_backend->upower)); + else + rfcomm_write_response(fd, "+CIND: 0,0,0,0"); +- c->state = 3; ++ ++ /* ++ * Only change codec negotiation state when the first AT+CIND? command is received. ++ * The Bluetooth HFP 1.8 specification does not define how many times this command is issued. ++ * Some HF devices issue this one multiple times breaking the codec negotiation stateful state, ++ * so only update the state once. ++ */ ++ if (c->state < 3) ++ c->state = 3; + + return true; + } else if ((c->state == 2 || c->state == 3) && pa_startswith(buf, "AT+CMER=")) { +-- +2.35.1 + diff --git a/temp/pulseaudio/0008-bluetooth-support-AT-CMEE.patch b/temp/pulseaudio/0008-bluetooth-support-AT-CMEE.patch new file mode 100644 index 000000000..93a8609b4 --- /dev/null +++ b/temp/pulseaudio/0008-bluetooth-support-AT-CMEE.patch @@ -0,0 +1,197 @@ +From ff2d7b76bf4878f47a2df1261219c9d9e14f8c9e Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Wed, 13 Apr 2022 19:56:41 +0200 +Subject: [PATCH 08/26] bluetooth: support AT+CMEE + +AT+CMEE is now supported and extended error messages are returned +when an ERROR occurs. By default, these error codes are disabled, +the HF must enable them explicitely with AT+CMEE=1 (numeric mode). +--- + src/modules/bluetooth/backend-native.c | 71 +++++++++++++++++++++----- + src/modules/bluetooth/bluez5-util.h | 6 +++ + 2 files changed, 65 insertions(+), 12 deletions(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 77792146a..3110c5ef2 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -57,6 +57,7 @@ struct pa_bluetooth_backend { + bool enable_hsp_hs; + bool enable_hfp_hf; + bool cmer_indicator_reporting_enabled; ++ bool cmee_extended_error_reporting_enabled; + uint32_t cind_enabled_indicators; + + PA_LLIST_HEAD(pa_dbus_pending, pending); +@@ -125,7 +126,7 @@ typedef enum pa_bluetooth_ag_to_hf_indicators { + /* gateway features we support, which is as little as we can get away with */ + static uint32_t hfp_features = + /* HFP 1.6 requires this */ +- (1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS) | (1 << HFP_AG_INDICATORS); ++ (1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS) | (1 << HFP_AG_INDICATORS) | (1 << HFP_AG_EERR); + + #define HSP_AG_PROFILE "/Profile/HSPAGProfile" + #define HFP_AG_PROFILE "/Profile/HFPAGProfile" +@@ -256,6 +257,14 @@ static void rfcomm_write_response(int fd, const char *fmt, ...) + va_end(ap); + } + ++/* Send Extended Error messages if enabled */ ++static void rfcomm_write_error(pa_bluetooth_backend *b, int fd, unsigned int error) { ++ if (b->cmee_extended_error_reporting_enabled) ++ rfcomm_write_response(fd, "+CME ERROR: %d", error); ++ else ++ rfcomm_write_response(fd, "ERROR"); ++} ++ + static int sco_setsockopt_enable_bt_voice(pa_bluetooth_transport *t, int fd) { + /* the mSBC codec requires a special transparent eSCO connection */ + struct bt_voice voice; +@@ -621,6 +630,30 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + size_t len; + const char *state; + ++ /* Extended error reporting. Described in Bluetooth HFP 1.8 spec. */ ++ if (sscanf(buf, "AT+CMEE=%d", &val) == 1) { ++ switch(val) { ++ /* Disable extended error reporting */ ++ case 0: ++ discovery->native_backend->cmee_extended_error_reporting_enabled = false; ++ return true; ++ /* Enable extend error reporting in numeric mode */ ++ case 1: ++ discovery->native_backend->cmee_extended_error_reporting_enabled = true; ++ return true; ++ /* Enable extend error reporting in verbose mode */ ++ case 2: ++ pa_log_warn("Extended Error Reporting verbose mode is unsupported by Bluetooth HFP 1.8"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_SUPPORTED); ++ return false; ++ default: ++ pa_log_warn("Unknown AT+CMEE value: %d", val); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); ++ return false; ++ } ++ pa_assert_not_reached(); ++ } ++ + /* first-time initialize selected codec to CVSD */ + if (c->selected_codec == 0) + c->selected_codec = 1; +@@ -655,7 +688,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + discovery->native_backend->cind_enabled_indicators &= ~(1 << indicator); + else { + pa_log_error("Unable to parse indicator of AT+BIA command: %s", buf); +- rfcomm_write_response(fd, "ERROR"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); + return false; + } + +@@ -741,7 +774,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + } + else { + pa_log_error("Unable to parse AT+CMER command: %s", buf); +- rfcomm_write_response(fd, "ERROR"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); + return false; + } + +@@ -767,7 +800,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("mSBC"), sco_transport_write, sco_setsockopt_enable_bt_voice); + } else { + pa_assert_fp(val != 1 && val != 2); +- rfcomm_write_response(fd, "ERROR"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); + return false; + } + +@@ -798,14 +831,14 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + pa_log_notice("Battery Level: %d%%", val); + if (val < 0 || val > 100) { + pa_log_error("Battery HF indicator %d out of [0, 100] range", val); +- rfcomm_write_response(fd, "ERROR"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); + return false; + } + pa_bluetooth_device_report_battery_level(t->device, val, "HFP 1.7 HF indicator"); + break; + default: + pa_log_error("Unknown HF indicator %u", indicator); +- rfcomm_write_response(fd, "ERROR"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_SUPPORTED); + return false; + } + return true; +@@ -819,12 +852,23 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + if (c->state != 5) { + pa_log_error("HFP negotiation failed in state %d with inbound %s\n", + c->state, buf); +- rfcomm_write_response(fd, "ERROR"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); + return false; + } + +- /* Unsupported commands return ERROR */ +- return false ++ /* ++ * Unsupported commands return ERROR. ++ * Unsupported AT commands from HFP 1.8: ++ * - AT+BINP ++ * - AT+BLDN ++ * - ATD> ++ * - AT+BVRA ++ * - AT+BTRH ++ * - AT+CHLD ++ * - AT+CCWA ++ */ ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_SUPPORTED); ++ return false; + } + + static int get_rfcomm_fd (pa_bluetooth_discovery *discovery) { +@@ -996,12 +1040,13 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i + break; + } + } +- if (!do_reply) +- rfcomm_write_response(fd, "ERROR"); ++ if (!do_reply) { ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); ++ } + } else if (t->config) { /* t->config is only non-null for hfp profile */ + do_reply = hfp_rfcomm_handle(fd, t, buf); + } else { +- rfcomm_write_response(fd, "ERROR"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); + do_reply = false; + } + +@@ -1391,6 +1436,8 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d + /* While all CIND indicators are enabled, event reporting is not enabled by default */ + backend->cmer_indicator_reporting_enabled = false; + ++ /* CMEE is disabled by default */ ++ backend->cmee_extended_error_reporting_enabled = false; + + return backend; + } +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index f69176425..4cc5c2882 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -211,6 +211,12 @@ struct pa_bluetooth_adapter { + bool battery_provider_registered; + }; + ++/* extended error reporting values, described in Bluetooth HFP 1.8 spec */ ++typedef enum pa_bluetooth_cmee { ++ CMEE_AG_FAILURE = 0, ++ CMEE_OPERATION_NOT_SUPPORTED = 4, ++} pa_bluetooth_cmee_t; ++ + #ifdef HAVE_BLUEZ_5_OFONO_HEADSET + pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_discovery *y); + void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b); +-- +2.35.1 + diff --git a/temp/pulseaudio/0009-bluetooth-support-AT-NREC.patch b/temp/pulseaudio/0009-bluetooth-support-AT-NREC.patch new file mode 100644 index 000000000..d42b6c2a8 --- /dev/null +++ b/temp/pulseaudio/0009-bluetooth-support-AT-NREC.patch @@ -0,0 +1,48 @@ +From 0dc5b589220e830ab18ddbcf90f2cfbe1c4927f7 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Wed, 13 Apr 2022 20:25:47 +0200 +Subject: [PATCH 09/26] bluetooth: support AT+NREC + +AT+NREC=0 disables Noise Reduction and Echo Canceling in the AG. +Bluetooth HFP 1.8 only supports the disable operation, so return +an CME ERROR extended error code if another value is send by the HF. +--- + src/modules/bluetooth/backend-native.c | 7 +++++++ + src/modules/bluetooth/bluez5-util.h | 3 ++- + 2 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 3110c5ef2..394c2a4da 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -652,6 +652,13 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + return false; + } + pa_assert_not_reached(); ++ } else if (sscanf(buf, "AT+NREC=%d", &val) == 1) { ++ if (val == 0) ++ return true; ++ ++ /* Noise Redction and Echo Canceling can only have value '0' (disable) following Bluetooth HFP 1.8 */ ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED); ++ return false; + } + + /* first-time initialize selected codec to CVSD */ +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index 4cc5c2882..fa26a8361 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -214,7 +214,8 @@ struct pa_bluetooth_adapter { + /* extended error reporting values, described in Bluetooth HFP 1.8 spec */ + typedef enum pa_bluetooth_cmee { + CMEE_AG_FAILURE = 0, +- CMEE_OPERATION_NOT_SUPPORTED = 4, ++ CMEE_OPERATION_NOT_ALLOWED = 3, ++ CMEE_OPERATION_NOT_SUPPORTED = 4 + } pa_bluetooth_cmee_t; + + #ifdef HAVE_BLUEZ_5_OFONO_HEADSET +-- +2.35.1 + diff --git a/temp/pulseaudio/0010-bluetooth-support-AT-BCC.patch b/temp/pulseaudio/0010-bluetooth-support-AT-BCC.patch new file mode 100644 index 000000000..e01cb786e --- /dev/null +++ b/temp/pulseaudio/0010-bluetooth-support-AT-BCC.patch @@ -0,0 +1,33 @@ +From 79d8cf24785fc52a3d0cee48590143fb409201b8 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Thu, 14 Apr 2022 19:15:34 +0200 +Subject: [PATCH 10/26] bluetooth: support AT+BCC + +HFs send AT+BCC to (re)start codec negotiations. +Support this command by (re)setting the codec negotiations state and +reply OK. +--- + src/modules/bluetooth/backend-native.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 394c2a4da..89a174f9a 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -666,7 +666,12 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + c->selected_codec = 1; + + /* stateful negotiation */ +- if (c->state == 0 && sscanf(buf, "AT+BRSF=%d", &val) == 1) { ++ if (strstr(buf, "AT+BCC")) { ++ pa_log_debug("HF asked to start codec negotiations"); ++ c->state = 0; ++ return true; ++ } ++ else if (c->state == 0 && sscanf(buf, "AT+BRSF=%d", &val) == 1) { + c->capabilities = val; + pa_log_info("HFP capabilities returns 0x%x", val); + rfcomm_write_response(fd, "+BRSF: %d", hfp_features); +-- +2.35.1 + diff --git a/temp/pulseaudio/0011-bluetooth-support-AT-COPS.patch b/temp/pulseaudio/0011-bluetooth-support-AT-COPS.patch new file mode 100644 index 000000000..a168c7ada --- /dev/null +++ b/temp/pulseaudio/0011-bluetooth-support-AT-COPS.patch @@ -0,0 +1,165 @@ +From ccac2886c516aa0ac2e76406b782de0abc700e92 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Thu, 14 Apr 2022 19:47:46 +0200 +Subject: [PATCH 11/26] bluetooth: support AT+COPS + +AT+COPS=3,X sets the operator name format and AT+COPS? returns +the current network operator name. Supports this command when +ModemManager is available to provide this information. +--- + src/modules/bluetooth/backend-native.c | 72 +++++++++++++++++++++++++- + src/modules/bluetooth/bluez5-util.h | 11 +++- + 2 files changed, 81 insertions(+), 2 deletions(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 89a174f9a..c0c14acf3 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -40,6 +40,7 @@ + #include "bluez5-util.h" + #include "bt-codec-msbc.h" + #include "upower.h" ++#include "modemmanager.h" + + #define MANDATORY_CALL_INDICATORS \ + "(\"call\",(0-1))," \ +@@ -53,12 +54,14 @@ struct pa_bluetooth_backend { + pa_hook_slot *adapter_uuids_changed_slot; + pa_hook_slot *host_battery_level_changed_slot; + pa_upower_backend *upower; ++ pa_modemmanager_backend *modemmanager; + bool enable_shared_profiles; + bool enable_hsp_hs; + bool enable_hfp_hf; + bool cmer_indicator_reporting_enabled; + bool cmee_extended_error_reporting_enabled; + uint32_t cind_enabled_indicators; ++ pa_bluetooth_cops_t cops_format; + + PA_LLIST_HEAD(pa_dbus_pending, pending); + }; +@@ -624,7 +627,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + { + struct pa_bluetooth_discovery *discovery = t->device->discovery; + struct hfp_config *c = t->config; +- int indicator, mode, val; ++ int indicator, mode, val, val2; + char str[5]; + const char *r; + size_t len; +@@ -659,6 +662,68 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + /* Noise Redction and Echo Canceling can only have value '0' (disable) following Bluetooth HFP 1.8 */ + rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED); + return false; ++ } else if (sscanf(buf, "AT+COPS=%d,%d", &val, &val2) == 2) { ++ /* Return error if ModemManager is unavailable */ ++ if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ pa_log_debug("ModemManager backend unavailable, cannot set AT+COPS format"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; ++ } ++ ++ /* Only AT+COPS=3,X is allowed by Bluetooth HFP 1.8 */ ++ if (val != 3) ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED); ++ ++ /* Set AT+COPS format */ ++ if (val2 == 0) { ++ discovery->native_backend->cops_format = COPS_LONG_FORMAT; ++ return true; ++ } else if (val2 == 2) { ++ discovery->native_backend->cops_format = COPS_NUMERIC_FORMAT; ++ return true; ++ } else { ++ discovery->native_backend->cops_format = COPS_UNKNOWN_FORMAT; ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED); ++ return false; ++ } ++ } else if (strstr(buf, "AT+COPS?")) { ++ /* Return error if ModemManager is unavailable */ ++ if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ pa_log_debug("ModemManager backend unavailable, cannot answer AT+COPS?"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; ++ } else if (!pa_modemmanager_has_service(discovery->native_backend->modemmanager)) { ++ pa_log_debug("No network service, cannot answer AT+COPS?"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE); ++ return false; ++ } ++ ++ switch(discovery->native_backend->cops_format) { ++ case COPS_LONG_FORMAT: { ++ char *operator_name = pa_modemmanager_get_operator_name(discovery->native_backend->modemmanager); ++ if (operator_name) { ++ rfcomm_write_response(fd, "+COPS: 0,0,\"%s\"", operator_name); ++ return true; ++ } ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE); ++ return false; ++ } ++ case COPS_NUMERIC_FORMAT: { ++ char *operator_code = pa_modemmanager_get_operator_code(discovery->native_backend->modemmanager); ++ if (operator_code) { ++ rfcomm_write_response(fd, "+COPS: 2,0,\"%s\"", operator_code); ++ return true; ++ } ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE); ++ return false; ++ } ++ case COPS_UNKNOWN_FORMAT: { ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); ++ return false; ++ } ++ default: ++ pa_assert_not_reached(); ++ } + } + + /* first-time initialize selected codec to CVSD */ +@@ -1441,6 +1506,8 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d + + backend->upower = pa_upower_backend_new(c, y); + ++ backend->modemmanager = pa_modemmanager_backend_new(c, y); ++ + /* All CIND indicators are enabled by default until overriden by AT+BIA */ + for (i = 1; i < CIND_INDICATOR_MAX; i++) + backend->cind_enabled_indicators |= (1 << i); +@@ -1473,6 +1540,9 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) { + + if (backend->upower) + pa_upower_backend_free(backend->upower); ++ ++ if (backend->modemmanager) ++ pa_modemmanager_backend_free(backend->modemmanager); + + pa_dbus_connection_unref(backend->connection); + +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index fa26a8361..9ac9880a8 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -214,10 +214,19 @@ struct pa_bluetooth_adapter { + /* extended error reporting values, described in Bluetooth HFP 1.8 spec */ + typedef enum pa_bluetooth_cmee { + CMEE_AG_FAILURE = 0, ++ CMEE_NO_CONNECTION_TO_PHONE = 1, + CMEE_OPERATION_NOT_ALLOWED = 3, +- CMEE_OPERATION_NOT_SUPPORTED = 4 ++ CMEE_OPERATION_NOT_SUPPORTED = 4, ++ CMEE_NO_NETWORK_SERVICE = 30 + } pa_bluetooth_cmee_t; + ++/* AT+COPS format, described in Bluetooth HFP 1.8 spec */ ++typedef enum pa_bluetooth_cops { ++ COPS_LONG_FORMAT = 0, ++ COPS_NUMERIC_FORMAT = 1, ++ COPS_UNKNOWN_FORMAT = 2 ++} pa_bluetooth_cops_t; ++ + #ifdef HAVE_BLUEZ_5_OFONO_HEADSET + pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_discovery *y); + void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b); +-- +2.35.1 + diff --git a/temp/pulseaudio/0012-bluetooth-support-AT-CNUM.patch b/temp/pulseaudio/0012-bluetooth-support-AT-CNUM.patch new file mode 100644 index 000000000..4dc789da5 --- /dev/null +++ b/temp/pulseaudio/0012-bluetooth-support-AT-CNUM.patch @@ -0,0 +1,68 @@ +From 739ca09cc36553703d2b24ba99b64f8641fcf987 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Thu, 14 Apr 2022 20:16:24 +0200 +Subject: [PATCH 12/26] bluetooth: support AT+CNUM + +AT+CNUM returns the phone number of the subscriber. +Supports this AT command when ModemManager is available. +--- + src/modules/bluetooth/backend-native.c | 25 +++++++++++++++++++++++++ + src/modules/bluetooth/bluez5-util.h | 6 ++++++ + 2 files changed, 31 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index c0c14acf3..19ca497d5 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -724,6 +724,31 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + default: + pa_assert_not_reached(); + } ++ } else if (strstr(buf, "AT+CNUM")) { ++ char *number; ++ unsigned int type; ++ ++ /* Return error if ModemManager is unavailable */ ++ if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ pa_log_debug("ModemManager backend unavailable, cannot answer AT+CNUM"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; ++ } ++ ++ number = pa_modemmanager_get_own_number(discovery->native_backend->modemmanager); ++ if (!number) { ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); ++ return false; ++ } ++ ++ /* International numbers start with '+' */ ++ if (strncmp("+", number, 1) == 0) ++ type = CLIP_INTERNATIONAL_NUMBER; ++ else ++ type = CLIP_NATIONAL_NUMBER; ++ ++ rfcomm_write_response(fd, "+CNUM: ,\"%s\",%d,,4", number, type); ++ return true; + } + + /* first-time initialize selected codec to CVSD */ +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index 9ac9880a8..638a793b7 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -227,6 +227,12 @@ typedef enum pa_bluetooth_cops { + COPS_UNKNOWN_FORMAT = 2 + } pa_bluetooth_cops_t; + ++/* CLIP number formats, described in Bluetooth HFP 1.8 spec */ ++typedef enum pa_bluetooth_clip { ++ CLIP_INTERNATIONAL_NUMBER = 145, ++ CLIP_NATIONAL_NUMBER = 129 ++} pa_bluetooth_clip_t; ++ + #ifdef HAVE_BLUEZ_5_OFONO_HEADSET + pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_discovery *y); + void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b); +-- +2.35.1 + diff --git a/temp/pulseaudio/0013-bluetooth-support-more-AT-CIND-indicators.patch b/temp/pulseaudio/0013-bluetooth-support-more-AT-CIND-indicators.patch new file mode 100644 index 000000000..3cf64a966 --- /dev/null +++ b/temp/pulseaudio/0013-bluetooth-support-more-AT-CIND-indicators.patch @@ -0,0 +1,67 @@ +From fafe1fc716a9482e2ea883fb79edf90a7c89d33d Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Thu, 14 Apr 2022 20:43:42 +0200 +Subject: [PATCH 13/26] bluetooth: support more AT+CIND? indicators + +If ModemManager is available, report service status, roaming status and +signal strength as well when an AT+CIND? command is received. +--- + src/modules/bluetooth/backend-native.c | 24 +++++++++++++++++------- + 1 file changed, 17 insertions(+), 7 deletions(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 19ca497d5..fcbae5540 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -123,7 +123,9 @@ typedef enum pa_bluetooth_ag_to_hf_indicators { + CIND_CALL_HELD_INDICATOR = 3, + CIND_SERVICE_INDICATOR = 4, + CIND_BATT_CHG_INDICATOR = 5, +- CIND_INDICATOR_MAX = 6 ++ CIND_ROAMING_INDICATOR = 6, ++ CIND_SIGNAL_STRENGTH_INDICATOR = 7, ++ CIND_INDICATOR_MAX = 8 + } pa_bluetooth_ag_to_hf_indicators_t; + + /* gateway features we support, which is as little as we can get away with */ +@@ -822,14 +824,16 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + + return true; + } else if (c->state >= 1 && pa_startswith(buf, "AT+CIND=?")) { +- /* UPower backend available, declare support for more indicators */ +- if (discovery->native_backend->upower) { ++ /* UPower and ModemManager backends available, declare support for more indicators */ ++ if (discovery->native_backend->upower && discovery->native_backend->modemmanager && pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { + rfcomm_write_response(fd, "+CIND: " + MANDATORY_CALL_INDICATORS "," + "(\"service\",(0-1))," +- "(\"battchg\",(0-5))"); ++ "(\"battchg\",(0-5))," ++ "(\"roam\",(0-1))," ++ "(\"signal\",(0-5))"); + +- /* Minimal indicators supported without any additional backend */ ++ /* Minimal indicators supported without any additional backends */ + } else { + rfcomm_write_response(fd, "+CIND: " + MANDATORY_CALL_INDICATORS "," +@@ -845,8 +849,14 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + + return true; + } else if (c->state >= 2 && pa_startswith(buf, "AT+CIND?")) { +- if (discovery->native_backend->upower) +- rfcomm_write_response(fd, "+CIND: 0,0,0,0,%u", pa_upower_get_battery_level(discovery->native_backend->upower)); ++ if (discovery->native_backend->upower && discovery->native_backend->modemmanager && pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) ++ rfcomm_write_response(fd, "+CIND: %u,%u,0,%u,%u,%u,%u", ++ discovery->native_backend->cind_call_indicator, ++ discovery->native_backend->cind_call_setup_indicator, ++ pa_modemmanager_has_service(discovery->native_backend->modemmanager), ++ pa_upower_get_battery_level(discovery->native_backend->upower), ++ pa_modemmanager_is_roaming(discovery->native_backend->modemmanager), ++ pa_modemmanager_get_signal_strength(discovery->native_backend->modemmanager)); + else + rfcomm_write_response(fd, "+CIND: 0,0,0,0"); + +-- +2.35.1 + diff --git a/temp/pulseaudio/0014-bluetooth-support-AT-CLIP.patch b/temp/pulseaudio/0014-bluetooth-support-AT-CLIP.patch new file mode 100644 index 000000000..4081e8290 --- /dev/null +++ b/temp/pulseaudio/0014-bluetooth-support-AT-CLIP.patch @@ -0,0 +1,60 @@ +From 07adf1b246ba1db7b4c5bb9555f1d392b856c049 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Thu, 14 Apr 2022 20:52:36 +0200 +Subject: [PATCH 14/26] bluetooth: support AT+CLIP + +Enable or disable Call Line Indication reporting (+CLIP) when +a call is incoming to display the number that is calling on the HF's +screen. +--- + src/modules/bluetooth/backend-native.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index fcbae5540..1eaa36b4a 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -62,6 +62,7 @@ struct pa_bluetooth_backend { + bool cmee_extended_error_reporting_enabled; + uint32_t cind_enabled_indicators; + pa_bluetooth_cops_t cops_format; ++ bool clip_call_line_reporting_enabled; + + PA_LLIST_HEAD(pa_dbus_pending, pending); + }; +@@ -751,6 +752,22 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + + rfcomm_write_response(fd, "+CNUM: ,\"%s\",%d,,4", number, type); + return true; ++ } else if (sscanf(buf, "AT+CLIP=%d", &val) == 1) { ++ switch(val) { ++ /* Disable call line reporting */ ++ case 0: ++ discovery->native_backend->clip_call_line_reporting_enabled = false; ++ return true; ++ /* Enable call line reporting */ ++ case 1: ++ discovery->native_backend->clip_call_line_reporting_enabled = true; ++ return true; ++ default: ++ pa_log_warn("Unknown AT+CLIP value: %d", val); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); ++ return false; ++ } ++ pa_assert_not_reached(); + } + + /* first-time initialize selected codec to CVSD */ +@@ -1553,6 +1570,9 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d + /* CMEE is disabled by default */ + backend->cmee_extended_error_reporting_enabled = false; + ++ /* CLIP is disabled by default */ ++ backend->clip_call_line_reporting_enabled = false; ++ + return backend; + } + +-- +2.35.1 + diff --git a/temp/pulseaudio/0015-bluetooth-support-AT-CREG.patch b/temp/pulseaudio/0015-bluetooth-support-AT-CREG.patch new file mode 100644 index 000000000..f74dccd07 --- /dev/null +++ b/temp/pulseaudio/0015-bluetooth-support-AT-CREG.patch @@ -0,0 +1,48 @@ +From d10422fafd9eececfa6755617c41b05f895e5a5b Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Thu, 14 Apr 2022 21:03:37 +0200 +Subject: [PATCH 15/26] bluetooth: support AT+CREG? + +AT+CREG? is not officialy supported by the Bluetooth HFP 1.8 spec, +but some car multimedia systems use it. AT+CREG? reports if the phone +has network available or not with a +CREG response. +--- + src/modules/bluetooth/backend-native.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 1eaa36b4a..2fa769d07 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -770,6 +770,28 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + pa_assert_not_reached(); + } + ++ /* ++ * Out-of-spec Bluetooth HFP 1.8 AT commands. ++ * Some car multimedia systems implement the HFP spec, ++ * but also use out-of-spec AT commands which are normally supported ++ * by any 3GPP compliant modem such as: ++ * - AT+CREG?: Get service status ++ * - AT+CGMM: Get modem model ++ * - AT+CGMI: Get modem manufacturer ++ * - AT+CGMR: Get modem revision ++ * - AT+CGSN: Get modem IMEI ++ * ++ * These AT commands are additionally supported if ModemManager is available. ++ */ ++ if (strstr(buf, "AT+CREG?")) { ++ if (discovery->native_backend->modemmanager && pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ rfcomm_write_response(fd, "+CREG: 0,%d", pa_modemmanager_has_service(discovery->native_backend->modemmanager)); ++ return true; ++ } ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; ++ } ++ + /* first-time initialize selected codec to CVSD */ + if (c->selected_codec == 0) + c->selected_codec = 1; +-- +2.35.1 + diff --git a/temp/pulseaudio/0016-bluetooth-support-AT-CGMM.patch b/temp/pulseaudio/0016-bluetooth-support-AT-CGMM.patch new file mode 100644 index 000000000..1931e1a82 --- /dev/null +++ b/temp/pulseaudio/0016-bluetooth-support-AT-CGMM.patch @@ -0,0 +1,32 @@ +From ee52128fbb8bc953c43b8c53763443c715fa64c8 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Fri, 15 Apr 2022 07:29:49 +0200 +Subject: [PATCH 16/26] bluetooth: support AT+CGMM + +Return the modem model if ModemManager is available when the HF +sends an out-of-spec AT+CGMM command. +--- + src/modules/bluetooth/backend-native.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 2fa769d07..2c39a090c 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -790,6 +790,13 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + } + rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); + return false; ++ } else if (strstr(buf, "AT+CGMM") && pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ if (discovery->native_backend->modemmanager) { ++ rfcomm_write_response(fd, "%s", pa_modemmanager_get_modem_model(discovery->native_backend->modemmanager)); ++ return true; ++ } ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; + } + + /* first-time initialize selected codec to CVSD */ +-- +2.35.1 + diff --git a/temp/pulseaudio/0017-bluetooth-support-AT-CGMI.patch b/temp/pulseaudio/0017-bluetooth-support-AT-CGMI.patch new file mode 100644 index 000000000..3dceb2f4e --- /dev/null +++ b/temp/pulseaudio/0017-bluetooth-support-AT-CGMI.patch @@ -0,0 +1,33 @@ +From 6e851563dbe4e90a37b0f8fef1604395acba47b4 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Fri, 15 Apr 2022 07:31:43 +0200 +Subject: [PATCH 17/26] bluetooth: support AT+CGMI + +Return the modem manufacturer if ModemManager is available. +AT+CGMI is an out-of-spec AT command for Bluetooth HFP, but car +multimedia units use it since it is a standard 3GPP command. +--- + src/modules/bluetooth/backend-native.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 2c39a090c..3099b13fc 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -797,6 +797,13 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + } + rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); + return false; ++ } else if (strstr(buf, "AT+CGMI") && pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ if (discovery->native_backend->modemmanager) { ++ rfcomm_write_response(fd, "%s", pa_modemmanager_get_modem_manufacturer(discovery->native_backend->modemmanager)); ++ return true; ++ } ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; + } + + /* first-time initialize selected codec to CVSD */ +-- +2.35.1 + diff --git a/temp/pulseaudio/0018-bluetooth-support-AT-CGMR.patch b/temp/pulseaudio/0018-bluetooth-support-AT-CGMR.patch new file mode 100644 index 000000000..4868b22db --- /dev/null +++ b/temp/pulseaudio/0018-bluetooth-support-AT-CGMR.patch @@ -0,0 +1,32 @@ +From 5fd316c23c3051be2394c16bf97ee189fc07ce23 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Fri, 15 Apr 2022 07:33:19 +0200 +Subject: [PATCH 18/26] bluetooth: support AT+CGMR + +Return the modem revision when HF sends an out-of-spec AT+CGMR +command. This is a 3GPP standardized command so lets support it. +--- + src/modules/bluetooth/backend-native.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 3099b13fc..cf016a282 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -804,6 +804,13 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + } + rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); + return false; ++ } else if (strstr(buf, "AT+CGMR")) { ++ if (discovery->native_backend->modemmanager && pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ rfcomm_write_response(fd, "%s", pa_modemmanager_get_modem_revision(discovery->native_backend->modemmanager)); ++ return true; ++ } ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; + } + + /* first-time initialize selected codec to CVSD */ +-- +2.35.1 + diff --git a/temp/pulseaudio/0019-bluetooth-support-AT-CGSN.patch b/temp/pulseaudio/0019-bluetooth-support-AT-CGSN.patch new file mode 100644 index 000000000..28193527c --- /dev/null +++ b/temp/pulseaudio/0019-bluetooth-support-AT-CGSN.patch @@ -0,0 +1,33 @@ +From f39e071a558a9b3a301028c7f3506f024d60ceea Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Fri, 15 Apr 2022 07:35:11 +0200 +Subject: [PATCH 19/26] bluetooth: support AT+CGSN + +Return the modem's IMEI number when car multimedia systems (HF) +send an AT+CGSN 3GPP AT command, even though it is not supported by the +Bluetooth HFP 1.8 spec. +--- + src/modules/bluetooth/backend-native.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index cf016a282..fec24a57b 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -811,6 +811,13 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + } + rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); + return false; ++ } else if (strstr(buf, "AT+CGSN") && pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ if (discovery->native_backend->modemmanager) { ++ rfcomm_write_response(fd, "%s", pa_modemmanager_get_modem_imei(discovery->native_backend->modemmanager)); ++ return true; ++ } ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; + } + + /* first-time initialize selected codec to CVSD */ +-- +2.35.1 + diff --git a/temp/pulseaudio/0020-bluetooth-support-AT-CLCC.patch b/temp/pulseaudio/0020-bluetooth-support-AT-CLCC.patch new file mode 100644 index 000000000..52220ce25 --- /dev/null +++ b/temp/pulseaudio/0020-bluetooth-support-AT-CLCC.patch @@ -0,0 +1,108 @@ +From 320d906f8e64b1713dc09c8e95316c4de515f70f Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Fri, 15 Apr 2022 08:18:48 +0200 +Subject: [PATCH 20/26] bluetooth: support AT+CLCC + +Report ongoing calls when the HF sends an AT+CLCC command. +Currently, only one ongoing call is supported, three-way calling is not +implemented. Any additional calls are ignored. +--- + src/modules/bluetooth/backend-native.c | 61 ++++++++++++++++++++++++++ + src/modules/bluetooth/bluez5-util.h | 9 ++++ + 2 files changed, 70 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index fec24a57b..faefed9bd 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -768,6 +768,67 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + return false; + } + pa_assert_not_reached(); ++ } else if (strstr(buf, "AT+CLCC")) { ++ pa_hashmap *calls; ++ call_status_t *call; ++ unsigned int type; ++ unsigned int clcc_status; ++ ++ /* Return error if ModemManager is unavailable */ ++ if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ pa_log_debug("ModemManager backend unavailable, cannot answer AT+CLCC"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; ++ } else if (!pa_modemmanager_has_service(discovery->native_backend->modemmanager)) { ++ pa_log_debug("No network service, cannot answer AT+CLCC"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE); ++ return false; ++ } ++ ++ calls = pa_modemmanager_get_calls(discovery->native_backend->modemmanager); ++ ++ /* Check if we have any ongoing calls, if not return OK */ ++ if (pa_hashmap_isempty(calls)) ++ return true; ++ ++ /* FIXME: support three-way calling */ ++ call = pa_hashmap_first(calls); ++ ++ /* Map call status to CLCC status numbers */ ++ if (call->status == PA_MODEMMANAGER_CALL_STATE_INVALID) { ++ pa_log_warn("Unable to answer AT+CLCC, call state is unknown"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); ++ return false; ++ } else if (call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_RINGING) { ++ clcc_status = CLCC_INCOMING; ++ } else if (!call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_RINGING) { ++ clcc_status = CLCC_ALERTING; ++ } else if (!call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_DIALING) { ++ clcc_status = CLCC_DIALING; ++ } else if (call->status == PA_MODEMMANAGER_CALL_STATE_ACTIVE) { ++ clcc_status = CLCC_ACTIVE; ++ } else if (call->status == PA_MODEMMANAGER_CALL_STATE_TERMINATED) { ++ pa_log_debug("Call terminated already, do not report it."); ++ return true; ++ /* FIXME: support three-way calling */ ++ } else { ++ pa_assert_not_reached(); ++ } ++ ++ /* Check if call has a number, if not drop it since number and type are optional */ ++ if (!call->number) { ++ rfcomm_write_response(fd, "+CLCC: %d,%d,%d,%d,%d", 1, call->is_incoming, clcc_status, 0, 0); ++ return true; ++ } ++ ++ /* International numbers start with '+' */ ++ if (strncmp("+", call->number, 1) == 0) ++ type = CLIP_INTERNATIONAL_NUMBER; ++ else ++ type = CLIP_NATIONAL_NUMBER; ++ ++ rfcomm_write_response(fd, "+CLCC: %d,%d,%d,%d,%d,\"%s\",%d", 1, call->is_incoming, clcc_status, 0, 0, call->number, type); ++ return true; + } + + /* +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index 638a793b7..fb88f00e2 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -233,6 +233,15 @@ typedef enum pa_bluetooth_clip { + CLIP_NATIONAL_NUMBER = 129 + } pa_bluetooth_clip_t; + ++/* CLCC call states, described in Bluetooth HFP 1.8 spec */ ++/* FIXME: support three-way calling */ ++typedef enum pa_bluetooth_clcc { ++ CLCC_ACTIVE = 0, ++ CLCC_DIALING = 2, ++ CLCC_ALERTING = 3, ++ CLCC_INCOMING = 4 ++} pa_bluetooth_clcc_t; ++ + #ifdef HAVE_BLUEZ_5_OFONO_HEADSET + pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_discovery *y); + void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b); +-- +2.35.1 + diff --git a/temp/pulseaudio/0021-bluetooth-support-CIEV-RING-and-CLIP-URCs.patch b/temp/pulseaudio/0021-bluetooth-support-CIEV-RING-and-CLIP-URCs.patch new file mode 100644 index 000000000..9e3196e1b --- /dev/null +++ b/temp/pulseaudio/0021-bluetooth-support-CIEV-RING-and-CLIP-URCs.patch @@ -0,0 +1,365 @@ +From 6861de91294c8bed206e8338d1074ebc57683ae4 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Sat, 16 Apr 2022 08:46:13 +0200 +Subject: [PATCH 21/26] bluetooth: support +CIEV, RING, and +CLIP URCs + +Report changes of the CIND indicators and calls to the HF with URCs: + +- +CIEV: report any change to the CIND indicators such as signal + strength, roaming status, service status, etc. +- RING: play a ringtone on the HF side for incoming calls. +- +CLIP: enhanced call status reports the subscriber number to the HF + for incoming calls. +--- + src/modules/bluetooth/backend-native.c | 258 +++++++++++++++++++++++++ + src/modules/bluetooth/bluez5-util.h | 12 ++ + 2 files changed, 270 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index faefed9bd..e12b38d71 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -29,6 +29,8 @@ + #include + #include + #include ++#include ++#include + + #include + #include +@@ -42,6 +44,7 @@ + #include "upower.h" + #include "modemmanager.h" + ++#define RING_WAIT_TIME ((pa_usec_t) (3 * PA_USEC_PER_SEC)) + #define MANDATORY_CALL_INDICATORS \ + "(\"call\",(0-1))," \ + "(\"callsetup\",(0-3))," \ +@@ -49,10 +52,17 @@ + + struct pa_bluetooth_backend { + pa_core *core; ++ pa_time_event *timer_ring_event; + pa_dbus_connection *connection; + pa_bluetooth_discovery *discovery; + pa_hook_slot *adapter_uuids_changed_slot; + pa_hook_slot *host_battery_level_changed_slot; ++ pa_hook_slot *host_operation_succeed_slot; ++ pa_hook_slot *host_operation_failed_slot; ++ pa_hook_slot *host_signal_strength_changed_slot; ++ pa_hook_slot *host_has_service_changed_slot; ++ pa_hook_slot *host_is_roaming_changed_slot; ++ pa_hook_slot *host_calls_changed_slot; + pa_upower_backend *upower; + pa_modemmanager_backend *modemmanager; + bool enable_shared_profiles; +@@ -63,6 +73,8 @@ struct pa_bluetooth_backend { + uint32_t cind_enabled_indicators; + pa_bluetooth_cops_t cops_format; + bool clip_call_line_reporting_enabled; ++ unsigned int cind_call_indicator; ++ unsigned int cind_call_setup_indicator; + + PA_LLIST_HEAD(pa_dbus_pending, pending); + }; +@@ -1172,6 +1184,206 @@ static pa_hook_result_t host_battery_level_changed_cb(pa_bluetooth_discovery *y, + return PA_HOOK_OK; + } + ++static pa_hook_result_t host_operation_failed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) { ++ int rfcomm_fd; ++ ++ pa_assert(y); ++ pa_assert(m); ++ pa_assert(b); ++ ++ /* Get RFCOMM channel if available */ ++ rfcomm_fd = get_rfcomm_fd (y); ++ if (rfcomm_fd < 0) ++ return PA_HOOK_OK; ++ ++ /* Notify HF about AG operation failure over RFCOMM */ ++ rfcomm_write_error(b, rfcomm_fd, CMEE_AG_FAILURE); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t host_operation_succeed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) { ++ int rfcomm_fd; ++ ++ pa_assert(y); ++ pa_assert(m); ++ pa_assert(b); ++ ++ /* Get RFCOMM channel if available */ ++ rfcomm_fd = get_rfcomm_fd (y); ++ if (rfcomm_fd < 0) ++ return PA_HOOK_OK; ++ ++ /* Notify HF about AG operation succeed over RFCOMM */ ++ rfcomm_write_response(rfcomm_fd, "OK"); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t host_signal_strength_changed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) { ++ int rfcomm_fd; ++ ++ pa_assert(y); ++ pa_assert(m); ++ pa_assert(b); ++ ++ /* Get RFCOMM channel if available */ ++ rfcomm_fd = get_rfcomm_fd (y); ++ if (rfcomm_fd < 0) ++ return PA_HOOK_OK; ++ ++ /* Notify HF about AG signal strength change */ ++ if (b->cmer_indicator_reporting_enabled && (b->cind_enabled_indicators & (1 << CIND_SIGNAL_STRENGTH_INDICATOR))) { ++ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_SIGNAL_STRENGTH_INDICATOR, pa_modemmanager_get_signal_strength(m)); ++ /* Skip notification if indicator is disabled or event reporting is completely disabled */ ++ } else ++ pa_log_debug("Signal strength change indicator disabled, skipping notification"); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t host_has_service_changed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) { ++ int rfcomm_fd; ++ ++ pa_assert(y); ++ pa_assert(m); ++ pa_assert(b); ++ ++ /* Get RFCOMM channel if available */ ++ rfcomm_fd = get_rfcomm_fd (y); ++ if (rfcomm_fd < 0) ++ return PA_HOOK_OK; ++ ++ /* Notify HF about AG cellular service status change */ ++ if (b->cmer_indicator_reporting_enabled && (b->cind_enabled_indicators & (1 << CIND_SERVICE_INDICATOR))) { ++ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_SERVICE_INDICATOR, pa_modemmanager_has_service(m)); ++ /* Skip notification if indicator is disabled or event reporting is completely disabled */ ++ } else ++ pa_log_debug("Cellular service status change indicator disabled, skipping notification"); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t host_is_roaming_changed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) { ++ int rfcomm_fd; ++ ++ pa_assert(y); ++ pa_assert(m); ++ pa_assert(b); ++ ++ /* Get RFCOMM channel if available */ ++ rfcomm_fd = get_rfcomm_fd (y); ++ if (rfcomm_fd < 0) ++ return PA_HOOK_OK; ++ ++ /* Notify HF about AG roaming status change */ ++ if (b->cmer_indicator_reporting_enabled && (b->cind_enabled_indicators & (1 << CIND_ROAMING_INDICATOR))) { ++ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_ROAMING_INDICATOR, pa_modemmanager_is_roaming(m)); ++ /* Skip notification if indicator is disabled or event reporting is completely disabled */ ++ } else ++ pa_log_debug("Roaming status change indicator disabled, skipping notification"); ++ ++ return PA_HOOK_OK; ++} ++ ++static void timer_ring_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) { ++ int rfcomm_fd; ++ unsigned int type; ++ pa_bluetooth_discovery *y = userdata; ++ pa_hashmap *calls; ++ call_status_t *call; ++ ++ /* Get RFCOMM channel if available */ ++ rfcomm_fd = get_rfcomm_fd (y); ++ if (rfcomm_fd < 0) ++ return; ++ ++ calls = pa_modemmanager_get_calls(y->native_backend->modemmanager); ++ ++ /* Stop ringing if no calls are available */ ++ if (pa_hashmap_isempty(calls)) ++ return; ++ ++ /* FIXME: support three-way calling */ ++ call = pa_hashmap_first(calls); ++ ++ /* Send RING indicator */ ++ rfcomm_write_response(rfcomm_fd, "RING"); ++ ++ /* If enabled, send +CLIP indications to HF about the caller */ ++ if (y->native_backend->clip_call_line_reporting_enabled && call->number) { ++ /* International numbers start with '+' */ ++ if (strncmp("+", call->number, 1) == 0) ++ type = CLIP_INTERNATIONAL_NUMBER; ++ else ++ type = CLIP_NATIONAL_NUMBER; ++ rfcomm_write_response(rfcomm_fd, "+CLIP: \"%s\",%d", call->number, type); ++ } ++ ++ if (y->native_backend->timer_ring_event) ++ pa_core_rttime_restart(y->native_backend->core, y->native_backend->timer_ring_event, pa_rtclock_now() + RING_WAIT_TIME); ++} ++ ++static pa_hook_result_t host_calls_changed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) { ++ int rfcomm_fd; ++ unsigned int call_indicator; ++ unsigned int call_setup_indicator; ++ pa_hashmap *calls; ++ call_status_t *call; ++ ++ pa_assert(y); ++ pa_assert(m); ++ pa_assert(b); ++ ++ /* Get RFCOMM channel if available */ ++ rfcomm_fd = get_rfcomm_fd (y); ++ if (rfcomm_fd < 0) ++ return PA_HOOK_OK; ++ ++ calls = pa_modemmanager_get_calls(m); ++ call = pa_hashmap_isempty(calls)? NULL: pa_hashmap_first(calls); ++ ++ /* Notify HF about call indicator state, if changed */ ++ if (call && call->status == PA_MODEMMANAGER_CALL_STATE_ACTIVE) ++ call_indicator = CIND_CALL_AT_LEAST_ONE; ++ else ++ call_indicator = CIND_CALL_NONE; ++ ++ if (b->cind_call_indicator != call_indicator) { ++ b->cind_call_indicator = call_indicator; ++ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_CALL_INDICATOR, b->cind_call_indicator); ++ } ++ ++ /* Notify HF about call setup indicator state, if changed */ ++ if (call && call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_RINGING) ++ call_setup_indicator = CIND_CALL_SETUP_INCOMING; ++ else if (call && !call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_DIALING) ++ call_setup_indicator = CIND_CALL_SETUP_OUTGOING_DIALING; ++ else if (call && !call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_RINGING) ++ call_setup_indicator = CIND_CALL_SETUP_OUTGOING_ALERTING; ++ else ++ call_setup_indicator = CIND_CALL_SETUP_NONE; ++ ++ if (b->cind_call_setup_indicator != call_setup_indicator) { ++ b->cind_call_setup_indicator = call_setup_indicator; ++ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_CALL_SETUP_INDICATOR, b->cind_call_setup_indicator); ++ } ++ ++ /* Start/stop ringing */ ++ if (call && call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_RINGING) { ++ if (!b->timer_ring_event) { ++ pa_log_debug("RING indicator started"); ++ b->timer_ring_event = pa_core_rttime_new(b->core, pa_rtclock_now(), timer_ring_cb, y); ++ } ++ } else if (b->timer_ring_event) { ++ pa_log_debug("RING indicator stopped"); ++ b->core->mainloop->time_free(b->timer_ring_event); ++ b->timer_ring_event = NULL; ++ } ++ ++ return PA_HOOK_OK; ++} ++ + static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_bluetooth_transport *t = userdata; + pa_bluetooth_discovery *discovery = t->device->discovery; +@@ -1658,6 +1870,30 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d + pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), PA_HOOK_NORMAL, + (pa_hook_cb_t) host_battery_level_changed_cb, backend); + ++ backend->host_operation_failed_slot = ++ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), PA_HOOK_NORMAL, ++ (pa_hook_cb_t) host_operation_failed_cb, backend); ++ ++ backend->host_operation_succeed_slot = ++ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), PA_HOOK_NORMAL, ++ (pa_hook_cb_t) host_operation_succeed_cb, backend); ++ ++ backend->host_signal_strength_changed_slot = ++ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_SIGNAL_STRENGTH_CHANGED), PA_HOOK_NORMAL, ++ (pa_hook_cb_t) host_signal_strength_changed_cb, backend); ++ ++ backend->host_has_service_changed_slot = ++ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_HAS_SERVICE_CHANGED), PA_HOOK_NORMAL, ++ (pa_hook_cb_t) host_has_service_changed_cb, backend); ++ ++ backend->host_is_roaming_changed_slot = ++ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_IS_ROAMING_CHANGED), PA_HOOK_NORMAL, ++ (pa_hook_cb_t) host_is_roaming_changed_cb, backend); ++ ++ backend->host_calls_changed_slot = ++ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), PA_HOOK_NORMAL, ++ (pa_hook_cb_t) host_calls_changed_cb, backend); ++ + if (!backend->enable_hsp_hs && !backend->enable_hfp_hf) + pa_log_warn("Both HSP HS and HFP HF bluetooth profiles disabled in native backend. Native backend will not register for headset connections."); + +@@ -1684,6 +1920,10 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d + /* CLIP is disabled by default */ + backend->clip_call_line_reporting_enabled = false; + ++ /* TODO: Initiate CIND call & callsetup indicators at startup */ ++ backend->cind_call_indicator = CIND_CALL_NONE; ++ backend->cind_call_setup_indicator = CIND_CALL_SETUP_NONE; ++ + return backend; + } + +@@ -1698,6 +1938,24 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) { + if (backend->host_battery_level_changed_slot) + pa_hook_slot_free(backend->host_battery_level_changed_slot); + ++ if (backend->host_operation_failed_slot) ++ pa_hook_slot_free(backend->host_operation_failed_slot); ++ ++ if (backend->host_operation_succeed_slot) ++ pa_hook_slot_free(backend->host_operation_succeed_slot); ++ ++ if (backend->host_signal_strength_changed_slot) ++ pa_hook_slot_free(backend->host_signal_strength_changed_slot); ++ ++ if (backend->host_has_service_changed_slot) ++ pa_hook_slot_free(backend->host_has_service_changed_slot); ++ ++ if (backend->host_is_roaming_changed_slot) ++ pa_hook_slot_free(backend->host_is_roaming_changed_slot); ++ ++ if (backend->host_calls_changed_slot) ++ pa_hook_slot_free(backend->host_calls_changed_slot); ++ + if (backend->enable_shared_profiles) + native_backend_apply_profile_registration_change(backend, false); + +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index fb88f00e2..35b3153b8 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -242,6 +242,18 @@ typedef enum pa_bluetooth_clcc { + CLCC_INCOMING = 4 + } pa_bluetooth_clcc_t; + ++typedef enum pa_bluetooth_cind_call { ++ CIND_CALL_NONE = 0, ++ CIND_CALL_AT_LEAST_ONE = 1, ++} pa_bluetooth_cind_call_t; ++ ++typedef enum pa_bluetooth_cind_call_setup { ++ CIND_CALL_SETUP_NONE = 0, ++ CIND_CALL_SETUP_INCOMING = 1, ++ CIND_CALL_SETUP_OUTGOING_DIALING = 2, ++ CIND_CALL_SETUP_OUTGOING_ALERTING = 3 ++} pa_bluetooth_cind_call_setup_t; ++ + #ifdef HAVE_BLUEZ_5_OFONO_HEADSET + pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_discovery *y); + void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b); +-- +2.35.1 + diff --git a/temp/pulseaudio/0022-bluetooth-support-AT-VTS.patch b/temp/pulseaudio/0022-bluetooth-support-AT-VTS.patch new file mode 100644 index 000000000..8eed861db --- /dev/null +++ b/temp/pulseaudio/0022-bluetooth-support-AT-VTS.patch @@ -0,0 +1,71 @@ +From 8d2504c42a1910b4b6bf80a0474f4b5502c4f490 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Sat, 16 Apr 2022 16:37:10 +0200 +Subject: [PATCH 22/26] bluetooth: support AT+VTS + +Send DTMF tones to the active call when the HF sends AT+VTS=$character. +Return an error if more than one character is given or ModemManager is +unavailable. +--- + src/modules/bluetooth/backend-native.c | 32 ++++++++++++++++++++++++++ + src/modules/bluetooth/bluez5-util.h | 1 + + 2 files changed, 33 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index e12b38d71..306de44a7 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -841,6 +841,38 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + + rfcomm_write_response(fd, "+CLCC: %d,%d,%d,%d,%d,\"%s\",%d", 1, call->is_incoming, clcc_status, 0, 0, call->number, type); + return true; ++ } else if (strstr(buf, "AT+VTS=")) { ++ char *call_key = NULL; ++ char dtmf_char[2]; /* character and '\0' */ ++ ++ /* AT+VTS is only possible if ModemManager is availble */ ++ if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ pa_log_debug("ModemManager backend unavailable, cannot send DTMF tone with AT+VTS"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; ++ } else if (!pa_modemmanager_has_service(discovery->native_backend->modemmanager)) { ++ pa_log_debug("No network service, cannot send DTMF tone with AT+VTS"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE); ++ return false; ++ } ++ ++ /* Parse DTMF character, HFP spec allows 1 character. Return an error if parsing failed. */ ++ val = sscanf(buf, "AT+VTS=%1s", dtmf_char); ++ if (val != 1) { ++ pa_log_warn("Cannot parse DTMF character from \"%s\"", buf); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_INVALID_CHARACTERS_TEXT_STRING); ++ return false; ++ } ++ call_key = pa_modemmanager_get_active_call_key(discovery->native_backend->modemmanager); ++ if (call_key) ++ pa_modemmanager_send_dtmf(discovery->native_backend->modemmanager, call_key, dtmf_char); ++ else { ++ pa_log_warn("No active call, cannot send DTMF tone with AT+VTS"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED); ++ } ++ ++ /* AT response will be reported through PA_BLUETOOTH_HOST_OPERATION_{SUCCEED, FAILED} hook */ ++ return false; + } + + /* +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index 35b3153b8..4802cca69 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -217,6 +217,7 @@ typedef enum pa_bluetooth_cmee { + CMEE_NO_CONNECTION_TO_PHONE = 1, + CMEE_OPERATION_NOT_ALLOWED = 3, + CMEE_OPERATION_NOT_SUPPORTED = 4, ++ CMEE_INVALID_CHARACTERS_TEXT_STRING = 25, + CMEE_NO_NETWORK_SERVICE = 30 + } pa_bluetooth_cmee_t; + +-- +2.35.1 + diff --git a/temp/pulseaudio/0023-bluetooth-support-ATA.patch b/temp/pulseaudio/0023-bluetooth-support-ATA.patch new file mode 100644 index 000000000..f5c25813d --- /dev/null +++ b/temp/pulseaudio/0023-bluetooth-support-ATA.patch @@ -0,0 +1,57 @@ +From 48286245124ed08e57624b1e069cfa2ce4776568 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Sat, 16 Apr 2022 19:37:04 +0200 +Subject: [PATCH 23/26] bluetooth: support ATA + +Accept incoming calls if the HF sends 'ATA'. +The first call in the call list is accepted, +multiparty calling is not supported (yet). +--- + src/modules/bluetooth/backend-native.c | 31 ++++++++++++++++++++++++++ + 1 file changed, 31 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 306de44a7..6f6241479 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -873,6 +873,37 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + + /* AT response will be reported through PA_BLUETOOTH_HOST_OPERATION_{SUCCEED, FAILED} hook */ + return false; ++ } else if (strstr(buf, "ATA")) { ++ pa_hashmap *calls; ++ char *call_key = NULL; ++ call_status_t *call = NULL; ++ ++ /* ATA is only possible if ModemManager is availble */ ++ if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ pa_log_debug("ModemManager backend unavailable, cannot reject call with ATA"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; ++ } else if (!pa_modemmanager_has_service(discovery->native_backend->modemmanager)) { ++ pa_log_debug("No network service, cannot accept call with ATA"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE); ++ return false; ++ } ++ ++ /* Accept call is available */ ++ calls = pa_modemmanager_get_calls(discovery->native_backend->modemmanager); ++ call_key = pa_modemmanager_get_active_call_key(discovery->native_backend->modemmanager); ++ if (call_key) ++ call = pa_hashmap_get(calls, call_key); ++ ++ if (call && call->status == PA_MODEMMANAGER_CALL_STATE_RINGING && call->is_incoming) ++ pa_modemmanager_accept_call(discovery->native_backend->modemmanager, call_key); ++ else { ++ pa_log_warn("Cannot accept call, call must be incoming and ringing"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED); ++ } ++ ++ /* AT response will be reported through PA_BLUETOOTH_HOST_OPERATION_{SUCCEED, FAILED} hook */ ++ return false; + } + + /* +-- +2.35.1 + diff --git a/temp/pulseaudio/0024-bluetooth-support-AT-CHUP.patch b/temp/pulseaudio/0024-bluetooth-support-AT-CHUP.patch new file mode 100644 index 000000000..e37886306 --- /dev/null +++ b/temp/pulseaudio/0024-bluetooth-support-AT-CHUP.patch @@ -0,0 +1,72 @@ +From 12610afa56b74e7db44086d81709faedda2b83e9 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Sat, 16 Apr 2022 19:53:38 +0200 +Subject: [PATCH 24/26] bluetooth: support AT+CHUP + +Hangup or reject a call when the HF sends 'AT+CHUP'. +If the call is on going, it is ended. If the call is ringing and +incoming, it is rejected. The same interface in ModemManager is used for +this functionality. This AT command is only available if ModemManager is +present. +--- + src/modules/bluetooth/backend-native.c | 37 ++++++++++++++++++++++++-- + 1 file changed, 35 insertions(+), 2 deletions(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index 6f6241479..d502cff78 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -144,7 +144,7 @@ typedef enum pa_bluetooth_ag_to_hf_indicators { + /* gateway features we support, which is as little as we can get away with */ + static uint32_t hfp_features = + /* HFP 1.6 requires this */ +- (1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS) | (1 << HFP_AG_INDICATORS) | (1 << HFP_AG_EERR); ++ (1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS) | (1 << HFP_AG_INDICATORS) | (1 << HFP_AG_EERR) | (1 << HFP_AG_REJECT); + + #define HSP_AG_PROFILE "/Profile/HSPAGProfile" + #define HFP_AG_PROFILE "/Profile/HFPAGProfile" +@@ -904,7 +904,40 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + + /* AT response will be reported through PA_BLUETOOTH_HOST_OPERATION_{SUCCEED, FAILED} hook */ + return false; +- } ++ } else if (strstr(buf, "AT+CHUP")) { ++ pa_hashmap *calls; ++ char *call_key = NULL; ++ call_status_t *call = NULL; ++ ++ /* AT+CHUP is only possible if ModemManager is availble */ ++ if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ pa_log_debug("ModemManager backend unavailable, cannot reject call with AT+CHUP"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; ++ } else if (!pa_modemmanager_has_service(discovery->native_backend->modemmanager)) { ++ pa_log_debug("No network service, cannot reject call with ATD+CHUP"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE); ++ return false; ++ } ++ ++ /* Hangup/reject call is available */ ++ calls = pa_modemmanager_get_calls(discovery->native_backend->modemmanager); ++ call_key = pa_modemmanager_get_active_call_key(discovery->native_backend->modemmanager); ++ if (call_key) ++ call = pa_hashmap_get(calls, call_key); ++ ++ if (call && (call->status == PA_MODEMMANAGER_CALL_STATE_ACTIVE ++ || call->status == PA_MODEMMANAGER_CALL_STATE_RINGING ++ || call->status == PA_MODEMMANAGER_CALL_STATE_DIALING)) ++ pa_modemmanager_end_call(discovery->native_backend->modemmanager, call_key); ++ else { ++ pa_log_warn("Cannot end call, call must be active, ringing or dialing"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED); ++ } ++ ++ /* AT response will be reported through PA_BLUETOOTH_HOST_OPERATION_{SUCCEED, FAILED} hook */ ++ return false; ++ } + + /* + * Out-of-spec Bluetooth HFP 1.8 AT commands. +-- +2.35.1 + diff --git a/temp/pulseaudio/0025-bluetooth-support-ATD-number.patch b/temp/pulseaudio/0025-bluetooth-support-ATD-number.patch new file mode 100644 index 000000000..eb92ce75c --- /dev/null +++ b/temp/pulseaudio/0025-bluetooth-support-ATD-number.patch @@ -0,0 +1,104 @@ +From d2748e1952c282ce82e93daaeb2f25bb10ce46ab Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Sat, 16 Apr 2022 20:29:54 +0200 +Subject: [PATCH 25/26] bluetooth: support ATD$number; + +Start a call when HF sends 'ATD$number;' +This AT command is only available when ModemManager is present. +Number is filtered for any invalid characters, if an invalid character +is found, the call is aborted and an error is returned. +--- + src/modules/bluetooth/backend-native.c | 57 ++++++++++++++++++++++++++ + src/modules/bluetooth/bluez5-util.h | 1 + + 2 files changed, 58 insertions(+) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index d502cff78..b6991ae13 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -44,6 +44,9 @@ + #include "upower.h" + #include "modemmanager.h" + ++#define MAX_NUMBER_LENGTH 30 ++#define STR_VALUE(x) STR(x) ++#define STR(x) #x + #define RING_WAIT_TIME ((pa_usec_t) (3 * PA_USEC_PER_SEC)) + #define MANDATORY_CALL_INDICATORS \ + "(\"call\",(0-1))," \ +@@ -935,6 +938,60 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf + rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED); + } + ++ /* AT response will be reported through PA_BLUETOOTH_HOST_OPERATION_{SUCCEED, FAILED} hook */ ++ return false; ++ } else if (strstr(buf, "ATD")) { ++ int k, j; ++ char number[MAX_NUMBER_LENGTH]; ++ char number_filtered[MAX_NUMBER_LENGTH]; ++ ++ /* ATD$number; is only possible if ModemManager is availble */ ++ if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) { ++ pa_log_debug("ModemManager backend unavailable, cannot create call with ATD$number;"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE); ++ return false; ++ } else if (!pa_modemmanager_has_service(discovery->native_backend->modemmanager)) { ++ pa_log_debug("No network service, cannot create call with ATD$number;"); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE); ++ return false; ++ } ++ ++ /* Try to parse number from ATD command */ ++ if (sscanf(buf, "ATD%" STR_VALUE(MAX_NUMBER_LENGTH) "s;", number) != 1) { ++ pa_log_debug("Cannot parse ATD$number: \"%s\"", buf); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE); ++ return false; ++ } ++ ++ /* ++ * Filter extracted number from invalid characters ++ * Allowed characters: 0-9, *, #, +, A-C ++ */ ++ k=0; ++ memset(number_filtered, '\0', sizeof(char) * MAX_NUMBER_LENGTH); ++ for (j=0; j < MAX_NUMBER_LENGTH; j++) { ++ if ((number[j] >= '0' && number[j] <= '9') ++ || (number[j] == '*') ++ || (number[j] == '#') ++ || (number[j] == '+') ++ || (number[j] >= 'A' && number[j] <= 'C')) { ++ number_filtered[k] = number[j]; ++ k++; ++ } ++ /* ATD commands ends with ';'. */ ++ else if (number[j] == ';') ++ break; ++ /* Send error for invalid characters */ ++ else { ++ pa_log_warn("Call creation canceled, invalid character found in dial string: %c", number[j]); ++ rfcomm_write_error(discovery->native_backend, fd, CMEE_INVALID_CHARACTERS_DIAL_STRING); ++ return false; ++ } ++ } ++ ++ /* Create call for filtered number */ ++ pa_modemmanager_start_call(discovery->native_backend->modemmanager, number_filtered); ++ + /* AT response will be reported through PA_BLUETOOTH_HOST_OPERATION_{SUCCEED, FAILED} hook */ + return false; + } +diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h +index 4802cca69..ccbe6ba78 100644 +--- a/src/modules/bluetooth/bluez5-util.h ++++ b/src/modules/bluetooth/bluez5-util.h +@@ -218,6 +218,7 @@ typedef enum pa_bluetooth_cmee { + CMEE_OPERATION_NOT_ALLOWED = 3, + CMEE_OPERATION_NOT_SUPPORTED = 4, + CMEE_INVALID_CHARACTERS_TEXT_STRING = 25, ++ CMEE_INVALID_CHARACTERS_DIAL_STRING = 27, + CMEE_NO_NETWORK_SERVICE = 30 + } pa_bluetooth_cmee_t; + +-- +2.35.1 + diff --git a/temp/pulseaudio/0026-bluetooth-strip-additional-out-of-spec-r-n-chars.patch b/temp/pulseaudio/0026-bluetooth-strip-additional-out-of-spec-r-n-chars.patch new file mode 100644 index 000000000..1130009d0 --- /dev/null +++ b/temp/pulseaudio/0026-bluetooth-strip-additional-out-of-spec-r-n-chars.patch @@ -0,0 +1,60 @@ +From aff9bf69960c3e2ac1d7c35176e9f3b34389f656 Mon Sep 17 00:00:00 2001 +From: Dylan Van Assche +Date: Tue, 19 Apr 2022 20:15:09 +0200 +Subject: [PATCH 26/26] bluetooth: strip additional out-of-spec '\r\n' chars + +Some HF devices do not comply strictly with the HFP specification +and send additional '\r\n' sequences besides the expected ones. +This breaks AT command parsing in PulseAudio. + +To work around this problem, strip these characters before processing +the AT command and responses. +--- + src/modules/bluetooth/backend-native.c | 22 ++++++++++++++++++---- + 1 file changed, 18 insertions(+), 4 deletions(-) + +diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c +index b6991ae13..8aa83f025 100644 +--- a/src/modules/bluetooth/backend-native.c ++++ b/src/modules/bluetooth/backend-native.c +@@ -1556,19 +1556,33 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i + } + + if (events & PA_IO_EVENT_INPUT) { +- char buf[512]; ++ char buf[512], raw_buf[512]; + ssize_t len; + int gain, dummy; + bool do_reply = false; + int vendor, product, version, features; +- int num; ++ int num, i, j; + +- len = pa_read(fd, buf, 511, NULL); ++ len = pa_read(fd, raw_buf, 511, NULL); + if (len < 0) { + pa_log_error("RFCOMM read error: %s", pa_cstrerror(errno)); + goto fail; + } +- buf[len] = 0; ++ raw_buf[len] = 0; ++ ++ /* ++ * Filter '\r' and '\n' characters out since some HF devices ++ * send too much \r\n sequences in their responses ++ */ ++ memset(buf, '\0', sizeof(char) * 512); ++ j=0; ++ for (i=0; i<512; i++) { ++ if (raw_buf[i] == '\r' || raw_buf[i] == '\n') ++ continue; ++ buf[j] = raw_buf[i]; ++ j++; ++ } ++ buf[j] = 0; + pa_log_debug("RFCOMM << %s", buf); + + /* There are only four HSP AT commands: +-- +2.35.1 + diff --git a/temp/pulseaudio/APKBUILD b/temp/pulseaudio/APKBUILD new file mode 100644 index 000000000..c58dd7ac5 --- /dev/null +++ b/temp/pulseaudio/APKBUILD @@ -0,0 +1,288 @@ +# Forked from Alpine for enhanced Bluetooth HFP support + +pkgname=pulseaudio +pkgver=9999_git20220528 +_pkgver=16.0 +pkgrel=0 +pkgdesc="featureful, general-purpose sound server" +provider_priority=10 +url="https://www.freedesktop.org/wiki/Software/PulseAudio/" +arch="all" +license="LGPL-2.1-or-later" +options="!check" +makedepends=" + meson + tdb-dev + alsa-lib-dev + libasyncns-dev + dbus-dev + glib-dev + gtk+3.0-dev + orc-dev + orc-compiler + libsndfile-dev + soxr-dev + libx11-dev + libxcb-dev + libice-dev + libsm-dev + libxtst-dev + avahi-dev + sbc-dev + fftw-dev + jack-dev + openssl1.1-compat-dev + speexdsp-dev + eudev-dev + libcap-dev + bluez-dev + check-dev + libtool + perl + perl-xml-parser + m4 + gstreamer-dev + gst-plugins-base-dev + modemmanager-dev + " +depends_openrc="alsa-utils-openrc" +subpackages=" + $pkgname-dev + $pkgname-doc + $pkgname-bluez + libpulse-mainloop-glib:_libpulse_mainloop_glib + $pkgname-alsa + $pkgname-utils + $pkgname-jack + $pkgname-zeroconf + $pkgname-openrc + $pkgname-bash-completion + $pkgname-zsh-completion + $pkgname-lang + $pkgname-equalizer + libpulse:_libpulse + " +install="pulseaudio.post-install" +builddir="$srcdir/$pkgname-$_pkgver" +source="https://freedesktop.org/software/pulseaudio/releases/pulseaudio-$_pkgver.tar.xz + 0001-bluez5-util-move-pa_bluetooth_discovery-to-header.patch + 0002-bluetooth-add-AT-BIA-support.patch + 0003-bluetooth-add-UPower-backend.patch + 0004-bluetooth-hook-up-UPower-backend.patch + 0005-bluetooth-add-ModemManager-backend.patch + 0006-bluetooth-only-reply-OK-for-supported-AT-cmds.patch + 0007-bluetooth-Always-reply-to-AT-CIND.patch + 0008-bluetooth-support-AT-CMEE.patch + 0009-bluetooth-support-AT-NREC.patch + 0010-bluetooth-support-AT-BCC.patch + 0011-bluetooth-support-AT-COPS.patch + 0012-bluetooth-support-AT-CNUM.patch + 0013-bluetooth-support-more-AT-CIND-indicators.patch + 0014-bluetooth-support-AT-CLIP.patch + 0015-bluetooth-support-AT-CREG.patch + 0016-bluetooth-support-AT-CGMM.patch + 0017-bluetooth-support-AT-CGMI.patch + 0018-bluetooth-support-AT-CGMR.patch + 0019-bluetooth-support-AT-CGSN.patch + 0020-bluetooth-support-AT-CLCC.patch + 0021-bluetooth-support-CIEV-RING-and-CLIP-URCs.patch + 0022-bluetooth-support-AT-VTS.patch + 0023-bluetooth-support-ATA.patch + 0024-bluetooth-support-AT-CHUP.patch + 0025-bluetooth-support-ATD-number.patch + 0026-bluetooth-strip-additional-out-of-spec-r-n-chars.patch + link-libintl.patch + $pkgname.initd + $pkgname.confd + " + +case "$CARCH" in + aarch64|armv7|armhf|x86|ppc64le) + options="$options !check" # once-test fails, all others pass + ;; + s390x) + options="$options !check" # mix-test fails, all others pass + ;; +esac + +case "$CARCH" in + x86|x86_64|aarch64) + makedepends="$makedepends webrtc-audio-processing-dev" + _webrtc_aec="disabled" + ;; + * ) + _webrtc_aec="disabled" # webrtc-audio-processing not available + ;; +esac + +prepare() { + default_prepare + + sed "s|sysconfdir, 'dbus-1'|datadir, 'dbus-1'|" \ + -i src/daemon/meson.build +} + +build() { + abuild-meson \ + -Dgcov=false \ + -Dman=true \ + -Dtests=true \ + -Dsystem_user=pulse \ + -Dsystem_group=pulse \ + -Ddatabase=tdb \ + -Dalsa=enabled \ + -Dasyncns=enabled \ + -Davahi=enabled \ + -Dbluez5=enabled \ + -Ddbus=enabled \ + -Dfftw=enabled \ + -Dglib=enabled \ + -Dgsettings=enabled \ + -Dgtk=enabled \ + -Dhal-compat=false \ + -Dipv6=true \ + -Djack=enabled \ + -Dlirc=disabled \ + -Dopenssl=enabled \ + -Dorc=enabled \ + -Dsamplerate=disabled \ + -Dsoxr=enabled \ + -Dspeex=enabled \ + -Dsystemd=disabled \ + -Dudev=enabled \ + -Dx11=enabled \ + -Dudevrulesdir=/usr/lib/udev/rules.d \ + -Dgstreamer=enabled \ + -Dwebrtc-aec="$_webrtc_aec" \ + -Ddoxygen=false \ + -Dstream-restore-clear-old-devices=true \ + . output + meson compile ${JOBS:+-j ${JOBS}} -C output +} + +check() { + meson test --no-rebuild -v -C output +} + +package() { + DESTDIR="$pkgdir" meson install --no-rebuild -C output + + install -Dm755 "$srcdir"/$pkgname.initd "$pkgdir"/etc/init.d/$pkgname + install -Dm644 "$srcdir"/$pkgname.confd "$pkgdir"/etc/conf.d/$pkgname + + # Assumes that any volume adjustment is intended by the user, who can control + # each app's volume. Misbehaving clients can trigger earsplitting volume + # jumps. App volumes can diverge wildly and cause apps without their own + # volume control to fall below sink volume; a sink-only volume control will + # suddenly be unable to make such an app loud enough. + sed '/flat-volumes/iflat-volumes = no' -i "$pkgdir"/etc/pulse/daemon.conf + + # Disable cork-request module, can result in e.g. media players unpausing + # when there's a Skype call incoming + sed 's|/usr/bin/pactl load-module module-x11-cork-request|#&|' \ + -i "$pkgdir"/usr/bin/start-pulseaudio-x11 + + # Required by qpaeq + sed '/Load several protocols/aload-module module-dbus-protocol' \ + -i "$pkgdir"/etc/pulse/default.pa +} + +openrc() { + replaces="$pkgname-system" # Backward compatibility + default_openrc +} + +_libpulse() { + pkgdesc="Pulseaudio libraries" + replaces="$pkgname-libs" + + amove usr/lib/pulseaudio/libpulse* + amove usr/lib/libpulse.so.0* + amove usr/lib/libpulse-simple.so.0* + amove etc/pulse/client.conf +} + +_libpulse_mainloop_glib() { + pkgdesc="Pulseaudio mainloop-glib library" + + amove usr/lib/libpulse-mainloop-glib.so.* +} + +bluez() { + pkgdesc="Pulseaudio Bluetooth support" + install_if="$pkgname=$pkgver-r$pkgrel bluez" + provider_priority=10 # highest (other provider is pipewire-pulse) + + amove usr/lib/pulseaudio/modules/*bluez*.so + amove usr/lib/pulseaudio/modules/*bluetooth*.so +} + +alsa() { + pkgdesc="Pulseaudio ALSA support" + install_if="$pkgname=$pkgver-r$pkgrel alsa-lib" + provider_priority=10 # highest (other provider is pipewire-pulse) + + amove usr/lib/pulseaudio/modules/*alsa*.so +} + +utils() { + pkgdesc="Pulseaudio utilities" + + amove usr/bin/pa* +} + +jack() { + pkgdesc="Pulseaudio JACK support" + + amove usr/lib/pulseaudio/modules/*jack*.so +} + +zeroconf() { + pkgdesc="Pulseaudio Zeroconf support" + depends="avahi" + + amove usr/lib/pulseaudio/modules/*avahi*.so + amove usr/lib/pulseaudio/modules/*zeroconf*.so + amove usr/lib/pulseaudio/modules/*raop*.so +} + +equalizer() { + pkgdesc="Equalizer for $pkgname" + depends="pulseaudio=$pkgver-r$pkgrel py3-qt5 py3-dbus" + + amove usr/bin/qpaeq + amove usr/lib/pulseaudio/modules/module-equalizer-sink.so +} + +sha512sums=" +42d4968c2dc88f5e39a5358d124e399e40a5abdf815eff387087141bc9dddd217012acb35649a8e0e24a44e8a402d90eb193ce2eef186f7d59550f757a6cc26d pulseaudio-16.0.tar.xz +c0a5a4854be7f8c5421671b7c5b890d9f097f8d0a1ac20009549786c28a66c9f28ac58863eea12f467ced87cbdb6093ea1a8444b9f1c1abda2aaa7e64c6c640a 0001-bluez5-util-move-pa_bluetooth_discovery-to-header.patch +c84e4e0591c21348be197fae4b54bdd437a8e39803daa8448fcaf03119a39723214b81b8c32b5a6b166fc4449b78a44c7633bd09a77902903d15b17299a58234 0002-bluetooth-add-AT-BIA-support.patch +a2ceac55f6cc8097569fec5e7a76f9e6ce3c79dacb27f5f19baed3a808e9be30e3c9a22b84b74e3d774d16f2d45c93e64a1248fc56b6d3cd897f620d46f581f9 0003-bluetooth-add-UPower-backend.patch +ffe399905f400628c0bcb732c6000ced30a3835884dfd0910bebf4af81ca63e743bb144e845978fda02ec4c81295e87ccf596b55d68e4c59ff827e5b1102a72e 0004-bluetooth-hook-up-UPower-backend.patch +58f7fc83c3c15e9df3b36cc192113d29fc6020823a36b30b68702373248a93c6e60a174a3c71c693547d33de55a9d20e72cf65f3957cea323b04c222d5d701ff 0005-bluetooth-add-ModemManager-backend.patch +98c0b80638603899b1639fedcc5edcde721f22cd3e7abed425015c933e65769b295a80b0d6a444fc5144cd1c6851dce41710dd8361fce2bfe5022ed913ad82ba 0006-bluetooth-only-reply-OK-for-supported-AT-cmds.patch +535b242b76fc494138a16b326708eaa4d410fbc5a779bda1471e25c56440d7e9a4c80b0bccd3ba5e0026f40c09e3b7b84bbb409e51c020deff8c9b0ead74fb71 0007-bluetooth-Always-reply-to-AT-CIND.patch +cdd7d3d47932c11ff7bd2bd4589b26dd3d92180fd4c156e70f57dcf5b560e05cd37d548a6e5280e47d258804f9dcaa3dd498d9dcdbb2e5c4e87f9f81dc7de73a 0008-bluetooth-support-AT-CMEE.patch +7b6a99b1d76781c36007b746bf2a47f163efeca843f36d75d4485c688d8f3a7fcafc9c36c1aa2ff02759184e20d387a89beea36ca0da64e23bb29dfc71feb55c 0009-bluetooth-support-AT-NREC.patch +b2bda75da4a4a5a2511fd3cd9ac8da8b3c59276a6396106e2249dd31053b68797f3f923177e6cd06fd7ae3cb3e2110e8bf8facb269d80a0e637a6f38cd8bb68c 0010-bluetooth-support-AT-BCC.patch +53bdf256fb2db1d6997204ba9504b584423946e177f8e1a819de0fafea29092a1925a0f7cde253635aa1f83c67e6b91bf3608fe543bc2185cd2ddf4aa5942054 0011-bluetooth-support-AT-COPS.patch +f3063daac100ef5d1fba792bb900a434be8cd0f9093a9be1ab68d84b8f94f39efc16c59d749f293e9b7556632866688757dc3d6e922096102f4583acb3f3cf5a 0012-bluetooth-support-AT-CNUM.patch +19880702e533bb46a67f09e8848bc2741a275c20b847dea96a0dba0424639715b0cd3d25a9168916cdc4ae4462b67855a3bb3b1d8384c12292fbacc032c2f0fa 0013-bluetooth-support-more-AT-CIND-indicators.patch +cd3d67672ff77d7908b01b4a377901d5e008b31b88d0e2c86918263c10a250aaf807cbf54cc3b095b1bf38ee533b3c0f409c5e1e2ced1df045fc64f421e02f78 0014-bluetooth-support-AT-CLIP.patch +967983f1e589945da4c0139d3d7d7be2c796f4a9ab52b344acd19723170c220dd7e00feafd2ad0a0481940f27c01176ccd54418758acedbc1c686d3426df9812 0015-bluetooth-support-AT-CREG.patch +61c400797405120cb065dea285e79a0056d716ff5ade3c8ea5a7e9f205c6895c0885f5ddb8c1abeec08ce3cf195b79bf7dc4d20281c5f7d5ee668accddab8cb9 0016-bluetooth-support-AT-CGMM.patch +42a3739a44bdcc01df0f3fa6fac5efb88f4fcf69686b529f8390d58b4becfd9b948d3ddea80aa5b8d007d96e22b4a87dedb5cbff610f008c317b31c8ae700774 0017-bluetooth-support-AT-CGMI.patch +6a4cd357126ebe99647bd3f4780ca109f2e823e0653851663d1d000b98e156ac17293058c8b94263371e303233f1c8e56073b523c91588d75989acb45b660ac2 0018-bluetooth-support-AT-CGMR.patch +b83d00a32c8e28c566e74ab8edd35e107313f31f4ff2ead21d6f9446a79f6ec520e7a737ef79844d46fbcceaad74fdaeb14ad37a865284c259f768156514f858 0019-bluetooth-support-AT-CGSN.patch +c58265d7c8f201765a254b2b26ff38f89ac2392acfb57024592e01096a997394322e726425fd02c37e21f6ab72638206d891354b2cd950102e7c13d87e026d40 0020-bluetooth-support-AT-CLCC.patch +c93be49e11055af356a01cc3545f984348dcff052f8777ef0127e2a9cab241fa83682550a5ffd69589d6603f1fb47ad3da248ff055b6c983e50ed817c63d1008 0021-bluetooth-support-CIEV-RING-and-CLIP-URCs.patch +d0663f8962f916d2149f44fe6d598ddea6df3cee542ee5ba9b418d05397c88a9c16368f9da2f96750e8bfb6a8e747e59fdc10f52fa59d1096ed6e59625c5dabd 0022-bluetooth-support-AT-VTS.patch +a39cbab500be77e88a7b8dd615ce7e1a2f8d678b19fe823552bb5d1279e2ae06aac3c353e03e10d601c53a5befe731ce00f8163c7b3a1e7b4b0c26a4fdde1730 0023-bluetooth-support-ATA.patch +9b93fd3dd2859b600d9eed9097f89b2a26220603eccdc61c6edb9b3b7488a97dbc5cee0dd4c40da3d1cbd8849023fd48f30ea1da0668c0bae189337bc8d1db9f 0024-bluetooth-support-AT-CHUP.patch +3eec77a1b39dbab93a4284de433946b70d6b9f972e62b02f076cb4a45b1f288e32ae05d8d557ed13ef21eded407951f9c0f48c78bdac9d47f318d2c7b288e10c 0025-bluetooth-support-ATD-number.patch +76b486095b1f2bb5d23837ed8f08d5ac7533e7c9621b2534c85b39a932663ffcf86b7fefaba1f2abc6aa7a95660c658cbcdeef20427b8ebb6e05cf532ee48731 0026-bluetooth-strip-additional-out-of-spec-r-n-chars.patch +2c31c5bc592e748248215f8f62f85687cfec230b05f65441e6dafa5fa77d4967e97636209b2011a4501ed1337ecd880b264baa175586fc85183a980846cb8146 link-libintl.patch +34fe54ece5df60ce63a7955cd828a2716670fef71f40960698ae5518fdaf9cd599f4d8f8852e2c88d715600a9ad06a38984415e5eb320071012e5eb6e5c1b8b1 pulseaudio.initd +75b54581591519d63a3362b155c0f9b0501a60763ab394693a456c44d0216138cf3a40bdd0f7442028663bc045e9ffee286f8f8eaf2ee3bb17379b43615fee0e pulseaudio.confd +" diff --git a/temp/pulseaudio/link-libintl.patch b/temp/pulseaudio/link-libintl.patch new file mode 100644 index 000000000..7dbbb3ecd --- /dev/null +++ b/temp/pulseaudio/link-libintl.patch @@ -0,0 +1,30 @@ +diff --git a/meson.build b/meson.build +index 658eeee..349752e 100644 +--- a/meson.build ++++ b/meson.build +@@ -307,6 +307,12 @@ else + libintl_dep = cc.find_library('intl') + endif + ++if cc.has_function('libintl_dgettext') ++ libintl_dep = [] ++else ++ libintl_dep = cc.find_library('intl') ++endif ++ + # Symbols + + if cc.has_header_symbol('signal.h', 'SIGXCPU') +diff --git a/src/pulse/meson.build b/src/pulse/meson.build +index aaebff5..3f68ac3 100644 +--- a/src/pulse/meson.build ++++ b/src/pulse/meson.build +@@ -84,7 +84,7 @@ libpulse = shared_library('pulse', + dependencies : [libm_dep, thread_dep, libpulsecommon_dep, dbus_dep, dl_dep, iconv_dep, libintl_dep], + implicit_include_directories : false) + +-libpulse_dep = declare_dependency(link_with: libpulse) ++libpulse_dep = declare_dependency(link_with: libpulse, dependencies: libintl_dep) + + install_headers( + libpulse_headers, 'simple.h', diff --git a/temp/pulseaudio/pulseaudio.confd b/temp/pulseaudio/pulseaudio.confd new file mode 100644 index 000000000..1d31d342a --- /dev/null +++ b/temp/pulseaudio/pulseaudio.confd @@ -0,0 +1,7 @@ +# Config file for /etc/init.d/pulseaudio +# $Header: /var/cvsroot/gentoo-x86/media-sound/pulseaudio/files/pulseaudio.conf.d,v 1.6 2006/07/29 15:34:18 flameeyes Exp $ + +# For more see "pulseaudio -h". + +# Startup options +PA_OPTS="--log-target=syslog --disallow-module-loading=1" diff --git a/temp/pulseaudio/pulseaudio.initd b/temp/pulseaudio/pulseaudio.initd new file mode 100644 index 000000000..6dc11bf82 --- /dev/null +++ b/temp/pulseaudio/pulseaudio.initd @@ -0,0 +1,81 @@ +#!/sbin/openrc-run +# Copyright 1999-2011 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: /var/cvsroot/gentoo-x86/media-sound/pulseaudio/files/pulseaudio.init.d-5,v 1.1 2011/03/27 16:58:49 ssuominen Exp $ + +depend() { + need localmount + use net + + local script="/etc/pulse/system.pa" + + for opt in ${PA_OPTS}; do + case "$opt" in + --file=*) script="${opt#*=}" ;; + -F*) script="${opt#-F}" ;; + esac + done + + config "$script" + + local needs="$(get_options need)" + if [ -n "${needs}" ]; then + need ${needs} + return + fi + + if egrep -q '^[[:space:]]*load-module[[:space:]]+module-console-kit' "$script"; then + needs="${needs} consolekit" + fi + +#ifdef UDEV + if egrep -q '^[[:space:]]*load-module[[:space:]]+module-udev-detect' "$script"; then + needs="${needs} udev" + fi +#endif + +#ifdef AVAHI + if egrep -q '^[[:space:]]*load-module[[:space:]]+module-zeroconf-publish' "$script"; then + needs="${needs} avahi-daemon" + fi +#endif + +#ifdef BLUETOOTH + if egrep -q '^[[:space:]]*load-module[[:space:]]+module-bt-proximity' "$script"; then + needs="${needs} bluetooth" + fi +#endif + +#ifdef ALSA + if egrep -q '^[[:space:]]*load-module[[:space:]]+module-alsa-(sink|source)' "$script" || + egrep -q '^[[:space:]]*load-module[[:space:]]+module-(udev-)?detect' "$script" || + egrep -q '^[[:space:]]*add-autoload-source[[:space:]]+(input|output)[[:space:]]+module-alsa-(sink|source)' "$script"; then + needs="${needs} alsa" # in Alpine install alsa-utils to provide /etc/init.d/alsa (not alsasound as in Gentoo). + fi +#endif + + need "${needs}" + save_options need "${needs}" +} + +start() { + if [ -z "${PULSEAUDIO_SHOULD_NOT_GO_SYSTEMWIDE}" ]; then + eerror "Please don't use system wide PulseAudio unless you read the" + eerror "documentation available at http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/SystemWide/" + eerror "" + eerror "When you're done, please set the variable PULSEAUDIO_SHOULD_NOT_GO_SYSTEMWIDE in" + eerror "/etc/conf.d/pulseaudio . Please remember that upstream does not support this mode" + eerror "when used for standard desktop configurations." + return 1 + fi + ebegin "Starting pulseaudio" + PA_ALL_OPTS="${PA_OPTS} --fail=1 --daemonize=1 --system" + start-stop-daemon --start --exec /usr/bin/pulseaudio -- ${PA_ALL_OPTS} + eend $? +} + +stop() { + ebegin "Stopping pulseaudio" + start-stop-daemon --stop --quiet --exec /usr/bin/pulseaudio --pidfile /var/run/pulse/pid + eend $? +} diff --git a/temp/pulseaudio/pulseaudio.post-install b/temp/pulseaudio/pulseaudio.post-install new file mode 100644 index 000000000..2464e7515 --- /dev/null +++ b/temp/pulseaudio/pulseaudio.post-install @@ -0,0 +1,11 @@ +#!/bin/sh +# Alpine Linux post-install script for socklog +# Copyright 2019 Leo (thinkabit.ukim@gmail.com) +# Distributed under the terms of the GNU General Public License, v2 or later + +addgroup -S pulse-access 2>/dev/null +addgroup -S pulse 2>/dev/null + +adduser -S -D -H -h /var/run/pulse -g "PulseAudio daemon" -G pulse pulse 2>/dev/null +adduser pulse audio 2>/dev/null +