Squashed commit of the following: 18/239018/1
authorSeungbae Shin <seungbae.shin@samsung.com>
Tue, 21 Jul 2020 05:58:19 +0000 (14:58 +0900)
committerSeungbae Shin <seungbae.shin@samsung.com>
Tue, 21 Jul 2020 05:58:19 +0000 (14:58 +0900)
commit 90776b9d67bd74896923c705ce86a221bdcd7e6f
Author: Jaechul Lee <jcsing.lee@samsung.com>
Date:   Mon Jul 20 11:28:27 2020 +0900

    stream-manager: [RFC] broadcast remote.access when source prop is changed

    Change-Id: Ie391641b8eb9b498667c1aaa794a3fad42dd7194
Signed-off-by: Jaechul Lee <jcsing.lee@samsung.com>
commit 6fd63b6e5844aec200816eb81547507ab68f8c3a
Author: Seungbae Shin <seungbae.shin@samsung.com>
Date:   Fri Jul 17 14:11:33 2020 +0900

    update few more

    Change-Id: I763bf8f918189e689fef7a490f33a63ad0ae68dc

commit 23d59d21b443389a1d5a0a8e7292cd280108140f
Author: Jaechul Lee <jcsing.lee@samsung.com>
Date:   Fri Jul 17 13:01:56 2020 +0900

    stream-manager: [RFC] send message when remote devices are found.

    Change-Id: Ifc7bd99220b326485e93a1489c1d5b36a87a58f7
Signed-off-by: Jaechul Lee <jcsing.lee@samsung.com>
commit 5aafc03c418b981e89ff4eb914c5c7d7b743545d
Author: Seungbae Shin <seungbae.shin@samsung.com>
Date:   Fri Jul 17 13:40:08 2020 +0900

    use predefined event/property

    Change-Id: I40e0c9cca0aa973d9288c974a00303eddf9a985e

commit 4448765b37447450224fa09d2ac98bc190e13624
Author: Jaechul Lee <jcsing.lee@samsung.com>
Date:   Wed Jul 15 15:29:15 2020 +0900

    stream-manager: Add update proplist api

    handle_update_proplist is added. Remote capture uses a proplist for
    checking permissions.

    [Version] 13.0.21
    [Issue Type] Improvement

    Change-Id: I9a2ea3641efa75ed65e067e562c3172c755e925a
Signed-off-by: Jaechul Lee <jcsing.lee@samsung.com>
commit e5b61682f006c7f96e552fb9bbc87d6a8856f89a
Author: Seungbae Shin <seungbae.shin@samsung.com>
Date:   Tue Jul 14 20:29:56 2020 +0900

    reject disallowed remote-recording stream

    Change-Id: I4ce6c4ed0f89c696d3790c42aacbf327ddb3346f

commit 712a11d3aa6277354b5d12cdce0ad26e549ed478
Author: Seungbae Shin <seungbae.shin@samsung.com>
Date:   Tue Jul 14 20:24:16 2020 +0900

    use new tunnel module

    Change-Id: I42946234489dffb5e936d49ce7a6eb46aec5dcaa

commit 1d578dd675c4f5b46990b581f73205d096152f8e
Author: Seungbae Shin <seungbae.shin@samsung.com>
Date:   Mon Jul 13 11:13:47 2020 +0900

    Publish only built-in device

    Change-Id: I69753da561c813c80c35df7690d4efc1efc48a43

commit c45d5cf2076faf3bd2e28a66be5e8416dc3cc46b
Author: Seungbae Shin <seungbae.shin@samsung.com>
Date:   Mon Jul 13 11:13:18 2020 +0900

    network device for test

    Change-Id: I929dcea0cc6ab8b96c857bf8d0f0c3391d0495bb

commit 0a107b68bbdcb758f84de1fc5a8042eee8942021
Author: Seungbae Shin <seungbae.shin@samsung.com>
Date:   Mon Jul 13 11:12:18 2020 +0900

    skip local services

    Change-Id: I6e8f334e6a16f5f4367c002ea5bd5809dc6b561b

commit c69ed2cf96eec8ad22479c4037788bb821447671
Author: Seungbae Shin <seungbae.shin@samsung.com>
Date:   Mon Mar 23 20:22:54 2020 +0900

    draft proptotyping of tizenaudio discover/publish

    Change-Id: Id47dad038bfae487bbe57a09aaece674b30f9008

Change-Id: Ifc643d6054579c4ff4e40f99e2e916aa79c92c5b

12 files changed:
Makefile.am
configure.ac
packaging/pulseaudio-modules-tizen.spec
src/device-manager.c
src/module-tizenaudio-discover.c [new file with mode: 0644]
src/module-tizenaudio-publish.c [new file with mode: 0644]
src/stream-manager-dbus-priv.h
src/stream-manager-dbus.c
src/stream-manager-priv.h
src/stream-manager-restriction-priv.h
src/stream-manager-restriction.c
src/stream-manager.c

index 9b59496883ff0d1ee1d9c88e5d4685ba6b669ab4..72316adf411a8cbede01ab83a13ff27f8cde733f 100644 (file)
@@ -36,6 +36,8 @@ pulsemodlibexec_LTLIBRARIES = \
                module-tizenaudio-sink.la \
                module-tizenaudio-source.la \
                module-tizenaudio-policy.la \
+               module-tizenaudio-discover.la \
+               module-tizenaudio-publish.la \
                module-sound-player.la \
                module-tone-player.la \
                module-poweroff.la
@@ -115,6 +117,17 @@ module_acm_sink_la_LIBADD = $(MODULE_LIBADD)
 module_acm_sink_la_CFLAGS = $(MODULE_CFLAGS)
 endif
 
+module_tizenaudio_discover_la_SOURCES = src/module-tizenaudio-discover.c
+module_tizenaudio_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_tizenaudio_discover_la_LIBADD = $(MODULE_LIBADD) $(DNSSD_LIBS) $(VCONF_LIBS)
+module_tizenaudio_discover_la_CFLAGS = $(MODULE_CFLAGS) $(DNSSD_CFLAGS) $(VCONF_CFLAGS)
+
+module_tizenaudio_publish_la_SOURCES = src/module-tizenaudio-publish.c
+module_tizenaudio_publish_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_tizenaudio_publish_la_LIBADD = $(MODULE_LIBADD) $(DNSSD_LIBS)
+module_tizenaudio_publish_la_CFLAGS = $(AM_CFLAGS) $(DNSSD_CFLAGS)
+
+
 if ENABLE_VCONF_HELPER
 pulsemodlibexec_LTLIBRARIES += module-vconf.la
 
index f38d6754197ecfed039243a66198630d99ddef11..8557992b782b2bf6cec159c8ae31ec2fe559bf37 100644 (file)
@@ -361,6 +361,10 @@ PKG_CHECK_MODULES(VCONF, vconf)
 AC_SUBST(VCONF_CFLAGS)
 AC_SUBST(VCONF_LIBS)
 
