diff options
Diffstat (limited to 'src/modules')
48 files changed, 2778 insertions, 408 deletions
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index 41e8b477..59a5ca75 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -116,6 +116,8 @@ struct userdata { pa_reserve_wrapper *reserve; pa_hook_slot *reserve_slot; + pa_reserve_monitor_wrapper *monitor; + pa_hook_slot *monitor_slot; }; static void userdata_free(struct userdata *u); @@ -124,7 +126,7 @@ static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct u pa_assert(r); pa_assert(u); - if (pa_sink_suspend(u->sink, TRUE) < 0) + if (pa_sink_suspend(u->sink, TRUE, PA_SUSPEND_APPLICATION) < 0) return PA_HOOK_CANCEL; return PA_HOOK_OK; @@ -185,6 +187,57 @@ static int reserve_init(struct userdata *u, const char *dname) { return 0; } +static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) { + pa_bool_t b; + + pa_assert(w); + pa_assert(u); + + b = PA_PTR_TO_UINT(busy) && !u->reserve; + + pa_sink_suspend(u->sink, b, PA_SUSPEND_APPLICATION); + return PA_HOOK_OK; +} + +static void monitor_done(struct userdata *u) { + pa_assert(u); + + if (u->monitor_slot) { + pa_hook_slot_free(u->monitor_slot); + u->monitor_slot = NULL; + } + + if (u->monitor) { + pa_reserve_monitor_wrapper_unref(u->monitor); + u->monitor = NULL; + } +} + +static int reserve_monitor_init(struct userdata *u, const char *dname) { + char *rname; + + pa_assert(u); + pa_assert(dname); + + if (pa_in_system_mode()) + return 0; + + /* We are resuming, try to lock the device */ + if (!(rname = pa_alsa_get_reserve_name(dname))) + return 0; + + u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname); + pa_xfree(rname); + + if (!(u->monitor)) + return -1; + + pa_assert(!u->monitor_slot); + u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u); + + return 0; +} + static void fix_min_sleep_wakeup(struct userdata *u) { size_t max_use, max_use_2; @@ -473,7 +526,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle u->since_start += frames * u->frame_size; #ifdef DEBUG_TIMING - pa_log_debug("Wrote %lu bytes", (unsigned long) (frames * u->frame_size)); + pa_log_debug("Wrote %lu bytes (of possible %lu bytes)", (unsigned long) (frames * u->frame_size), (unsigned long) n_bytes); #endif if ((size_t) frames * u->frame_size >= n_bytes) @@ -1146,7 +1199,7 @@ fail: static void sink_get_mute_cb(pa_sink *s) { struct userdata *u = s->userdata; - int err, sw; + int err, sw = 0; pa_assert(u); pa_assert(u->mixer_elem); @@ -1508,6 +1561,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca size_t frame_size; pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE; pa_sink_new_data data; + char *control_device = NULL; pa_assert(m); pa_assert(ma); @@ -1579,9 +1633,14 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca pa_rtclock_usec(), TRUE); - if (reserve_init(u, pa_modargs_get_value( - ma, "device_id", - pa_modargs_get_value(ma, "device", DEFAULT_DEVICE))) < 0) + dev_id = pa_modargs_get_value( + ma, "device_id", + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + + if (reserve_init(u, dev_id) < 0) + goto fail; + + if (reserve_monitor_init(u, dev_id) < 0) goto fail; b = use_mmap; @@ -1664,7 +1723,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - pa_alsa_find_mixer_and_elem(u->pcm_handle, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); + pa_alsa_find_mixer_and_elem(u->pcm_handle, &control_device, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); pa_sink_new_data_init(&data); data.driver = driver; @@ -1687,6 +1746,17 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca pa_alsa_init_description(data.proplist); + if (control_device) { + pa_alsa_init_proplist_ctl(data.proplist, control_device); + pa_xfree(control_device); + } + + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY|(u->use_tsched ? PA_SINK_DYNAMIC_LATENCY : 0)); pa_sink_new_data_done(&data); @@ -1730,7 +1800,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca pa_log_info("Time scheduling watermark is %0.2fms", (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); } else - u->sink->fixed_latency = pa_bytes_to_usec(u->hwbuf_size, &ss); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->hwbuf_size, &ss)); reserve_update(u); @@ -1770,7 +1840,8 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca fail: - userdata_free(u); + if (u) + userdata_free(u); return NULL; } @@ -1815,6 +1886,7 @@ static void userdata_free(struct userdata *u) { pa_smoother_free(u->smoother); reserve_done(u); + monitor_done(u); pa_xfree(u->device_name); pa_xfree(u); diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index 843f70bb..c176309e 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -114,6 +114,8 @@ struct userdata { pa_reserve_wrapper *reserve; pa_hook_slot *reserve_slot; + pa_reserve_monitor_wrapper *monitor; + pa_hook_slot *monitor_slot; }; static void userdata_free(struct userdata *u); @@ -122,7 +124,7 @@ static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct u pa_assert(r); pa_assert(u); - if (pa_source_suspend(u->source, TRUE) < 0) + if (pa_source_suspend(u->source, TRUE, PA_SUSPEND_APPLICATION) < 0) return PA_HOOK_CANCEL; return PA_HOOK_OK; @@ -183,6 +185,57 @@ static int reserve_init(struct userdata *u, const char *dname) { return 0; } +static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) { + pa_bool_t b; + + pa_assert(w); + pa_assert(u); + + b = PA_PTR_TO_UINT(busy) && !u->reserve; + + pa_source_suspend(u->source, b, PA_SUSPEND_APPLICATION); + return PA_HOOK_OK; +} + +static void monitor_done(struct userdata *u) { + pa_assert(u); + + if (u->monitor_slot) { + pa_hook_slot_free(u->monitor_slot); + u->monitor_slot = NULL; + } + + if (u->monitor) { + pa_reserve_monitor_wrapper_unref(u->monitor); + u->monitor = NULL; + } +} + +static int reserve_monitor_init(struct userdata *u, const char *dname) { + char *rname; + + pa_assert(u); + pa_assert(dname); + + if (pa_in_system_mode()) + return 0; + + /* We are resuming, try to lock the device */ + if (!(rname = pa_alsa_get_reserve_name(dname))) + return 0; + + u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname); + pa_xfree(rname); + + if (!(u->monitor)) + return -1; + + pa_assert(!u->monitor_slot); + u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u); + + return 0; +} + static void fix_min_sleep_wakeup(struct userdata *u) { size_t max_use, max_use_2; pa_assert(u); @@ -455,7 +508,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled u->read_count += frames * u->frame_size; #ifdef DEBUG_TIMING - pa_log_debug("Read %lu bytes", (unsigned long) (frames * u->frame_size)); + pa_log_debug("Read %lu bytes (of possible %lu bytes)", (unsigned long) (frames * u->frame_size), (unsigned long) n_bytes); #endif if ((size_t) frames * u->frame_size >= n_bytes) @@ -1105,7 +1158,7 @@ fail: static void source_get_mute_cb(pa_source *s) { struct userdata *u = s->userdata; - int err, sw; + int err, sw = 0; pa_assert(u); pa_assert(u->mixer_elem); @@ -1366,6 +1419,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p size_t frame_size; pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE; pa_source_new_data data; + char *control_device = NULL; pa_assert(m); pa_assert(ma); @@ -1437,9 +1491,14 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_rtclock_usec(), FALSE); - if (reserve_init(u, pa_modargs_get_value( - ma, "device_id", - pa_modargs_get_value(ma, "device", DEFAULT_DEVICE))) < 0) + dev_id = pa_modargs_get_value( + ma, "device_id", + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + + if (reserve_init(u, dev_id) < 0) + goto fail; + + if (reserve_monitor_init(u, dev_id) < 0) goto fail; b = use_mmap; @@ -1519,7 +1578,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - pa_alsa_find_mixer_and_elem(u->pcm_handle, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); + pa_alsa_find_mixer_and_elem(u->pcm_handle, &control_device, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); pa_source_new_data_init(&data); data.driver = driver; @@ -1542,6 +1601,17 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_alsa_init_description(data.proplist); + if (control_device) { + pa_alsa_init_proplist_ctl(data.proplist, control_device); + pa_xfree(control_device); + } + + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|(u->use_tsched ? PA_SOURCE_DYNAMIC_LATENCY : 0)); pa_source_new_data_done(&data); @@ -1582,7 +1652,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_log_info("Time scheduling watermark is %0.2fms", (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); } else - u->source->fixed_latency = pa_bytes_to_usec(u->hwbuf_size, &ss); + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->hwbuf_size, &ss)); reserve_update(u); @@ -1621,7 +1691,8 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p fail: - userdata_free(u); + if (u) + userdata_free(u); return NULL; } @@ -1663,6 +1734,7 @@ static void userdata_free(struct userdata *u) { pa_smoother_free(u->smoother); reserve_done(u); + monitor_done(u); pa_xfree(u->device_name); pa_xfree(u); diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index 18d6880e..c03866cc 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -189,15 +189,6 @@ struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { fdl = pa_xnew0(struct pa_alsa_fdlist, 1); - fdl->num_fds = 0; - fdl->fds = NULL; - fdl->work_fds = NULL; - fdl->mixer = NULL; - fdl->m = NULL; - fdl->defer = NULL; - fdl->ios = NULL; - fdl->polled = FALSE; - return fdl; } @@ -281,6 +272,11 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; + pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", + snd_pcm_format_description(format_trans[*f]), + pa_alsa_strerror(ret)); + + if (*f == PA_SAMPLE_FLOAT32BE) *f = PA_SAMPLE_FLOAT32LE; else if (*f == PA_SAMPLE_FLOAT32LE) @@ -307,6 +303,10 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; + pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", + snd_pcm_format_description(format_trans[*f]), + pa_alsa_strerror(ret)); + try_auto: for (i = 0; try_order[i] != PA_SAMPLE_INVALID; i++) { @@ -314,6 +314,10 @@ try_auto: if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; + + pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", + snd_pcm_format_description(format_trans[*f]), + pa_alsa_strerror(ret)); } return -1; @@ -334,7 +338,6 @@ int pa_alsa_set_hw_params( int ret = -1; snd_pcm_uframes_t _period_size = period_size ? *period_size : 0; unsigned int _periods = periods ? *periods : 0; - snd_pcm_uframes_t buffer_size; unsigned int r = ss->rate; unsigned int c = ss->channels; pa_sample_format_t f = ss->format; @@ -348,11 +351,15 @@ int pa_alsa_set_hw_params( snd_pcm_hw_params_alloca(&hwparams); - if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) + if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) { + pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret)); goto finish; + } - if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) + if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_rate_resample() failed: %s", pa_alsa_strerror(ret)); goto finish; + } if (_use_mmap) { @@ -360,14 +367,18 @@ int pa_alsa_set_hw_params( /* mmap() didn't work, fall back to interleaved */ - if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret)); goto finish; + } _use_mmap = FALSE; } - } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret)); goto finish; + } if (!_use_mmap) _use_tsched = FALSE; @@ -375,54 +386,70 @@ int pa_alsa_set_hw_params( if ((ret = set_format(pcm_handle, hwparams, &f)) < 0) goto finish; - if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) + if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_rate_near() failed: %s", pa_alsa_strerror(ret)); goto finish; + } if (require_exact_channel_number) { - if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) + if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_channels() failed: %s", pa_alsa_strerror(ret)); goto finish; + } } else { - if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) + if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_channels_near() failed: %s", pa_alsa_strerror(ret)); goto finish; + } } - if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) + if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_periods_integer() failed: %s", pa_alsa_strerror(ret)); goto finish; + } if (_period_size && tsched_size && _periods) { + /* Adjust the buffer sizes, if we didn't get the rate we were asking for */ _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * r) / ss->rate); tsched_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * r) / ss->rate); if (_use_tsched) { - _period_size = tsched_size; - _periods = 1; + snd_pcm_uframes_t buffer_size; pa_assert_se(snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size) == 0); pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r); + + _period_size = tsched_size; + _periods = 1; } - buffer_size = _periods * _period_size; + if (_period_size > 0 && _periods > 0) { + snd_pcm_uframes_t buffer_size; + + buffer_size = _periods * _period_size; + + if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0) + pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret)); + } if (_periods > 0) { - /* First we pass 0 as direction to get exactly what we asked - * for. That this is necessary is presumably a bug in ALSA */ + /* First we pass 0 as direction to get exactly what we + * asked for. That this is necessary is presumably a bug + * in ALSA. All in all this is mostly a hint to ALSA, so + * we don't care if this fails. */ dir = 0; - if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { + if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir) < 0) { dir = 1; - if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { + if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir) < 0) { dir = -1; if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) - goto finish; + pa_log_info("snd_pcm_hw_params_set_periods_near() failed: %s", pa_alsa_strerror(ret)); } } } - - if (_period_size > 0) - if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0) - goto finish; } if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) @@ -528,7 +555,7 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) { static const struct pa_alsa_profile_info device_table[] = { {{ 1, { PA_CHANNEL_POSITION_MONO }}, - "hw", + "hw", NULL, N_("Analog Mono"), "analog-mono", 1, @@ -536,7 +563,7 @@ static const struct pa_alsa_profile_info device_table[] = { "Capture", "Mic" }, {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, - "front", + "front", "hw", N_("Analog Stereo"), "analog-stereo", 10, @@ -544,7 +571,7 @@ static const struct pa_alsa_profile_info device_table[] = { "Capture", "Mic" }, {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, - "iec958", + "iec958", NULL, N_("Digital Stereo (IEC958)"), "iec958-stereo", 5, @@ -552,7 +579,7 @@ static const struct pa_alsa_profile_info device_table[] = { "IEC958 In", NULL }, {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, - "hdmi", + "hdmi", NULL, N_("Digital Stereo (HDMI)"), "hdmi-stereo", 4, @@ -561,7 +588,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, - "surround40", + "surround40", NULL, N_("Analog Surround 4.0"), "analog-surround-40", 7, @@ -570,7 +597,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, - "a52", + "a52", NULL, N_("Digital Surround 4.0 (IEC958/AC3)"), "iec958-ac3-surround-40", 2, @@ -580,7 +607,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE }}, - "surround41", + "surround41", NULL, N_("Analog Surround 4.1"), "analog-surround-41", 7, @@ -590,7 +617,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_CENTER }}, - "surround50", + "surround50", NULL, N_("Analog Surround 5.0"), "analog-surround-50", 7, @@ -600,7 +627,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE }}, - "surround51", + "surround51", NULL, N_("Analog Surround 5.1"), "analog-surround-51", 8, @@ -610,7 +637,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE}}, - "a52", + "a52", NULL, N_("Digital Surround 5.1 (IEC958/AC3)"), "iec958-ac3-surround-51", 3, @@ -621,16 +648,72 @@ static const struct pa_alsa_profile_info device_table[] = { PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }}, - "surround71", + "surround71", NULL, N_("Analog Surround 7.1"), "analog-surround-71", 7, "Master", "PCM", "Capture", "Mic" }, - {{ 0, { 0 }}, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL } + {{ 0, { 0 }}, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL } }; +static snd_pcm_t *open_by_device_string_with_fallback( + const char *prefix, + const char *prefix_fallback, + const char *dev_id, + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + uint32_t *nfrags, + snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched, + pa_bool_t require_exact_channel_number) { + + snd_pcm_t *pcm_handle; + char *d; + + d = pa_sprintf_malloc("%s:%s", prefix, dev_id); + + pcm_handle = pa_alsa_open_by_device_string( + d, + dev, + ss, + map, + mode, + nfrags, + period_size, + tsched_size, + use_mmap, + use_tsched, + require_exact_channel_number); + pa_xfree(d); + + if (!pcm_handle && prefix_fallback) { + + d = pa_sprintf_malloc("%s:%s", prefix_fallback, dev_id); + + pcm_handle = pa_alsa_open_by_device_string( + d, + dev, + ss, + map, + mode, + nfrags, + period_size, + tsched_size, + use_mmap, + use_tsched, + require_exact_channel_number); + pa_xfree(d); + } + + return pcm_handle; +} + snd_pcm_t *pa_alsa_open_by_device_id_auto( const char *dev_id, char **dev, @@ -671,14 +754,14 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( pa_log_debug("Checking for %s (%s)", device_table[i].name, device_table[i].alsa_name); - d = pa_sprintf_malloc("%s:%s", device_table[i].alsa_name, dev_id); - try_ss.channels = device_table[i].map.channels; try_ss.rate = ss->rate; try_ss.format = ss->format; - pcm_handle = pa_alsa_open_by_device_string( - d, + pcm_handle = open_by_device_string_with_fallback( + device_table[i].alsa_name, + device_table[i].alsa_name_fallback, + dev_id, dev, &try_ss, map, @@ -690,8 +773,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( use_tsched, TRUE); - pa_xfree(d); - if (pcm_handle) { *ss = try_ss; @@ -703,6 +784,7 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( return pcm_handle; } + } if (direction > 0) { @@ -775,7 +857,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile( pa_bool_t *use_tsched, const pa_alsa_profile_info *profile) { - char *d; snd_pcm_t *pcm_handle; pa_sample_spec try_ss; @@ -787,14 +868,14 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile( pa_assert(period_size); pa_assert(profile); - d = pa_sprintf_malloc("%s:%s", profile->alsa_name, dev_id); - try_ss.channels = profile->map.channels; try_ss.rate = ss->rate; try_ss.format = ss->format; - pcm_handle = pa_alsa_open_by_device_string( - d, + pcm_handle = open_by_device_string_with_fallback( + profile->alsa_name, + profile->alsa_name_fallback, + dev_id, dev, &try_ss, map, @@ -806,8 +887,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile( use_tsched, TRUE); - pa_xfree(d); - if (!pcm_handle) return NULL; @@ -860,6 +939,8 @@ snd_pcm_t *pa_alsa_open_by_device_string( goto fail; } + pa_log_debug("Managed to open %s", d); + if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, require_exact_channel_number)) < 0) { if (!reformat) { @@ -928,26 +1009,25 @@ int pa_alsa_probe_profiles( snd_pcm_t *pcm_i = NULL; if (i->alsa_name) { - char *id; pa_sample_spec try_ss; pa_channel_map try_map; pa_log_debug("Checking for playback on %s (%s)", i->name, i->alsa_name); - id = pa_sprintf_malloc("%s:%s", i->alsa_name, dev_id); try_ss = *ss; try_ss.channels = i->map.channels; try_map = i->map; - pcm_i = pa_alsa_open_by_device_string( - id, NULL, + pcm_i = open_by_device_string_with_fallback( + i->alsa_name, + i->alsa_name_fallback, + dev_id, + NULL, &try_ss, &try_map, SND_PCM_STREAM_PLAYBACK, NULL, NULL, 0, NULL, NULL, TRUE); - pa_xfree(id); - if (!pcm_i) continue; } @@ -956,26 +1036,25 @@ int pa_alsa_probe_profiles( snd_pcm_t *pcm_j = NULL; if (j->alsa_name) { - char *jd; pa_sample_spec try_ss; pa_channel_map try_map; pa_log_debug("Checking for capture on %s (%s)", j->name, j->alsa_name); - jd = pa_sprintf_malloc("%s:%s", j->alsa_name, dev_id); try_ss = *ss; try_ss.channels = j->map.channels; try_map = j->map; - pcm_j = pa_alsa_open_by_device_string( - jd, NULL, + pcm_j = open_by_device_string_with_fallback( + j->alsa_name, + j->alsa_name_fallback, + dev_id, + NULL, &try_ss, &try_map, SND_PCM_STREAM_CAPTURE, NULL, NULL, 0, NULL, NULL, TRUE); - pa_xfree(jd); - if (!pcm_j) continue; } @@ -1117,6 +1196,7 @@ success: int pa_alsa_find_mixer_and_elem( snd_pcm_t *pcm, + char **ctl_device, snd_mixer_t **_m, snd_mixer_elem_t **_e, const char *control_name, @@ -1144,9 +1224,13 @@ int pa_alsa_find_mixer_and_elem( /* First, try by name */ if ((dev = snd_pcm_name(pcm))) - if (pa_alsa_prepare_mixer(m, dev) >= 0) + if (pa_alsa_prepare_mixer(m, dev) >= 0) { found = TRUE; + if (ctl_device) + *ctl_device = pa_xstrdup(dev); + } + /* Then, try by card index */ if (!found) { snd_pcm_info_t* info; @@ -1161,9 +1245,15 @@ int pa_alsa_find_mixer_and_elem( md = pa_sprintf_malloc("hw:%i", card_idx); if (!dev || !pa_streq(dev, md)) - if (pa_alsa_prepare_mixer(m, md) >= 0) + if (pa_alsa_prepare_mixer(m, md) >= 0) { found = TRUE; + if (ctl_device) { + *ctl_device = md; + md = NULL; + } + } + pa_xfree(md); } } @@ -1199,6 +1289,9 @@ int pa_alsa_find_mixer_and_elem( } if (!e) { + if (ctl_device) + pa_xfree(*ctl_device); + snd_mixer_close(m); return -1; } @@ -1542,6 +1635,36 @@ void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm, snd_m pa_alsa_init_proplist_pcm_info(c, p, info); } +void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) { + int err; + snd_ctl_t *ctl; + snd_ctl_card_info_t *info; + const char *t; + + pa_assert(p); + + snd_ctl_card_info_alloca(&info); + + if ((err = snd_ctl_open(&ctl, name, 0)) < 0) { + pa_log_warn("Error opening low-level control device '%s'", name); + return; + } + + if ((err = snd_ctl_card_info(ctl, info)) < 0) { + pa_log_warn("Control device %s card info: %s", name, snd_strerror(err)); + snd_ctl_close(ctl); + return; + } + + if ((t = snd_ctl_card_info_get_mixername(info))) + pa_proplist_sets(p, "alsa.mixer_name", t); + + if ((t = snd_ctl_card_info_get_components(info))) + pa_proplist_sets(p, "alsa.components", t); + + snd_ctl_close(ctl); +} + int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) { snd_pcm_state_t state; int err; diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index 77ac8a7f..27f43712 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -56,6 +56,7 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min); typedef struct pa_alsa_profile_info { pa_channel_map map; const char *alsa_name; + const char *alsa_name_fallback; const char *description; /* internationalized */ const char *name; unsigned priority; @@ -65,7 +66,9 @@ typedef struct pa_alsa_profile_info { int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev); snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback); -int pa_alsa_find_mixer_and_elem(snd_pcm_t *pcm, snd_mixer_t **_m, snd_mixer_elem_t **_e, const char *control_name, const pa_alsa_profile_info*profile); +int pa_alsa_find_mixer_and_elem(snd_pcm_t *pcm, char **ctl_device, snd_mixer_t **_m, snd_mixer_elem_t **_e, const char *control_name, const pa_alsa_profile_info*profile); + +void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name); /* Picks a working profile based on the specified ss/map */ snd_pcm_t *pa_alsa_open_by_device_id_auto( diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index 51d466e1..ad52f5e3 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -43,9 +43,12 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "name=<name for the card/sink/source, to be prefixed> " - "card_name=<name for card> " - "sink_name=<name for sink> " - "source_name=<name for source> " + "card_name=<name for the card> " + "card_properties=<properties for the card> " + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "source_name=<name for the source> " + "source_properties=<properties for the source> " "device_id=<ALSA card index> " "format=<sample format> " "rate=<sample rate> " @@ -61,8 +64,11 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "name", "card_name", + "card_properties", "sink_name", + "sink_properties", "source_name", + "source_properties", "device_id", "format", "rate", @@ -135,7 +141,7 @@ static void enumerate_cb( bonus += 20000; } - pa_log_info("Found output profile '%s'", t); + pa_log_info("Found profile '%s'", t); p = pa_card_profile_new(n, t, sizeof(struct profile_data)); @@ -340,6 +346,12 @@ int pa__init(pa_module *m) { add_disabled_profile(data.profiles); + if (pa_modargs_get_proplist(ma, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_card_new_data_done(&data); + goto fail; + } + u->card = pa_card_new(m->core, &data); pa_card_new_data_done(&data); diff --git a/src/modules/alsa/module-alsa-sink.c b/src/modules/alsa/module-alsa-sink.c index 8e600ab8..058ea205 100644 --- a/src/modules/alsa/module-alsa-sink.c +++ b/src/modules/alsa/module-alsa-sink.c @@ -40,6 +40,7 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "name=<name of the sink, to be prefixed> " "sink_name=<name for the sink> " + "sink_properities=<properties for the sink> " "device=<ALSA device> " "device_id=<ALSA card index> " "format=<sample format> " @@ -58,6 +59,7 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "name", "sink_name", + "sink_properties", "device", "device_id", "format", diff --git a/src/modules/alsa/module-alsa-source.c b/src/modules/alsa/module-alsa-source.c index e6b27b3d..3bd1b451 100644 --- a/src/modules/alsa/module-alsa-source.c +++ b/src/modules/alsa/module-alsa-source.c @@ -64,6 +64,7 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "name=<name for the source, to be prefixed> " "source_name=<name for the source> " + "source_properties=<properties for the source> " "device=<ALSA device> " "device_id=<ALSA card index> " "format=<sample format> " @@ -82,6 +83,7 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "name", "source_name", + "source_properties", "device", "device_id", "format", diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index ecb5e83d..dbec00d4 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -64,8 +64,11 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "name=<name for the card/sink/source, to be prefixed> " "card_name=<name for the card> " + "card_properties=<properties for the card> " "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "source_name=<name for the source> " + "source_properties=<properties for the source> " "address=<address of the device> " "profile=<a2dp|hsp> " "rate=<sample rate> " @@ -84,8 +87,11 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "name", "card_name", + "card_properties", "sink_name", + "sink_properties", "source_name", + "source_properties", "address", "profile", "rate", @@ -174,6 +180,8 @@ struct userdata { #define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC) #define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC) +#define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC) + #ifdef NOKIA #define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source)) #endif @@ -1296,15 +1304,37 @@ static void thread_func(void *userdata) { if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) { pa_usec_t time_passed; - uint64_t should_have_written; + pa_usec_t audio_sent; /* Hmm, there is no input stream we could synchronize * to. So let's do things by time */ time_passed = pa_rtclock_usec() - u->started_at; - should_have_written = pa_usec_to_bytes(time_passed, &u->sample_spec); + audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec); + + if (audio_sent <= time_passed) { + pa_usec_t audio_to_send = time_passed - audio_sent; + + /* Never try to catch up for more than 100ms */ + if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) { + pa_usec_t skip_usec; + uint64_t skip_bytes; + pa_memchunk tmp; + + skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC; + skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec); + + pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream", + (unsigned long long) skip_usec, + (unsigned long long) skip_bytes); - do_write = u->write_index <= should_have_written; + pa_sink_render_full(u->sink, skip_bytes, &tmp); + pa_memblock_unref(tmp.memblock); + u->write_index += skip_bytes; + } + + do_write = 1; + } } if (writable && do_write > 0) { @@ -1596,6 +1626,12 @@ static int add_sink(struct userdata *u) { data.name = get_name("sink", u->modargs, u->address, &b); data.namereg_fail = b; + if (pa_modargs_get_proplist(u->modargs, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + return -1; + } + u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY | (u->profile == PROFILE_HSP ? PA_SINK_HW_VOLUME_CTRL : 0)); pa_sink_new_data_done(&data); @@ -1608,9 +1644,9 @@ static int add_sink(struct userdata *u) { u->sink->parent.process_msg = sink_process_msg; pa_sink_set_max_request(u->sink, u->block_size); - u->sink->fixed_latency = - (u->profile == PROFILE_A2DP ? FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) + - pa_bytes_to_usec(u->block_size, &u->sample_spec); + pa_sink_set_fixed_latency(u->sink, + (u->profile == PROFILE_A2DP ? FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) + + pa_bytes_to_usec(u->block_size, &u->sample_spec)); } if (u->profile == PROFILE_HSP) { @@ -1648,6 +1684,12 @@ static int add_source(struct userdata *u) { data.name = get_name("source", u->modargs, u->address, &b); data.namereg_fail = b; + if (pa_modargs_get_proplist(u->modargs, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + return -1; + } + u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY | (u->profile == PROFILE_HSP ? PA_SOURCE_HW_VOLUME_CTRL : 0)); pa_source_new_data_done(&data); @@ -1659,9 +1701,9 @@ static int add_source(struct userdata *u) { u->source->userdata = u; u->source->parent.process_msg = source_process_msg; - u->source->fixed_latency = - (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) + - pa_bytes_to_usec(u->block_size, &u->sample_spec); + pa_source_set_fixed_latency(u->source, + (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) + + pa_bytes_to_usec(u->block_size, &u->sample_spec)); } if (u->profile == PROFILE_HSP) { @@ -1939,13 +1981,17 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { } /* Run from main thread */ -static int add_card(struct userdata *u, const char *default_profile, const pa_bluetooth_device *device) { +static int add_card(struct userdata *u, const pa_bluetooth_device *device) { pa_card_new_data data; pa_bool_t b; pa_card_profile *p; enum profile *d; const char *ff; char *n; + const char *default_profile; + + pa_assert(u); + pa_assert(device); pa_card_new_data_init(&data); data.driver = __FILE__; @@ -1966,6 +2012,12 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl data.name = get_name("card", u->modargs, device->address, &b); data.namereg_fail = b; + if (pa_modargs_get_proplist(u->modargs, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_card_new_data_done(&data); + return -1; + } + data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); /* we base hsp/a2dp availability on UUIDs. @@ -2008,7 +2060,7 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl *d = PROFILE_OFF; pa_hashmap_put(data.profiles, p->name, p); - if (default_profile) { + if ((default_profile = pa_modargs_get_value(u->modargs, "profile", NULL))) { if (pa_hashmap_get(data.profiles, default_profile)) pa_card_new_data_set_profile(&data, default_profile); else @@ -2154,7 +2206,7 @@ int pa__init(pa_module* m) { goto fail; /* Add the card structure. This will also initialize the default profile */ - if (add_card(u, pa_modargs_get_value(ma, "profile", NULL), device) < 0) + if (add_card(u, device) < 0) goto fail; /* Connect to the BT service and query capabilities */ diff --git a/src/modules/module-jack-sink.c b/src/modules/jack/module-jack-sink.c index 31b8a96e..290038e7 100644 --- a/src/modules/module-jack-sink.c +++ b/src/modules/jack/module-jack-sink.c @@ -67,12 +67,13 @@ PA_MODULE_DESCRIPTION("JACK Sink"); PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_USAGE( - "sink_name=<name of sink> " + "sink_name=<name for the sink> " + "sink_properties=<properties for the card> " "server_name=<jack server name> " "client_name=<jack client name> " "channels=<number of channels> " - "connect=<connect ports?> " - "channel_map=<channel map>"); + "channel_map=<channel map> " + "connect=<connect ports?>"); #define DEFAULT_SINK_NAME "jack_out" @@ -102,11 +103,12 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "server_name", "client_name", "channels", - "connect", "channel_map", + "connect", NULL }; @@ -386,6 +388,12 @@ int pa__init(pa_module*m) { pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack sink (%s)", jack_get_client_name(u->client)); pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client)); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); pa_sink_new_data_done(&data); diff --git a/src/modules/module-jack-source.c b/src/modules/jack/module-jack-source.c index 9a215c54..ef89a98e 100644 --- a/src/modules/module-jack-source.c +++ b/src/modules/jack/module-jack-source.c @@ -57,12 +57,13 @@ PA_MODULE_DESCRIPTION("JACK Source"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_USAGE( - "source_name=<name of source> " + "source_name=<name for the source> " + "source_properties=<properties for the source> " "server_name=<jack server name> " "client_name=<jack client name> " "channels=<number of channels> " - "connect=<connect ports?>" - "channel_map=<channel map>"); + "channel_map=<channel map> " + "connect=<connect ports?>"); #define DEFAULT_SOURCE_NAME "jack_in" @@ -89,11 +90,12 @@ struct userdata { static const char* const valid_modargs[] = { "source_name", + "source_properties", "server_name", "client_name", "channels", - "connect", "channel_map", + "connect", NULL }; @@ -338,6 +340,12 @@ int pa__init(pa_module*m) { pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack source (%s)", jack_get_client_name(u->client)); pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client)); + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); pa_source_new_data_done(&data); diff --git a/src/modules/module-augment-properties.c b/src/modules/module-augment-properties.c index c3e5997a..15aa3a1e 100644 --- a/src/modules/module-augment-properties.c +++ b/src/modules/module-augment-properties.c @@ -58,6 +58,7 @@ struct rule { char *process_name; char *application_name; char *icon_name; + char *role; pa_proplist *proplist; }; @@ -72,12 +73,21 @@ static void rule_free(struct rule *r) { pa_xfree(r->process_name); pa_xfree(r->application_name); pa_xfree(r->icon_name); + pa_xfree(r->role); if (r->proplist) pa_proplist_free(r->proplist); pa_xfree(r); } -static int parse_properties(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int parse_properties( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + struct rule *r = userdata; pa_proplist *n; @@ -93,11 +103,56 @@ static int parse_properties(const char *filename, unsigned line, const char *sec return 0; } -static int check_type(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int parse_categories( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + struct rule *r = userdata; + const char *state = NULL; + char *c; + + while ((c = pa_split(rvalue, ";", &state))) { + + if (pa_streq(c, "Game")) { + pa_xfree(r->role); + r->role = pa_xstrdup("game"); + } else if (pa_streq(c, "Telephony")) { + pa_xfree(r->role); + r->role = pa_xstrdup("phone"); + } + + pa_xfree(c); + } + + return 0; +} + +static int check_type( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + return pa_streq(rvalue, "Application") ? 0 : -1; } -static int catch_all(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int catch_all( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + return 0; } @@ -109,6 +164,7 @@ static void update_rule(struct rule *r) { { "Icon", pa_config_parse_string, NULL, "Desktop Entry" }, { "Type", check_type, NULL, "Desktop Entry" }, { "X-PulseAudio-Properties", parse_properties, NULL, "Desktop Entry" }, + { "Categories", parse_categories, NULL, "Desktop Entry" }, { NULL, catch_all, NULL, NULL }, { NULL, NULL, NULL, NULL }, }; @@ -131,7 +187,8 @@ static void update_rule(struct rule *r) { r->mtime = st.st_mtime; pa_xfree(r->application_name); pa_xfree(r->icon_name); - r->application_name = r->icon_name = NULL; + pa_xfree(r->role); + r->application_name = r->icon_name = r->role = NULL; if (r->proplist) pa_proplist_clear(r->proplist); @@ -151,6 +208,9 @@ static void apply_rule(struct rule *r, pa_proplist *p) { if (!r->good) return; + if (r->proplist) + pa_proplist_update(p, PA_UPDATE_MERGE, r->proplist); + if (r->icon_name) if (!pa_proplist_contains(p, PA_PROP_APPLICATION_ICON_NAME)) pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, r->icon_name); @@ -164,8 +224,9 @@ static void apply_rule(struct rule *r, pa_proplist *p) { pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, r->application_name); } - if (r->proplist) - pa_proplist_update(p, PA_UPDATE_MERGE, r->proplist); + if (r->role) + if (!pa_proplist_contains(p, PA_PROP_MEDIA_ROLE)) + pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, r->role); } static void make_room(pa_hashmap *cache) { diff --git a/src/modules/module-card-restore.c b/src/modules/module-card-restore.c index 17f1f8c3..85478d12 100644 --- a/src/modules/module-card-restore.c +++ b/src/modules/module-card-restore.c @@ -30,7 +30,6 @@ #include <stdio.h> #include <stdlib.h> #include <ctype.h> -#include <gdbm.h> #include <pulse/xmalloc.h> #include <pulse/volume.h> @@ -45,6 +44,7 @@ #include <pulsecore/core-subscribe.h> #include <pulsecore/card.h> #include <pulsecore/namereg.h> +#include <pulsecore/database.h> #include "module-card-restore-symdef.h" @@ -65,7 +65,7 @@ struct userdata { pa_subscription *subscription; pa_hook_slot *card_new_hook_slot; pa_time_event *save_time_event; - GDBM_FILE gdbm_file; + pa_database *database; }; #define ENTRY_VERSION 1 @@ -87,31 +87,31 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct u->core->mainloop->time_free(u->save_time_event); u->save_time_event = NULL; - gdbm_sync(u->gdbm_file); + pa_database_sync(u->database); pa_log_info("Synced."); } static struct entry* read_entry(struct userdata *u, const char *name) { - datum key, data; + pa_datum key, data; struct entry *e; pa_assert(u); pa_assert(name); - key.dptr = (char*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - data = gdbm_fetch(u->gdbm_file, key); + pa_zero(data); - if (!data.dptr) + if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.dsize != sizeof(struct entry)) { - pa_log_debug("Database contains entry for card %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + if (data.size != sizeof(struct entry)) { + pa_log_debug("Database contains entry for card %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); goto fail; } - e = (struct entry*) data.dptr; + e = (struct entry*) data.data; if (e->version != ENTRY_VERSION) { pa_log_debug("Version of database entry for card %s doesn't match our version. Probably due to upgrade, ignoring.", name); @@ -127,7 +127,7 @@ static struct entry* read_entry(struct userdata *u, const char *name) { fail: - pa_xfree(data.dptr); + pa_datum_free(&data); return NULL; } @@ -145,7 +145,7 @@ static void trigger_save(struct userdata *u) { static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { struct userdata *u = userdata; struct entry entry, *old; - datum key, data; + pa_datum key, data; pa_card *card; pa_assert(c); @@ -155,7 +155,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - memset(&entry, 0, sizeof(entry)); + pa_zero(entry); entry.version = ENTRY_VERSION; if (!(card = pa_idxset_get_by_index(c->cards, idx))) @@ -176,15 +176,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 pa_xfree(old); } - key.dptr = card->name; - key.dsize = (int) strlen(card->name); + key.data = card->name; + key.size = strlen(card->name); - data.dptr = (void*) &entry; - data.dsize = sizeof(entry); + data.data = &entry; + data.size = sizeof(entry); pa_log_info("Storing profile for card %s.", card->name); - gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); + pa_database_set(u->database, &key, &data, TRUE); trigger_save(u); } @@ -211,10 +211,9 @@ static pa_hook_result_t card_new_hook_callback(pa_core *c, pa_card_new_data *new int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname, *fn; + char *fname; pa_card *card; uint32_t idx; - int gdbm_cache_size; pa_assert(m); @@ -227,33 +226,21 @@ int pa__init(pa_module*m) { u->core = m->core; u->module = m; u->save_time_event = NULL; - u->gdbm_file = NULL; + u->database = NULL; u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_CARD, subscribe_callback, u); u->card_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) card_new_hook_callback, u); - /* We include the host identifier in the file name because gdbm - * files are CPU dependant, and we don't want things to go wrong - * if we are on a multiarch system. */ - - fn = pa_sprintf_malloc("card-database."CANONICAL_HOST".gdbm"); - fname = pa_state_path(fn, TRUE); - pa_xfree(fn); - - if (!fname) + if (!(fname = pa_state_path("card-database", TRUE))) goto fail; - if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) { - pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); + if (!(u->database = pa_database_open(fname, TRUE))) { + pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); pa_xfree(fname); goto fail; } - /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ - gdbm_cache_size = 10; - gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); - pa_log_info("Sucessfully opened database file '%s'.", fname); pa_xfree(fname); @@ -289,8 +276,8 @@ void pa__done(pa_module*m) { if (u->save_time_event) u->core->mainloop->time_free(u->save_time_event); - if (u->gdbm_file) - gdbm_close(u->gdbm_file); + if (u->database) + pa_database_close(u->database); pa_xfree(u); } diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index a1ef8da4..725faa0c 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -55,12 +55,13 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "slaves=<slave sinks> " "adjust_time=<seconds> " "resample_method=<method> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " "channel_map=<channel map>"); #define DEFAULT_SINK_NAME "combined" @@ -73,12 +74,13 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "slaves", "adjust_time", "resample_method", "format", - "channels", "rate", + "channels", "channel_map", NULL }; @@ -591,7 +593,7 @@ static void unsuspend(struct userdata *u) { /* Let's resume */ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { - pa_sink_suspend(o->sink, FALSE); + pa_sink_suspend(o->sink, FALSE, PA_SUSPEND_IDLE); if (PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) enable_output(o); @@ -871,7 +873,7 @@ static struct output *output_new(struct userdata *u, pa_sink *sink) { } if (PA_SINK_IS_OPENED(state) || state == PA_SINK_INIT) { - pa_sink_suspend(sink, FALSE); + pa_sink_suspend(sink, FALSE, PA_SUSPEND_IDLE); if (PA_SINK_IS_OPENED(pa_sink_get_state(sink))) if (output_create_sink_input(o) < 0) @@ -1080,6 +1082,12 @@ int pa__init(pa_module*m) { if (slaves) pa_proplist_sets(data.proplist, "combine.slaves", slaves); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); pa_sink_new_data_done(&data); diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4 index f9924cfa..b6a60b6a 100644 --- a/src/modules/module-defs.h.m4 +++ b/src/modules/module-defs.h.m4 @@ -17,6 +17,7 @@ gen_symbol(pa__get_author) gen_symbol(pa__get_description) gen_symbol(pa__get_usage) gen_symbol(pa__get_version) +gen_symbol(pa__get_deprecated) gen_symbol(pa__load_once) gen_symbol(pa__get_n_used) @@ -28,6 +29,7 @@ const char* pa__get_author(void); const char* pa__get_description(void); const char* pa__get_usage(void); const char* pa__get_version(void); +const char* pa__get_deprecated(void); pa_bool_t pa__load_once(void); #endif diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index 49127abc..18479df3 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -50,6 +50,7 @@ PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_USAGE("just-one=<boolean>"); +PA_MODULE_DEPRECATED("Please use module-hal-detect instead of module-detect!"); static const char* const valid_modargs[] = { "just-one", diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c index 7d87ca0f..ae21acd5 100644 --- a/src/modules/module-device-restore.c +++ b/src/modules/module-device-restore.c @@ -30,7 +30,6 @@ #include <stdio.h> #include <stdlib.h> #include <ctype.h> -#include <gdbm.h> #include <pulse/xmalloc.h> #include <pulse/volume.h> @@ -46,6 +45,7 @@ #include <pulsecore/sink-input.h> #include <pulsecore/source-output.h> #include <pulsecore/namereg.h> +#include <pulsecore/database.h> #include "module-device-restore-symdef.h" @@ -73,7 +73,7 @@ struct userdata { *sink_fixate_hook_slot, *source_fixate_hook_slot; pa_time_event *save_time_event; - GDBM_FILE gdbm_file; + pa_database *database; pa_bool_t restore_volume:1; pa_bool_t restore_muted:1; @@ -100,31 +100,31 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct u->core->mainloop->time_free(u->save_time_event); u->save_time_event = NULL; - gdbm_sync(u->gdbm_file); + pa_database_sync(u->database); pa_log_info("Synced."); } static struct entry* read_entry(struct userdata *u, const char *name) { - datum key, data; + pa_datum key, data; struct entry *e; pa_assert(u); pa_assert(name); - key.dptr = (char*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - data = gdbm_fetch(u->gdbm_file, key); + pa_zero(data); - if (!data.dptr) + if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.dsize != sizeof(struct entry)) { - pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + if (data.size != sizeof(struct entry)) { + pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); goto fail; } - e = (struct entry*) data.dptr; + e = (struct entry*) data.data; if (e->version != ENTRY_VERSION) { pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name); @@ -150,7 +150,7 @@ static struct entry* read_entry(struct userdata *u, const char *name) { fail: - pa_xfree(data.dptr); + pa_datum_free(&data); return NULL; } @@ -169,7 +169,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 struct userdata *u = userdata; struct entry entry, *old; char *name; - datum key, data; + pa_datum key, data; pa_assert(c); pa_assert(u); @@ -180,7 +180,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - memset(&entry, 0, sizeof(entry)); + pa_zero(entry); entry.version = ENTRY_VERSION; if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { @@ -221,15 +221,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 pa_xfree(old); } - key.dptr = name; - key.dsize = (int) strlen(name); + key.data = name; + key.size = strlen(name); - data.dptr = (void*) &entry; - data.dsize = sizeof(entry); + data.data = &entry; + data.size = sizeof(entry); pa_log_info("Storing volume/mute for device %s.", name); - gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); + pa_database_set(u->database, &key, &data, TRUE); pa_xfree(name); @@ -312,12 +312,11 @@ static pa_hook_result_t source_fixate_hook_callback(pa_core *c, pa_source_new_da int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname, *fn; + char *fname; pa_sink *sink; pa_source *source; uint32_t idx; pa_bool_t restore_volume = TRUE, restore_muted = TRUE; - int gdbm_cache_size; pa_assert(m); @@ -341,7 +340,7 @@ int pa__init(pa_module*m) { u->save_time_event = NULL; u->restore_volume = restore_volume; u->restore_muted = restore_muted; - u->gdbm_file = NULL; + u->database = NULL; u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u); @@ -350,27 +349,15 @@ int pa__init(pa_module*m) { u->source_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_fixate_hook_callback, u); } - /* We include the host identifier in the file name because gdbm - * files are CPU dependant, and we don't want things to go wrong - * if we are on a multiarch system. */ - - fn = pa_sprintf_malloc("device-volumes."CANONICAL_HOST".gdbm"); - fname = pa_state_path(fn, TRUE); - pa_xfree(fn); - - if (!fname) + if (!(fname = pa_state_path("device-volumes", TRUE))) goto fail; - if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) { - pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); + if (!(u->database = pa_database_open(fname, TRUE))) { + pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); pa_xfree(fname); goto fail; } - /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ - gdbm_cache_size = 10; - gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); - pa_log_info("Sucessfully opened database file '%s'.", fname); pa_xfree(fname); @@ -411,8 +398,8 @@ void pa__done(pa_module*m) { if (u->save_time_event) u->core->mainloop->time_free(u->save_time_event); - if (u->gdbm_file) - gdbm_close(u->gdbm_file); + if (u->database) + pa_database_close(u->database); pa_xfree(u); } diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index a1a783aa..8cb25c51 100644 --- a/src/modules/module-esound-sink.c +++ b/src/modules/module-esound-sink.c @@ -68,10 +68,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "server=<address> cookie=<filename> " "format=<sample format> " - "channels=<number of channels> " - "rate=<sample rate>"); + "rate=<sample rate> " + "channels=<number of channels>"); #define DEFAULT_SINK_NAME "esound_out" @@ -118,12 +119,13 @@ struct userdata { }; static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", "server", "cookie", - "rate", "format", + "rate", "channels", - "sink_name", NULL }; @@ -586,6 +588,12 @@ int pa__init(pa_module*m) { pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "esd"); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "EsounD Output on %s", espeaker); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK); pa_sink_new_data_done(&data); diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index b6139e43..658b3e55 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -64,6 +64,7 @@ PA_MODULE_USAGE("api=<alsa> " #elif defined(HAVE_OSS) PA_MODULE_USAGE("api=<oss>"); #endif +PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!"); struct device { char *udi, *originating_udi; @@ -232,7 +233,7 @@ static int hal_device_load_alsa(struct userdata *u, const char *udi, struct devi goto fail; card_name = pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi)); - args = pa_sprintf_malloc("device_id=%u name=%s card_name=%s tsched=%i", card, strip_udi(originating_udi), card_name, (int) u->use_tsched); + args = pa_sprintf_malloc("device_id=%u name=\"%s\" card_name=\"%s\" tsched=%i card_properties=\"module-hal-detect.discovered=1\"", card, strip_udi(originating_udi), card_name, (int) u->use_tsched); pa_log_debug("Loading module-alsa-card with arguments '%s'", args); m = pa_module_load(u->core, "module-alsa-card", args); @@ -567,7 +568,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_sink *sink; if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) { - pa_bool_t success = pa_sink_suspend(sink, suspend) >= 0; + pa_bool_t success = pa_sink_suspend(sink, suspend, PA_SUSPEND_SESSION) >= 0; if (!success && !suspend) d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -580,7 +581,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_source *source; if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) { - pa_bool_t success = pa_source_suspend(source, suspend) >= 0; + pa_bool_t success = pa_source_suspend(source, suspend, PA_SUSPEND_SESSION) >= 0; if (!success && !suspend) d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -593,7 +594,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_card *card; if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) { - pa_bool_t success = pa_card_suspend(card, suspend) >= 0; + pa_bool_t success = pa_card_suspend(card, suspend, PA_SUSPEND_SESSION) >= 0; if (!success && !suspend) d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -637,21 +638,21 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_sink *sink; if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) - pa_sink_suspend(sink, FALSE); + pa_sink_suspend(sink, FALSE, PA_SUSPEND_SESSION); } if (d->source_name) { pa_source *source; if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) - pa_source_suspend(source, FALSE); + pa_source_suspend(source, FALSE, PA_SUSPEND_SESSION); } if (d->card_name) { pa_card *card; if ((card = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_CARD))) - pa_card_suspend(card, FALSE); + pa_card_suspend(card, FALSE, PA_SUSPEND_SESSION); } } diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index 44052c9c..15af74a6 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -50,10 +50,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "master=<name of sink to remap> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " "channel_map=<channel map> " "plugin=<ladspa plugin name> " "label=<ladspa plugin label> " @@ -85,10 +86,11 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "master", "format", - "channels", "rate", + "channels", "channel_map", "plugin", "label", @@ -705,6 +707,12 @@ int pa__init(pa_module*m) { pa_proplist_sets(sink_data.proplist, "device.ladspa.copyright", d->Copyright); pa_proplist_setf(sink_data.proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID); + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_data); + goto fail; + } + u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); pa_sink_new_data_done(&sink_data); diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index d9bab6bd..30a99ca7 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -54,12 +54,12 @@ PA_MODULE_DESCRIPTION("Clocked NULL sink"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( + "sink_name=<name of sink> " + "sink_properties=<properties for the sink> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " - "sink_name=<name of sink> " - "channel_map=<channel map> " - "description=<description for the sink>"); + "channels=<number of channels> " + "channel_map=<channel map>"); #define DEFAULT_SINK_NAME "null" #define BLOCK_USEC (PA_USEC_PER_SEC * 2) @@ -78,12 +78,13 @@ struct userdata { }; static const char* const valid_modargs[] = { - "rate", + "sink_name", + "sink_properties", "format", + "rate", "channels", - "sink_name", "channel_map", - "description", + "description", /* supported for compatibility reasons, made redundant by sink_properties= */ NULL }; @@ -289,6 +290,12 @@ int pa__init(pa_module*m) { pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output")); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); pa_sink_new_data_done(&data); diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c index 9d3e55d8..5b0f6414 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -54,10 +54,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "file=<path of the FIFO> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate>" + "channels=<number of channels> " "channel_map=<channel map>"); #define DEFAULT_FILE_NAME "fifo_output" @@ -83,11 +84,12 @@ struct userdata { }; static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", "file", - "rate", "format", + "rate", "channels", - "sink_name", "channel_map", NULL }; @@ -279,6 +281,12 @@ int pa__init(pa_module*m) { pa_sink_new_data_set_sample_spec(&data, &ss); pa_sink_new_data_set_channel_map(&data, &map); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); pa_sink_new_data_done(&data); @@ -293,7 +301,7 @@ int pa__init(pa_module*m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); pa_sink_set_max_request(u->sink, PIPE_BUF); - u->sink->fixed_latency = pa_bytes_to_usec(PIPE_BUF, &u->sink->sample_spec); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(PIPE_BUF, &u->sink->sample_spec)); u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c index df72d798..61c9fc0e 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -54,10 +54,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "source_name=<name for the source> " + "source_properties=<properties for the source> " "file=<path of the FIFO> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " "channel_map=<channel map>"); #define DEFAULT_FILE_NAME "/tmp/music.input" @@ -81,11 +82,12 @@ struct userdata { }; static const char* const valid_modargs[] = { + "source_name", + "source_properties", "file", + "format", "rate", "channels", - "format", - "source_name", "channel_map", NULL }; @@ -264,6 +266,12 @@ int pa__init(pa_module*m) { pa_source_new_data_set_sample_spec(&data, &ss); pa_source_new_data_set_channel_map(&data, &map); + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); pa_source_new_data_done(&data); @@ -277,7 +285,7 @@ int pa__init(pa_module*m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); - u->source->fixed_latency = pa_bytes_to_usec(PIPE_BUF, &u->source->sample_spec); + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(PIPE_BUF, &u->source->sample_spec)); u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c index ce3dcd03..5b351d19 100644 --- a/src/modules/module-protocol-stub.c +++ b/src/modules/module-protocol-stub.c @@ -251,7 +251,7 @@ int pa__init(pa_module*m) { int r; #endif -#if defined(USE_PROTOCOL_NATIVE) +#if defined(USE_PROTOCOL_NATIVE) || defined(USE_PROTOCOL_HTTP) char t[256]; #endif @@ -382,6 +382,24 @@ int pa__init(pa_module*m) { # endif #endif +#if defined(USE_PROTOCOL_HTTP) +#if defined(USE_TCP_SOCKETS) + if (u->socket_server_ipv4) + if (pa_socket_server_get_address(u->socket_server_ipv4, t, sizeof(t))) + pa_http_protocol_add_server_string(u->http_protocol, t); + +#ifdef HAVE_IPV6 + if (u->socket_server_ipv6) + if (pa_socket_server_get_address(u->socket_server_ipv6, t, sizeof(t))) + pa_http_protocol_add_server_string(u->http_protocol, t); +#endif /* HAVE_IPV6 */ +#else /* USE_TCP_SOCKETS */ + if (pa_socket_server_get_address(u->socket_server_unix, t, sizeof(t))) + pa_http_protocol_add_server_string(u->http_protocol, t); + +#endif /* USE_TCP_SOCKETS */ +#endif /* USE_PROTOCOL_HTTP */ + if (ma) pa_modargs_free(ma); @@ -419,6 +437,24 @@ void pa__done(pa_module*m) { } #elif defined(USE_PROTOCOL_HTTP) if (u->http_protocol) { + char t[256]; + +#if defined(USE_TCP_SOCKETS) + if (u->socket_server_ipv4) + if (pa_socket_server_get_address(u->socket_server_ipv4, t, sizeof(t))) + pa_http_protocol_remove_server_string(u->http_protocol, t); + +#ifdef HAVE_IPV6 + if (u->socket_server_ipv6) + if (pa_socket_server_get_address(u->socket_server_ipv6, t, sizeof(t))) + pa_http_protocol_remove_server_string(u->http_protocol, t); +#endif /* HAVE_IPV6 */ +#else /* USE_TCP_SOCKETS */ + if (u->socket_server_unix) + if (pa_socket_server_get_address(u->socket_server_unix, t, sizeof(t))) + pa_http_protocol_remove_server_string(u->http_protocol, t); +#endif /* USE_PROTOCOL_HTTP */ + pa_http_protocol_disconnect(u->http_protocol, u->module); pa_http_protocol_unref(u->http_protocol); } diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 4d68b1b0..052a3a5e 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -72,11 +72,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " - "description=<description for the sink> " + "sink_properties=<properties for the sink> " "server=<address> " "format=<sample format> " - "channels=<number of channels> " - "rate=<sample rate>"); + "rate=<sample rate> " + "channels=<number of channels>"); #define DEFAULT_SINK_NAME "raop" @@ -118,12 +118,13 @@ struct userdata { }; static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", "server", - "rate", "format", + "rate", "channels", - "sink_name", - "description", + "description", /* supported for compatibility reasons, made redundant by sink_properties= */ NULL }; @@ -587,6 +588,12 @@ int pa__init(pa_module*m) { else pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK); pa_sink_new_data_done(&data); diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index f9777bef..119f5b9f 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -44,6 +44,7 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "master=<name of sink to remap> " "master_channel_map=<channel map> " "format=<sample format> " @@ -62,10 +63,11 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "master", "master_channel_map", - "rate", "format", + "rate", "channels", "channel_map", "remix", @@ -354,6 +356,12 @@ int pa__init(pa_module*m) { pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_data); + goto fail; + } + u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); pa_sink_new_data_done(&sink_data); diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index 7c99a9b9..c22711ae 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -31,6 +31,7 @@ #include <pulsecore/modargs.h> #include <pulsecore/log.h> #include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> #include "module-rescue-streams-symdef.h" @@ -49,6 +50,7 @@ struct userdata { static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { pa_sink_input *i; + uint32_t idx; pa_sink *target; pa_assert(c); @@ -58,15 +60,14 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user if (c->state == PA_CORE_SHUTDOWN) return PA_HOOK_OK; - if (!pa_idxset_size(sink->inputs)) { + if (pa_idxset_size(sink->inputs) <= 0) { pa_log_debug("No sink inputs to move away."); return PA_HOOK_OK; } if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK)) || target == sink) { - uint32_t idx; - for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx)) + PA_IDXSET_FOREACH(target, c->sinks, idx) if (target != sink) break; @@ -76,20 +77,24 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user } } - while ((i = pa_idxset_first(sink->inputs, NULL))) { + pa_assert(target != sink); + + PA_IDXSET_FOREACH(i, sink->inputs, idx) { if (pa_sink_input_move_to(i, target, FALSE) < 0) - pa_log_warn("Failed to move sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME), target->name); + pa_log_info("Failed to move sink input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); else - pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME), target->name); + pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); } - return PA_HOOK_OK; } static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void* userdata) { pa_source_output *o; pa_source *target; + uint32_t idx; pa_assert(c); pa_assert(source); @@ -98,15 +103,14 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void if (c->state == PA_CORE_SHUTDOWN) return PA_HOOK_OK; - if (!pa_idxset_size(source->outputs)) { + if (pa_idxset_size(source->outputs) <= 0) { pa_log_debug("No source outputs to move away."); return PA_HOOK_OK; } if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE)) || target == source) { - uint32_t idx; - for (target = pa_idxset_first(c->sources, &idx); target; target = pa_idxset_next(c->sources, &idx)) + PA_IDXSET_FOREACH(target, c->sources, idx) if (target != source && !target->monitor_of == !source->monitor_of) break; @@ -118,19 +122,20 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void pa_assert(target != source); - while ((o = pa_idxset_first(source->outputs, NULL))) { + PA_IDXSET_FOREACH(o, source->outputs, idx) { if (pa_source_output_move_to(o, target, FALSE) < 0) - pa_log_warn("Failed to move source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name); + pa_log_info("Failed to move source output %u \"%s\" to %s.", o->index, + pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), target->name); else - pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name); + pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, + pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), target->name); } - return PA_HOOK_OK; } int pa__init(pa_module*m) { - pa_modargs *ma = NULL; + pa_modargs *ma; struct userdata *u; pa_assert(m); @@ -153,10 +158,9 @@ void pa__done(pa_module*m) { pa_assert(m); - if (!m->userdata) + if (!(u = m->userdata)) return; - u = m->userdata; if (u->sink_slot) pa_hook_slot_free(u->sink_slot); if (u->source_slot) diff --git a/src/modules/module-rygel-media-server.c b/src/modules/module-rygel-media-server.c new file mode 100644 index 00000000..4c02e958 --- /dev/null +++ b/src/modules/module-rygel-media-server.c @@ -0,0 +1,847 @@ +/*** + This file is part of PulseAudio. + + Copyright 2005-2009 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/i18n.h> +#include <pulse/utf8.h> + +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/modargs.h> +#include <pulsecore/dbus-shared.h> +#include <pulsecore/endianmacros.h> +#include <pulsecore/namereg.h> +#include <pulsecore/mime-type.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/protocol-http.h> +#include <pulsecore/parseaddr.h> + +#include "module-rygel-media-server-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("UPnP MediaServer Plugin for Rygel"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "display_name=<UPnP Media Server name>"); + +/* This implements http://live.gnome.org/Rygel/MediaServerSpec */ + +#define SERVICE_NAME "org.gnome.UPnP.MediaServer1.PulseAudio" + +#define OBJECT_ROOT "/org/gnome/UPnP/MediaServer1/PulseAudio" +#define OBJECT_SINKS "/org/gnome/UPnP/MediaServer1/PulseAudio/Sinks" +#define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer1/PulseAudio/Sources" + +#define CONTAINER_INTROSPECT_XML_PREFIX \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <!-- If you are looking for documentation make sure to check out" \ + " http://live.gnome.org/Rygel/MediaServerSpec -->" \ + " <interface name=\"org.gnome.UPnP.MediaContainer1\">" \ + " <signal name=\"Updated\">" \ + " <arg name=\"path\" type=\"o\"/>" \ + " </signal>" \ + " <property name=\"Items\" type=\"ao\" access=\"read\"/>" \ + " <property name=\"ItemCount\" type=\"u\" access=\"read\"/>" \ + " <property name=\"Containers\" type=\"ao\" access=\"read\"/>" \ + " <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>" \ + " </interface>" \ + " <interface name=\"org.gnome.UPnP.MediaObject1\">" \ + " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \ + " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Properties\">" \ + " <method name=\"Get\">" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"property\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"value\" direction=\"out\" type=\"v\"/>" \ + " </method>" \ + " <method name=\"GetAll\">" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \ + " </method>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Introspectable\">" \ + " <method name=\"Introspect\">" \ + " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \ + " </method>" \ + " </interface>" + +#define CONTAINER_INTROSPECT_XML_POSTFIX \ + "</node>" + +#define ROOT_INTROSPECT_XML \ + CONTAINER_INTROSPECT_XML_PREFIX \ + "<node name=\"Sinks\"/>" \ + "<node name=\"Sources\"/>" \ + CONTAINER_INTROSPECT_XML_POSTFIX + +#define ITEM_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <!-- If you are looking for documentation make sure to check out" \ + " http://live.gnome.org/Rygel/MediaProviderSpec -->" \ + " <interface name=\"org.gnome.UPnP.MediaItem1\">" \ + " <property name=\"URLs\" type=\"as\" access=\"read\"/>" \ + " <property name=\"MIMEType\" type=\"s\" access=\"read\"/>" \ + " <property name=\"Type\" type=\"s\" access=\"read\"/>" \ + " </interface>" \ + " <interface name=\"org.gnome.UPnP.MediaObject1\">" \ + " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \ + " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Properties\">" \ + " <method name=\"Get\">" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"property\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"value\" direction=\"out\" type=\"v\"/>" \ + " </method>" \ + " <method name=\"GetAll\">" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \ + " </method>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Introspectable\">" \ + " <method name=\"Introspect\">" \ + " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \ + " </method>" \ + " </interface>" \ + "</node>" + + +static const char* const valid_modargs[] = { + "display_name", + NULL +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_dbus_connection *bus; + pa_bool_t got_name:1; + + char *display_name; + + pa_hook_slot *source_new_slot, *source_unlink_slot; + + pa_http_protocol *http; +}; + +static void send_signal(struct userdata *u, pa_source *s) { + DBusMessage *m; + const char *parent; + + pa_assert(u); + pa_source_assert_ref(s); + + if (u->core->state == PA_CORE_SHUTDOWN) + return; + + if (s->monitor_of) + parent = OBJECT_SINKS; + else + parent = OBJECT_SOURCES; + + pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer1", "Updated")); + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), m, NULL)); + + dbus_message_unref(m); +} + +static pa_hook_result_t source_new_or_unlink_cb(pa_core *c, pa_source *s, struct userdata *u) { + pa_assert(c); + pa_source_assert_ref(s); + + send_signal(u, s); + + return PA_HOOK_OK; +} + +static pa_bool_t message_is_property_get(DBusMessage *m, const char *interface, const char *property) { + const char *i, *p; + DBusError error; + + dbus_error_init(&error); + + pa_assert(m); + + if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get")) + return FALSE; + + if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_STRING, &p, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + dbus_error_free(&error); + return FALSE; + } + + return pa_streq(i, interface) && pa_streq(p, property); +} + +static pa_bool_t message_is_property_get_all(DBusMessage *m, const char *interface) { + const char *i; + DBusError error; + + dbus_error_init(&error); + + pa_assert(m); + + if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "GetAll")) + return FALSE; + + if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + dbus_error_free(&error); + return FALSE; + } + + return pa_streq(i, interface); +} + +static void append_variant_object_array(DBusMessage *m, DBusMessageIter *iter, const char *path[], unsigned n) { + DBusMessageIter _iter, variant, array; + unsigned c; + + pa_assert(m); + pa_assert(path); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "ao", &variant)); + pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "o", &array)); + + for (c = 0; c < n; c++) + pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH, path + c)); + + pa_assert_se(dbus_message_iter_close_container(&variant, &array)); + pa_assert_se(dbus_message_iter_close_container(iter, &variant)); +} + +static void append_variant_string(DBusMessage *m, DBusMessageIter *iter, const char *s) { + DBusMessageIter _iter, sub; + + pa_assert(m); + pa_assert(s); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "s", &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &s)); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_variant_object(DBusMessage *m, DBusMessageIter *iter, const char *s) { + DBusMessageIter _iter, sub; + + pa_assert(m); + pa_assert(s); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "o", &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &s)); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_variant_unsigned(DBusMessage *m, DBusMessageIter *iter, unsigned u) { + DBusMessageIter _iter, sub; + + pa_assert(m); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "u", &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u)); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_object_array(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *path[], unsigned n) { + DBusMessageIter sub; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); + append_variant_object_array(m, &sub, path, n); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_string(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) { + DBusMessageIter sub; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); + append_variant_string(m, &sub, value); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_object(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) { + DBusMessageIter sub; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); + append_variant_object(m, &sub, value); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_unsigned(DBusMessage *m, DBusMessageIter *iter, const char *name, unsigned u) { + DBusMessageIter sub; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); + append_variant_unsigned(m, &sub, u); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static const char *array_root_containers[] = { OBJECT_SINKS, OBJECT_SOURCES }; +static const char *array_no_children[] = { }; + +static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *userdata) { + struct userdata *u = userdata; + DBusMessage *r = NULL; + + pa_assert(u); + + if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object_array(r, NULL, (const char**) array_root_containers, PA_ELEMENTSOF(array_root_containers)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_object_array(r, &sub, "Containers", array_root_containers, PA_ELEMENTSOF(array_root_containers)); + append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers)); + append_property_dict_entry_object_array(r, &sub, "Items", array_no_children, PA_ELEMENTSOF(array_no_children)); + append_property_dict_entry_unsigned(r, &sub, "ItemCount", PA_ELEMENTSOF(array_no_children)); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object(r, NULL, OBJECT_ROOT); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, u->display_name); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_string(r, &sub, "DisplayName", u->display_name); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + const char *xml = ROOT_INTROSPECT_XML; + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_STRING, &xml, + DBUS_TYPE_INVALID)); + + } else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (r) { + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static char *compute_url(struct userdata *u, const char *name) { + pa_strlist *i; + + pa_assert(u); + pa_assert(name); + + for (i = pa_http_protocol_servers(u->http); 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)) { + + const char *address; + char *s; + + if (pa_is_ip_address(a.path_or_host)) + address = a.path_or_host; + else + address = "@ADDRESS@"; + + if (a.port <= 0) + a.port = 4714; + + s = pa_sprintf_malloc("http://%s:%u/listen/source/%s", address, a.port, name); + + pa_xfree(a.path_or_host); + return s; + } + + pa_xfree(a.path_or_host); + } + + return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name); +} + +static char **child_array(struct userdata *u, const char *path, unsigned *n) { + unsigned m; + uint32_t idx; + char **array; + + pa_assert(u); + pa_assert(path); + pa_assert(n); + + if (pa_streq(path, OBJECT_SINKS)) + m = pa_idxset_size(u->core->sinks); + else + m = pa_idxset_size(u->core->sources); + + array = pa_xnew(char*, m); + *n = 0; + + if (pa_streq(path, OBJECT_SINKS)) { + pa_sink *sink; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) + array[(*n)++] = pa_sprintf_malloc(OBJECT_SINKS "/%u", sink->index); + } else { + pa_source *source; + + PA_IDXSET_FOREACH(source, u->core->sources, idx) + if (!source->monitor_of) + array[(*n)++] = pa_sprintf_malloc(OBJECT_SOURCES "/%u", source->index); + } + + pa_assert((*n) <= m); + + return array; +} + +static void free_child_array(char **array, unsigned n) { + + for (; n >= 1; n--) + pa_xfree(array[n-1]); + + pa_xfree(array); +} + +static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessage *m, void *userdata) { + struct userdata *u = userdata; + DBusMessage *r = NULL; + const char *path; + + pa_assert(u); + + path = dbus_message_get_path(m); + + if (pa_streq(path, OBJECT_SINKS) || pa_streq(path, OBJECT_SOURCES)) { + + /* Container nodes */ + + if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { + char ** array; + unsigned n; + + array = child_array(u, path, &n); + + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object_array(r, NULL, (const char**) array, n); + + free_child_array(array, n); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_unsigned(r, NULL, + pa_streq(path, OBJECT_SINKS) ? + pa_idxset_size(u->core->sinks) : + pa_idxset_size(u->core->sources)); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { + DBusMessageIter iter, sub; + char **array; + unsigned n; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_object_array(r, &sub, "Containers", array_no_children, PA_ELEMENTSOF(array_no_children)); + append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0); + + array = child_array(u, path, &n); + append_property_dict_entry_object_array(r, &sub, "Items", (const char**) array, n); + free_child_array(array, n); + append_property_dict_entry_unsigned(r, &sub, "ItemCount", + pa_streq(path, OBJECT_SINKS) ? + pa_idxset_size(u->core->sinks) : + pa_idxset_size(u->core->sources)); + + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object(r, NULL, OBJECT_ROOT); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, + NULL, + pa_streq(path, OBJECT_SINKS) ? + _("Output Devices") : + _("Input Devices")); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_object(m, &sub, "Parent", OBJECT_ROOT); + append_property_dict_entry_string(m, &sub, "DisplayName", + pa_streq(path, OBJECT_SINKS) ? + _("Output Devices") : + _("Input Devices")); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + pa_strbuf *sb; + char *xml; + uint32_t idx; + + sb = pa_strbuf_new(); + pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_PREFIX); + + if (pa_streq(path, OBJECT_SINKS)) { + pa_sink *sink; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) + pa_strbuf_printf(sb, "<node name=\"%u\"/>", sink->index); + } else { + pa_source *source; + + PA_IDXSET_FOREACH(source, u->core->sources, idx) + if (!source->monitor_of) + pa_strbuf_printf(sb, "<node name=\"%u\"/>", source->index); + } + + pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_POSTFIX); + xml = pa_strbuf_tostring_free(sb); + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_STRING, &xml, + DBUS_TYPE_INVALID)); + + pa_xfree(xml); + } else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + } else { + pa_sink *sink = NULL; + pa_source *source = NULL; + + /* Child nodes */ + + if (pa_startswith(path, OBJECT_SINKS "/")) + sink = pa_namereg_get(u->core, path + sizeof(OBJECT_SINKS), PA_NAMEREG_SINK); + else if (pa_startswith(path, OBJECT_SOURCES "/")) + source = pa_namereg_get(u->core, path + sizeof(OBJECT_SOURCES), PA_NAMEREG_SOURCE); + + if (!sink && !source) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES); + append_property_dict_entry_string(r, &sub, "DisplayName", pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "Type")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, "audio"); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "MIMEType")) { + char *t; + + if (sink) + t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); + else + t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, t); + pa_xfree(t); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "URLs")) { + DBusMessageIter iter, sub, array; + char *url; + + pa_assert_se(r = dbus_message_new_method_return(m)); + + dbus_message_iter_init_append(r, &iter); + + url = compute_url(u, sink ? sink->monitor_source->name : source->name); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "as", &sub)); + pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array)); + pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); + pa_assert_se(dbus_message_iter_close_container(&sub, &array)); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + pa_xfree(url); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem1")) { + DBusMessageIter iter, sub, dict, variant, array; + char *url, *t; + const char *un = "URLs"; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_string(r, &sub, "Type", "audio"); + + if (sink) + t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); + else + t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + + append_property_dict_entry_string(r, &sub, "MIMEType", t); + pa_xfree(t); + + pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &dict)); + pa_assert_se(dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &un)); + + url = compute_url(u, sink ? sink->monitor_source->name : source->name); + + pa_assert_se(dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "as", &variant)); + pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array)); + pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); + pa_assert_se(dbus_message_iter_close_container(&variant, &array)); + pa_assert_se(dbus_message_iter_close_container(&dict, &variant)); + pa_assert_se(dbus_message_iter_close_container(&sub, &dict)); + + pa_xfree(url); + + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + const char *xml = + ITEM_INTROSPECT_XML; + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_STRING, &xml, + DBUS_TYPE_INVALID)); + + } else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (r) { + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +int pa__init(pa_module *m) { + + struct userdata *u; + pa_modargs *ma = NULL; + DBusError error; + const char *t; + + static const DBusObjectPathVTable vtable_root = { + .message_function = root_handler, + }; + static const DBusObjectPathVTable vtable_sinks_and_sources = { + .message_function = sinks_and_sources_handler, + }; + + dbus_error_init(&error); + + 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->http = pa_http_protocol_get(u->core); + + if ((t = pa_modargs_get_value(ma, "display_name", NULL))) + u->display_name = pa_utf8_filter(t); + else + u->display_name = pa_xstrdup(_("Audio on @HOSTNAME@")); + + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) source_new_or_unlink_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_new_or_unlink_cb, u); + + if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error))) { + pa_log("Failed to get session bus connection: %s", error.message); + goto fail; + } + + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT, &vtable_root, u)); + pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SINKS, &vtable_sinks_and_sources, u)); + pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SOURCES, &vtable_sinks_and_sources, u)); + + if (dbus_bus_request_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + pa_log("Failed to request service name " SERVICE_NAME ": %s", error.message); + goto fail; + } + + u->got_name = TRUE; + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + dbus_error_free(&error); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata*u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->source_new_slot) + pa_hook_slot_free(u->source_new_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); + + if (u->bus) { + DBusError error; + + dbus_error_init(&error); + + dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT); + dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SINKS); + dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SOURCES); + + if (u->got_name) { + if (dbus_bus_release_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, &error) != DBUS_RELEASE_NAME_REPLY_RELEASED) { + pa_log("Failed to release service name " SERVICE_NAME ": %s", error.message); + dbus_error_free(&error); + } + } + + pa_dbus_connection_unref(u->bus); + } + + pa_xfree(u->display_name); + + if (u->http) + pa_http_protocol_unref(u->http); + + pa_xfree(u); +} diff --git a/src/modules/module-sine-source.c b/src/modules/module-sine-source.c index a5f1ce70..14a04e47 100644 --- a/src/modules/module-sine-source.c +++ b/src/modules/module-sine-source.c @@ -55,8 +55,9 @@ PA_MODULE_DESCRIPTION("Sine wave generator source"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( - "rate=<sample rate> " "source_name=<name for the source> " + "source_properties=<properties for the source> " + "rate=<sample rate> " "frequency=<frequency in Hz>"); #define DEFAULT_SOURCE_NAME "sine_input" @@ -79,8 +80,9 @@ struct userdata { }; static const char* const valid_modargs[] = { - "rate", "source_name", + "source_properties", + "rate", "frequency", NULL }; @@ -248,6 +250,12 @@ int pa__init(pa_module*m) { pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency); pa_source_new_data_set_sample_spec(&data, &ss); + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); pa_source_new_data_done(&data); @@ -264,8 +272,7 @@ int pa__init(pa_module*m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); - u->source->fixed_latency = u->block_usec; - + pa_source_set_fixed_latency(u->source, u->block_usec); if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index 3f46406a..5cfa97a7 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -68,7 +68,9 @@ PA_MODULE_DESCRIPTION("Solaris Sink/Source"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "source_name=<name for the source> " + "source_properties=<properties for the source> " "device=<audio device file name> " "record=<enable source?> " "playback=<enable sink?> " @@ -112,7 +114,9 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "source_name", + "source_properties", "device", "record", "playback", @@ -897,10 +901,16 @@ int pa__init(pa_module *m) { pa_source_new_data_set_channel_map(&source_new_data, &map); pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_STRING, u->device_name); pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_API, "solaris"); - pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM source"); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM source"); pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, "serial"); pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) u->buffer_size); + if (pa_modargs_get_proplist(ma, "source_properties", source_new_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&source_new_data); + goto fail; + } + u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL); pa_source_new_data_done(&source_new_data); pa_xfree(name_buf); @@ -939,9 +949,15 @@ int pa__init(pa_module *m) { pa_sink_new_data_set_channel_map(&sink_new_data, &map); pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_STRING, u->device_name); pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_API, "solaris"); - pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM sink"); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM sink"); pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, "serial"); + if (pa_modargs_get_proplist(ma, "sink_properties", sink_new_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_new_data); + goto fail; + } + u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL); pa_sink_new_data_done(&sink_new_data); diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index 70cd89a7..2de98f4e 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -30,7 +30,6 @@ #include <stdio.h> #include <stdlib.h> #include <ctype.h> -#include <gdbm.h> #include <pulse/xmalloc.h> #include <pulse/volume.h> @@ -49,6 +48,7 @@ #include <pulsecore/protocol-native.h> #include <pulsecore/pstream.h> #include <pulsecore/pstream-util.h> +#include <pulsecore/database.h> #include "module-stream-restore-symdef.h" @@ -81,7 +81,7 @@ struct userdata { *source_output_new_hook_slot, *connection_unlink_hook_slot; pa_time_event *save_time_event; - GDBM_FILE gdbm_file; + pa_database* database; pa_bool_t restore_device:1; pa_bool_t restore_volume:1; @@ -123,7 +123,7 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct u->core->mainloop->time_free(u->save_time_event); u->save_time_event = NULL; - gdbm_sync(u->gdbm_file); + pa_database_sync(u->database); pa_log_info("Synced."); } @@ -153,28 +153,28 @@ static char *get_name(pa_proplist *p, const char *prefix) { } static struct entry* read_entry(struct userdata *u, const char *name) { - datum key, data; + pa_datum key, data; struct entry *e; pa_assert(u); pa_assert(name); - key.dptr = (char*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - data = gdbm_fetch(u->gdbm_file, key); + pa_zero(data); - if (!data.dptr) + if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.dsize != sizeof(struct entry)) { + if (data.size != sizeof(struct entry)) { /* This is probably just a database upgrade, hence let's not * consider this more than a debug message */ - pa_log_debug("Database contains entry for stream %s of wrong size %lu != %lu. Probably due to uprade, ignoring.", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + pa_log_debug("Database contains entry for stream %s of wrong size %lu != %lu. Probably due to uprade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); goto fail; } - e = (struct entry*) data.dptr; + e = (struct entry*) data.data; if (e->version != ENTRY_VERSION) { pa_log_debug("Version of database entry for stream %s doesn't match our version. Probably due to upgrade, ignoring.", name); @@ -205,7 +205,7 @@ static struct entry* read_entry(struct userdata *u, const char *name) { fail: - pa_xfree(data.dptr); + pa_datum_free(&data); return NULL; } @@ -261,7 +261,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 struct userdata *u = userdata; struct entry entry, *old; char *name; - datum key, data; + pa_datum key, data; pa_assert(c); pa_assert(u); @@ -272,7 +272,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - memset(&entry, 0, sizeof(entry)); + pa_zero(entry); entry.version = ENTRY_VERSION; if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { @@ -334,15 +334,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 pa_xfree(old); } - key.dptr = name; - key.dsize = (int) strlen(name); + key.data = name; + key.size = strlen(name); - data.dptr = (void*) &entry; - data.dsize = sizeof(entry); + data.data = &entry; + data.size = sizeof(entry); pa_log_info("Storing volume/mute/device for stream %s.", name); - gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); + pa_database_set(u->database, &key, &data, TRUE); pa_xfree(name); @@ -471,25 +471,6 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou #define EXT_VERSION 1 -static void clear_db(struct userdata *u) { - datum key; - - pa_assert(u); - - key = gdbm_firstkey(u->gdbm_file); - while (key.dptr) { - datum next_key; - next_key = gdbm_nextkey(u->gdbm_file, key); - - gdbm_delete(u->gdbm_file, key); - pa_xfree(key.dptr); - - key = next_key; - } - - gdbm_reorganize(u->gdbm_file); -} - static void apply_entry(struct userdata *u, const char *name, struct entry *e) { pa_sink_input *si; pa_source_output *so; @@ -559,18 +540,20 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { #if 0 static void dump_database(struct userdata *u) { - datum key; + pa_datum key; + pa_bool_t done; - key = gdbm_firstkey(u->gdbm_file); - while (key.dptr) { - datum next_key; + done = !pa_database_first(u->database, &key, NULL); + + while (!done) { + pa_datum next_key; struct entry *e; char *name; - next_key = gdbm_nextkey(u->gdbm_file, key); + done = !pa_database_next(u->database, &key, &next_key, NULL); - name = pa_xstrndup(key.dptr, key.dsize); - pa_xfree(key.dptr); + name = pa_xstrndup(key.data, key.size); + pa_datum_free(&key); if ((e = read_entry(u, name))) { char t[256]; @@ -618,21 +601,23 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio } case SUBCOMMAND_READ: { - datum key; + pa_datum key; + pa_bool_t done; if (!pa_tagstruct_eof(t)) goto fail; - key = gdbm_firstkey(u->gdbm_file); - while (key.dptr) { - datum next_key; + done = !pa_database_first(u->database, &key, NULL); + + while (!done) { + pa_datum next_key; struct entry *e; char *name; - next_key = gdbm_nextkey(u->gdbm_file, key); + done = !pa_database_next(u->database, &key, &next_key, NULL); - name = pa_xstrndup(key.dptr, (size_t) key.dsize); - pa_xfree(key.dptr); + name = pa_xstrndup(key.data, key.size); + pa_datum_free(&key); if ((e = read_entry(u, name))) { pa_cvolume r; @@ -669,16 +654,15 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio goto fail; if (mode == PA_UPDATE_SET) - clear_db(u); + pa_database_clear(u->database); while (!pa_tagstruct_eof(t)) { const char *name, *device; pa_bool_t muted; struct entry entry; - datum key, data; - int k; + pa_datum key, data; - memset(&entry, 0, sizeof(entry)); + pa_zero(entry); entry.version = ENTRY_VERSION; if (pa_tagstruct_gets(t, &name) < 0 || @@ -708,13 +692,13 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio !pa_namereg_is_valid_name(entry.device)) goto fail; - key.dptr = (void*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - data.dptr = (void*) &entry; - data.dsize = sizeof(entry); + data.data = &entry; + data.size = sizeof(entry); - if ((k = gdbm_store(u->gdbm_file, key, data, mode == PA_UPDATE_REPLACE ? GDBM_REPLACE : GDBM_INSERT)) == 0) + if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0) if (apply_immediately) apply_entry(u, name, &entry); } @@ -728,15 +712,15 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio while (!pa_tagstruct_eof(t)) { const char *name; - datum key; + pa_datum key; if (pa_tagstruct_gets(t, &name) < 0) goto fail; - key.dptr = (void*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - gdbm_delete(u->gdbm_file, key); + pa_database_unset(u->database, &key); } trigger_save(u); @@ -786,12 +770,11 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname, *fn; + char *fname; pa_sink_input *si; pa_source_output *so; uint32_t idx; pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE; - int gdbm_cache_size; pa_assert(m); @@ -817,7 +800,7 @@ int pa__init(pa_module*m) { u->restore_device = restore_device; u->restore_volume = restore_volume; u->restore_muted = restore_muted; - u->gdbm_file = NULL; + u->database = NULL; u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); u->protocol = pa_native_protocol_get(m->core); @@ -835,27 +818,18 @@ int pa__init(pa_module*m) { if (restore_volume || restore_muted) u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); - /* We include the host identifier in the file name because gdbm - * files are CPU dependant, and we don't want things to go wrong - * if we are on a multiarch system. */ - fn = pa_sprintf_malloc("stream-volumes."CANONICAL_HOST".gdbm"); - fname = pa_state_path(fn, TRUE); - pa_xfree(fn); + fname = pa_state_path("stream-volumes", TRUE); if (!fname) goto fail; - if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) { - pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); + if (!(u->database = pa_database_open(fname, TRUE))) { + pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); pa_xfree(fname); goto fail; } - /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ - gdbm_cache_size = 10; - gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); - pa_log_info("Sucessfully opened database file '%s'.", fname); pa_xfree(fname); @@ -901,8 +875,8 @@ void pa__done(pa_module*m) { if (u->save_time_event) u->core->mainloop->time_free(u->save_time_event); - if (u->gdbm_file) - gdbm_close(u->gdbm_file); + if (u->database) + pa_database_close(u->database); if (u->protocol) { pa_native_protocol_remove_ext(u->protocol, m); diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 7e17f8f7..c5b78911 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -27,6 +27,7 @@ #include <pulse/timeval.h> #include <pulsecore/core.h> +#include <pulsecore/core-util.h> #include <pulsecore/sink-input.h> #include <pulsecore/source-output.h> #include <pulsecore/modargs.h> @@ -85,30 +86,39 @@ static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval d->userdata->core->mainloop->time_restart(d->time_event, NULL); - if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) { + if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && !(d->sink->suspend_cause & PA_SUSPEND_IDLE)) { pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name); - pa_sink_suspend(d->sink, TRUE); + pa_sink_suspend(d->sink, TRUE, PA_SUSPEND_IDLE); } - if (d->source && pa_source_check_suspend(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) { + if (d->source && pa_source_check_suspend(d->source) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) { pa_log_info("Source %s idle for too long, suspending ...", d->source->name); - pa_source_suspend(d->source, TRUE); + pa_source_suspend(d->source, TRUE, PA_SUSPEND_IDLE); } } static void restart(struct device_info *d) { struct timeval tv; + const char *s; + uint32_t timeout; pa_assert(d); + pa_assert(d->sink || d->source); pa_gettimeofday(&tv); d->last_use = tv; - pa_timeval_add(&tv, d->userdata->timeout*1000000); + + s = pa_proplist_gets(d->sink ? d->sink->proplist : d->source->proplist, "module-suspend-on-idle.timeout"); + if (!s || pa_atou(s, &timeout) < 0) + timeout = d->userdata->timeout; + + pa_timeval_add(&tv, timeout * PA_USEC_PER_SEC); + d->userdata->core->mainloop->time_restart(d->time_event, &tv); if (d->sink) - pa_log_debug("Sink %s becomes idle.", d->sink->name); + pa_log_debug("Sink %s becomes idle, timeout in %u seconds.", d->sink->name, timeout); if (d->source) - pa_log_debug("Source %s becomes idle.", d->source->name); + pa_log_debug("Source %s becomes idle, timeout in %u seconds.", d->source->name, timeout); } static void resume(struct device_info *d) { @@ -117,13 +127,13 @@ static void resume(struct device_info *d) { d->userdata->core->mainloop->time_restart(d->time_event, NULL); if (d->sink) { - pa_sink_suspend(d->sink, FALSE); + pa_sink_suspend(d->sink, FALSE, PA_SUSPEND_IDLE); pa_log_debug("Sink %s becomes busy.", d->sink->name); } if (d->source) { - pa_source_suspend(d->source, FALSE); + pa_source_suspend(d->source, FALSE, PA_SUSPEND_IDLE); pa_log_debug("Source %s becomes busy.", d->source->name); } diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 5ea58aa0..6f525da3 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -64,24 +64,26 @@ #ifdef TUNNEL_SINK PA_MODULE_DESCRIPTION("Tunnel module for sinks"); PA_MODULE_USAGE( + "sink_name=<name for the local sink> " + "sink_properties=<properties for the local sink> " "server=<address> " "sink=<remote sink name> " "cookie=<filename> " "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "sink_name=<name for the local sink> " "channel_map=<channel map>"); #else PA_MODULE_DESCRIPTION("Tunnel module for sources"); PA_MODULE_USAGE( + "source_name=<name for the local source> " + "source_properties=<properties for the local source> " "server=<address> " "source=<remote source name> " "cookie=<filename> " "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "source_name=<name for the local source> " "channel_map=<channel map>"); #endif @@ -97,9 +99,11 @@ static const char* const valid_modargs[] = { "rate", #ifdef TUNNEL_SINK "sink_name", + "sink_properties", "sink", #else "source_name", + "source_properties", "source", #endif "channel_map", @@ -1873,6 +1877,12 @@ int pa__init(pa_module*m) { if (u->sink_name) pa_proplist_sets(data.proplist, "tunnel.remote.sink", u->sink_name); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL); pa_sink_new_data_done(&data); @@ -1911,6 +1921,12 @@ int pa__init(pa_module*m) { if (u->source_name) pa_proplist_sets(data.proplist, "tunnel.remote.source", u->source_name); + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_NETWORK|PA_SOURCE_LATENCY); pa_source_new_data_done(&data); diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c new file mode 100644 index 00000000..1ad6fa2d --- /dev/null +++ b/src/modules/module-udev-detect.c @@ -0,0 +1,457 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <limits.h> +#include <sys/inotify.h> +#include <libudev.h> + +#include <pulsecore/modargs.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/namereg.h> + +#include "module-udev-detect-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +struct device { + char *path; + pa_bool_t accessible; + char *card_name; + uint32_t module; +}; + +struct userdata { + pa_core *core; + pa_hashmap *devices; + pa_bool_t use_tsched; + + struct udev* udev; + struct udev_monitor *monitor; + pa_io_event *udev_io; + + int inotify_fd; + pa_io_event *inotify_io; +}; + +static const char* const valid_modargs[] = { + "tsched", + NULL +}; + +static void device_free(struct device *d) { + pa_assert(d); + + pa_xfree(d->path); + pa_xfree(d->card_name); + pa_xfree(d); +} + +static const char *path_get_card_id(const char *path) { + const char *e; + + if (!path) + return NULL; + + if (!(e = strrchr(path, '/'))) + return NULL; + + if (!pa_startswith(e, "/card")) + return NULL; + + return e + 5; +} + +static void verify_access(struct userdata *u, struct device *d) { + char *cd; + pa_card *card; + + pa_assert(u); + pa_assert(d); + + if (!(card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) + return; + + cd = pa_sprintf_malloc("%s/snd/controlC%s", udev_get_dev_path(u->udev), path_get_card_id(d->path)); + d->accessible = access(cd, W_OK) >= 0; + pa_log_info("%s is accessible: %s", cd, pa_yes_no(d->accessible)); + pa_xfree(cd); + + pa_card_suspend(card, !d->accessible, PA_SUSPEND_SESSION); +} + +static void card_changed(struct userdata *u, struct udev_device *dev) { + struct device *d; + const char *path; + const char *t; + char *card_name, *args; + pa_module *m; + char *n; + + pa_assert(u); + pa_assert(dev); + + path = udev_device_get_devpath(dev); + + if ((d = pa_hashmap_get(u->devices, path))) { + verify_access(u, d); + return; + } + + if (!(t = udev_device_get_property_value(dev, "PULSE_NAME"))) + if (!(t = udev_device_get_property_value(dev, "ID_ID"))) + if (!(t = udev_device_get_property_value(dev, "ID_PATH"))) + t = path_get_card_id(path); + + n = pa_namereg_make_valid_name(t); + + card_name = pa_sprintf_malloc("alsa_card.%s", n); + args = pa_sprintf_malloc("device_id=\"%s\" " + "name=\"%s\" " + "card_name=\"%s\" " + "tsched=%i " + "card_properties=\"module-udev-detect.discovered=1\"", + path_get_card_id(path), + n, + card_name, + (int) u->use_tsched); + + pa_log_debug("Loading module-alsa-card with arguments '%s'", args); + m = pa_module_load(u->core, "module-alsa-card", args); + pa_xfree(args); + + if (m) { + pa_log_info("Card %s (%s) added.", path, n); + + d = pa_xnew(struct device, 1); + d->path = pa_xstrdup(path); + d->card_name = card_name; + d->module = m->index; + d->accessible = TRUE; + + pa_hashmap_put(u->devices, d->path, d); + } else + pa_xfree(card_name); + + pa_xfree(n); +} + +static void remove_card(struct userdata *u, struct udev_device *dev) { + struct device *d; + + pa_assert(u); + pa_assert(dev); + + if (!(d = pa_hashmap_remove(u->devices, udev_device_get_devpath(dev)))) + return; + + pa_log_info("Card %s removed.", d->path); + pa_module_unload_request_by_index(u->core, d->module, TRUE); + device_free(d); +} + +static void process_device(struct userdata *u, struct udev_device *dev) { + const char *action, *ff; + + pa_assert(u); + pa_assert(dev); + + if (udev_device_get_property_value(dev, "PULSE_IGNORE")) { + pa_log_debug("Ignoring %s, because marked so.", udev_device_get_devpath(dev)); + return; + } + + if ((ff = udev_device_get_property_value(dev, "SOUND_FORM_FACTOR")) && + pa_streq(ff, "modem")) { + pa_log_debug("Ignoring %s, because it is a modem.", udev_device_get_devpath(dev)); + return; + } + + action = udev_device_get_action(dev); + + if (action && pa_streq(action, "remove")) + remove_card(u, dev); + else if ((!action || pa_streq(action, "change")) && + udev_device_get_property_value(dev, "SOUND_INITIALIZED")) + card_changed(u, dev); + + /* For an explanation why we don't look for 'add' events here + * have a look into /lib/udev/rules.d/78-sound-card.rules! */ +} + +static void process_path(struct userdata *u, const char *path) { + struct udev_device *dev; + + if (!path_get_card_id(path)) + return; + + if (!(dev = udev_device_new_from_syspath(u->udev, path))) { + pa_log("Failed to get udev device object from udev."); + return; + } + + process_device(u, dev); + udev_device_unref(dev); +} + +static void monitor_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct userdata *u = userdata; + struct udev_device *dev; + + pa_assert(a); + + if (!(dev = udev_monitor_receive_device(u->monitor))) { + pa_log("Failed to get udev device object from monitor."); + goto fail; + } + + if (!path_get_card_id(udev_device_get_devpath(dev))) + return; + + process_device(u, dev); + udev_device_unref(dev); + return; + +fail: + a->io_free(u->udev_io); + u->udev_io = NULL; +} + +static void inotify_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct { + struct inotify_event e; + char name[NAME_MAX]; + } buf; + struct userdata *u = userdata; + static int type = 0; + pa_bool_t verify = FALSE; + + for (;;) { + ssize_t r; + + pa_zero(buf); + if ((r = pa_read(fd, &buf, sizeof(buf), &type)) <= 0) { + + if (r < 0 && errno == EAGAIN) + break; + + pa_log("read() from inotify failed: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + goto fail; + } + + if ((buf.e.mask & IN_CLOSE_WRITE) && pa_startswith(buf.e.name, "pcmC")) + verify = TRUE; + } + + if (verify) { + struct device *d; + void *state; + + pa_log_debug("Verifying access."); + + PA_HASHMAP_FOREACH(d, u->devices, state) + verify_access(u, d); + } + + return; + +fail: + a->io_free(u->inotify_io); + u->inotify_io = NULL; + + if (u->inotify_fd >= 0) { + pa_close(u->inotify_fd); + u->inotify_fd = -1; + } +} + +static int setup_inotify(struct userdata *u) { + char *dev_snd; + int r; + + if ((u->inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) { + pa_log("inotify_init1() failed: %s", pa_cstrerror(errno)); + return -1; + } + + dev_snd = pa_sprintf_malloc("%s/snd", udev_get_dev_path(u->udev)); + r = inotify_add_watch(u->inotify_fd, dev_snd, IN_CLOSE_WRITE); + pa_xfree(dev_snd); + + if (r < 0) { + pa_log("inotify_add_watch() failed: %s", pa_cstrerror(errno)); + return -1; + } + + pa_assert_se(u->inotify_io = u->core->mainloop->io_new(u->core->mainloop, u->inotify_fd, PA_IO_EVENT_INPUT, inotify_cb, u)); + + return 0; +} + +int pa__init(pa_module *m) { + struct userdata *u = NULL; + pa_modargs *ma; + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + int fd; + + pa_assert(m); + + 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->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + u->use_tsched = TRUE; + u->inotify_fd = -1; + + if (pa_modargs_get_value_boolean(ma, "tsched", &u->use_tsched) < 0) { + pa_log("Failed to parse tsched argument."); + goto fail; + } + + if (!(u->udev = udev_new())) { + pa_log("Failed to initialize udev library."); + goto fail; + } + + if (setup_inotify(u) < 0) + goto fail; + + if (!(u->monitor = udev_monitor_new_from_netlink(u->udev, "udev"))) { + pa_log("Failed to initialize monitor."); + goto fail; + } + + errno = 0; + if (udev_monitor_enable_receiving(u->monitor) < 0) { + pa_log("Failed to enable monitor: %s", pa_cstrerror(errno)); + if (errno == EPERM) + pa_log_info("Most likely your kernel is simply too old and " + "allows only priviliged processes to listen to device events. " + "Please upgrade your kernel to at least 2.6.30."); + goto fail; + } + + if ((fd = udev_monitor_get_fd(u->monitor)) < 0) { + pa_log("Failed to get udev monitor fd."); + goto fail; + } + + pa_assert_se(u->udev_io = u->core->mainloop->io_new(u->core->mainloop, fd, PA_IO_EVENT_INPUT, monitor_cb, u)); + + if (!(enumerate = udev_enumerate_new(u->udev))) { + pa_log("Failed to initialize udev enumerator."); + goto fail; + } + + if (udev_enumerate_add_match_subsystem(enumerate, "sound") < 0) { + pa_log("Failed to match to subsystem."); + goto fail; + } + + if (udev_enumerate_scan_devices(enumerate) < 0) { + pa_log("Failed to scan for devices."); + goto fail; + } + + first = udev_enumerate_get_list_entry(enumerate); + udev_list_entry_foreach(item, first) + process_path(u, udev_list_entry_get_name(item)); + + udev_enumerate_unref(enumerate); + + pa_log_info("Loaded %u modules.", pa_hashmap_size(u->devices)); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (enumerate) + udev_enumerate_unref(enumerate); + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->udev_io) + m->core->mainloop->io_free(u->udev_io); + + if (u->monitor) + udev_monitor_unref(u->monitor); + + if (u->udev) + udev_unref(u->udev); + + if (u->inotify_io) + m->core->mainloop->io_free(u->inotify_io); + + if (u->inotify_fd >= 0) + pa_close(u->inotify_fd); + + if (u->devices) { + struct device *d; + + while ((d = pa_hashmap_steal_first(u->devices))) + device_free(d); + + pa_hashmap_free(u->devices, NULL, NULL); + } + + pa_xfree(u); +} diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 61858afa..91da598e 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -36,6 +36,7 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("Compatibility module"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_DEPRECATED("Please use module-stream-restore instead of module-volume-restore!"); static const char* const valid_modargs[] = { "table", @@ -62,7 +63,7 @@ int pa__init(pa_module*m) { goto fail; } - pa_log_warn("module-volume-restore is obsolete. It has been replaced by module-stream-restore. We will now load the latter but please make sure to remove module-volume-restore from your configuration."); + pa_log_warn("We will now load module-stream-restore. Please make sure to remove module-volume-restore from your configuration."); t = pa_sprintf_malloc("restore_volume=%s restore_device=%s", pa_yes_no(restore_volume), pa_yes_no(restore_device)); pa_module_load(m->core, "module-stream-restore", t); diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c index 692ffe91..d72d2647 100644 --- a/src/modules/module-zeroconf-publish.c +++ b/src/modules/module-zeroconf-publish.c @@ -37,6 +37,7 @@ #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> @@ -47,6 +48,7 @@ #include <pulsecore/modargs.h> #include <pulsecore/avahi-wrap.h> #include <pulsecore/endianmacros.h> +#include <pulsecore/protocol-native.h> #include "module-zeroconf-publish-symdef.h" @@ -54,7 +56,6 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); -PA_MODULE_USAGE("port=<IP port number>"); #define SERVICE_TYPE_SINK "_pulse-sink._tcp" #define SERVICE_TYPE_SOURCE "_pulse-source._tcp" @@ -67,7 +68,6 @@ PA_MODULE_USAGE("port=<IP port number>"); #define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE static const char* const valid_modargs[] = { - "port", NULL }; @@ -88,6 +88,7 @@ struct service { struct userdata { pa_core *core; pa_module *module; + AvahiPoll *avahi_poll; AvahiClient *client; @@ -96,15 +97,15 @@ struct userdata { AvahiEntryGroup *main_entry_group; - uint16_t port; - 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; }; -static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, const char **ret_description, enum service_subtype *ret_subtype) { +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_description); + pa_assert(ret_proplist); pa_assert(ret_subtype); if (pa_sink_isinstance(s->device)) { @@ -113,7 +114,7 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann *ret_ss = sink->sample_spec; *ret_map = sink->channel_map; *ret_name = sink->name; - *ret_description = pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); + *ret_proplist = sink->proplist; *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; } else if (pa_source_isinstance(s->device)) { @@ -122,7 +123,7 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann *ret_ss = source->sample_spec; *ret_map = source->channel_map; *ret_name = source->name; - *ret_description = pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)); + *ret_proplist = source->proplist; *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL); } else @@ -131,11 +132,24 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) { char s[128]; + char *t; pa_assert(c); l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION); - l = avahi_string_list_add_pair(l, "user-name", pa_get_user_name(s, sizeof(s))); + + t = pa_get_user_name_malloc(); + l = avahi_string_list_add_pair(l, "user-name", t); + pa_xfree(t); + + t = pa_machine_id(); + l = avahi_string_list_add_pair(l, "machine-id", t); + pa_xfree(t); + + t = pa_uname_string(); + l = avahi_string_list_add_pair(l, "uname", t); + pa_xfree(t); + l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s))); l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie); @@ -184,10 +198,35 @@ static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupStat static void service_free(struct service *s); +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 a.port; + } + + pa_xfree(a.path_or_host); + } + + return PA_NATIVE_DEFAULT_PORT; +} + static int publish_service(struct service *s) { int r = -1; AvahiStringList *txt = NULL; - const char *description = NULL, *name = NULL; + const char *name = NULL, *t; + pa_proplist *proplist = NULL; pa_sample_spec ss; pa_channel_map map; char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; @@ -214,7 +253,7 @@ static int publish_service(struct service *s) { txt = txt_record_server_data(s->userdata->core, txt); - get_service_data(s, &ss, &map, &name, &description, &subtype); + get_service_data(s, &ss, &map, &name, &proplist, &subtype); txt = avahi_string_list_add_pair(txt, "device", name); txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate); txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels); @@ -222,6 +261,19 @@ static int publish_service(struct service *s) { txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &map)); txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[subtype]); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION))) + txt = avahi_string_list_add_pair(txt, "description", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME))) + txt = avahi_string_list_add_pair(txt, "icon-name", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME))) + txt = avahi_string_list_add_pair(txt, "vendor-name", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME))) + txt = avahi_string_list_add_pair(txt, "product-name", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS))) + txt = avahi_string_list_add_pair(txt, "class", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR))) + txt = avahi_string_list_add_pair(txt, "form-factor", t); + if (avahi_entry_group_add_service_strlst( s->entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, @@ -230,7 +282,7 @@ static int publish_service(struct service *s) { pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, NULL, NULL, - s->userdata->port, + compute_port(s->userdata), txt) < 0) { pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); @@ -287,7 +339,7 @@ finish: static struct service *get_service(struct userdata *u, pa_object *device) { struct service *s; - char hn[64], un[64]; + char *hn, *un; const char *n; pa_assert(u); @@ -309,11 +361,13 @@ static struct service *get_service(struct userdata *u, pa_object *device) { n = PA_SOURCE(device)->name; } - s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", - pa_get_user_name(un, sizeof(un)), - pa_get_host_name(hn, sizeof(hn)), - n), - AVAHI_LABEL_MAX-1); + 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), AVAHI_LABEL_MAX-1); + + pa_xfree(un); + pa_xfree(hn); pa_hashmap_put(u->services, s->device, s); @@ -430,7 +484,7 @@ static int publish_main_service(struct userdata *u) { SERVICE_TYPE_SERVER, NULL, NULL, - u->port, + compute_port(u), txt) < 0) { pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client))); @@ -552,9 +606,8 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda int pa__init(pa_module*m) { struct userdata *u; - uint32_t port = PA_NATIVE_DEFAULT_PORT; pa_modargs *ma = NULL; - char hn[256], un[256]; + char *hn, *un; int error; if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { @@ -562,15 +615,10 @@ int pa__init(pa_module*m) { goto fail; } - if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) { - pa_log("Invalid port specified."); - goto fail; - } - m->userdata = u = pa_xnew(struct userdata, 1); u->core = m->core; u->module = m; - u->port = (uint16_t) port; + u->native = pa_native_protocol_get(u->core); u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); @@ -585,7 +633,11 @@ int pa__init(pa_module*m) { u->main_entry_group = NULL; - u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn))), AVAHI_LABEL_MAX); + 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), AVAHI_LABEL_MAX-1); + pa_xfree(un); + pa_xfree(hn); if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); @@ -643,6 +695,9 @@ void pa__done(pa_module*m) { if (u->avahi_poll) pa_avahi_poll_free(u->avahi_poll); + if (u->native) + pa_native_protocol_unref(u->native); + pa_xfree(u->service_name); pa_xfree(u); } diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c index 9f7863f5..b1afcfd6 100644 --- a/src/modules/oss/module-oss.c +++ b/src/modules/oss/module-oss.c @@ -85,17 +85,22 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "source_name=<name for the source> " + "source_properties=<properties for the source> " "device=<OSS device> " "record=<enable source?> " "playback=<enable sink?> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " "fragments=<number of fragments> " "fragment_size=<fragment size> " - "channel_map=<channel map> " "mmap=<enable memory mapping?>"); +#ifdef __linux__ +PA_MODULE_DEPRECATED("Please use module-alsa-card instead of module-oss!"); +#endif #define DEFAULT_DEVICE "/dev/dsp" @@ -140,7 +145,9 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "source_name", + "source_properties", "device", "record", "playback", @@ -477,6 +484,7 @@ static void build_pollfd(struct userdata *u) { pollfd->revents = 0; } +/* Called from IO context */ static int suspend(struct userdata *u) { pa_assert(u); pa_assert(u->fd >= 0); @@ -526,6 +534,7 @@ static int suspend(struct userdata *u) { return 0; } +/* Called from IO context */ static int unsuspend(struct userdata *u) { int m; pa_sample_spec ss, *ss_original; @@ -616,10 +625,10 @@ static int unsuspend(struct userdata *u) { build_pollfd(u); - if (u->sink) - pa_sink_get_volume(u->sink, TRUE, FALSE); - if (u->source) - pa_source_get_volume(u->source, TRUE); + if (u->sink && u->sink->get_volume) + u->sink->get_volume(u->sink); + if (u->source && u->source->get_volume) + u->source->get_volume(u->source); pa_log_info("Resumed successfully..."); @@ -631,6 +640,7 @@ fail: return -1; } +/* Called from IO context */ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; int ret; @@ -1311,6 +1321,12 @@ int pa__init(pa_module*m) { pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->in_hwbuf_size)); pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->in_fragment_size)); + if (pa_modargs_get_proplist(ma, "source_properties", source_new_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&source_new_data); + goto fail; + } + u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); pa_source_new_data_done(&source_new_data); pa_xfree(name_buf); @@ -1325,8 +1341,8 @@ int pa__init(pa_module*m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->in_hwbuf_size, &u->source->sample_spec)); u->source->refresh_volume = TRUE; - u->source->fixed_latency = pa_bytes_to_usec(u->in_hwbuf_size, &u->source->sample_spec); if (use_mmap) u->in_mmap_memblocks = pa_xnew0(pa_memblock*, u->in_nfrags); @@ -1373,6 +1389,12 @@ int pa__init(pa_module*m) { pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->out_hwbuf_size)); pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->out_fragment_size)); + if (pa_modargs_get_proplist(ma, "sink_properties", sink_new_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_new_data); + goto fail; + } + u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY); pa_sink_new_data_done(&sink_new_data); pa_xfree(name_buf); @@ -1387,8 +1409,8 @@ int pa__init(pa_module*m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->out_hwbuf_size, &u->sink->sample_spec)); u->sink->refresh_volume = TRUE; - u->sink->fixed_latency = pa_bytes_to_usec(u->out_hwbuf_size, &u->sink->sample_spec); pa_sink_set_max_request(u->sink, u->out_hwbuf_size); diff --git a/src/modules/reserve-monitor.c b/src/modules/reserve-monitor.c new file mode 100644 index 00000000..64d2a7cc --- /dev/null +++ b/src/modules/reserve-monitor.c @@ -0,0 +1,259 @@ +/*** + Copyright 2009 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +#include "reserve-monitor.h" + +struct rm_monitor { + int ref; + + char *device_name; + char *service_name; + + DBusConnection *connection; + + unsigned busy:1; + unsigned filtering:1; + unsigned matching:1; + + rm_change_cb_t change_cb; + void *userdata; +}; + +#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." + +static DBusHandlerResult filter_handler( + DBusConnection *c, + DBusMessage *s, + void *userdata) { + + DBusMessage *reply; + rm_monitor *m; + DBusError error; + + dbus_error_init(&error); + + m = userdata; + assert(m->ref >= 1); + + if (dbus_message_is_signal(s, "org.freedesktop.DBus", "NameOwnerChanged")) { + const char *name, *old, *new; + + if (!dbus_message_get_args( + s, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) + goto invalid; + + if (strcmp(name, m->service_name) == 0) { + + m->busy = !!(new && *new); + + if (m->change_cb) { + m->ref++; + m->change_cb(m); + rm_release(m); + } + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +invalid: + if (!(reply = dbus_message_new_error( + s, + DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"))) + goto oom; + + if (!dbus_connection_send(c, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +int rm_watch( + rm_monitor **_m, + DBusConnection *connection, + const char*device_name, + rm_change_cb_t change_cb, + DBusError *error) { + + rm_monitor *m = NULL; + int r; + DBusError _error; + + if (!error) + error = &_error; + + dbus_error_init(error); + + if (!_m) + return -EINVAL; + + if (!connection) + return -EINVAL; + + if (!device_name) + return -EINVAL; + + if (!(m = calloc(sizeof(rm_monitor), 1))) + return -ENOMEM; + + m->ref = 1; + + if (!(m->device_name = strdup(device_name))) { + r = -ENOMEM; + goto fail; + } + + m->connection = dbus_connection_ref(connection); + m->change_cb = change_cb; + + if (!(m->service_name = malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) { + r = -ENOMEM; + goto fail; + } + sprintf(m->service_name, SERVICE_PREFIX "%s", m->device_name); + + if (!(dbus_connection_add_filter(m->connection, filter_handler, m, NULL))) { + r = -ENOMEM; + goto fail; + } + + m->filtering = 1; + + dbus_bus_add_match(m->connection, + "type='signal'," + "sender='" DBUS_SERVICE_DBUS "'," + "interface='" DBUS_INTERFACE_DBUS "'," + "member='NameOwnerChanged'", error); + + if (dbus_error_is_set(error)) { + r = -EIO; + goto fail; + } + + m->matching = 1; + + m->busy = dbus_bus_name_has_owner(m->connection, m->service_name, error); + + if (dbus_error_is_set(error)) { + r = -EIO; + goto fail; + } + + *_m = m; + return 0; + +fail: + if (&_error == error) + dbus_error_free(&_error); + + if (m) + rm_release(m); + + return r; +} + +void rm_release(rm_monitor *m) { + if (!m) + return; + + assert(m->ref > 0); + + if (--m->ref > 0) + return; + + if (m->matching) + dbus_bus_remove_match( + m->connection, + "type='signal'," + "sender='" DBUS_SERVICE_DBUS "'," + "interface='" DBUS_INTERFACE_DBUS "'," + "member='NameOwnerChanged'", NULL); + + if (m->filtering) + dbus_connection_remove_filter( + m->connection, + filter_handler, + m); + + free(m->device_name); + free(m->service_name); + + if (m->connection) + dbus_connection_unref(m->connection); + + free(m); +} + +int rm_busy(rm_monitor *m) { + if (!m) + return -EINVAL; + + assert(m->ref > 0); + + return m->busy; +} + +void rm_set_userdata(rm_monitor *m, void *userdata) { + + if (!m) + return; + + assert(m->ref > 0); + m->userdata = userdata; +} + +void* rm_get_userdata(rm_monitor *m) { + + if (!m) + return NULL; + + assert(m->ref > 0); + + return m->userdata; +} diff --git a/src/modules/reserve-monitor.h b/src/modules/reserve-monitor.h new file mode 100644 index 00000000..4f4a8332 --- /dev/null +++ b/src/modules/reserve-monitor.h @@ -0,0 +1,62 @@ +#ifndef fooreservemonitorhfoo +#define fooreservemonitorhfoo + +/*** + Copyright 2009 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <dbus/dbus.h> +#include <inttypes.h> + +typedef struct rm_monitor rm_monitor; + +/* Prototype for a function that is called whenever the reservation + * device of a device changes. Use rm_monitor_busy() to find out the + * new state.*/ +typedef void (*rm_change_cb_t)(rm_monitor *m); + +/* Creates a monitor for watching the lock status of a device. Returns + * 0 on success, a negative errno style return value on error. The + * DBus error might be set as well if the error was caused D-Bus. */ +int rm_watch( + rm_monitor **m, /* On success a pointer to the newly allocated rm_device object will be filled in here */ + DBusConnection *connection, /* Session bus (when D-Bus learns about user busses we should switchg to user busses) */ + const char *device_name, /* The device to monitor, e.g. "Audio0" */ + rm_change_cb_t change_cb, /* Will be called whenever the lock status changes. May be NULL */ + DBusError *error); /* If we fail due to a D-Bus related issue the error will be filled in here. May be NULL. */ + +/* Free a rm_monitor object */ +void rm_release(rm_monitor *m); + +/* Checks whether the device is currently reserved, and returns 1 + * then, 0 if not, negative errno style error code value on error. */ +int rm_busy(rm_monitor *m); + +/* Attach a userdata pointer to an rm_monitor */ +void rm_set_userdata(rm_monitor *m, void *userdata); + +/* Query the userdata pointer from an rm_monitor. Returns NULL if no + * userdata was set. */ +void* rm_get_userdata(rm_monitor *m); + +#endif diff --git a/src/modules/reserve-wrap.c b/src/modules/reserve-wrap.c index d0d014d3..07b592d3 100644 --- a/src/modules/reserve-wrap.c +++ b/src/modules/reserve-wrap.c @@ -35,6 +35,7 @@ #ifdef HAVE_DBUS #include <pulsecore/dbus-shared.h> #include "reserve.h" +#include "reserve-monitor.h" #endif #include "reserve-wrap.h" @@ -50,6 +51,17 @@ struct pa_reserve_wrapper { #endif }; +struct pa_reserve_monitor_wrapper { + PA_REFCNT_DECLARE; + pa_core *core; + pa_hook hook; + char *shared_name; +#ifdef HAVE_DBUS + pa_dbus_connection *connection; + struct rm_monitor *monitor; +#endif +}; + static void reserve_wrapper_free(pa_reserve_wrapper *r) { pa_assert(r); @@ -83,7 +95,7 @@ static int request_cb(rd_device *d, int forced) { PA_REFCNT_INC(r); k = pa_hook_fire(&r->hook, PA_INT_TO_PTR(forced)); - pa_log_debug("Device unlock has been requested and %s.", k < 0 ? "failed" : "succeeded"); + pa_log_debug("Device unlock of %s has been requested and %s.", r->shared_name, k < 0 ? "failed" : "succeeded"); pa_reserve_wrapper_unref(r); @@ -191,3 +203,138 @@ void pa_reserve_wrapper_set_application_device_name(pa_reserve_wrapper *r, const rd_set_application_device_name(r->device, name); #endif } + +static void reserve_monitor_wrapper_free(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + +#ifdef HAVE_DBUS + if (w->monitor) + rm_release(w->monitor); + + if (w->connection) + pa_dbus_connection_unref(w->connection); +#endif + + pa_hook_done(&w->hook); + + if (w->shared_name) { + pa_assert_se(pa_shared_remove(w->core, w->shared_name) >= 0); + pa_xfree(w->shared_name); + } + + pa_xfree(w); +} + +#ifdef HAVE_DBUS +static void change_cb(rm_monitor *m) { + pa_reserve_monitor_wrapper *w; + int k; + + pa_assert(m); + pa_assert_se(w = rm_get_userdata(m)); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + PA_REFCNT_INC(w); + + if ((k = rm_busy(w->monitor)) < 0) + return; + + pa_hook_fire(&w->hook, PA_INT_TO_PTR(!!k)); + pa_log_debug("Device lock status of %s changed: %s", w->shared_name, k ? "busy" : "not busy"); + + pa_reserve_monitor_wrapper_unref(w); +} +#endif + +pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const char *device_name) { + pa_reserve_monitor_wrapper *w; + int k; + char *t; +#ifdef HAVE_DBUS + DBusError error; + + dbus_error_init(&error); +#endif + + pa_assert(c); + pa_assert(device_name); + + t = pa_sprintf_malloc("reserve-monitor-wrapper@%s", device_name); + + if ((w = pa_shared_get(c, t))) { + pa_xfree(t); + + pa_assert(PA_REFCNT_VALUE(w) >= 1); + PA_REFCNT_INC(w); + + return w; + } + + w = pa_xnew0(pa_reserve_monitor_wrapper, 1); + PA_REFCNT_INIT(w); + w->core = c; + pa_hook_init(&w->hook, w); + w->shared_name = t; + + pa_assert_se(pa_shared_set(c, w->shared_name, w) >= 0); + +#ifdef HAVE_DBUS + if (!(w->connection = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { + pa_log_warn("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); + + /* We don't treat this as error here because we want allow PA + * to run even when no session bus is available. */ + return w; + } + + if ((k = rm_watch( + &w->monitor, + pa_dbus_connection_get(w->connection), + device_name, + change_cb, + NULL)) < 0) { + + pa_log_warn("Failed to create watch on device '%s': %s", device_name, pa_cstrerror(-k)); + goto fail; + } + + pa_log_debug("Successfully create reservation lock monitor for device '%s'", device_name); + + rm_set_userdata(w->monitor, w); + return w; + +fail: + dbus_error_free(&error); + + reserve_monitor_wrapper_free(w); + + return NULL; +#else + return w; +#endif +} + +void pa_reserve_monitor_wrapper_unref(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + if (PA_REFCNT_DEC(w) > 0) + return; + + reserve_monitor_wrapper_free(w); +} + +pa_hook* pa_reserve_monitor_wrapper_hook(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + return &w->hook; +} + +pa_bool_t pa_reserve_monitor_wrapper_busy(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + return rm_busy(w->monitor) > 0; +} diff --git a/src/modules/reserve-wrap.h b/src/modules/reserve-wrap.h index 2b97c91c..2de6c093 100644 --- a/src/modules/reserve-wrap.h +++ b/src/modules/reserve-wrap.h @@ -28,11 +28,18 @@ typedef struct pa_reserve_wrapper pa_reserve_wrapper; pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name); - void pa_reserve_wrapper_unref(pa_reserve_wrapper *r); pa_hook* pa_reserve_wrapper_hook(pa_reserve_wrapper *r); void pa_reserve_wrapper_set_application_device_name(pa_reserve_wrapper *r, const char *name); +typedef struct pa_reserve_monitor_wrapper pa_reserve_monitor_wrapper; + +pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const char *device_name); +void pa_reserve_monitor_wrapper_unref(pa_reserve_monitor_wrapper *m); + +pa_hook* pa_reserve_monitor_wrapper_hook(pa_reserve_monitor_wrapper *m); +pa_bool_t pa_reserve_monitor_wrapper_busy(pa_reserve_monitor_wrapper *m); + #endif diff --git a/src/modules/reserve.c b/src/modules/reserve.c index 9a9591d2..09bc46cb 100644 --- a/src/modules/reserve.c +++ b/src/modules/reserve.c @@ -43,16 +43,15 @@ struct rd_device { DBusConnection *connection; - int owning:1; - int registered:1; - int filtering:1; - int gave_up:1; + unsigned owning:1; + unsigned registered:1; + unsigned filtering:1; + unsigned gave_up:1; rd_request_cb_t request_cb; void *userdata; }; - #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/" @@ -297,6 +296,7 @@ static DBusHandlerResult filter_handler( dbus_error_init(&error); d = userdata; + assert(d->ref >= 1); if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) { const char *name; @@ -560,7 +560,7 @@ void rd_release( assert(d->ref > 0); - if (--d->ref) + if (--d->ref > 0) return; @@ -575,17 +575,11 @@ void rd_release( d->connection, d->object_path); - if (d->owning) { - DBusError error; - dbus_error_init(&error); - + if (d->owning) dbus_bus_release_name( d->connection, d->service_name, - &error); - - dbus_error_free(&error); - } + NULL); free(d->device_name); free(d->application_name); diff --git a/src/modules/reserve.h b/src/modules/reserve.h index b315a08c..31071298 100644 --- a/src/modules/reserve.h +++ b/src/modules/reserve.h @@ -45,7 +45,7 @@ typedef int (*rd_request_cb_t)( * the error was caused D-Bus. */ int rd_acquire( rd_device **d, /* On success a pointer to the newly allocated rd_device object will be filled in here */ - DBusConnection *connection, + DBusConnection *connection, /* Session bus (when D-Bus learns about user busses we should switchg to user busses) */ const char *device_name, /* The device to lock, e.g. "Audio0" */ const char *application_name, /* A human readable name of the application, e.g. "PulseAudio Sound Server" */ int32_t priority, /* The priority for this application. If unsure use 0 */ diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index cdd2c57d..39ee4d75 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -347,8 +347,8 @@ int pa__init(pa_module*m) { o->push = source_output_push; o->kill = source_output_kill; - pa_log_info("Configured source latency of %lu ms.", - pa_source_output_set_requested_latency(o, pa_bytes_to_usec(mtu, &o->sample_spec)) / PA_USEC_PER_MSEC); + pa_log_info("Configured source latency of %llu ms.", + (unsigned long long) pa_source_output_set_requested_latency(o, pa_bytes_to_usec(mtu, &o->sample_spec)) / PA_USEC_PER_MSEC); m->userdata = o->userdata = u = pa_xnew(struct userdata, 1); u->module = m; diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 629328ad..cb037de6 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -30,6 +30,7 @@ #include <arpa/inet.h> #include <unistd.h> #include <sys/ioctl.h> +#include <netinet/in.h> #ifdef HAVE_SYS_FILIO_H #include <sys/filio.h> diff --git a/src/modules/module-x11-bell.c b/src/modules/x11/module-x11-bell.c index ac303c3b..ac303c3b 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/x11/module-x11-bell.c diff --git a/src/modules/module-x11-cork-request.c b/src/modules/x11/module-x11-cork-request.c index c1380c27..c1380c27 100644 --- a/src/modules/module-x11-cork-request.c +++ b/src/modules/x11/module-x11-cork-request.c diff --git a/src/modules/module-x11-publish.c b/src/modules/x11/module-x11-publish.c index 2c7fdc12..2c7fdc12 100644 --- a/src/modules/module-x11-publish.c +++ b/src/modules/x11/module-x11-publish.c diff --git a/src/modules/module-x11-xsmp.c b/src/modules/x11/module-x11-xsmp.c index 28fd373a..28fd373a 100644 --- a/src/modules/module-x11-xsmp.c +++ b/src/modules/x11/module-x11-xsmp.c |