diff --git a/PROTOCOL b/PROTOCOL index 72d3af3c0..6ae9cb6da 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -452,6 +452,14 @@ parameters: The command returns a string, which may be empty or NULL (NULL should be treated the same as an empty string). +## v36, implemented by >= 16.0 + +Added signal sending capability and command PA_COMMAND_SUBSCRIBE_SIGNALS. +This command allows clients to subscribe to signals that PulseAudio sends. + +parameters: + uint64_t facility_mask - subscription mask + #### If you just changed the protocol, read this ## module-tunnel depends on the sink/source/sink-input/source-input protocol ## internals, so if you changed these, you might have broken module-tunnel. diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in index ca365dc23..191a3c860 100644 --- a/man/pactl.1.xml.in +++ b/man/pactl.1.xml.in @@ -303,7 +303,7 @@ License along with PulseAudio; if not, see . subscribe - Subscribe to events, pactl does not exit by itself, but keeps waiting for new events. + Subscribe to events and signals, pactl does not exit by itself, but keeps waiting for new events. diff --git a/src/pulse/def.h b/src/pulse/def.h index 4097bd89c..f8f43df68 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -615,6 +615,11 @@ typedef enum pa_subscription_event_type { PA_SUBSCRIPTION_EVENT_REMOVE = 0x0020U, /**< An object was removed */ +/** \cond fulldocs */ + PA_SUBSCRIPTION_EVENT_SIGNAL = 0x0040U, + /** A signal was issued. Only used internally. */ +/** \endcond */ + PA_SUBSCRIPTION_EVENT_TYPE_MASK = 0x0030U /**< A mask to extract the event operation from an event value */ @@ -650,6 +655,7 @@ typedef enum pa_subscription_event_type { #define PA_SUBSCRIPTION_EVENT_NEW PA_SUBSCRIPTION_EVENT_NEW #define PA_SUBSCRIPTION_EVENT_CHANGE PA_SUBSCRIPTION_EVENT_CHANGE #define PA_SUBSCRIPTION_EVENT_REMOVE PA_SUBSCRIPTION_EVENT_REMOVE +#define PA_SUBSCRIPTION_EVENT_SIGNAL PA_SUBSCRIPTION_EVENT_SIGNAL #define PA_SUBSCRIPTION_EVENT_TYPE_MASK PA_SUBSCRIPTION_EVENT_TYPE_MASK /** \endcond */ diff --git a/src/pulse/internal.h b/src/pulse/internal.h index a4797ff95..8998403a3 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -99,6 +99,8 @@ struct pa_context { void *subscribe_userdata; pa_context_event_cb_t event_callback; void *event_userdata; + pa_context_signal_cb_t signal_callback; + void *signal_userdata; pa_mempool *mempool; diff --git a/src/pulse/map-file b/src/pulse/map-file index 3df03cdd3..ffa107556 100644 --- a/src/pulse/map-file +++ b/src/pulse/map-file @@ -94,6 +94,7 @@ pa_context_set_default_source; pa_context_set_event_callback; pa_context_set_name; pa_context_set_port_latency_offset; +pa_context_set_signal_callback; pa_context_set_sink_input_mute; pa_context_set_sink_input_volume; pa_context_set_sink_mute_by_index; @@ -114,6 +115,7 @@ pa_context_set_state_callback; pa_context_set_subscribe_callback; pa_context_stat; pa_context_subscribe; +pa_context_subscribe_signals; pa_context_suspend_sink_by_index; pa_context_suspend_sink_by_name; pa_context_suspend_source_by_index; diff --git a/src/pulse/subscribe.c b/src/pulse/subscribe.c index e7cce2e31..fa5567fb2 100644 --- a/src/pulse/subscribe.c +++ b/src/pulse/subscribe.c @@ -32,7 +32,6 @@ void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_context *c = userdata; pa_subscription_event_type_t e; - uint32_t idx; pa_assert(pd); pa_assert(command == PA_COMMAND_SUBSCRIBE_EVENT); @@ -42,15 +41,44 @@ void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag pa_context_ref(c); - if (pa_tagstruct_getu32(t, &e) < 0 || - pa_tagstruct_getu32(t, &idx) < 0 || - !pa_tagstruct_eof(t)) { + if (pa_tagstruct_getu32(t, &e) < 0) { pa_context_fail(c, PA_ERR_PROTOCOL); goto finish; } - if (c->subscribe_callback) - c->subscribe_callback(c, e, idx, c->subscribe_userdata); + if (e != PA_SUBSCRIPTION_EVENT_SIGNAL) { + uint32_t idx; + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (c->subscribe_callback) + c->subscribe_callback(c, e, idx, c->subscribe_userdata); + + } else { + const char *object_path; + const char *signal; + const char *signal_parameters; + + if (pa_tagstruct_gets(t, &object_path) < 0 || + pa_tagstruct_gets(t, &signal) < 0 || + pa_tagstruct_gets(t, &signal_parameters) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (c->signal_callback) { + char *signal_parameter_copy; + + signal_parameter_copy = pa_xstrdup(signal_parameters); + c->signal_callback(c, object_path, signal, signal_parameter_copy, c->signal_userdata); + pa_xfree(signal_parameter_copy); + } + } finish: pa_context_unref(c); @@ -86,3 +114,34 @@ void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t c->subscribe_callback = cb; c->subscribe_userdata = userdata; } + +void pa_context_set_signal_callback(pa_context *c, pa_context_signal_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED) + return; + + c->signal_callback = cb; + c->signal_userdata = userdata; +} + +pa_operation* pa_context_subscribe_signals(pa_context *c, uint64_t signal_mask, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUBSCRIBE_SIGNALS, &tag); + pa_tagstruct_putu64(t, signal_mask); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} diff --git a/src/pulse/subscribe.h b/src/pulse/subscribe.h index b43c8ea44..a59ef5537 100644 --- a/src/pulse/subscribe.h +++ b/src/pulse/subscribe.h @@ -72,12 +72,21 @@ PA_C_DECL_BEGIN /** Subscription event callback prototype */ typedef void (*pa_context_subscribe_cb_t)(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata); +/** Signal event callback prototype, \since 16.0 */ +typedef void (*pa_context_signal_cb_t)(pa_context *c, const char *object_path, const char *signal, char *signal_parameters, void *userdata); + /** Enable event notification */ pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata); +/** Enable signal notification, \since 16.0 */ +pa_operation* pa_context_subscribe_signals(pa_context *c, uint64_t signal_mask, pa_context_success_cb_t cb, void *userdata); + /** Set the context specific call back function that is called whenever the state of the daemon changes */ void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t cb, void *userdata); +/** Set the context specific call back function that is called whenever pulseaudio sends a signal, \since 16.0 */ +void pa_context_set_signal_callback(pa_context *c, pa_context_signal_cb_t cb, void *userdata); + PA_C_DECL_END #endif diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index 7ac06f5d7..cdd083464 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -144,6 +144,7 @@ typedef enum pa_core_hook { PA_CORE_HOOK_SAMPLE_CACHE_NEW, PA_CORE_HOOK_SAMPLE_CACHE_CHANGED, PA_CORE_HOOK_SAMPLE_CACHE_UNLINK, + PA_CORE_HOOK_SEND_SIGNAL, PA_CORE_HOOK_MAX } pa_core_hook_t; diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c index 41adbfda7..0589f9516 100644 --- a/src/pulsecore/message-handler.c +++ b/src/pulsecore/message-handler.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "message-handler.h" @@ -69,6 +70,7 @@ static bool object_path_is_valid(const char *test_string) { /* Register message handler for the specified object. object_path must be a unique name starting with "/". */ void pa_message_handler_register(pa_core *c, const char *object_path, const char *description, pa_message_handler_cb_t cb, void *userdata) { struct pa_message_handler *handler; + char *sig_param; pa_assert(c); pa_assert(object_path); @@ -85,11 +87,17 @@ void pa_message_handler_register(pa_core *c, const char *object_path, const char handler->description = pa_xstrdup(description); pa_assert_se(pa_hashmap_put(c->message_handlers, handler->object_path, handler) == 0); + + /* Notify clients that a handler was added. */ + sig_param = pa_sprintf_malloc("\"%s\"", object_path); + pa_signal_post(c, "/core", 1, "handler-added", sig_param); + pa_xfree(sig_param); } /* Unregister a message handler */ void pa_message_handler_unregister(pa_core *c, const char *object_path) { struct pa_message_handler *handler; + char *sig_param; pa_assert(c); pa_assert(object_path); @@ -99,6 +107,11 @@ void pa_message_handler_unregister(pa_core *c, const char *object_path) { pa_xfree(handler->object_path); pa_xfree(handler->description); pa_xfree(handler); + + /* Notify clients that a handler was removed. */ + sig_param = pa_sprintf_malloc("\"%s\"", object_path); + pa_signal_post(c, "/core", 1, "handler-removed", sig_param); + pa_xfree(sig_param); } /* Send a message to an object identified by object_path */ @@ -165,6 +178,8 @@ int pa_message_handler_send_message(pa_core *c, const char *object_path, const c /* Set handler description */ int pa_message_handler_set_description(pa_core *c, const char *object_path, const char *description) { struct pa_message_handler *handler; + char *sig_param; + pa_json_encoder *encoder; pa_assert(c); pa_assert(object_path); @@ -172,8 +187,36 @@ int pa_message_handler_set_description(pa_core *c, const char *object_path, cons if (!(handler = pa_hashmap_get(c->message_handlers, object_path))) return -PA_ERR_NOENTITY; + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_string(encoder, "Handler name", object_path); + pa_json_encoder_add_member_string(encoder, "Old description", handler->description); + pa_json_encoder_add_member_string(encoder, "New description", description); + pa_json_encoder_end_object(encoder); + sig_param = pa_json_encoder_to_string_free(encoder); + pa_xfree(handler->description); handler->description = pa_xstrdup(description); + /* Notify clients that a handler description changed. */ + pa_signal_post(c, "/core", 1, "handler-changed", sig_param); + pa_xfree(sig_param); + return PA_OK; } + +/* Send a signal */ +void pa_signal_post(pa_core *c, const char *object_path, uint64_t facility, const char *signal, const char *signal_parameters) { + struct pa_signal_descriptor sd; + + pa_assert(object_path); + pa_assert(facility); + pa_assert(signal); + + sd.object_path = object_path; + sd.facility = facility; + sd.signal = signal; + sd.parameters = signal_parameters; + + pa_hook_fire(&c->hooks[PA_CORE_HOOK_SEND_SIGNAL], &sd); +} diff --git a/src/pulsecore/message-handler.h b/src/pulsecore/message-handler.h index 16dfc310e..474bf6bde 100644 --- a/src/pulsecore/message-handler.h +++ b/src/pulsecore/message-handler.h @@ -48,4 +48,17 @@ int pa_message_handler_send_message(pa_core *c, const char *object_path, const c /* Set handler description */ int pa_message_handler_set_description(pa_core *c, const char *object_path, const char *description); + +/* Signals */ + +/* Structure to pass signal information */ +struct pa_signal_descriptor { + const char *object_path; + const char *signal; + const char *parameters; + uint64_t facility; +}; + +/* Send a signal */ +void pa_signal_post(pa_core *c, const char *object_path, uint64_t facility, const char *signal, const char *signal_parameters); #endif diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index 3de960def..fd9c5f017 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -187,9 +187,12 @@ enum { * BOTH DIRECTIONS */ PA_COMMAND_REGISTER_MEMFD_SHMID, - /* Supported since protocol v34 (14.0) */ + /* Supported since protocol v35 (15.0) */ PA_COMMAND_SEND_OBJECT_MESSAGE, + /* Supported since protocol v36 (16.0) */ + PA_COMMAND_SUBSCRIBE_SIGNALS, + PA_COMMAND_MAX }; diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c index c2d26e63e..1dd056c7d 100644 --- a/src/pulsecore/pdispatch.c +++ b/src/pulsecore/pdispatch.c @@ -85,6 +85,7 @@ static const char *command_names[PA_COMMAND_MAX] = { [PA_COMMAND_GET_SOURCE_OUTPUT_INFO] = "GET_SOURCE_OUTPUT_INFO", [PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST] = "GET_SOURCE_OUTPUT_INFO_LIST", [PA_COMMAND_SUBSCRIBE] = "SUBSCRIBE", + [PA_COMMAND_SUBSCRIBE_SIGNALS] = "SUBSCRIBE_SIGNALS", [PA_COMMAND_SET_SINK_VOLUME] = "SET_SINK_VOLUME", [PA_COMMAND_SET_SINK_INPUT_VOLUME] = "SET_SINK_INPUT_VOLUME", diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 231d640aa..dd303c8df 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -187,6 +187,7 @@ struct pa_native_connection { pa_idxset *record_streams, *output_streams; uint32_t rrobin_index; pa_subscription *subscription; + uint64_t signal_mask; pa_time_event *auth_timeout_event; pa_srbchannel *srbpending; }; @@ -204,6 +205,8 @@ struct pa_native_protocol { pa_hook hooks[PA_NATIVE_HOOK_MAX]; pa_hashmap *extensions; + + pa_hook_slot *signal_hook_slot; }; enum { @@ -3762,6 +3765,26 @@ static void command_subscribe(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_pstream_send_simple_ack(c->pstream, tag); } +static void command_signal_subscribe(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint64_t m; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu64(t, &m) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + c->signal_mask = m; + + pa_pstream_send_simple_ack(c->pstream, tag); +} + static void command_set_volume( pa_pdispatch *pd, uint32_t command, @@ -4971,6 +4994,7 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_GET_SAMPLE_INFO_LIST] = command_get_info_list, [PA_COMMAND_GET_SERVER_INFO] = command_get_server_info, [PA_COMMAND_SUBSCRIBE] = command_subscribe, + [PA_COMMAND_SUBSCRIBE_SIGNALS] = command_signal_subscribe, [PA_COMMAND_SET_SINK_VOLUME] = command_set_volume, [PA_COMMAND_SET_SINK_INPUT_VOLUME] = command_set_volume, @@ -5319,6 +5343,33 @@ void pa_native_protocol_disconnect(pa_native_protocol *p, pa_module *m) { native_connection_unlink(c); } +static pa_hook_result_t native_protocol_signal_hook(pa_core *core, struct pa_signal_descriptor *sd, pa_native_protocol *p) { + pa_native_connection *c; + uint32_t idx; + + pa_assert(p); + pa_assert(sd); + + PA_IDXSET_FOREACH(c, p->connections, idx) { + if (sd->facility & c->signal_mask) { + pa_tagstruct *t; + + pa_native_connection_assert_ref(c); + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE_EVENT); + pa_tagstruct_putu32(t, (uint32_t) -1); + pa_tagstruct_putu32(t, PA_SUBSCRIPTION_EVENT_SIGNAL); + pa_tagstruct_puts(t, sd->object_path); + pa_tagstruct_puts(t, sd->signal); + pa_tagstruct_puts(t, sd->parameters); + pa_pstream_send_tagstruct(c->pstream, t); + } + } + + return PA_HOOK_OK; +} + static pa_native_protocol* native_protocol_new(pa_core *c) { pa_native_protocol *p; pa_native_hook_t h; @@ -5337,6 +5388,8 @@ static pa_native_protocol* native_protocol_new(pa_core *c) { for (h = 0; h < PA_NATIVE_HOOK_MAX; h++) pa_hook_init(&p->hooks[h], p); + p->signal_hook_slot = pa_hook_connect(&c->hooks[PA_CORE_HOOK_SEND_SIGNAL], PA_HOOK_NORMAL, (pa_hook_cb_t) native_protocol_signal_hook, p); + pa_assert_se(pa_shared_set(c, "native-protocol", p) >= 0); return p; @@ -5377,6 +5430,9 @@ void pa_native_protocol_unref(pa_native_protocol *p) { pa_strlist_free(p->servers); + if (p->signal_hook_slot) + pa_hook_slot_free(p->signal_hook_slot); + for (h = 0; h < PA_NATIVE_HOOK_MAX; h++) pa_hook_done(&p->hooks[h]); diff --git a/src/utils/pactl.c b/src/utils/pactl.c index 922817fa7..88c999ddd 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -2267,6 +2267,17 @@ static void context_subscribe_callback(pa_context *c, pa_subscription_event_type fflush(stdout); } +static void context_signal_callback(pa_context *c, const char *signal_object_path, const char *signal, char *signal_parameters, void *userdata) { + pa_assert(c); + + printf(_("Signal '%s' from %s\n"), + signal, + signal_object_path); + if (signal_parameters) + printf(_("Signal parameters: '%s'\n"), signal_parameters); + fflush(stdout); +} + static void context_state_callback(pa_context *c, void *userdata) { pa_operation *o = NULL; @@ -2537,6 +2548,15 @@ static void context_state_callback(pa_context *c, void *userdata) { PA_SUBSCRIPTION_MASK_CARD, NULL, NULL); + if (o) { + pa_operation_unref(o); + actions++; + } + + pa_context_set_signal_callback(c, context_signal_callback, NULL); + + o = pa_context_subscribe_signals(c, (uint64_t) -1, NULL, NULL); + break; default:
subscribe
Subscribe to events, pactl does not exit by itself, but keeps waiting for new events.
Subscribe to events and signals, pactl does not exit by itself, but keeps waiting for new events.