+PKG_CHECK_MODULES(DNSSD, dns_sd)
+AC_SUBST(DNSSD_CFLAGS)
+AC_SUBST(DNSSD_LIBS)
+
 dnl use hal tc ------------------------------------------------------------
 AC_ARG_ENABLE(haltc, AC_HELP_STRING([--enable-haltc], [using haltc]),
 [
index 3944878db531471aa7189828709f441fe8192a2e..c3749d4bb9f31adc20d9a65b8d5209e4377fd21c 100644 (file)
@@ -1,6 +1,6 @@
 Name:             pulseaudio-modules-tizen
 Summary:          Pulseaudio modules for Tizen
-Version:          13.0.20
+Version:          13.0.21
 Release:          0
 Group:            Multimedia/Audio
 License:          LGPL-2.1+
@@ -20,6 +20,7 @@ BuildRequires:    mm-hal-interface-devel
 BuildRequires:    pkgconfig(libpulse)
 BuildRequires:    pkgconfig(pulsecore)
 BuildRequires:    pkgconfig(libsystemd)
+BuildRequires:    pkgconfig(dns_sd)
 BuildRequires:    pulseaudio
 BuildRequires:    m4
 Requires(post):   /sbin/ldconfig
@@ -74,6 +75,8 @@ install -m 0644 %SOURCE1 %{buildroot}%{_tmpfilesdir}/pulseaudio.conf
 %{_libdir}/pulse-13.0/modules/module-tizenaudio-policy.so
 %{_libdir}/pulse-13.0/modules/module-tizenaudio-sink.so
 %{_libdir}/pulse-13.0/modules/module-tizenaudio-source.so
+%{_libdir}/pulse-13.0/modules/module-tizenaudio-discover.so
+%{_libdir}/pulse-13.0/modules/module-tizenaudio-publish.so
 %{_libdir}/pulse-13.0/modules/libhal-interface.so
 %{_libdir}/pulse-13.0/modules/libcommunicator.so
 %{_tmpfilesdir}/pulseaudio.conf
index 627630858700703a050ed2e01c236151967f9b1f..eb8be9cec29e20341ea31f369e621597783fd9a0 100644 (file)
@@ -1633,6 +1633,10 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
         pulse_device_set_use_internal_codec(PA_OBJECT(source), false);
         handle_bt_pulse_device(PA_OBJECT(source), true, dm);
         return PA_HOOK_OK;
+    else if (pulse_device_is_raop(PA_OBJECT(source))) {
+        pulse_device_set_use_internal_codec(PA_OBJECT(source), false);
+        handle_raop_pulse_device(PA_OBJECT(source), true, dm);
+        return PA_HOOK_OK;
     } else if (pulse_device_is_alsa(PA_OBJECT(source)) || pulse_device_is_tizenaudio(PA_OBJECT(source))) {
         pulse_device_set_use_internal_codec(PA_OBJECT(source), true);
         handle_internal_pulse_device(PA_OBJECT(source), true, dm);
diff --git a/src/module-tizenaudio-discover.c b/src/module-tizenaudio-discover.c
new file mode 100644 (file)
index 0000000..4c1ba3f
--- /dev/null
@@ -0,0 +1,783 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2004-2006 Lennart Poettering
+
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define __MDNSRESPONDER__
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+
+#ifdef __MDNSRESPONDER__
+#include <dns_sd.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#endif
+
+#include <vconf.h>
+#include <vconf-keys.h>
+
+#include <stdbool.h>
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+
+#define SERVICE_TYPE_SINK "_pulse-sink._tcp"
+#define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
+
+static const char * const valid_modargs[] = {
+    NULL
+};
+
+#ifdef __MDNSRESPONDER__
+
+#define MAX_IP_ADDR 32
+#define MAX_RECORD_LEN 256
+
+typedef struct _dns_request_data {
+    DNSServiceRef sd_ref;
+    pa_io_event *io;
+} dns_request_data_t;
+
+typedef struct _service_data {
+    struct userdata *u;
+
+    int protocol;
+    dns_request_data_t resolve;
+    dns_request_data_t addr;
+
+    /* browse */
+    int flags;
+    unsigned int interface;
+    char *service_name;
+    char *service_type;
+    char *domain;
+
+    /* resolve */
+    char *host_name;
+    unsigned short port;
+
+    char *device;
+    pa_sample_spec ss;
+    pa_channel_map cm;
+    bool channel_map_set;
+
+    /* address */
+    char *ip_address;
+} service_data_t;
+#endif /* __MDNSRESPONDER__ */
+
+struct tunnel {
+#ifdef __MDNSRESPONDER__
+    unsigned int interface;
+    int protocol;
+#endif
+    char *name, *type, *domain;
+    uint32_t module_index;
+};
+
+struct userdata {
+    pa_core *core;
+    pa_module *module;
+
+#ifdef __MDNSRESPONDER__
+    dns_request_data_t browse_sink;
+    dns_request_data_t browse_source;
+#endif
+
+    pa_hashmap *tunnels;
+};
+
+void pa__done(pa_module *m);
+
+static unsigned tunnel_hash(const void *p) {
+    const struct tunnel *t = p;
+
+    return
+        (unsigned) t->interface +
+        (unsigned) t->protocol +
+        pa_idxset_string_hash_func(t->name) +
+        pa_idxset_string_hash_func(t->type) +
+        pa_idxset_string_hash_func(t->domain);
+}
+
+static int tunnel_compare(const void *a, const void *b) {
+    const struct tunnel *ta = a, *tb = b;
+    int r;
+
+    if (ta->interface != tb->interface)
+        return 1;
+    if (ta->protocol != tb->protocol)
+        return 1;
+    if ((r = strcmp(ta->name, tb->name)))
+        return r;
+    if ((r = strcmp(ta->type, tb->type)))
+        return r;
+    if ((r = strcmp(ta->domain, tb->domain)))
+        return r;
+
+    return 0;
+}
+
+static struct tunnel *tunnel_new(
+#ifdef __MDNSRESPONDER__
+        unsigned int interface, int protocol,
+#endif
+        const char *name, const char *type, const char *domain) {
+
+    struct tunnel *t;
+    t = pa_xnew(struct tunnel, 1);
+    t->interface = interface;
+    t->protocol = protocol;
+    t->name = pa_xstrdup(name);
+    t->type = pa_xstrdup(type);
+    t->domain = pa_xstrdup(domain);
+    t->module_index = PA_IDXSET_INVALID;
+    return t;
+}
+
+static void tunnel_free(struct tunnel *t) {
+    pa_assert(t);
+    pa_xfree(t->name);
+    pa_xfree(t->type);
+    pa_xfree(t->domain);
+    pa_xfree(t);
+}
+
+#ifdef __MDNSRESPONDER__
+
+
+static service_data_t *service_data_new() {
+
+    return pa_xnew0(service_data_t, 1);
+}
+
+static void service_data_free(service_data_t *data) {
+    pa_assert(data);
+
+    pa_xfree(data->service_name);
+    pa_xfree(data->service_type);
+    pa_xfree(data->domain);
+    pa_xfree(data->host_name);
+    pa_xfree(data->ip_address);
+    pa_xfree(data->device);
+
+    pa_xfree(data);
+}
+
+
+static int request_set_io_callback(pa_mainloop_api *m_api, dns_request_data_t *request, pa_io_event_cb_t cb, void *user_data) {
+    int fd = -1;
+
+    pa_assert(request);
+    pa_assert(request->sd_ref);
+
+    fd = DNSServiceRefSockFD(request->sd_ref);
+
+    if (fd < 0) {
+        pa_log_error("DNSServiceRefSockFD(%p) error", request->sd_ref);
+        return -1;
+    }
+
+    request->io = m_api->io_new(m_api, fd,
+                                PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP,
+                                cb, user_data);
+
+    pa_log_info("io(%p) with cb(%p) added for fd(%d)", request->io, cb, fd);
+
+    return 0;
+}
+
+static void request_clear(pa_mainloop_api *m_api, dns_request_data_t *request) {
+    if (request->sd_ref) {
+        DNSServiceRefDeallocate(request->sd_ref);
+        request->sd_ref = NULL;
+    }
+
+    if (request->io) {
+        m_api->io_free(request->io);
+        request->io = NULL;
+    }
+}
+
+static char *get_ip_str(const struct sockaddr *sa) {
+    char addr_str[MAX_IP_ADDR] = { 0, };
+
+    switch(sa->sa_family) {
+    case AF_INET:
+        inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), addr_str, MAX_IP_ADDR);
+        break;
+
+    case AF_INET6:
+        inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), addr_str, MAX_IP_ADDR);
+        break;
+
+    default:
+        pa_strlcpy(addr_str, "Unknown AF", MAX_IP_ADDR);
+        break;
+    }
+
+    return pa_xstrdup(addr_str);
+}
+
+static void parse_txt_record(service_data_t *data,
+                            const unsigned char *txt_record,
+                            unsigned short txt_len) {
+    char record_str[MAX_RECORD_LEN] = { 0, };
+    int record_len;
+    const char *key_str = NULL;
+    const char *val_str = NULL;
+    const char *state = NULL;
+    size_t val_len;
+    const char *delimiter = "=";
+    const unsigned char *cur = txt_record;
+
+    pa_assert(data);
+    pa_assert(txt_record);
+    pa_assert(txt_len > 0);
+
+    while (cur < txt_record + txt_len) {
+        record_len = *cur;
+        state = NULL;
+
+        pa_snprintf(record_str, record_len + 1, "%s", ++cur);
+
+        key_str = pa_split_in_place(record_str, delimiter, &val_len, &state);
+        if (pa_strneq(key_str, "device", val_len)) {
+            data->device = pa_split(record_str, delimiter, &state);
+        } else if (pa_strneq(key_str, "rate", val_len)) {
+            data->ss.rate = (uint32_t)atoi(pa_split_in_place(record_str, delimiter, &val_len, &state));
+        } else if (pa_strneq(key_str, "channels", val_len)) {
+            data->ss.channels = (uint8_t)atoi(pa_split_in_place(record_str, delimiter, &val_len, &state));
+        } else if (pa_strneq(key_str, "format", val_len)) {
+            val_str = pa_split_in_place(record_str, delimiter, &val_len, &state);
+            data->ss.format = pa_parse_sample_format(val_str);
+        } else if (pa_strneq(key_str, "channel_map", val_len)) {
+            val_str = pa_split_in_place(record_str, delimiter, &val_len, &state);
+            pa_channel_map_parse(&data->cm, val_str);
+        }
+
+        cur += record_len;
+    }
+    pa_log_info("-----------------------------------");
+    pa_log_info("device      : [%s]", data->device);
+    pa_log_info("rate        : [%d]", data->ss.rate);
+    pa_log_info("channels    : [%d]", data->ss.channels);
+    pa_log_info("format      : [%s]", pa_sample_format_to_string(data->ss.format));
+    pa_log_info("channel_map : [%s]", pa_channel_map_to_pretty_name(&data->cm));
+    pa_log_info("-----------------------------------");
+}
+
+static void tunnel_add(service_data_t *data) {
+    struct tunnel *tnl;
+    char *dname, *module_name, *args;
+    const char *t;
+    char *if_suffix = NULL;
+    char cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
+    char *properties = NULL;
+    pa_module *m;
+
+    pa_assert(data);
+    pa_assert(data->u);
+
+    tnl = tunnel_new(data->interface, data->protocol, data->service_name, data->service_type, data->domain);
+
+    data->ss = data->u->core->default_sample_spec;
+    data->cm = data->u->core->default_channel_map;
+
+    properties = pa_sprintf_malloc("%s=raop", PA_PROP_DEVICE_API);
+
+    if (!data->channel_map_set && data->cm.channels != data->ss.channels)
+        pa_channel_map_init_extend(&data->cm, data->ss.channels, PA_CHANNEL_MAP_DEFAULT);
+
+    if (!pa_sample_spec_valid(&data->ss)) {
+        pa_log("Service '%s' contains an invalid sample specification.", data->service_name);
+        pa_xfree(properties);
+        goto finish;
+    }
+
+    if (!pa_channel_map_valid(&data->cm) || data->cm.channels != data->ss.channels) {
+        pa_log("Service '%s' contains an invalid channel map.", data->service_name);
+        pa_xfree(properties);
+        goto finish;
+    }
+
+    if (data->device)
+        dname = pa_sprintf_malloc("tunnel.%s.%s", data->host_name, data->device);
+    else
+        dname = pa_sprintf_malloc("tunnel.%s", data->host_name);
+
+    if (!pa_namereg_is_valid_name(dname)) {
+        pa_log("Cannot construct valid device name from credentials of service '%s'.", dname);
+        pa_xfree(dname);
+        pa_xfree(properties);
+        goto finish;
+    }
+
+    t = strstr(data->service_type, "sink") ? "sink" : "source";
+
+    module_name = pa_sprintf_malloc("module-tunnel-%s-new", t);
+    args = pa_sprintf_malloc("server=[%s%s]:%u "
+                             "%s=%s "
+                             "format=%s "
+                             "channels=%u "
+                             "rate=%u "
+                             "%s_properties=%s "
+                             "%s_name=%s "
+                             "channel_map=%s",
+                             data->ip_address,
+                             if_suffix ? if_suffix : "", data->port,
+                             t, data->device,
+                             pa_sample_format_to_string(data->ss.format),
+                             data->ss.channels,
+                             data->ss.rate,
+                             t, properties ? properties : "",
+                             t, dname,
+                             pa_channel_map_snprint(cmt, sizeof(cmt), &data->cm));
+
+    pa_log_debug("Loading %s with arguments '%s'", module_name, args);
+
+    if (pa_module_load(&m, data->u->core, module_name, args) >= 0) {
+        tnl->module_index = m->index;
+        pa_hashmap_put(data->u->tunnels, tnl, tnl);
+        pa_log_info("Module(%u) loaded, Tunnel(%p) added success", m->index, tnl);
+        tnl = NULL;
+    } else {
+        pa_log_error("Failed to load Module(%s)", module_name);
+    }
+
+    pa_xfree(module_name);
+    pa_xfree(dname);
+    pa_xfree(args);
+    pa_xfree(if_suffix);
+    pa_xfree(properties);
+
+finish:
+
+    if (tnl)
+        tunnel_free(tnl);
+}
+
+static bool is_local_service(const char *ip_addr) {
+    char *ipv4_addr = vconf_get_str(VCONFKEY_NETWORK_IP);
+    bool is_local = pa_safe_streq(ipv4_addr, ip_addr);
+    pa_xfree(ipv4_addr);
+    return is_local;
+}
+
+static void dnssd_getaddrinfo_reply_cb(DNSServiceRef sd_ref,
+                                       unsigned int flags, unsigned int if_index,
+                                       DNSServiceErrorType error_code, const char *host_name,
+                                       const struct sockaddr *address, unsigned int ttl,
+                                       void *user_data) {
+    service_data_t *data = (service_data_t *)user_data;
+
+    pa_assert(data);
+
+    if (flags & kDNSServiceFlagsMoreComing) {
+        pa_log_warn("More results are queued, No need to send callback to "
+                    "application at this stage");
+        return;
+    }
+
+    if (data->interface != if_index)
+        pa_log_warn("diff interface %d <=> %d", data->interface, if_index);
+    if (!pa_safe_streq(data->host_name, host_name))
+        pa_log_warn("diff host_name %s <=> %s", data->host_name, host_name);
+
+    data->ip_address = get_ip_str(address);
+
+    pa_log_info("-----------------------------------");
+    pa_log_info("Address Reply     : ref(%p) userdata(%p), error(%d), flags(0x%x)",
+            sd_ref, user_data, error_code, flags);
+    pa_log_info("  Interface Index : %d", if_index);
+    pa_log_info("  Host Name       : %s", data->host_name);
+    pa_log_info("  IP Address      : %s", data->ip_address);
+    pa_log_info("  TTL             : %d", ttl);
+    pa_log_info("-----------------------------------");
+
+    if (is_local_service(data->ip_address))
+        pa_log_info("Skip local service!!!");
+    else
+        tunnel_add(data);
+    pa_log_info("======================================================================");
+
+    /*  cleanup addr */
+    request_clear(data->u->module->core->mainloop, &data->addr);
+
+    /* cleanup data */
+    service_data_free(data);
+}
+
+static void io_addr_event_callback(pa_mainloop_api *io, pa_io_event *e,
+                                   int fd, pa_io_event_flags_t events,
+                                   void *user_data) {
+    service_data_t *data = (service_data_t *)user_data;
+    DNSServiceErrorType err;
+
+    pa_assert(io);
+    pa_assert(data);
+    pa_assert(data->u);
+
+    if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+        pa_log_warn("Lost connection fd : %d", fd);
+        goto fail;
+    }
+
+    if (events & PA_IO_EVENT_INPUT) {
+        err = DNSServiceProcessResult(data->addr.sd_ref);
+        if (err == 0)
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) success", data->addr.sd_ref, fd);
+        else
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) error(%d)", data->addr.sd_ref, fd, err);
+    }
+
+    return;
+
+fail:
+    request_clear(data->u->module->core->mainloop, &data->addr);
+}
+
+static int dnssd_getaddrinfo(service_data_t *data) {
+    int ret;
+
+    ret = DNSServiceGetAddrInfo(&(data->addr.sd_ref), data->flags, data->interface,
+                                data->protocol, data->host_name,
+                                dnssd_getaddrinfo_reply_cb, data);
+    if (ret != kDNSServiceErr_NoError) {
+        pa_log_error("DNSServiceGetAddrInfo() error(%d)", ret);
+        return -1;
+    }
+
+    pa_log_info("DNSServiceGetAddrInfo(%p) success", data->addr.sd_ref);
+
+    return request_set_io_callback(data->u->module->core->mainloop, &data->addr, io_addr_event_callback, data);
+}
+
+static void dnssd_resolve_reply_cb(DNSServiceRef sd_ref, unsigned int flags,
+                                   unsigned int if_index, DNSServiceErrorType error_code,
+                                   const char *fullname, const char *host_name,
+                                   unsigned short port, unsigned short txt_len,
+                                   const unsigned char *txt_record, void *user_data) {
+    int ret;
+    service_data_t *data = (service_data_t *)user_data;
+
+    if (flags & kDNSServiceFlagsMoreComing) {
+        pa_log_warn("More results are queued");
+        return;
+    }
+
+    if (data->interface != if_index)
+        pa_log_warn("diff interface %d <=> %d", data->interface, if_index);
+
+    data->flags = flags;
+    data->host_name = pa_xstrdup(host_name);
+    data->port = ntohs(port);
+
+    pa_log_info("-----------------------------------");
+    pa_log_info("Resolve Reply     : ref(%p), userdata(%p), error(%d), flags(0x%x)",
+            sd_ref, user_data, error_code, flags);
+    pa_log_info("  Interface Index : %d", if_index);
+    pa_log_info("  Full Name       : %s", fullname);
+    pa_log_info("  Host Name       : %s", data->host_name);
+    pa_log_info("  Port            : %d", data->port);
+    pa_log_info("  Text Record     : [%p][%d]", txt_record, txt_len);
+    pa_log_info("-----------------------------------");
+
+    parse_txt_record(data, txt_record, txt_len);
+
+    /*  cleanup resolve */
+    request_clear(data->u->module->core->mainloop, &data->resolve);
+
+    /* Get Address Info details and send browse callback */
+    ret = dnssd_getaddrinfo(data);
+    if (ret < 0) {
+        pa_log_error("failed to get address info!");
+        service_data_free(data);
+    }
+}
+
+
+static void io_resolve_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd,
+                                      pa_io_event_flags_t events, void *user_data) {
+    service_data_t *data = (service_data_t *)user_data;
+    DNSServiceErrorType err;
+
+    pa_assert(io);
+    pa_assert(data);
+    pa_assert(data->u);
+
+    if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+        pa_log_warn("Lost connection fd : %d", fd);
+        goto fail;
+    }
+
+    if (events & PA_IO_EVENT_INPUT) {
+        err = DNSServiceProcessResult(data->resolve.sd_ref);
+        if (err == 0)
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) success", data->resolve.sd_ref, fd);
+        else
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) error(%d)", data->resolve.sd_ref, fd, err);
+    }
+
+    return;
+
+fail:
+    request_clear(data->u->module->core->mainloop, &data->resolve);
+}
+
+
+static int dnssd_resolve_dns_service(struct userdata *u,
+                                     unsigned int flags, unsigned int if_index, int protocol,
+                                     const char *service_name, const char *service_type,
+                                     const char *domain) {
+    int ret;
+
+    service_data_t *data = service_data_new();
+    data->u = u;
+    data->flags = flags;
+    data->interface = if_index;
+    data->protocol = protocol;
+    data->service_name = pa_xstrdup(service_name);
+    data->service_type = pa_xstrdup(service_type);
+    data->domain = pa_xstrdup(domain);
+
+    ret = DNSServiceResolve(&(data->resolve.sd_ref),
+                            flags, if_index,
+                            service_name, service_type,
+                            domain,
+                            dnssd_resolve_reply_cb,
+                            data);
+    if (ret != kDNSServiceErr_NoError) {
+        pa_log_error("DNSServiceResolve() error(%d)", ret);
+        goto fail;
+    }
+
+    pa_log_info("DNSServiceResolve(%p) success", data->resolve.sd_ref);
+
+    ret = request_set_io_callback(u->module->core->mainloop, &data->resolve, io_resolve_event_callback, data);
+    if (ret < 0)
+        goto fail;
+
+    return 0;
+
+fail:
+    service_data_free(data);
+    return -1;
+}
+
+static void dnssd_browse_reply_cb(DNSServiceRef sd_ref, unsigned int flags,
+                                  unsigned int if_index, DNSServiceErrorType error_code,
+                                  const char *service_name, const char *service_type,
+                                  const char *domain, void *user_data) {
+    struct userdata *u = user_data;
+    struct tunnel *t;
+    int protocol = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6;
+
+    pa_log_info("======================================================================");
+    pa_log_info("Browse Reply      : ref(%p), userdata(%p), error(%d), flags(0x%x)",
+            sd_ref, user_data, error_code, flags);
+    pa_log_info("  Interface Index : %d", if_index);
+    pa_log_info("  Service Name    : %s", service_name);
+    pa_log_info("  Service Type    : %s", service_type);
+    pa_log_info("  Domain          : %s", domain);
+    pa_log_info("-----------------------------------");
+
+    t = tunnel_new(if_index, protocol, service_name, service_type, domain);
+
+    if (flags & kDNSServiceFlagsAdd) {
+        if (!pa_hashmap_get(u->tunnels, t))
+            dnssd_resolve_dns_service(u, flags, if_index, protocol,
+                                    service_name, service_type, domain);
+        else
+            pa_log_warn("already exists %p", t);
+    } else {
+        struct tunnel *t2;
+
+        if ((t2 = pa_hashmap_get(u->tunnels, t))) {
+            pa_module_unload_request_by_index(u->core, t2->module_index, true);
+            pa_hashmap_remove(u->tunnels, t2);
+            pa_log_info("Module(%u) Unloaded, Tunnel(%p) removed success", t2->module_index, t2);
+            tunnel_free(t2);
+        } else {
+            pa_log_warn("Not found %p", t);
+        }
+    }
+}
+
+static void io_browse_handle_event(pa_mainloop_api *io, pa_io_event *e, int fd,
+                                          pa_io_event_flags_t events, struct userdata *u, dns_request_data_t *request) {
+    DNSServiceErrorType err;
+
+    pa_assert(io);
+    pa_assert(u);
+
+    if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+        pa_log_warn("Lost connection fd : %d", fd);
+        goto fail;
+    }
+
+    if (events & PA_IO_EVENT_INPUT) {
+        err = DNSServiceProcessResult(request->sd_ref);
+        if (err == 0)
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) success", request->sd_ref, fd);
+        else
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) error(%d)", request->sd_ref, fd, err);
+    }
+
+    return;
+
+fail:
+    request_clear(u->module->core->mainloop, request);
+    pa_module_unload_request(u->module, true);
+}
+
+static void io_browse_sink_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd,
+                              pa_io_event_flags_t events, void *user_data) {
+    struct userdata *u = user_data;
+
+    io_browse_handle_event(io, e, fd, events, u, &u->browse_sink);
+}
+
+static void io_browse_source_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd,
+                                     pa_io_event_flags_t events, void *user_data) {
+    struct userdata *u = user_data;
+
+    io_browse_handle_event(io, e, fd, events, u, &u->browse_source);
+}
+
+static int dnssd_browse_dns_service(struct userdata *u, const char *service_type) {
+    int ret;
+    dns_request_data_t *request;
+    pa_io_event_cb_t cb;
+
+    if (pa_safe_streq(service_type, SERVICE_TYPE_SINK)) {
+        request = &u->browse_sink;
+        cb = io_browse_sink_event_callback;
+    } else if (pa_safe_streq(service_type, SERVICE_TYPE_SOURCE)) {
+        request = &u->browse_source;
+        cb = io_browse_source_event_callback;
+    } else {
+        pa_log_error("invalid service type %s", pa_strnull(service_type));
+        return -1;
+    }
+
+    ret = DNSServiceBrowse(&request->sd_ref, 0,
+                           kDNSServiceInterfaceIndexAny, service_type,
+                           NULL, dnssd_browse_reply_cb,
+                           u);
+    if (ret != kDNSServiceErr_NoError) {
+        pa_log_error("DNSServiceBrowse() with %s error(%d)", service_type, ret);
+        return -1;
+    }
+
+    pa_log_info("DNSServiceBrowse(%p) success", request->sd_ref);
+
+    ret = request_set_io_callback(u->module->core->mainloop, request, cb, u);
+    if (ret < 0) {
+        request_clear(u->module->core->mainloop, request);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+#endif /* __MDNSRESPONDER__ */
+
+int pa__init(pa_module *m) {
+
+    struct userdata *u;
+    pa_modargs *ma = NULL;
+#ifdef __MDNSRESPONDER__
+    int ret;
+#endif
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments.");
+        goto fail;
+    }
+
+    m->userdata = u = pa_xnew(struct userdata, 1);
+    u->core = m->core;
+    u->module = m;
+    u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
+
+#ifdef __MDNSRESPONDER__
+    ret = dnssd_browse_dns_service(u, SERVICE_TYPE_SINK);
+    if (ret < 0)
+        goto fail;
+
+    ret = dnssd_browse_dns_service(u, SERVICE_TYPE_SOURCE);
+    if (ret < 0)
+        goto fail;
+#endif /* __MDNSRESPONDER__ */
+
+    pa_modargs_free(ma);
+
+    return 0;
+
+fail:
+    pa__done(m);
+
+    if (ma)
+        pa_modargs_free(ma);
+
+    return -1;
+}
+
+void pa__done(pa_module *m) {
+    struct userdata*u;
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->tunnels) {
+        struct tunnel *t;
+
+        while ((t = pa_hashmap_steal_first(u->tunnels))) {
+            pa_module_unload_request_by_index(u->core, t->module_index, true);
+            tunnel_free(t);
+        }
+
+        pa_hashmap_free(u->tunnels);
+    }
+
+#ifdef __MDNSRESPONDER__
+    request_clear(u->module->core->mainloop, &u->browse_sink);
+    request_clear(u->module->core->mainloop, &u->browse_source);
+#endif
+
+    pa_xfree(u);
+}
diff --git a/src/module-tizenaudio-publish.c b/src/module-tizenaudio-publish.c
new file mode 100644 (file)
index 0000000..f6a2e4f
--- /dev/null
@@ -0,0 +1,536 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Daniel Mack
+  based on module-bonjour-publish.c
+
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <dns_sd.h>
+
+#ifndef __TIZEN__
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#ifdef __TIZEN__
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/parseaddr.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/native-common.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/dynarray.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/protocol-native.h>
+
+PA_MODULE_AUTHOR("Daniel Mack");
+PA_MODULE_DESCRIPTION("Mac OS X Bonjour Service Publisher");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+
+#define SERVICE_TYPE_SINK "_pulse-sink._tcp"
+#define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
+#define SERVICE_TYPE_SERVER "_pulse-server._tcp"
+
+static const char* const valid_modargs[] = {
+    NULL
+};
+
+enum service_subtype {
+    SUBTYPE_HARDWARE,
+    SUBTYPE_VIRTUAL,
+    SUBTYPE_MONITOR
+};
+
+struct service {
+    struct userdata *userdata;
+    DNSServiceRef service;
+    DNSRecordRef rec, rec2;
+    char *service_name;
+    pa_object *device;
+    enum service_subtype subtype;
+};
+
+struct userdata {
+    pa_core *core;
+    pa_module *module;
+
+    pa_hashmap *services;
+    char *service_name;
+
+    pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
+
+    pa_native_protocol *native;
+    DNSServiceRef main_service;
+};
+
+void pa__done(pa_module *m);
+
+static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, pa_proplist **ret_proplist, enum service_subtype *ret_subtype) {
+    pa_assert(s);
+    pa_assert(ret_ss);
+    pa_assert(ret_proplist);
+    pa_assert(ret_subtype);
+
+    if (pa_sink_isinstance(s->device)) {
+        pa_sink *sink = PA_SINK(s->device);
+
+        *ret_ss = sink->sample_spec;
+        *ret_map = sink->channel_map;
+        *ret_name = sink->name;
+        *ret_proplist = sink->proplist;
+        *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
+
+    } else if (pa_source_isinstance(s->device)) {
+        pa_source *source = PA_SOURCE(s->device);
+
+        *ret_ss = source->sample_spec;
+        *ret_map = source->channel_map;
+        *ret_name = source->name;
+        *ret_proplist = source->proplist;
+        *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
+
+    } else
+        pa_assert_not_reached();
+}
+
+static void txt_record_server_data(pa_core *c, TXTRecordRef *txt) {
+    char s[128];
+    char *t;
+
+    pa_assert(c);
+
+    TXTRecordSetValue(txt, "server-version", strlen(PACKAGE_NAME" "PACKAGE_VERSION), PACKAGE_NAME" "PACKAGE_VERSION);
+
+    t = pa_get_user_name_malloc();
+    TXTRecordSetValue(txt, "user-name", strlen(t), t);
+    pa_xfree(t);
+
+    t = pa_machine_id();
+    TXTRecordSetValue(txt, "machine-id", strlen(t), t);
+    pa_xfree(t);
+
+    t = pa_uname_string();
+    TXTRecordSetValue(txt, "uname", strlen(t), t);
+    pa_xfree(t);
+
+    t = pa_get_fqdn(s, sizeof(s));
+    TXTRecordSetValue(txt, "fqdn", strlen(t), t);
+
+    snprintf(s, sizeof(s), "0x%08x", c->cookie);
+    TXTRecordSetValue(txt, "cookie", strlen(s), s);
+}
+
+static void service_free(struct service *s);
+
+static void dns_service_register_reply(DNSServiceRef sdRef,
+                                       DNSServiceFlags flags,
+                                       DNSServiceErrorType errorCode,
+                                       const char *name,
+                                       const char *regtype,
+                                       const char *domain,
+                                       void *context) {
+    struct service *s = context;
+
+    pa_assert(s);
+
+    switch (errorCode) {
+    case kDNSServiceErr_NameConflict:
+        pa_log("DNS service reported kDNSServiceErr_NameConflict\n");
+        service_free(s);
+        break;
+
+    case kDNSServiceErr_NoError:
+    default:
+        break;
+    }
+}
+
+static uint16_t compute_port(struct userdata *u) {
+    pa_strlist *i;
+
+    pa_assert(u);
+
+    for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) {
+        pa_parsed_address a;
+
+        if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
+            (a.type == PA_PARSED_ADDRESS_TCP4 ||
+             a.type == PA_PARSED_ADDRESS_TCP6 ||
+             a.type == PA_PARSED_ADDRESS_TCP_AUTO) &&
+            a.port > 0) {
+
+            pa_xfree(a.path_or_host);
+            return htons(a.port);
+        }
+
+        pa_xfree(a.path_or_host);
+    }
+
+    return htons(PA_NATIVE_DEFAULT_PORT);
+}
+
+static int publish_service(struct service *s) {
+    int r = -1;
+    TXTRecordRef txt;
+    DNSServiceErrorType err;
+    const char *name = NULL, *t;
+    pa_proplist *proplist = NULL;
+    pa_sample_spec ss;
+    pa_channel_map map;
+    char cm[PA_CHANNEL_MAP_SNPRINT_MAX], tmp[64];
+    enum service_subtype subtype;
+
+    const char * const subtype_text[] = {
+        [SUBTYPE_HARDWARE] = "hardware",
+        [SUBTYPE_VIRTUAL] = "virtual",
+        [SUBTYPE_MONITOR] = "monitor"
+    };
+
+    pa_assert(s);
+
+    if (s->service) {
+        DNSServiceRefDeallocate(s->service);
+        s->service = NULL;
+    }
+
+    TXTRecordCreate(&txt, 0, NULL);
+
+    txt_record_server_data(s->userdata->core, &txt);
+
+    get_service_data(s, &ss, &map, &name, &proplist, &subtype);
+    TXTRecordSetValue(&txt, "device", strlen(name), name);
+
+    snprintf(tmp, sizeof(tmp), "%u", ss.rate);
+    TXTRecordSetValue(&txt, "rate", strlen(tmp), tmp);
+
+    snprintf(tmp, sizeof(tmp), "%u", ss.channels);
+    TXTRecordSetValue(&txt, "channels", strlen(tmp), tmp);
+
+    t = pa_sample_format_to_string(ss.format);
+    TXTRecordSetValue(&txt, "format", strlen(t), t);
+
+    t = pa_channel_map_snprint(cm, sizeof(cm), &map);
+    TXTRecordSetValue(&txt, "channel_map", strlen(t), t);
+
+    t = subtype_text[subtype];
+    TXTRecordSetValue(&txt, "subtype", strlen(t), t);
+
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION)))
+        TXTRecordSetValue(&txt, "description", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME)))
+        TXTRecordSetValue(&txt, "icon-name", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME)))
+        TXTRecordSetValue(&txt, "vendor-name", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
+        TXTRecordSetValue(&txt, "product-name", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS)))
+        TXTRecordSetValue(&txt, "class", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR)))
+        TXTRecordSetValue(&txt, "form-factor", strlen(t), t);
+
+    err = DNSServiceRegister(&s->service,
+                             0,         /* flags */
+                             kDNSServiceInterfaceIndexAny,
+                             s->service_name,
+                             pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
+                             NULL,      /* domain */
+                             NULL,      /* host */
+                             compute_port(s->userdata),
+                             TXTRecordGetLength(&txt),
+                             TXTRecordGetBytesPtr(&txt),
+                             dns_service_register_reply, s);
+
+    if (err != kDNSServiceErr_NoError) {
+        pa_log("DNSServiceRegister() returned err %d", err);
+        goto finish;
+    }
+
+    pa_log_debug("Successfully registered Bonjour services for >%s<.", s->service_name);
+    return 0;
+
+finish:
+
+    /* Remove this service */
+    if (r < 0)
+        service_free(s);
+
+    TXTRecordDeallocate(&txt);
+
+    return r;
+}
+
+static struct service *get_service(struct userdata *u, pa_object *device) {
+    struct service *s;
+    char *hn, *un;
+    const char *n;
+
+    pa_assert(u);
+    pa_object_assert_ref(device);
+
+    if ((s = pa_hashmap_get(u->services, device)))
+        return s;
+
+    s = pa_xnew0(struct service, 1);
+    s->userdata = u;
+    s->device = device;
+
+    if (pa_sink_isinstance(device)) {
+        if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+            n = PA_SINK(device)->name;
+    } else {
+        if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+            n = PA_SOURCE(device)->name;
+    }
+
+    hn = pa_get_host_name_malloc();
+    un = pa_get_user_name_malloc();
+
+    s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1);
+
+    pa_xfree(un);
+    pa_xfree(hn);
+
+    pa_hashmap_put(u->services, s->device, s);
+
+    return s;
+}
+
+static void service_free(struct service *s) {
+    pa_assert(s);
+
+    pa_hashmap_remove(s->userdata->services, s->device);
+
+    if (s->service)
+        DNSServiceRefDeallocate(s->service);
+
+    pa_xfree(s->service_name);
+    pa_xfree(s);
+}
+
+static bool is_builtin_device(pa_proplist *pl)
+{
+    /* NOTE: checking method of builtin device can be updated */
+    return pa_safe_streq(pa_proplist_gets(pl, PA_PROP_DEVICE_FORM_FACTOR), "internal");
+}
+
+static bool shall_ignore(pa_object *o) {
+    pa_object_assert_ref(o);
+
+    if (pa_sink_isinstance(o)) {
+        if (!is_builtin_device(PA_SINK(o)->proplist)) {
+            pa_log_error("sink [%s] is not an internal, skip this", PA_SINK(o)->name);
+            return true;
+        }
+        return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
+    }
+
+    if (pa_source_isinstance(o)) {
+        if (!is_builtin_device(PA_SOURCE(o)->proplist)) {
+            pa_log_error("source [%s] is not an internal, skip this", PA_SOURCE(o)->name);
+            return true;
+        }
+        return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
+    }
+
+    pa_assert_not_reached();
+}
+
+static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
+    pa_assert(c);
+    pa_object_assert_ref(o);
+
+    if (!shall_ignore(o))
+        publish_service(get_service(u, o));
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
+    struct service *s;
+
+    pa_assert(c);
+    pa_object_assert_ref(o);
+
+    if ((s = pa_hashmap_get(u->services, o)))
+        service_free(s);
+
+    return PA_HOOK_OK;
+}
+
+static int publish_main_service(struct userdata *u) {
+    DNSServiceErrorType err;
+    TXTRecordRef txt;
+
+    pa_assert(u);
+
+    if (u->main_service) {
+        DNSServiceRefDeallocate(u->main_service);
+        u->main_service = NULL;
+    }
+
+    TXTRecordCreate(&txt, 0, NULL);
+    txt_record_server_data(u->core, &txt);
+
+    err = DNSServiceRegister(&u->main_service,
+                             0, /* flags */
+                             kDNSServiceInterfaceIndexAny,
+                             u->service_name,
+                             SERVICE_TYPE_SERVER,
+                             NULL, /* domain */
+                             NULL, /* host */
+                             compute_port(u),
+                             TXTRecordGetLength(&txt),
+                             TXTRecordGetBytesPtr(&txt),
+                             NULL, NULL);
+
+    if (err != kDNSServiceErr_NoError) {
+        pa_log("%s(): DNSServiceRegister() returned err %d", __func__, err);
+        return err;
+    }
+
+    TXTRecordDeallocate(&txt);
+
+    return 0;
+}
+
+static int publish_all_services(struct userdata *u) {
+    pa_sink *sink;
+    pa_source *source;
+    uint32_t idx;
+
+    pa_assert(u);
+
+    pa_log_debug("Publishing services in Bonjour");
+
+    for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
+        if (!shall_ignore(PA_OBJECT(sink)))
+            publish_service(get_service(u, PA_OBJECT(sink)));
+
+    for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
+        if (!shall_ignore(PA_OBJECT(source)))
+            publish_service(get_service(u, PA_OBJECT(source)));
+
+    return publish_main_service(u);
+}
+
+static void unpublish_all_services(struct userdata *u) {
+    void *state = NULL;
+    struct service *s;
+
+    pa_assert(u);
+
+    pa_log_debug("Unpublishing services in Bonjour");
+
+    while ((s = pa_hashmap_iterate(u->services, &state, NULL)))
+        service_free(s);
+
+    if (u->main_service)
+        DNSServiceRefDeallocate(u->main_service);
+}
+
+int pa__init(pa_module*m) {
+
+    struct userdata *u;
+    pa_modargs *ma = NULL;
+    char *hn, *un;
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments.");
+        goto fail;
+    }
+
+    m->userdata = u = pa_xnew0(struct userdata, 1);
+    u->core = m->core;
+    u->module = m;
+    u->native = pa_native_protocol_get(u->core);
+
+    u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+    u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
+    u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
+
+    un = pa_get_user_name_malloc();
+    hn = pa_get_host_name_malloc();
+    u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), kDNSServiceMaxDomainName-1);
+    pa_xfree(un);
+    pa_xfree(hn);
+
+    if (publish_all_services(u) != 0)
+        goto fail;
+
+    pa_modargs_free(ma);
+
+    return 0;
+
+fail:
+    pa__done(m);
+
+    if (ma)
+        pa_modargs_free(ma);
+
+    return -1;
+}
+
+void pa__done(pa_module *m) {
+    struct userdata*u;
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    unpublish_all_services(u);
+
+    if (u->services)
+        pa_hashmap_free(u->services);
+
+    if (u->sink_new_slot)
+        pa_hook_slot_free(u->sink_new_slot);
+    if (u->source_new_slot)
+        pa_hook_slot_free(u->source_new_slot);
+    if (u->sink_changed_slot)
+        pa_hook_slot_free(u->sink_changed_slot);
+    if (u->source_changed_slot)
+        pa_hook_slot_free(u->source_changed_slot);
+    if (u->sink_unlink_slot)
+        pa_hook_slot_free(u->sink_unlink_slot);
+    if (u->source_unlink_slot)
+        pa_hook_slot_free(u->source_unlink_slot);
+
+    if (u->native)
+        pa_native_protocol_unref(u->native);
+
+    pa_xfree(u->service_name);
+    pa_xfree(u);
+}
index 166c3a3e132ad3ee7edbaf293d3f29cd27db6f91..c035ab751190acd2b1ba53196df19a38375b5522 100644 (file)
 #define STREAM_MANAGER_METHOD_NAME_GET_PID_OF_LATEST_STREAM          "GetPidOfLatestStream"
 #define STREAM_MANAGER_METHOD_NAME_ACTIVATE_DUCKING                  "ActivateDucking"
 #define STREAM_MANAGER_METHOD_NAME_GET_DUCKING_STATE                 "GetDuckingState"
