The client will send a list of extension-id along with SET_NAME. --- PROTOCOL | 12 ++++++ src/modules/module-stream-restore.c | 4 +- src/pulse/context.c | 51 ++++++++++++++++++++++++-- src/pulse/context.h | 3 ++ src/pulse/ext-stream-restore.c | 9 +++-- src/pulse/internal.h | 3 +- src/pulse/xmalloc.h | 13 +++++++ src/pulsecore/core-util.h | 16 ++++++++ src/pulsecore/native-common.h | 1 + src/pulsecore/protocol-native.c | 69 ++++++++++++++++++++++++++++++++--- src/pulsecore/protocol-native.h | 1 + src/pulsecore/strlist.c | 37 +++++++++++++++++++ src/pulsecore/strlist.h | 9 +++++ src/tests/strlist-test.c | 9 +++++ 14 files changed, 220 insertions(+), 17 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index 9955da5..9603051 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -183,6 +183,18 @@ new messages: PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED +### v15, implemented by >= 0.9.16 + +PA_COMMAND_SET_CLIENT_NAME, add at the end: + + uint32 n number of extension-version supported by the client + string n times of the form "extension-name-version" + +On response of PA_COMMAND_SET_CLIENT_NAME, add at the end: + + uint32 n number of extension-version supported by the server + string n times of the form "extension-name-version" + ### ext-stream-restore v1, implemented by >= 0.9.12 First version supported. diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index db30528..2fc34a2 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -648,7 +648,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio pa_tagstruct_put_cvolume(reply, e->volume_valid ? &e->volume : pa_cvolume_init(&r)); pa_tagstruct_puts(reply, e->device_valid ? e->device : NULL); pa_tagstruct_put_boolean(reply, e->muted_valid ? e->muted : FALSE); - if (c->version >= 16) /* FIXME: should be client side extension version */ + if (pa_native_connection_extension_supported(c, "stream-restore-2")) pa_tagstruct_put_boolean(reply, e->volume_is_absolute_valid ? e->volume_is_absolute : FALSE); pa_xfree(e); @@ -695,7 +695,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio pa_tagstruct_get_boolean(t, &muted) < 0) goto fail; - if (c->version >= 16) { + if (pa_native_connection_extension_supported(c, "stream-restore-2")) { if (pa_tagstruct_get_boolean(t, &volume_is_absolute) < 0) goto fail; diff --git a/src/pulse/context.c b/src/pulse/context.c index 90c15b1..5443bac 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -168,8 +168,6 @@ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char * reset_callbacks(c); - c->ext_stream_restore.version = 1; /* default version, updated by TEST message */ - c->is_local = FALSE; c->server_list = NULL; c->server = NULL; @@ -205,6 +203,8 @@ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char * } } + c->supported_extensions = NULL; + return c; } @@ -276,6 +276,9 @@ static void context_free(pa_context *c) { if (c->proplist) pa_proplist_free(c->proplist); + + pa_strv_free(c->supported_extensions); + pa_xfree(c->server); pa_xfree(c); } @@ -498,6 +501,12 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t } else pa_tagstruct_puts(reply, pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)); + if (c->version >= 16) { + pa_tagstruct_putu32(reply, 2); + pa_tagstruct_puts(reply, "stream-restore-1"); + pa_tagstruct_puts(reply, "stream-restore-2"); + } + pa_pstream_send_tagstruct(c->pstream, reply); pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c, NULL); @@ -508,8 +517,35 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t case PA_CONTEXT_SETTING_NAME : if ((c->version >= 13 && (pa_tagstruct_getu32(t, &c->client_index) < 0 || - c->client_index == PA_INVALID_INDEX)) || - !pa_tagstruct_eof(t)) { + c->client_index == PA_INVALID_INDEX))) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (c->version >= 16) { + uint32_t n; + const char *supported; + + if (pa_tagstruct_getu32(t, &n) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + c->supported_extensions = pa_xnew(char *, n + 1); + c->supported_extensions[n] = NULL; + + while (n) { + --n; + if (pa_tagstruct_gets(t, &supported) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + c->supported_extensions[n] = pa_xstrdup(supported); + } + } + + if (!pa_tagstruct_eof(t)) { pa_context_fail(c, PA_ERR_PROTOCOL); goto finish; } @@ -1444,3 +1480,10 @@ finish: if (pl) pa_proplist_free(pl); } + +int pa_context_extension_supported(pa_context *c, const char *extension) { + pa_assert(c); + pa_assert(extension); + + return !pa_strv_index((const char * const *)c->supported_extensions, extension, NULL); +} diff --git a/src/pulse/context.h b/src/pulse/context.h index 139d0e0..8762b51 100644 --- a/src/pulse/context.h +++ b/src/pulse/context.h @@ -260,6 +260,9 @@ pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[] * introspection functions, such as pa_context_get_client_info(). \since 0.9.11 */ uint32_t pa_context_get_index(pa_context *s); +/** Check if "extension" is supported in server side. \since 0.9.16 */ +int pa_context_extension_supported(pa_context *c, const char *extension); + PA_C_DECL_END #endif diff --git a/src/pulse/ext-stream-restore.c b/src/pulse/ext-stream-restore.c index a361ff4..ba05a3f 100644 --- a/src/pulse/ext-stream-restore.c +++ b/src/pulse/ext-stream-restore.c @@ -46,6 +46,7 @@ enum { }; static void ext_stream_restore_test_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + uint32_t version; pa_operation *o = userdata; pa_assert(pd); @@ -59,7 +60,7 @@ static void ext_stream_restore_test_cb(pa_pdispatch *pd, uint32_t command, uint3 if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; - } else if (pa_tagstruct_getu32(t, &o->context->ext_stream_restore.version) < 0 || + } else if (pa_tagstruct_getu32(t, &version) < 0 || !pa_tagstruct_eof(t)) { pa_context_fail(o->context, PA_ERR_PROTOCOL); @@ -68,7 +69,7 @@ static void ext_stream_restore_test_cb(pa_pdispatch *pd, uint32_t command, uint3 if (o->callback) { pa_ext_stream_restore_test_cb_t cb = (pa_ext_stream_restore_test_cb_t) o->callback; - cb(o->context, o->context->ext_stream_restore.version, o->userdata); + cb(o->context, version, o->userdata); } finish: @@ -173,7 +174,7 @@ static void ext_stream_restore2_read_cb(pa_pdispatch *pd, uint32_t command, uint goto finish; } - if (o->context->ext_stream_restore.version >= 2 && + if (pa_context_extension_supported(o->context, "stream-restore-2") && pa_tagstruct_get_boolean(t, &volume_is_absolute) < 0) { pa_context_fail(o->context, PA_ERR_PROTOCOL); @@ -307,7 +308,7 @@ pa_operation *pa_ext_stream_restore2_write( pa_tagstruct_put_cvolume(t, &data[i]->volume); pa_tagstruct_puts(t, data[i]->device); pa_tagstruct_put_boolean(t, data[i]->mute); - if (c->ext_stream_restore.version >= 2) + if (pa_context_extension_supported(c, "stream-restore-2")) pa_tagstruct_put_boolean(t, data[i]->volume_is_absolute); } diff --git a/src/pulse/internal.h b/src/pulse/internal.h index 3c58c49..d1ac12c 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -99,11 +99,12 @@ struct pa_context { uint32_t client_index; + char **supported_extensions; + /* Extension specific data */ struct { pa_ext_stream_restore_subscribe_cb_t callback; void *userdata; - uint32_t version; } ext_stream_restore; }; diff --git a/src/pulse/xmalloc.h b/src/pulse/xmalloc.h index db20496..39f2d0d 100644 --- a/src/pulse/xmalloc.h +++ b/src/pulse/xmalloc.h @@ -91,6 +91,19 @@ static inline void* _pa_xnewdup_internal(const void *p, size_t n, size_t k) { /** Same as pa_xnew() but set the memory to zero */ #define pa_xnewdup(type, p, n) ((type*) _pa_xnewdup_internal((p), (n), sizeof(type))) +/** Free a string array and its strings */ +static inline void pa_strv_free(char *strv[]) { + unsigned i; + + if (!strv) + return; + + for (i = 0; strv[i]; ++i) + pa_xfree(strv[i]); + + pa_xfree(strv); +} + PA_C_DECL_END #endif diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index f96fa44..6f929e4 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -201,6 +201,22 @@ pa_bool_t pa_in_system_mode(void); #define pa_streq(a,b) (!strcmp((a),(b))) +static inline int pa_strv_index(const char * const *strv, const char *needle, unsigned *idx) { + unsigned i; + + if (!strv) + return -1; + + for (i = 0; strv[i]; ++i) + if (pa_streq(strv[i], needle)) { + if (idx) + *idx = i; + return 0; + } + + return -1; +} + char *pa_machine_id(void); char *pa_session_id(void); char *pa_uname_string(void); diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index d4d7f3e..19b8c22 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -25,6 +25,7 @@ #include <pulse/cdecl.h> #include <pulse/def.h> +#include <pulsecore/macro.h> PA_C_DECL_BEGIN diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 5db080f..38c8409 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -178,6 +178,7 @@ struct pa_native_connection { uint32_t rrobin_index; pa_subscription *subscription; pa_time_event *auth_timeout_event; + char **supported_extensions; }; PA_DECLARE_CLASS(pa_native_connection); @@ -1233,6 +1234,8 @@ static void native_connection_free(pa_object *o) { pa_pstream_unref(c->pstream); pa_client_free(c->client); + pa_strv_free(c->supported_extensions); + pa_xfree(c); } @@ -2398,11 +2401,25 @@ static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta #endif } +static pa_strlist *get_extension_list(pa_native_connection *c) { + void *state = NULL, *lstate = NULL; + native_protocol_ext *ext; + pa_strlist *l = NULL; + const char *e; + + while ((ext = pa_hashmap_iterate(c->protocol->extensions, &state, NULL))) + while ((e = pa_strlist_iterate(ext->supported, &lstate))) + l = pa_strlist_prepend(l, e); + + return l; +} + static void command_set_client_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); - const char *name = NULL; + const char *name = NULL, *ext; pa_proplist *p; pa_tagstruct *reply; + void *state = NULL; pa_native_connection_assert_ref(c); pa_assert(t); @@ -2410,14 +2427,29 @@ static void command_set_client_name(pa_pdispatch *pd, uint32_t command, uint32_t p = pa_proplist_new(); if ((c->version < 13 && pa_tagstruct_gets(t, &name) < 0) || - (c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) || - !pa_tagstruct_eof(t)) { + (c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0)) + goto error; - protocol_error(c); - pa_proplist_free(p); - return; + if (c->version >= 16) { + uint32_t n; + + if (pa_tagstruct_getu32(t, &n) < 0) + goto error; + + c->supported_extensions = pa_xnew(char *, n + 1); + c->supported_extensions[n] = NULL; + + while (n) { + --n; + if (pa_tagstruct_gets(t, &ext) < 0) + goto error; + c->supported_extensions[n] = pa_xstrdup(ext); + } } + if (!pa_tagstruct_eof(t)) + goto error; + if (name) if (pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); @@ -2433,7 +2465,25 @@ static void command_set_client_name(pa_pdispatch *pd, uint32_t command, uint32_t if (c->version >= 13) pa_tagstruct_putu32(reply, c->client->index); + if (c->version >= 16) { + pa_strlist *extl; + + extl = get_extension_list(c); + + pa_tagstruct_putu32(reply, pa_strlist_size(extl)); + + while ((ext = pa_strlist_iterate(extl, &state))) + pa_tagstruct_puts(reply, ext); + + pa_strlist_free(extl); + } + pa_pstream_send_tagstruct(c->pstream, reply); + return; + +error: + protocol_error(c); + pa_proplist_free(p); } static void command_lookup(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { @@ -4474,6 +4524,7 @@ void pa_native_protocol_connect(pa_native_protocol *p, pa_iochannel *io, pa_nati c->rrobin_index = PA_IDXSET_INVALID; c->subscription = NULL; + c->supported_extensions = NULL; pa_idxset_put(p->connections, c, NULL); @@ -4751,3 +4802,9 @@ pa_pstream* pa_native_connection_get_pstream(pa_native_connection *c) { return c->pstream; } + +int pa_native_connection_extension_supported(pa_native_connection *c, const char *extension) { + pa_native_connection_assert_ref(c); + + return !pa_strv_index((const char * const *)c->supported_extensions, extension, NULL); +} diff --git a/src/pulsecore/protocol-native.h b/src/pulsecore/protocol-native.h index 7bd159a..9962125 100644 --- a/src/pulsecore/protocol-native.h +++ b/src/pulsecore/protocol-native.h @@ -82,6 +82,7 @@ int pa_native_protocol_install_ext(pa_native_protocol *p, pa_module *m, pa_nativ void pa_native_protocol_remove_ext(pa_native_protocol *p, pa_module *m); pa_pstream* pa_native_connection_get_pstream(pa_native_connection *c); +int pa_native_connection_extension_supported(pa_native_connection *c, const char *extension); pa_native_options* pa_native_options_new(void); pa_native_options* pa_native_options_ref(pa_native_options *o); diff --git a/src/pulsecore/strlist.c b/src/pulsecore/strlist.c index cbafbba..86f5797 100644 --- a/src/pulsecore/strlist.c +++ b/src/pulsecore/strlist.c @@ -159,3 +159,40 @@ pa_strlist *pa_strlist_reverse(pa_strlist *l) { return r; } + +const char* pa_strlist_iterate(pa_strlist *l, void **state) { + pa_strlist *e; + + pa_assert(state); + + if (*state == (void*) -1) + goto at_end; + + if (!*state && !l) + goto at_end; + + e = *state ? *state : l; + + if (e->next) + *state = e->next; + else + *state = (void*) -1; + + return ITEM_TO_TEXT(e); + +at_end: + *state = (void *) -1; + + return NULL; +} + +unsigned pa_strlist_size(pa_strlist *l) { + unsigned n = 0; + + while (l) { + l = l->next; + ++n; + } + + return n; +} diff --git a/src/pulsecore/strlist.h b/src/pulsecore/strlist.h index 2584e86..b85dcc4 100644 --- a/src/pulsecore/strlist.h +++ b/src/pulsecore/strlist.h @@ -47,4 +47,13 @@ pa_strlist* pa_strlist_parse(const char *s); /* Reverse string list */ pa_strlist *pa_strlist_reverse(pa_strlist *l); +/* May be used to iterate through the strlist. Initially the opaque + pointer *state has to be set to NULL. The strlist may not be + modified during iteration. After the last entry in the strlist NULL + is returned. */ +const char* pa_strlist_iterate(pa_strlist *l, void **state); + +/* Return the current number of entries of the strlist */ +unsigned pa_strlist_size(pa_strlist *l); + #endif diff --git a/src/tests/strlist-test.c b/src/tests/strlist-test.c index 10f370c..adbf492 100644 --- a/src/tests/strlist-test.c +++ b/src/tests/strlist-test.c @@ -6,8 +6,10 @@ #include <pulsecore/strlist.h> int main(int argc, char* argv[]) { + const char *c; char *t, *u; pa_strlist *l = NULL; + void *state = NULL; l = pa_strlist_prepend(l, "e"); l = pa_strlist_prepend(l, "d"); @@ -37,6 +39,13 @@ int main(int argc, char* argv[]) { fprintf(stderr, "4: %s\n", t); pa_xfree(t); + fprintf(stderr, "5:"); + while ((c = pa_strlist_iterate(l, &state))) + fprintf(stderr, " %s", c); + fprintf(stderr, "\n"); + + fprintf(stderr, "6: %d\n", pa_strlist_size(l)); + pa_strlist_free(l); return 0; -- 1.6.2.4