diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 24794cf82..57d048a07 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -212,14 +212,25 @@ static void transport_destroy(void *data) rfcomm->transport = NULL; } +static void transport_state_changed (void *data, enum spa_bt_transport_state old, + enum spa_bt_transport_state state) +{ + struct rfcomm *rfcomm = data; + if (rfcomm->telephony_ag) { + rfcomm->telephony_ag->transport.state = state; + telephony_ag_transport_notify_updated_props(rfcomm->telephony_ag); + } +} + static const struct spa_bt_transport_events transport_events = { SPA_VERSION_BT_TRANSPORT_EVENTS, .destroy = transport_destroy, + .state_changed = transport_state_changed, }; static const struct spa_bt_transport_implementation sco_transport_impl; -static int rfcomm_new_transport(struct rfcomm *rfcomm) +static int rfcomm_new_transport(struct rfcomm *rfcomm, int codec) { struct impl *backend = rfcomm->backend; struct spa_bt_transport *t = NULL; @@ -248,7 +259,7 @@ static int rfcomm_new_transport(struct rfcomm *rfcomm) t->backend = &backend->this; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; - t->codec = HFP_AUDIO_CODEC_CVSD; + t->codec = codec; td = t->user_data; td->rfcomm = rfcomm; @@ -271,6 +282,12 @@ static int rfcomm_new_transport(struct rfcomm *rfcomm) spa_bt_transport_add_listener(t, &rfcomm->transport_listener, &transport_events, rfcomm); + if (rfcomm->telephony_ag) { + rfcomm->telephony_ag->transport.codec = codec; + rfcomm->telephony_ag->transport.state = SPA_BT_TRANSPORT_STATE_IDLE; + telephony_ag_transport_notify_updated_props(rfcomm->telephony_ag); + } + rfcomm->transport = t; return 0; @@ -939,10 +956,9 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) rfcomm_send_reply(rfcomm, "+BCS: 2"); codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC); } else { - if (rfcomm_new_transport(rfcomm) < 0) { + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { // TODO: We should manage the missing transport } else { - rfcomm->transport->codec = HFP_AUDIO_CODEC_CVSD; spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); rfcomm_emit_volume_changed(rfcomm, -1, SPA_BT_VOLUME_INVALID); } @@ -981,14 +997,13 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) spa_log_debug(backend->log, "RFCOMM selected_codec = %i", selected_codec); /* Recreate transport, since previous connection may now be invalid */ - if (rfcomm_new_transport(rfcomm) < 0) { + if (rfcomm_new_transport(rfcomm, selected_codec) < 0) { // TODO: We should manage the missing transport rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); if (was_switching_codec) spa_bt_device_emit_codec_switched(rfcomm->device, -ENOMEM); return true; } - rfcomm->transport->codec = selected_codec; spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); rfcomm_emit_volume_changed(rfcomm, -1, SPA_BT_VOLUME_INVALID); @@ -1745,10 +1760,9 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) rfcomm->hf_state = hfp_hf_bcs; if (!rfcomm->transport || (rfcomm->transport->codec != selected_codec) ) { - if (rfcomm_new_transport(rfcomm) < 0) { + if (rfcomm_new_transport(rfcomm, selected_codec) < 0) { // TODO: We should manage the missing transport } else { - rfcomm->transport->codec = selected_codec; spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); } } @@ -2116,10 +2130,9 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) rfcomm->slc_configured = true; if (!rfcomm->codec_negotiation_supported) { - if (rfcomm_new_transport(rfcomm) < 0) { + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { // TODO: We should manage the missing transport } else { - rfcomm->transport->codec = HFP_AUDIO_CODEC_CVSD; spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); } } @@ -2127,6 +2140,10 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) rfcomm->telephony_ag = telephony_ag_new(backend->telephony, sizeof(struct spa_hook)); telephony_ag_add_listener(rfcomm->telephony_ag, telephony_ag_get_user_data(rfcomm->telephony_ag), &telephony_ag_events, rfcomm); + if (rfcomm->transport) { + rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec; + rfcomm->telephony_ag->transport.state = rfcomm->transport->state; + } telephony_ag_register(rfcomm->telephony_ag); /* Report volume on SLC establishment */ @@ -2909,8 +2926,7 @@ static void codec_switch_timer_event(struct spa_source *source) /* Failure, try falling back to CVSD. */ rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE; if (rfcomm->transport == NULL) { - if (rfcomm_new_transport(rfcomm) == 0) { - rfcomm->transport->codec = HFP_AUDIO_CODEC_CVSD; + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) == 0) { spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); } } @@ -3085,10 +3101,9 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag spa_list_append(&backend->rfcomm_list, &rfcomm->link); if (profile == SPA_BT_PROFILE_HSP_HS || profile == SPA_BT_PROFILE_HSP_AG) { - if (rfcomm_new_transport(rfcomm) < 0) + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) goto fail_need_memory; - rfcomm->transport->codec = HFP_AUDIO_CODEC_CVSD; rfcomm->has_volume = rfcomm_volume_enabled(rfcomm); if (profile == SPA_BT_PROFILE_HSP_AG) { diff --git a/spa/plugins/bluez5/telephony.c b/spa/plugins/bluez5/telephony.c index 348a70c0a..2e88e866a 100644 --- a/spa/plugins/bluez5/telephony.c +++ b/spa/plugins/bluez5/telephony.c @@ -18,6 +18,7 @@ #define PW_TELEPHONY_OBJECT_PATH "/org/freedesktop/PipeWire/Telephony" #define PW_TELEPHONY_AG_IFACE "org.freedesktop.PipeWire.Telephony.AudioGateway1" +#define PW_TELEPHONY_AG_TRANSPORT_IFACE "org.freedesktop.PipeWire.Telephony.AudioGatewayTransport1" #define PW_TELEPHONY_CALL_IFACE "org.freedesktop.PipeWire.Telephony.Call1" #define OFONO_MANAGER_IFACE "org.ofono.Manager" @@ -114,6 +115,10 @@ " " \ PW_TELEPHONY_AG_COMMON_INTROSPECT_XML \ " " \ + " " \ + " " \ + " " \ + " " \ " " \ PW_TELEPHONY_AG_COMMON_INTROSPECT_XML \ " " \ @@ -128,6 +133,7 @@ " " \ " " \ DBUS_OBJECT_MANAGER_IFACE_INTROSPECT_XML \ + DBUS_PROPERTIES_IFACE_INTROSPECT_XML \ DBUS_INTROSPECTABLE_IFACE_INTROSPECT_XML \ "" @@ -190,6 +196,10 @@ struct agimpl { bool dial_in_progress; struct callimpl *dial_return; + + struct { + struct spa_bt_telephony_ag_transport transport; + } prev; }; struct callimpl { @@ -222,6 +232,7 @@ struct callimpl { #define call_emit_answer(s,e) call_emit(s,answer,0,e) #define call_emit_hangup(s,e) call_emit(s,hangup,0,e) +static void dbus_iter_append_ag_interfaces(DBusMessageIter *i, struct spa_bt_telephony_ag *ag); static void dbus_iter_append_call_properties(DBusMessageIter *i, struct spa_bt_telephony_call *call, bool all); #define PW_TELEPHONY_ERROR_FAILED "org.freedesktop.PipeWire.Telephony.Error.Failed" @@ -292,8 +303,7 @@ static DBusMessage *manager_get_managed_objects(struct impl *impl, DBusMessage * { struct agimpl *agimpl; spa_autoptr(DBusMessage) r = NULL; - DBusMessageIter iter, array1, entry1, array2, entry2, props_dict; - const char *interface = PW_TELEPHONY_AG_IFACE; + DBusMessageIter iter, array1, entry1, props_dict; if ((r = dbus_message_new_method_return(m)) == NULL) return NULL; @@ -305,18 +315,12 @@ static DBusMessage *manager_get_managed_objects(struct impl *impl, DBusMessage * spa_list_for_each (agimpl, &impl->ag_list, link) { if (agimpl->path) { dbus_message_iter_open_container(&array1, DBUS_TYPE_DICT_ENTRY, NULL, &entry1); - dbus_message_iter_append_basic(&entry1, DBUS_TYPE_OBJECT_PATH, &agimpl->path); if (ofono_compat) { + dbus_message_iter_append_basic(&entry1, DBUS_TYPE_OBJECT_PATH, &agimpl->path); dbus_message_iter_open_container(&entry1, DBUS_TYPE_ARRAY, "{sv}", &props_dict); dbus_message_iter_close_container(&entry1, &props_dict); } else { - dbus_message_iter_open_container(&entry1, DBUS_TYPE_ARRAY, "{sa{sv}}", &array2); - dbus_message_iter_open_container(&array2, DBUS_TYPE_DICT_ENTRY, NULL, &entry2); - dbus_message_iter_append_basic(&entry2, DBUS_TYPE_STRING, &interface); - dbus_message_iter_open_container(&entry2, DBUS_TYPE_ARRAY, "{sv}", &props_dict); - dbus_message_iter_close_container(&entry2, &props_dict); - dbus_message_iter_close_container(&array2, &entry2); - dbus_message_iter_close_container(&entry1, &array2); + dbus_iter_append_ag_interfaces(&entry1, &agimpl->this); } dbus_message_iter_close_container(&array1, &entry1); } @@ -466,6 +470,90 @@ void telephony_free(struct spa_bt_telephony *telephony) free(impl); } +static void telephony_ag_transport_commit_properties(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + agimpl->prev.transport = ag->transport; +} + +static const char * const * transport_state_to_string(int state) +{ + static const char * const state_str[] = { + "error", + "idle", + "pending", + "active", + }; + if (state < -1 || state > 2) + state = -1; + return &state_str[state + 1]; +} + +static bool +dbus_iter_append_ag_transport_properties(DBusMessageIter *i, struct spa_bt_telephony_ag *ag, bool all) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + DBusMessageIter dict, entry, variant; + bool changed = false; + + dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sv}", &dict); + + if (all || ag->transport.codec != agimpl->prev.transport.codec) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *name = "Codec"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &ag->transport.codec); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + changed = true; + } + + if (all || ag->transport.state != agimpl->prev.transport.state) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *name = "State"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + transport_state_to_string(ag->transport.state)); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + changed = true; + } + + dbus_message_iter_close_container(i, &dict); + return changed; +} + +static void +dbus_iter_append_ag_interfaces(DBusMessageIter *i, struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + DBusMessageIter entry, dict, props_dict; + + dbus_message_iter_append_basic(i, DBUS_TYPE_OBJECT_PATH, &agimpl->path); + dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict); + + const char *interface = PW_TELEPHONY_AG_IFACE; + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); + dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY, "{sv}", &props_dict); + dbus_message_iter_close_container(&entry, &props_dict); + dbus_message_iter_close_container(&dict, &entry); + + const char *interface2 = PW_TELEPHONY_AG_TRANSPORT_IFACE; + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface2); + dbus_iter_append_ag_transport_properties(&entry, ag, true); + dbus_message_iter_close_container(&dict, &entry); + + dbus_message_iter_close_container(i, &dict); +} + static DBusMessage *ag_introspect(struct agimpl *agimpl, DBusMessage *m) { const char *xml = PW_TELEPHONY_AG_INTROSPECT_XML; @@ -511,6 +599,80 @@ static DBusMessage *ag_get_managed_objects(struct agimpl *agimpl, DBusMessage *m return spa_steal_ptr(r); } +static DBusMessage *ag_properties_get(struct agimpl *agimpl, DBusMessage *m) +{ + const char *iface, *name; + DBusMessage *r; + DBusMessageIter i, v; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return NULL; + + if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface"); + + if (spa_streq(name, "Codec")) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_BYTE, + &agimpl->this.transport.codec); + dbus_message_iter_close_container(&i, &v); + return r; + } else if (spa_streq(name, "State")) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, + transport_state_to_string(agimpl->this.transport.state)); + dbus_message_iter_close_container(&i, &v); + return r; + } + + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_PROPERTY, + "No such property"); +} + +static DBusMessage *ag_properties_get_all(struct agimpl *agimpl, DBusMessage *m) +{ + DBusMessage *r; + DBusMessageIter i; + const char *iface; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_INVALID)) + return NULL; + + if (!spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface"); + + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + + dbus_message_iter_init_append(r, &i); + dbus_iter_append_ag_transport_properties(&i, &agimpl->this, true); + return r; +} + +static DBusMessage *ag_properties_set(struct agimpl *agimpl, DBusMessage *m) +{ + return dbus_message_new_error(m, DBUS_ERROR_PROPERTY_READ_ONLY, + "Property not writable"); +} + static bool validate_phone_number(const char *number) { const char *c; @@ -693,6 +855,12 @@ static DBusHandlerResult ag_handler(DBusConnection *c, DBusMessage *m, void *use r = ag_introspect(agimpl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects")) { r = ag_get_managed_objects(agimpl, m, false); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { + r = ag_properties_get(agimpl, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll")) { + r = ag_properties_get_all(agimpl, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) { + r = ag_properties_set(agimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "Dial") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "Dial")) { r = ag_dial(agimpl, m); @@ -799,20 +967,12 @@ int telephony_ag_register(struct spa_bt_telephony_ag *ag) /* notify on ObjectManager of the Manager object */ { spa_autoptr(DBusMessage) msg = NULL; - DBusMessageIter iter, entry, dict, props_dict; - const char *interface = PW_TELEPHONY_AG_IFACE; + DBusMessageIter iter; msg = dbus_message_new_signal(impl->path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded"); dbus_message_iter_init_append(msg, &iter); - dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); - dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict); - dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); - dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); - dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY, "{sv}", &props_dict); - dbus_message_iter_close_container(&entry, &props_dict); - dbus_message_iter_close_container(&dict, &entry); - dbus_message_iter_close_container(&iter, &dict); + dbus_iter_append_ag_interfaces(&iter, ag); if (!dbus_connection_send(impl->conn, msg, NULL)) { spa_log_error(impl->log, "failed to send InterfacesAdded for %s", path); @@ -859,6 +1019,7 @@ void telephony_ag_unregister(struct spa_bt_telephony_ag *ag) spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter, entry; const char *interface = PW_TELEPHONY_AG_IFACE; + const char *interface2 = PW_TELEPHONY_AG_TRANSPORT_IFACE; msg = dbus_message_new_signal(impl->path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved"); @@ -867,6 +1028,7 @@ void telephony_ag_unregister(struct spa_bt_telephony_ag *ag) dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface2); dbus_message_iter_close_container(&iter, &entry); if (!dbus_connection_send(impl->conn, msg, NULL)) { @@ -895,6 +1057,37 @@ void telephony_ag_unregister(struct spa_bt_telephony_ag *ag) agimpl->path = NULL; } +/* send message to notify about property changes */ +void telephony_ag_transport_notify_updated_props(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + + spa_autoptr(DBusMessage) msg = NULL; + const char *interface = PW_TELEPHONY_AG_TRANSPORT_IFACE; + DBusMessageIter i, a; + + msg = dbus_message_new_signal(agimpl->path, + DBUS_INTERFACE_PROPERTIES, + "PropertiesChanged"); + + dbus_message_iter_init_append(msg, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface); + + if (!dbus_iter_append_ag_transport_properties(&i, ag, false)) + return; + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &a); + dbus_message_iter_close_container(&i, &a); + + if (!dbus_connection_send(impl->conn, msg, NULL)){ + spa_log_warn(impl->log, "sending PropertiesChanged failed"); + } + + telephony_ag_transport_commit_properties(ag); +} + void telephony_ag_add_listener(struct spa_bt_telephony_ag *ag, struct spa_hook *listener, const struct spa_bt_telephony_ag_events *events, diff --git a/spa/plugins/bluez5/telephony.h b/spa/plugins/bluez5/telephony.h index dfbdc73d4..725fb663b 100644 --- a/spa/plugins/bluez5/telephony.h +++ b/spa/plugins/bluez5/telephony.h @@ -30,11 +30,19 @@ struct spa_bt_telephony { }; +struct spa_bt_telephony_ag_transport { + int8_t codec; + enum spa_bt_transport_state state; +}; + struct spa_bt_telephony_ag { struct spa_bt_telephony *telephony; struct spa_list call_list; int id; + + /* D-Bus properties */ + struct spa_bt_telephony_ag_transport transport; }; struct spa_bt_telephony_call { @@ -91,6 +99,8 @@ void telephony_ag_add_listener(struct spa_bt_telephony_ag *ag, const struct spa_bt_telephony_ag_events *events, void *data); +void telephony_ag_transport_notify_updated_props(struct spa_bt_telephony_ag *ag); + /* register/unregister AudioGateway object on the bus */ int telephony_ag_register(struct spa_bt_telephony_ag *ag); void telephony_ag_unregister(struct spa_bt_telephony_ag *ag);