+#define STREAM_MANAGER_METHOD_NAME_UPDATE_PROPLIST                   "UpdateProplist"
 /* signal */
 #define STREAM_MANAGER_SIGNAL_NAME_VOLUME_CHANGED                    "VolumeChanged"
 #define STREAM_MANAGER_SIGNAL_NAME_DUCKING_STATE_CHANGED             "DuckingStateChanged"
 #define STREAM_MANAGER_SIGNAL_NAME_COMMAND                           "Command"
+#define STREAM_MANAGER_SIGNAL_NAME_FOUND_REMOTE                      "FoundRemote"
+#define STREAM_MANAGER_SIGNAL_NAME_REMOTE_ACCESS                     "RemoteAccess"
 
 enum method_handler_index {
     METHOD_HANDLER_GET_STREAM_INFO,
@@ -88,6 +91,7 @@ enum method_handler_index {
     METHOD_HANDLER_GET_PID_OF_LATEST_STREAM,
     METHOD_HANDLER_ACTIVATE_DUCKING,
     METHOD_HANDLER_GET_DUCKING_STATE,
+    METHOD_HANDLER_UPDATE_PROPLIST,
     METHOD_HANDLER_MAX
 };
 
@@ -266,6 +270,11 @@ pa_dbus_interface_info stream_manager_interface_info = {
     "   <arg name=\"is_ducked\" direction=\"out\" type=\"b\"/>"              \
     "   <arg name=\"ret_msg\" direction=\"out\" type=\"s\"/>"                \
     "  </method>"                                                            \
+    "  <method name=\"STREAM_MANAGER_METHOD_NAME_UPDATE_PROPLIST\">"         \
+    "   <arg name=\"type\" direction=\"in\" type=\"s\"/>"                    \
+    "   <arg name=\"index\" direction=\"in\" type=\"u\"/>"                   \
+    "   <arg name=\"proplist\" direction=\"in\" type=\"s\"/>"                \
+    "  </method>"                                                            \
     "  <signal name=\"STREAM_MANAGER_SIGNAL_NAME_VOLUME_CHANGED\">"          \
     "   <arg name=\"direction\" type=\"s\"/>"                                \
     "   <arg name=\"volume_type\" type=\"s\"/>"                              \
@@ -291,4 +300,4 @@ void deinit_sm_dbus(pa_stream_manager *m);
 
 #endif
 
-#endif
\ No newline at end of file
+#endif
index a6e75068facbb5a8069332dfcad3da6585080f36..4ac31a896212f12fb2e09f822d38320145b473e2 100644 (file)
@@ -70,6 +70,7 @@ static void handle_check_stream_exist_by_pid(DBusConnection *conn, DBusMessage *
 static void handle_get_pid_of_latest_stream(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_activate_ducking(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_get_ducking_state(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_update_proplist(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void send_volume_changed_signal(DBusConnection *conn, const char *direction, const char *volume_type, const uint32_t volume_level);
 
 static pa_dbus_arg_info get_stream_info_args[]  = { { "stream_type", "s", "in" },
@@ -179,6 +180,9 @@ static pa_dbus_arg_info activate_ducking_args[] = { { "index", "u", "in" },
 static pa_dbus_arg_info get_ducking_state_args[] = { { "index", "u", "in" },
                                                { "is_ducked", "b",  "out" },
                                                 { "ret_msg", "s", "out" } };
+static pa_dbus_arg_info update_proplist_args[] = { { "type", "s", "in" },
+                                                   { "index", "u",  "in" },
+                                                   { "property", "s", "in" } };
 
 static const char* signature_args_for_in[] = {
     "s",        /* METHOD_HANDLER_GET_STREAM_INFO */
@@ -208,7 +212,8 @@ static const char* signature_args_for_in[] = {
     "uss",      /* METHOD_HANDLER_CHECK_STREAM_EXIST_BY_PID */
     "sas",      /* METHOD_HANDLER_GET_PID_OF_LATEST_STREAM */
     "ubsud",    /* METHOD_HANDLER_ACTIVATE_DUCKING */
-    "u"         /* METHOD_HANDLER_GET_DUCKING_STATE */
+    "u",        /* METHOD_HANDLER_GET_DUCKING_STATE */
+    "sus"       /* METHOD_HANDLER_UPDATE_PROPLIST */
     };
 
 static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
@@ -352,6 +357,11 @@ static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
         .arguments = get_ducking_state_args,
         .n_arguments = sizeof(get_ducking_state_args) / sizeof(pa_dbus_arg_info),
         .receive_cb = handle_get_ducking_state },
+    [METHOD_HANDLER_UPDATE_PROPLIST] = {
+        .method_name = STREAM_MANAGER_METHOD_NAME_UPDATE_PROPLIST,
+        .arguments = update_proplist_args,
+        .n_arguments = sizeof(update_proplist_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_update_proplist },
 };
 
 static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata) {
@@ -2322,6 +2332,66 @@ static void handle_get_ducking_state(DBusConnection *conn, DBusMessage *msg, voi
     dbus_message_unref(reply);
 }
 
+static void handle_update_proplist(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_stream_manager *m = (pa_stream_manager*)userdata;
+    dbus_uint32_t index;
+    char *type = NULL, *proplist = NULL;
+    pa_proplist *p = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &type,
+                                       DBUS_TYPE_UINT32, &index,
+                                       DBUS_TYPE_STRING, &proplist,
+                                       DBUS_TYPE_INVALID));
+
+    if (!type || !proplist) {
+        pa_log_error("invalid arguments");
+        goto out;
+    }
+
+    pa_log_info("type(%s), index(%d), proplist(%s)", type, index, proplist);
+
+    p = pa_proplist_from_string(proplist);
+    if (!p) {
+        pa_log_error("failed to create proplist");
+        goto out;
+    }
+
+    if (pa_streq(type, "sink-input")) {
+        pa_sink_input *i;
+
+        i = pa_idxset_get_by_index(m->core->sink_inputs, index);
+        if (!i) {
+            pa_log_error("not found sink-input");
+            goto out;
+        }
+        pa_sink_input_update_proplist(i, PA_UPDATE_REPLACE, p);
+
+    } else if (pa_streq(type, "source-output")) {
+        pa_source_output *o;
+
+        o = pa_idxset_get_by_index(m->core->source_outputs, index);
+        if (!o) {
+            pa_log_error("not found source-output");
+            goto out;
+        }
+        pa_source_output_update_proplist(o, PA_UPDATE_REPLACE, p);
+        pa_source_output_send_event(o, PA_STREAM_EVENT_UPDATE_MEDIA_REMOTE_ACCESS, p);
+    } else {
+        pa_log_warn("unknown type");
+    }
+
+out:
+    if (p)
+        pa_proplist_free(p);
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
 static DBusHandlerResult handle_methods(DBusConnection *conn, DBusMessage *msg, void *userdata) {
     int idx = 0;
     pa_stream_manager *m = (pa_stream_manager*)userdata;
@@ -2423,7 +2493,7 @@ void send_command_signal(DBusConnection *conn, const char *name, int value) {
     pa_assert(conn);
     pa_assert(name);
 
-    pa_log_debug("name[%s], value[%d]", name, value);
+    pa_log_info("name[%s], value[%d]", name, value);
 
     pa_assert_se((signal_msg = dbus_message_new_signal(STREAM_MANAGER_OBJECT_PATH, STREAM_MANAGER_INTERFACE, STREAM_MANAGER_SIGNAL_NAME_COMMAND)));
     dbus_message_iter_init_append(signal_msg, &msg_iter);
@@ -2435,6 +2505,56 @@ void send_command_signal(DBusConnection *conn, const char *name, int value) {
     dbus_message_unref(signal_msg);
 }
 
+void broadcast_connected_remote_device(DBusConnection *conn, bool connected, unsigned int index,
+                                                      const char *remote_name, const char *peer_info) {
+    DBusMessage *signal_msg;
+    DBusMessageIter msg_iter;
+    dbus_bool_t c = (dbus_bool_t)connected;
+
+    pa_assert(conn);
+
+    pa_log_error("broadcast");
+
+    if (!remote_name || !peer_info) {
+        pa_log_error("Unknown device");
+        return;
+    }
+
+    pa_log_info("connected[%d], index[%d], remote_name[%s] peer_info[%s]", connected, index, remote_name, peer_info);
+
+    pa_assert_se((signal_msg = dbus_message_new_signal(STREAM_MANAGER_OBJECT_PATH, STREAM_MANAGER_INTERFACE, STREAM_MANAGER_SIGNAL_NAME_FOUND_REMOTE)));
+    dbus_message_iter_init_append(signal_msg, &msg_iter);
+
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_BOOLEAN, &c);
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_UINT32, &index);
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &remote_name);
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &peer_info);
+
+    pa_assert_se(dbus_connection_send(conn, signal_msg, NULL));
+    dbus_message_unref(signal_msg);
+}
+
+void broadcast_changed_remote_access(DBusConnection *conn, unsigned int index, const char *perm) {
+    DBusMessage *signal_msg;
+    DBusMessageIter msg_iter;
+
+    pa_log_error("test remote access");
+
+    if (!perm) {
+        pa_log_error("unknown permision");
+        return;
+    }
+
+    pa_assert_se((signal_msg = dbus_message_new_signal(STREAM_MANAGER_OBJECT_PATH, STREAM_MANAGER_INTERFACE, STREAM_MANAGER_SIGNAL_NAME_REMOTE_ACCESS)));
+    dbus_message_iter_init_append(signal_msg, &msg_iter);
+
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_UINT32, &index);
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &perm);
+
+    pa_assert_se(dbus_connection_send(conn, signal_msg, NULL));
+    dbus_message_unref(signal_msg);
+}
+
 int32_t init_sm_dbus(pa_stream_manager *m) {
     pa_assert(m);
 
index f78a063f8ced5e584ff345a86c2bcc8be8d29b43..19baf49e6064e8ef331719a47ed6a5ab3b0c8c73 100644 (file)
@@ -263,10 +263,13 @@ struct _stream_manager {
         *sink_input_ramp_finish_slot,
         *source_output_new_slot,
         *source_output_put_slot,
+        *remote_source_output_put_slot,
         *source_output_unlink_slot,
+        *remote_source_output_unlink_slot,
         *source_output_state_changed_slot,
         *source_output_move_start_slot,
-        *source_output_move_finish_slot;
+        *source_output_move_finish_slot,
+        *source_proplist_changed_slot;
 
 #ifdef HAVE_DBUS
 #ifdef USE_DBUS_PROTOCOL
index bc1906d5ef24d97e80e19fabddaeefd63885abea..b5e8b0746876383fc9e755d1b6ae138e6e1556a7 100644 (file)
@@ -28,6 +28,6 @@
 #define STREAM_MANAGER_METHOD_ARGS_BLOCK_RECORDING_MEDIA     "block_recording_media"
 
 int32_t handle_restrictions(pa_stream_manager *m, const char *name, uint32_t value);
-bool check_restrictions(pa_stream_manager *m, void *stream, stream_type_t stream_type);
+bool is_restricted(pa_stream_manager *m, void *stream, stream_type_t stream_type);
 
 #endif
index 818b6c5c3529cb7a9ab4e857cc950fc5f9ee140f..75f3c29ef29bd39a8ea55c373588832dc9f70618 100644 (file)
@@ -59,8 +59,10 @@ int32_t handle_restrictions(pa_stream_manager *m, const char *name, uint32_t val
     return 0;
 }
 
-bool check_restrictions(pa_stream_manager *m, void *stream, stream_type_t type) {
+bool is_restricted(pa_stream_manager *m, void *stream, stream_type_t type) {
     const char *role;
+    pa_source_output_new_data *data = (pa_source_output_new_data *)stream;
+    const char *media_remote_access = NULL;
 
     pa_assert(m);
 
@@ -73,5 +75,23 @@ bool check_restrictions(pa_stream_manager *m, void *stream, stream_type_t type)
         }
     }
 
+    /* ToDo : temporal test code */
+    pa_log("[REMOTE] driver = %s", data->driver);
+    pa_log("[REMOTE] source = %p, name = %s, driver = %s",
+            data->source,
+            data->source? data->source->name : "null",
+            data->source->driver);
+    pa_log("[REMOTE] dest source = %p, name = %s", data->destination_source, data->destination_source ? data->destination_source->name : "null");
+
+    if (pa_safe_streq(data->source->driver, "module-tunnel-source-new.c")) {
+        media_remote_access = pa_proplist_gets(data->source->proplist, PA_PROP_MEDIA_REMOTE_ACCESS);
+        pa_log("[REMOTE] %s = %s", PA_PROP_MEDIA_REMOTE_ACCESS, pa_strnull(media_remote_access));
+        if (pa_safe_streq(media_remote_access, "denied")) {
+            pa_log("[REMOTE] media remote access is currently denied!!!");
+            return true;
+        }
+    }
+    /* END */
+
     return false;
 }
index 981d4c62cc6f40358e6f0d830acf29ac93535c38..fce3b8d4df8b26b9118e4567c4a48922d57eeff0 100644 (file)
@@ -2635,7 +2635,7 @@ static pa_hook_result_t source_output_new_cb(pa_core *core, pa_source_output_new
     pa_log_debug("source-output-new-data(%p)", new_data);
 
     process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_PREPARE, true);
-    if (check_restrictions(m, new_data, STREAM_SOURCE_OUTPUT))
+    if (is_restricted(m, new_data, STREAM_SOURCE_OUTPUT))
         return PA_HOOK_CANCEL;
     process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_UPDATE_BUFFER_ATTR, true);
     process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_UPDATE_VOLUME, true);
@@ -2661,6 +2661,20 @@ static pa_hook_result_t source_output_put_cb(pa_core *core, pa_source_output *o,
     return PA_HOOK_OK;
 }
 
+static pa_hook_result_t remote_source_output_put_cb(pa_core *core, pa_source_output *o, pa_stream_manager *m) {
+    pa_core_assert_ref(core);
+    pa_source_output_assert_ref(o);
+
+    pa_log_info("source-output(%p, index:%u)", o, o->index);
+
+    if (pa_proplist_contains(o->proplist, PA_PROP_MEDIA_REMOTE_NAME)) {
+        const char *remote_name = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_REMOTE_NAME);
+        const char *peer_info = pa_proplist_gets(o->proplist, "native-protocol.peer");
+        pa_log_info("jcsing remote connected. %d %s %s", o->index, remote_name, peer_info);
+        broadcast_connected_remote_device(pa_dbus_connection_get(m->dbus_conn), true, o->index, remote_name, peer_info);
+    }
+}
+
 static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output *o, pa_stream_manager *m) {
     pa_core_assert_ref(core);
     pa_source_output_assert_ref(o);
@@ -2678,6 +2692,22 @@ static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output
     return PA_HOOK_OK;
 }
 
+static pa_hook_result_t remote_source_output_unlink_cb(pa_core *core, pa_source_output *o, pa_stream_manager *m) {
+    pa_core_assert_ref(core);
+    pa_source_output_assert_ref(o);
+
+    pa_log_info("source-output(%p, index:%u)", o, o->index);
+
+    if (pa_proplist_contains(o->proplist, PA_PROP_MEDIA_REMOTE_NAME)) {
+        const char *remote_name = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_REMOTE_NAME);
+        const char *peer_info = pa_proplist_gets(o->proplist, "native-protocol.peer");
+        pa_log_info("jcsing remote disconnected %d %s %s", o->index, remote_name, peer_info);
+        broadcast_connected_remote_device(pa_dbus_connection_get(m->dbus_conn), false, o->index, remote_name, peer_info);
+    }
+
+    return PA_HOOK_OK;
+}
+
 static pa_hook_result_t source_output_state_changed_cb(pa_core *core, pa_source_output *o, pa_stream_manager *m) {
     pa_assert(o);
     pa_assert(m);
@@ -2728,6 +2758,19 @@ static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_ou
     return PA_HOOK_OK;
 }
 
