static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
    struct device_info *d;
    pa_source *source;
    pa_sink *sink;

    pa_assert(c);
    pa_object_assert_ref(o);
    pa_assert(u);

    source = pa_source_isinstance(o) ? PA_SOURCE(o) : NULL;
    sink = pa_sink_isinstance(o) ? PA_SINK(o) : NULL;

    /* Never suspend monitors */
    if (source && source->monitor_of)
        return PA_HOOK_OK;

    pa_assert(source || sink);

    d = pa_xnew(struct device_info, 1);
    d->userdata = u;
    d->source = source ? pa_source_ref(source) : NULL;
    d->sink = sink ? pa_sink_ref(sink) : NULL;
    d->time_event = pa_core_rttime_new(c, PA_USEC_INVALID, timeout_cb, d);
    pa_hashmap_put(u->device_infos, o, d);

    if ((d->sink && pa_sink_check_suspend(d->sink) <= 0) ||
        (d->source && pa_source_check_suspend(d->source) <= 0))
        restart(d);

    return PA_HOOK_OK;
}
static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
    struct device_info *d;

    pa_assert(c);
    pa_object_assert_ref(o);
    pa_assert(u);

    if (!(d = pa_hashmap_get(u->device_infos, o)))
        return PA_HOOK_OK;

    if (pa_sink_isinstance(o)) {
        pa_sink *s = PA_SINK(o);
        pa_sink_state_t state = pa_sink_get_state(s);

        if (pa_sink_check_suspend(s) <= 0)
            if (PA_SINK_IS_OPENED(state))
                restart(d);

    } else if (pa_source_isinstance(o)) {
        pa_source *s = PA_SOURCE(o);
        pa_source_state_t state = pa_source_get_state(s);

        if (pa_source_check_suspend(s) <= 0)
            if (PA_SOURCE_IS_OPENED(state))
                restart(d);
    }

    return PA_HOOK_OK;
}
static struct service *get_service(struct userdata *u, pa_object *device) {
    struct service *s;
    char *hn, *un;
    const char *n;

    pa_assert(u);
    pa_object_assert_ref(device);

    if ((s = pa_hashmap_get(u->services, device)))
        return s;

    s = pa_xnew0(struct service, 1);
    s->userdata = u;
    s->device = device;

    if (pa_sink_isinstance(device)) {
        if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
            n = PA_SINK(device)->name;
    } else {
        if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
            n = PA_SOURCE(device)->name;
    }

    hn = pa_get_host_name_malloc();
    un = pa_get_user_name_malloc();

    s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1);

    pa_xfree(un);
    pa_xfree(hn);

    pa_hashmap_put(u->services, s->device, s);

    return s;
}
static pa_hook_result_t device_unlink_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
    pa_assert(c);
    pa_object_assert_ref(o);
    pa_assert(u);

    pa_hashmap_remove_and_free(u->device_infos, o);

    return PA_HOOK_OK;
}
static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
    pa_assert(c);
    pa_object_assert_ref(o);

    if (!shall_ignore(o))
        publish_service(get_service(u, o));

    return PA_HOOK_OK;
}
static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
    struct service *s;

    pa_assert(c);
    pa_object_assert_ref(o);

    if ((s = pa_hashmap_get(u->services, o)))
        service_free(s);

    return PA_HOOK_OK;
}
static bool shall_ignore(pa_object *o) {
    pa_object_assert_ref(o);

    if (pa_sink_isinstance(o))
        return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);

    if (pa_source_isinstance(o))
        return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);

    pa_assert_not_reached();
}
static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_object *o, struct pa_policy_activity_variable *var) {
    pa_sink *sink;

    pa_assert(c);
    pa_object_assert_ref(o);
    pa_assert(var);

    if (pa_sink_isinstance(o)) {
        sink = PA_SINK(o);
        perform_activity_action(sink, var, var->default_state);
    }

    return PA_HOOK_OK;
}
static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
    struct device_info *d;
    pa_source *source;
    pa_sink *sink;
    const char *timeout_str;
    int32_t timeout;
    bool timeout_valid;

    pa_assert(c);
    pa_object_assert_ref(o);
    pa_assert(u);

    source = pa_source_isinstance(o) ? PA_SOURCE(o) : NULL;
    sink = pa_sink_isinstance(o) ? PA_SINK(o) : NULL;

    /* Never suspend monitors */
    if (source && source->monitor_of)
        return PA_HOOK_OK;

    pa_assert(source || sink);

    timeout_str = pa_proplist_gets(sink ? sink->proplist : source->proplist, "module-suspend-on-idle.timeout");
    if (timeout_str && pa_atoi(timeout_str, &timeout) >= 0)
        timeout_valid = true;
    else
        timeout_valid = false;

    if (timeout_valid && timeout < 0)
        return PA_HOOK_OK;

    d = pa_xnew(struct device_info, 1);
    d->userdata = u;
    d->source = source ? pa_source_ref(source) : NULL;
    d->sink = sink ? pa_sink_ref(sink) : NULL;
    d->time_event = pa_core_rttime_new(c, PA_USEC_INVALID, timeout_cb, d);

    if (timeout_valid)
        d->timeout = timeout * PA_USEC_PER_SEC;
    else
        d->timeout = d->userdata->timeout;

    pa_hashmap_put(u->device_infos, o, d);

    if ((d->sink && pa_sink_check_suspend(d->sink) <= 0) ||
            (d->source && pa_source_check_suspend(d->source) <= 0))
        restart(d);

    return PA_HOOK_OK;
}