/* * Copyright (C) 2018 "IoT.bzh" * Author : Thierry Bultel * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "hal-bluealsa.h" #include "4a-hal-utilities-data.h" #define HAL_BLUEALSA_PLUGIN_NAME "hal-bluealsa" #define A2DP_LISTEN_FORMAT "S16_LE" #define SCO_TALK_RATE 44100 #define SCO_TALK_FORMAT "S16_LE" #define SCO_TALK_ZONE "sco_talk_zone" #define SCO_CHANNEL_MONO "sco-mono" #define ORG_FREEDESKTOP_DBUS_NAME "org.freedesktop.DBus" #define ORG_FREEDESKTOP_DBUS_PATH "/org/freedesktop/DBus" #define ORG_FREEDESKTOP_DBUS_INTERFACE ORG_FREEDESKTOP_DBUS_NAME #define BLUEZALSA_DBUS_NAME_PREFIX "org.bluez-alsa" /* forward static declarations */ static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData); static int halBlueAlsaFetchTransports(bluealsa_watch * plugin); static int halBlueAlsaRegisterAll(CtlPluginT* plugin); static int halBlueAlsaRegister(CtlPluginT* plugin, const char * interface); CTLP_CAPI_REGISTER(HAL_BLUEALSA_PLUGIN_NAME) // Call at initialization time ('requires' are forbidden at this stage) CTLP_ONLOAD(plugin, callbacks) { AFB_API_NOTICE(plugin->api, "%s Plugin Registered correctly: uid='%s' 'info='%s'", HAL_BLUEALSA_PLUGIN_NAME, plugin->uid, plugin->info); return 0; } CTLP_INIT(plugin, callbacks) { json_object *actionsToAdd = NULL; CtlConfigT *ctrlConfig; struct SpecificHalData *currentHalData; if (!(ctrlConfig = (CtlConfigT *) afb_api_get_userdata(plugin->api))) { AFB_API_ERROR(plugin->api, "Can't get current hal controller config"); goto fail; } if (!(currentHalData = (struct SpecificHalData *) ctrlConfig->external)) { AFB_API_ERROR(plugin->api, "Can't get current hal controller data"); goto fail; } if (currentHalData->status != HAL_STATUS_AVAILABLE) { AFB_API_WARNING(plugin->api, "Controller initialization of %s plugin cannot be done because hal is not ready to be used", HAL_BLUEALSA_PLUGIN_NAME); goto done; } wrap_json_pack(&actionsToAdd, "{s:s s:s s:s}", "uid", "init-bluealsa-plugin", "info", "Init Bluez-Alsa hal plugin", "action", "plugin://hal-bluealsa#init"); int idx = 0; while (ctrlConfig->sections[idx].key && strcasecmp(ctrlConfig->sections[idx].key, "onload")) idx++; if (!ctrlConfig->sections[idx].key) { AFB_API_ERROR(plugin->api, "Wasn't able to add '%s' as a new onload, 'onload' section not found", json_object_get_string(actionsToAdd)); goto fail; } if(AddActionsToSection(plugin->api, &ctrlConfig->sections[idx], actionsToAdd, 0)) { AFB_API_ERROR(plugin->api, "Wasn't able to add '%s' as a new onload to %s", json_object_get_string(actionsToAdd), ctrlConfig->sections[idx].uid); goto fail; } AFB_API_NOTICE(plugin->api, "Plugin initialization of %s plugin correctly done", HAL_BLUEALSA_PLUGIN_NAME); done: return 0; fail: if (actionsToAdd) json_object_put(actionsToAdd); return -1; } // Call at controller onload time CTLP_CAPI(init, source, argsJ, queryJ) { AFB_API_NOTICE(source->api, "Controller onload event"); CtlPluginT * plugin = source->plugin; hal_bluealsa_plugin_data_t * pluginData = (hal_bluealsa_plugin_data_t*) calloc(1, sizeof(hal_bluealsa_plugin_data_t)); int error; /* * "params": { "sco": { "mic": "2CH-GENERIC-USB", "zone": "full-stereo", "delayms": 300 }, "a2dp": { "zone": "full-stereo", "delayms": 1000 } } */ json_object * scoParamsJ = NULL; json_object * a2dpParamsJ = NULL; error = wrap_json_unpack(plugin->paramsJ, "{s?o,s?o !}", "sco", &scoParamsJ, "a2dp", &a2dpParamsJ); if (error) { AFB_API_ERROR(plugin->api, "%s: wrong parameters", __func__); goto fail; } if (scoParamsJ) { AFB_API_INFO(plugin->api, "%s: sco parameters: %s", __func__, json_object_get_string(scoParamsJ)); error = wrap_json_unpack(scoParamsJ, "{s:s,s:s,s?i !}", "mic", &pluginData->sco.mic, "zone", &pluginData->sco.speaker, "delayms", &pluginData->sco.delayms); if (error) { AFB_API_ERROR(plugin->api, "%s: wrong sco parameters: err %s", __func__, wrap_json_get_error_string(error)); goto fail; } } if (a2dpParamsJ) { AFB_API_INFO(plugin->api, "%s: a2dp parameters: %s", __func__, json_object_get_string(a2dpParamsJ)); error = wrap_json_unpack(a2dpParamsJ, "{s:s,s?i !}", "zone", &pluginData->a2dp.zone, "delayms", &pluginData->a2dp.delayms); if (error) { AFB_API_ERROR(plugin->api, "%s: wrong a2dp parameters: err=%s", __func__, wrap_json_get_error_string(error)); goto fail; } } halBlueAlsaTransportsInit(&pluginData->transport_list); setPluginContext(plugin, pluginData); if (halBlueAlsaRegisterAll(plugin) != 0) goto fail; return 0; fail: return -1; } static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { bluealsa_watch * watch = (bluealsa_watch *)userData; CtlPluginT * plugin = watch->plugin; struct ba_msg_event event; ssize_t ret; AFB_API_DEBUG(plugin->api, "------ %s ------!", __func__); if ((revents & EPOLLIN) == 0) goto done; if (revents & EPOLLHUP) { AFB_API_INFO(plugin->api, "Lost connection with bluealsa on interface %s", watch->interface); sd_event_source_unref(src); goto done; } while ((ret = recv(watch->fd, &event, sizeof(event), MSG_DONTWAIT)) == -1 && errno == EINTR) continue; if (ret != sizeof(event)) { AFB_API_ERROR(plugin->api, "Couldn't read event: %s", strerror(ret == -1 ? errno : EBADMSG)); goto done; } halBlueAlsaFetchTransports(watch); done: return 0; } /* * * The following function builds that kind of json data and send it to smixer * * A2DP Case: * * { "uid":"a2dp_F6:32:15:2A:80:70", "captures": { "uid":"a2dp_listen_capture", "pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=a2dp", "source" : { "channels": [ { "uid": "a2dp-right", "port": 0 }, { "uid": "a2dp-left", "port": 1 } ] } }, "streams": { "uid" : "a2dp_listen_stream", "source":"a2dp_listen_capture", "zone": "full-stereo" } } * *----------------------------------------------------------------------------------- * * SCO Case: * * { "uid":"sco_F6:32:15:2A:80:70",", "captures": { "uid":"sco_listen_capture", "pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=sco", "source" : { "channels": [ { "uid": "sco-right", "port": 0 }, { "uid": "sco-left", "port": 1 } ] } }, "playbacks" : { "uid"="sco_talk_playback", "pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=sco", "params": { "rate": 44100, "format": "S16_LE", }, "sink": { "channels": [ { "uid": "sco-mono" } ] } }, "zones" : [ { "uid": "sco_talk_zone", "sink": [ { "target": "sco-mono", "channel": 0, "volume": 0.5 }, { "target": "sco-mono", "channel": 1, "volume": 0.5 } ] } ] , "streams": [ { "uid" : "sco_listen_stream", "source":"sco_listen_capture", "zone": "full-stereo", "delayms": 300 }, { "uid" : "sco_talk_stream", "source": "2CH-GENERIC-USB", "zone": "sco_talk_zone", "delayms": 300 } ] } * * */ static json_object* halBlueAlsaA2DPTransportChannels(const char * transportTypeS) { json_object* channelRightJ; json_object* channelLeftJ; json_object* channelsJ = json_object_new_array(); char * channelRightS; char * channelLeftS; channelRightJ = json_object_new_object(); if (asprintf(&channelRightS, "%s-right", transportTypeS) == -1) goto fail; json_object_object_add(channelRightJ, "uid", json_object_new_string(channelRightS)); json_object_object_add(channelRightJ, "port", json_object_new_int(0)); channelLeftJ = json_object_new_object(); if (asprintf(&channelLeftS, "%s-left", transportTypeS) == -1) goto fail; json_object_object_add(channelLeftJ, "uid", json_object_new_string(channelLeftS)); json_object_object_add(channelLeftJ, "port", json_object_new_int(1)); json_object_array_add(channelsJ, channelRightJ); json_object_array_add(channelsJ, channelLeftJ); fail: return channelsJ; } static json_object* halBlueAlsaSCOTransportChannels(const char * transportTypeS) { json_object* channelMonoJ; json_object* channelsJ = json_object_new_array(); char * channelMonoS; channelMonoJ = json_object_new_object(); if (asprintf(&channelMonoS, "%s-mono", transportTypeS) == -1) goto fail; json_object_object_add(channelMonoJ, "uid", json_object_new_string(channelMonoS)); json_object_object_add(channelMonoJ, "port", json_object_new_int(0)); json_object_array_add(channelsJ, channelMonoJ); fail: return channelsJ; } static json_object* halBlueAlsaPcmPlugParams(const char * interface, const char * addr, const char * transportTypeS) { char * pcmplug_paramsS = NULL; if (asprintf(&pcmplug_paramsS, "bluealsa:HCI=%s,DEV=%s,PROFILE=%s", interface, addr, transportTypeS) == -1) goto fail; return json_object_new_string(pcmplug_paramsS); fail: return NULL; } static json_object* halBlueAlsaListenCapture( const char * listenCaptureS, json_object * pcmplugParamsJ, json_object * captureParamsJ, json_object * sourceJ) { json_object * captureJ = json_object_new_object(); json_object_object_add(captureJ, "uid", json_object_new_string(listenCaptureS)); json_object_object_add(captureJ, "pcmplug_params", pcmplugParamsJ); json_object_object_add(captureJ, "params", captureParamsJ); json_object_object_add(captureJ, "source", sourceJ); return captureJ; } static json_object * halBlueAlsaTalkPlayback( const char * talkPlaybackS, json_object* pcmplugParamsJ, json_object* paramsJ, json_object* sinkJ ) { json_object * playbackJ = json_object_new_object(); json_object_object_add(playbackJ, "uid", json_object_new_string(talkPlaybackS)); json_object_object_add(playbackJ, "pcmplug_params", pcmplugParamsJ); json_object_object_add(playbackJ, "params", paramsJ); json_object_object_add(playbackJ, "sink", sinkJ); return playbackJ; } static json_object * halBlueAlsaScoTalkParamsJ(uint32_t sampling) { json_object * paramsJ = json_object_new_object(); json_object_object_add(paramsJ, "rate", json_object_new_int(sampling)); json_object_object_add(paramsJ, "format", json_object_new_string(SCO_TALK_FORMAT)); json_object_object_add(paramsJ, "channels", json_object_new_int(1)); return paramsJ; } static json_object * halBlueAlsaListenParamsJ(uint32_t sampling) { json_object * paramsJ = json_object_new_object(); json_object_object_add(paramsJ, "rate", json_object_new_int(sampling)); json_object_object_add(paramsJ, "format", json_object_new_string(A2DP_LISTEN_FORMAT)); return paramsJ; } static json_object * halBlueAlsaScoZone() { json_object * zoneJ = json_object_new_object(); json_object * sinkJ = json_object_new_array(); json_object_object_add(zoneJ, "uid", json_object_new_string(SCO_TALK_ZONE)); json_object * channel1J = json_object_new_object(); json_object_object_add(channel1J, "target" , json_object_new_string(SCO_CHANNEL_MONO)); json_object_object_add(channel1J, "channel", json_object_new_int(0)); json_object_object_add(channel1J, "volume", json_object_new_double(0.5)); json_object * channel2J = json_object_new_object(); json_object_object_add(channel2J, "target" , json_object_new_string(SCO_CHANNEL_MONO)); json_object_object_add(channel2J, "channel", json_object_new_int(1)); json_object_object_add(channel2J, "volume", json_object_new_double(0.5)); json_object_array_add(sinkJ, channel1J); json_object_array_add(sinkJ, channel2J); json_object_object_add(zoneJ, "sink", sinkJ); return zoneJ; } static int halBlueAlsaAttachTransportStreams(bluealsa_transport_t * transport) { int ret = -1; const char* transportTypeS; struct ba_msg_transport * ba_transport = &transport->transport; const bluealsa_watch * watch = transport->watch; CtlPluginT* plugin = watch->plugin; hal_bluealsa_plugin_data_t * pluginData=getPluginContext(plugin); json_object* requestJ = NULL; json_object* sourceJ = NULL; json_object* playbackJ = NULL; json_object* pcmplugParamsJ, *pcmplugParamsJ2 = NULL; json_object* captureParamsJ; json_object* streamsJ = NULL; json_object* streamJ = NULL; json_object* zonesJ = NULL; char * playbackZoneS; uint32_t delayms; if (ba_transport->type & BA_PCM_TYPE_SCO) { transportTypeS = "sco"; delayms = pluginData->sco.delayms; playbackZoneS = pluginData->sco.speaker; } else if (ba_transport->type & BA_PCM_TYPE_A2DP) { transportTypeS = "a2dp"; delayms = pluginData->a2dp.delayms; playbackZoneS = pluginData->a2dp.zone; } else { AFB_API_ERROR(plugin->api, "%s: unsupported transport type", __func__ ); goto fail; } char * captureS = NULL; if (asprintf(&captureS, "%s_listen_capture", transportTypeS) == -1) goto fail; // for SCO only char * playbackS = NULL; if (asprintf(&playbackS, "%s_talk_playback", transportTypeS) == -1) goto fail; char * transactionUidS = NULL; char * streamS = NULL; char addr[18]; ba2str(&ba_transport->addr, addr); requestJ = json_object_new_object(); if (!requestJ) goto fail; if (asprintf(&transactionUidS, "%s_%s", transportTypeS, addr) == -1) goto fail; json_object_object_add(requestJ, "uid", json_object_new_string(transactionUidS)); pcmplugParamsJ = halBlueAlsaPcmPlugParams(watch->interface, addr, transportTypeS); sourceJ = json_object_new_object(); if (ba_transport->type & BA_PCM_TYPE_A2DP) json_object_object_add(sourceJ, "channels", halBlueAlsaA2DPTransportChannels(transportTypeS)); else json_object_object_add(sourceJ, "channels", halBlueAlsaSCOTransportChannels(transportTypeS)); captureParamsJ = halBlueAlsaListenParamsJ(ba_transport->sampling); json_object_object_add(requestJ, "captures", halBlueAlsaListenCapture(captureS, pcmplugParamsJ, captureParamsJ, sourceJ)); if (ba_transport->type & BA_PCM_TYPE_SCO) { playbackJ = json_object_new_object(); // that is a shame that deep copy in not available in the current json-c version pcmplugParamsJ2 = halBlueAlsaPcmPlugParams(watch->interface, addr, transportTypeS); json_object * paramsJ = halBlueAlsaScoTalkParamsJ(ba_transport->sampling); json_object_object_add(playbackJ, "channels", halBlueAlsaSCOTransportChannels(transportTypeS)); json_object_object_add(requestJ, "playbacks", halBlueAlsaTalkPlayback(playbackS, pcmplugParamsJ2, paramsJ, playbackJ)); } /* ZONES */ if (ba_transport->type & BA_PCM_TYPE_SCO) { zonesJ = json_object_new_array(); json_object_array_add(zonesJ, halBlueAlsaScoZone()); json_object_object_add(requestJ, "zones", zonesJ); } /* STREAMS */ /* Build the array of streams */ streamsJ = json_object_new_array(); streamJ = json_object_new_object(); if (asprintf(&streamS, "%s_listen_stream", transportTypeS) == -1) goto fail; json_object_object_add(streamJ, "uid", json_object_new_string(streamS)); json_object_object_add(streamJ, "source", json_object_new_string(captureS)); json_object_object_add(streamJ, "zone", json_object_new_string(playbackZoneS)); if (delayms != 0) { json_object_object_add(streamJ, "delayms", json_object_new_int(delayms)); } json_object_array_add(streamsJ, streamJ); /* In case of SCO, to have full-duplex, we instantiate a stream for 'talk' */ if (ba_transport->type & BA_PCM_TYPE_SCO ) { streamJ = json_object_new_object(); if (asprintf(&streamS, "%s_talk_stream", transportTypeS) == -1) goto fail; json_object_object_add(streamJ, "uid", json_object_new_string(streamS)); json_object_object_add(streamJ, "source", json_object_new_string(pluginData->sco.mic)); json_object_object_add(streamJ, "zone", json_object_new_string(SCO_TALK_ZONE)); if (delayms != 0) json_object_object_add(streamJ, "delayms", json_object_new_int(delayms)); json_object_array_add(streamsJ, streamJ); } json_object_object_add(requestJ, "streams", streamsJ); /* In softmixer, this will create a transaction verb (whose name is transactionUidS), * will be used later to destroy all the created objects upon transport removal */ /* a call sync is forbidden, because we in the context of an event io */ afb_api_call(plugin->api, SMIXER_API_NAME, "attach", requestJ, NULL, NULL); transport->transactionUidS = transactionUidS; ret = 0; goto done; fail: return -1; done: AFB_API_DEBUG(plugin->api, "DONE."); return ret; } static int halBluezAlsaRemoveTransportStream(bluealsa_transport_t * transport) { CtlPluginT * plugin = transport->watch->plugin; json_object* requestJ = NULL; AFB_API_INFO(plugin->api, "Call transaction detach verb %s", transport->transactionUidS); if (transport->transactionUidS == NULL) goto fail; requestJ = json_object_new_object(); if (!requestJ) goto fail; json_object_object_add(requestJ, "action", json_object_new_string("remove")); /* a call sync is forbidden, because we in the context of an event io */ afb_api_call(plugin->api, SMIXER_API_NAME, transport->transactionUidS, requestJ, NULL, NULL); return 0; fail: return -1; } static int halBlueAlsaFetchTransports(bluealsa_watch * watch) { ssize_t nbTransports; struct ba_msg_transport *transports; CtlPluginT * plugin = watch->plugin; hal_bluealsa_plugin_data_t * pluginData = (hal_bluealsa_plugin_data_t*)getPluginContext(plugin); bluealsa_transport_t * transport_list = &pluginData->transport_list; bluealsa_transport_t * transport = NULL; AFB_API_DEBUG(plugin->api, "Fetching available transports of interface %s", watch->interface); if ((nbTransports = bluealsa_get_transports(watch->fd, &transports)) == -1) { AFB_API_ERROR(plugin->api, "Couldn't get transports: %s", strerror(errno)); goto done; } AFB_API_DEBUG(plugin->api, "Got %zu transport(s)", nbTransports); for (int ix=0; ixaddr, addr); const char * typeS; if (ba_transport->type & BA_PCM_TYPE_SCO) typeS = "sco"; else if (ba_transport->type & BA_PCM_TYPE_A2DP) typeS = "a2dp"; else typeS = "unknown"; AFB_API_DEBUG(plugin->api, "Transport %d: type %s (%x), dev %s, codec %d", ix, typeS, ba_transport->type, addr, ba_transport->codec); if (BA_PCM_TYPE(ba_transport->type) == BA_PCM_TYPE_SCO && ba_transport->codec == 0) { AFB_API_DEBUG(plugin->api, "Transport has no codec, skip it"); continue; } if (halBlueAlsaTransportFind(watch, transport_list, ba_transport)) { AFB_API_DEBUG(plugin->api, "This transport is already streamed"); continue; } AFB_API_INFO(plugin->api, "Registering transport type %s, dev %s", typeS, addr); transport = halBlueAlsaTransportsAdd(watch, transport_list, ba_transport); if (transport == NULL) { AFB_API_ERROR(plugin->api, "Failed to register this transport"); goto done; } // Do the softmixer stuff if (halBlueAlsaAttachTransportStreams(transport) != 0) { AFB_API_ERROR(plugin->api, "Failed create transport streams"); goto done; } AFB_API_DEBUG(plugin->api, "%s: transaction id %s", __func__, transport->transactionUidS); } halBlueAlsaTransportUpdate(watch, transport_list, transports, nbTransports , halBluezAlsaRemoveTransportStream); done: free(transports); return 0; } static int halBlueAlsaUnregister(CtlPluginT * plugin, const char * dbus_name) { hal_bluealsa_plugin_data_t * pluginData = getPluginContext(plugin); const char * interface = dbus_name + strlen(BLUEZALSA_DBUS_NAME_PREFIX) + 1; bluealsa_transport_t * transport_list = &pluginData->transport_list; const bluealsa_watch * watcher = NULL; bluealsa_transport_t * tmp, *sav; cds_list_for_each_entry_safe(tmp, sav, &transport_list->list, list) { const bluealsa_watch * watch = tmp->watch; if (!watch) continue; if (strcmp(watch->interface, interface) != 0) continue; watcher = watch; cds_list_del(&tmp->list); halBluezAlsaRemoveTransportStream(tmp); free(tmp->transactionUidS); free(tmp); } if (watcher) { close(watcher->fd); free((char*)watcher->interface); free((bluealsa_watch*)watcher); } return 0; } static int halBlueAlsaRegister(CtlPluginT* plugin, const char * dbus_name) { int ret; sd_event * sdLoop; sd_event_source * evtsrc; const char * interface = dbus_name + strlen(BLUEZALSA_DBUS_NAME_PREFIX) +1 ; sdLoop = afb_api_get_event_loop(plugin->api); enum ba_event transport_mask = BA_EVENT_TRANSPORT_ADDED | BA_EVENT_TRANSPORT_CHANGED | BA_EVENT_TRANSPORT_REMOVED; bluealsa_watch * watch = (bluealsa_watch *)malloc(sizeof(bluealsa_watch)); if (watch == NULL) goto fail; watch->interface = strdup(interface); if (!watch->interface) { AFB_API_ERROR(plugin->api, "%s: Insufficient memory", __func__); goto fail; } watch->plugin = plugin; if ((watch->fd = bluealsa_open(interface)) == -1) { AFB_API_ERROR(plugin->api, "BlueALSA connection failed: %s", strerror(errno)); goto fail; } halBlueAlsaFetchTransports(watch); if (bluealsa_event_subscribe(watch->fd, transport_mask) == -1) { AFB_API_ERROR(plugin->api, "BlueALSA subscription failed: %s", strerror(errno)); goto fail; } // Register transport change event to the main loop of the binder if ((ret = sd_event_add_io(sdLoop, &evtsrc, watch->fd, EPOLLIN, halBlueAlsaTransportEventCB, watch)) < 0) { AFB_API_ERROR(plugin->api, "%s: Failed to register event fd to io loop", __func__); goto fail; } return 0; fail: if (watch->interface) free((char*)watch->interface); if (watch->fd) close(watch->fd); if (watch) free(watch); return -1; } /* Notification callback for DBus "NameOwnerChanged event. * Notice that since the dbus name of bluez-alsa daemon has a prefix * for the hci interface, * (eg, org.bluez-alsa.hci0, org.bluez-alsa.hci1 .., it is not possible * to set a match rule when registering the callback, so we have to perform * the filtering for the service name within the callback instead */ static int name_changed_cb(sd_bus_message *m, void *userdata, sd_bus_error * ret_error) { CtlPluginT * plugin = (CtlPluginT*) userdata; const char * signature = sd_bus_message_get_signature(m, 1); /* simply ignore silently that spurious message */ if (strcmp(signature,"s") == 0) { goto done; } if (strcmp(signature,"sss") != 0) { AFB_API_ERROR(plugin->api, "%s: wrong message signature '%s'", __func__, signature); goto done; } const char *name; const char *owner_old; const char *owner_new; if (sd_bus_message_read(m, "sss", &name, &owner_old, &owner_new) < 0) { AFB_API_ERROR(plugin->api, "%s: failed to read message parameters", __func__); goto done; } if (!strstr(name, BLUEZALSA_DBUS_NAME_PREFIX)) goto done; if (owner_old != NULL && owner_old[0] != '\0') { AFB_API_INFO(plugin->api, "bluez-alsa: %s disappeared", name); halBlueAlsaUnregister(plugin, name); } if (owner_new != NULL && owner_new[0] != '\0') { AFB_API_INFO(plugin->api, "bluez-alsa %s appeared", name); halBlueAlsaRegister(plugin, name); } done: return 0; } /* * Callback for the DBus "ListNames" call. * This will list all the available services on DBus (or more precisely, * the ones that have registered a name, because it is not mandatory) * The "ListNames" is done at startup to get the initial list of available * bluez-alsa daemons to connect to. * After, we use the NameOwnerChanged signals for the notification of new * instances of bluez-alsa, or when it disappears */ static int sd_bus_list_message_handler (sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char * signature = sd_bus_message_get_signature(m, 1); int ret; CtlPluginT * plugin = (CtlPluginT*) userdata; if (strcmp(signature,"as") != 0) { AFB_API_ERROR(plugin->api, "%s: wrong message signature: '%s'", __func__, signature); goto done; } const char * string = NULL; if ( (ret = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s")) < 0) { AFB_API_ERROR(plugin->api, "failed to enter container, err=%s\n", strerror(-ret)); goto failed; } for (;;) { ret = sd_bus_message_read(m, "s", &string); if (ret < 0) { AFB_API_ERROR(plugin->api, "%s: failed to read string", __func__); goto done; } if (ret == 0) break; if (strstr(string, BLUEZALSA_DBUS_NAME_PREFIX) != 0) { halBlueAlsaRegister(plugin, string); } } done: sd_bus_message_exit_container(m); failed: return 0; } static int halBlueAlsaRegisterAll(CtlPluginT* plugin) { sd_bus_slot * slot = NULL; struct sd_bus *bus = afb_api_get_system_bus(plugin->api); int ret; if (!bus) { AFB_API_ERROR(plugin->api, "%s: Unable to get a DBus connection", __func__); goto failed; } AFB_API_INFO(plugin->api, "Ask DBus for the list of services"); ret = sd_bus_call_method_async(bus, &slot, ORG_FREEDESKTOP_DBUS_NAME, ORG_FREEDESKTOP_DBUS_PATH, ORG_FREEDESKTOP_DBUS_INTERFACE, "ListNames", sd_bus_list_message_handler, plugin, NULL); if (ret < 0) { AFB_API_ERROR(plugin->api, "Unable to request service list from DBus: %s", strerror(-ret)); goto failed; } char * match = "type='signal',sender='" ORG_FREEDESKTOP_DBUS_NAME "', interface='" ORG_FREEDESKTOP_DBUS_INTERFACE "'"; if (sd_bus_add_match(bus, &slot, match, name_changed_cb, plugin) < 0) { AFB_API_ERROR(plugin->api, "Unable to add the dbus signal match rule"); goto failed; } return 0; failed: return -1; }