+static pa_hook_result_t source_proplist_changed(pa_core *core, pa_source *o, pa_stream_manager *m) {
+    pa_core_assert_ref(core);
+    pa_source_assert_ref(o);
+
+    const char *perm = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_REMOTE_ACCESS);
+    if (perm)
+        broadcast_changed_remote_access(pa_dbus_connection_get(m->dbus_conn), o->index, perm);
+
+    pa_log_error("jcsing test");
+
+    return PA_HOOK_OK;
+}
+
 static void find_next_device_for_auto_route(pa_stream_manager *m, stream_route_type_t route_type, const char *stream_role,
                                         stream_type_t stream_type, const char *cur_device_type, const char *preferred_device_role, pa_tz_device **next_device) {
     stream_info *si = NULL;
@@ -3726,11 +3769,15 @@ pa_stream_manager* pa_stream_manager_get(pa_core *c) {
     m->sink_input_ramp_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_RAMP_FINISH], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_ramp_finish_cb, m);
     m->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_new_cb, m);
     m->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_put_cb, m);
+    m->remote_source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) remote_source_output_put_cb, m);
     m->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_cb, m);
+    m->remote_source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_NORMAL, (pa_hook_cb_t) remote_source_output_unlink_cb, m);
     m->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_state_changed_cb, m);
     m->source_output_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_move_start_cb, m);
     m->source_output_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_move_finish_cb, m);
 
+    m->source_proplist_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_EARLY, (pa_hook_cb_t) source_proplist_changed, m);
+
     m->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_CLIENT | PA_SUBSCRIPTION_MASK_SAMPLE_CACHE, (pa_subscription_cb_t)subscribe_cb, m);
 
     m->comm.comm = pa_communicator_get(c);
@@ -3798,8 +3845,12 @@ static void free_hook_slots(pa_stream_manager *m) {
         pa_hook_slot_free(m->source_output_new_slot);
     if (m->source_output_put_slot)
         pa_hook_slot_free(m->source_output_put_slot);
+    if (m->remote_source_output_put_slot)
+        pa_hook_slot_free(m->remote_source_output_put_slot);
     if (m->source_output_unlink_slot)
         pa_hook_slot_free(m->source_output_unlink_slot);
+    if (m->remote_source_output_unlink_slot)
+        pa_hook_slot_free(m->remote_source_output_unlink_slot);
     if (m->source_output_state_changed_slot)
         pa_hook_slot_free(m->source_output_state_changed_slot);
     if (m->source_output_move_start_slot)