~schnouki/pustule

2a9a0382fea69f7fa1b56047a18f0a691987ba22 — Thomas Jost 4 years ago 9c1f9f9
Implement device events

Run new hooks when a device ("sink" in PulseAudio) is added or removed,
and make it possible to change their volumes.
4 files changed, 179 insertions(+), 24 deletions(-)

M events.c
M guile.c
M pulse.c
M pustule.h
M events.c => events.c +24 -2
@@ 52,13 52,15 @@ void pustule_run() {
                continue;

            pustule_input_event_t* iev = &event->input_event;
            pustule_device_event_t* dev = &event->device_event;

            pthread_mutex_unlock(&event_queue_mutex);

            SCM index = scm_from_uint32(iev->index);
            // Input events
            if (event->type == INPUT_ADDED) {
                if (scm_is_false(scm_hook_empty_p(input_added_hook))) {
                    SCM info = scm_from_input_event(event);
                    SCM index = scm_from_uint32(iev->index);
                    SCM info = scm_from_input_event(iev);
                    SCM args = scm_list_2(index, info);
                    scm_run_hook(input_added_hook, args);
                }


@@ 68,8 70,28 @@ void pustule_run() {
                free(iev->driver);
            }
            else if ((event->type == INPUT_REMOVED) && scm_is_false(scm_hook_empty_p(input_removed_hook))) {
                SCM index = scm_from_uint32(iev->index);
                scm_run_hook(input_removed_hook, scm_list_1(index));
            }

            // Device events
            else if (event->type == DEVICE_ADDED) {
                if (scm_is_false(scm_hook_empty_p(device_added_hook))) {
                    SCM index = scm_from_uint32(dev->index);
                    SCM info = scm_from_device_event(dev);
                    SCM args = scm_list_2(index, info);
                    scm_run_hook(device_added_hook, args);
                }
                if (dev->proplist)
                    pa_proplist_free(dev->proplist);
                free(dev->name);
                free(dev->description);
            }
            else if ((event->type == DEVICE_REMOVED) && scm_is_false(scm_hook_empty_p(device_removed_hook))) {
                SCM index = scm_from_uint32(dev->index);
                scm_run_hook(device_removed_hook, scm_list_1(index));
            }

            free(event);
            pthread_mutex_lock(&event_queue_mutex);
        }

M guile.c => guile.c +55 -4
@@ 40,12 40,19 @@ struct pustule_exported_func {
    {"pustule-set-input-volume", 2, 0, 0, &pustule_set_input_volume,
     "Sets the volume of the input whose index is INPUT-IDX (an integer) to\n"
     "VOLUME (a number between 0 and 1)."},
    {"pustule-get-device-volume", 1, 0, 0, &pustule_get_device_volume,
     "Gets the volume of the device whose index is DEVICE-IDX (an integer)."},
    {"pustule-set-device-volume", 2, 0, 0, &pustule_set_device_volume,
     "Sets the volume of the device whose index is DEVICE-IDX (an integer) to\n"
     "VOLUME (a number between 0 and 1)."},
    {NULL, 0, 0, 0, NULL}
};

/* Exported hooks */
SCM input_added_hook = NULL;
SCM input_removed_hook = NULL;
SCM device_added_hook = NULL;
SCM device_removed_hook = NULL;

/* Get the volume for the specified input */
SCM pustule_get_input_volume(SCM idx) {


@@ 53,7 60,7 @@ SCM pustule_get_input_volume(SCM idx) {

    /* Query the volume with PulseAudio */
    pthread_mutex_lock(&(vol.mutex));
    input_get_info(scm_to_uint32(idx), &vol);
    input_get_volume(scm_to_uint32(idx), &vol);
    pthread_cond_wait(&(vol.cond), &(vol.mutex));

    /* Cleanup */


@@ 64,7 71,7 @@ SCM pustule_get_input_volume(SCM idx) {
    return scm_from_double(vol.volume);
}

/* Set volume for the specified input */
/* Set the volume for the specified input */
SCM pustule_set_input_volume(SCM idx, SCM volume) {
    uint32_t idx_ = scm_to_uint32(idx);
    double volume_ = scm_to_double(volume);


@@ 78,6 85,36 @@ SCM pustule_set_input_volume(SCM idx, SCM volume) {
    return volume;
}

/* Get the volume for the specified device */
SCM pustule_get_device_volume(SCM idx) {
    pustule_volume_t vol = {0, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};

    /* Query the volume with PulseAudio */
    pthread_mutex_lock(&(vol.mutex));
    device_get_volume(scm_to_uint32(idx), &vol);
    pthread_cond_wait(&(vol.cond), &(vol.mutex));

    /* Cleanup */
    pthread_mutex_unlock(&(vol.mutex));
    pthread_mutex_destroy(&(vol.mutex));
    pthread_cond_destroy(&(vol.cond));

    return scm_from_double(vol.volume);}

/* Set the volume for the specified device */
SCM pustule_set_device_volume(SCM idx, SCM volume) {
    uint32_t idx_ = scm_to_uint32(idx);
    double volume_ = scm_to_double(volume);

    pa_cvolume cvol;
    pa_cvolume_init(&cvol);
    pa_volume_t new_vol = (pa_volume_t) (volume_ * PA_VOLUME_NORM);
    pa_cvolume_set(&cvol, 1, new_vol);
    pa_operation* op = pa_context_set_sink_volume_by_index(pactx, idx_, &cvol, NULL, NULL);
    pa_operation_unref(op);
    return volume;
}

/* Helpers */
static SCM _alist_add_str(SCM info, const char* name, const char* value) {
    return scm_acons(scm_from_utf8_string(name), scm_from_utf8_string(value), info);


@@ 107,8 144,7 @@ static SCM _alist_add_proplist(SCM info, pa_proplist* props) {
}

/* Convert an input event to an association list */
SCM scm_from_input_event(pustule_event_t* event) {
    pustule_input_event_t* iev = &event->input_event;
SCM scm_from_input_event(pustule_input_event_t* iev) {
    SCM info = SCM_EOL;
    info = _alist_add_str(info,  "name",            iev->name);
    info = _alist_add_str(info,  "driver",          iev->driver);


@@ 122,6 158,17 @@ SCM scm_from_input_event(pustule_event_t* event) {
    return info;
}

/* Convert a device event to an association list */
SCM scm_from_device_event(pustule_device_event_t* dev) {
    SCM info = SCM_EOL;
    info = _alist_add_str(info,  "name",        dev->name);
    info = _alist_add_str(info,  "description", dev->description);
    info = _alist_add_bool(info, "muted",       dev->mute);
    info = _alist_add_volume(info, &(dev->volume));
    info = _alist_add_proplist(info, dev->proplist);
    return info;
}

/* Register functions and other things with Guile */
void pustule_init_guile() {
    /* Register functions (and their docs) */


@@ 141,4 188,8 @@ void pustule_init_guile() {
    scm_c_define("pustule-input-added-hook", input_added_hook);
    input_removed_hook = scm_make_hook(scm_from_int(1));
    scm_c_define("pustule-input-removed-hook", input_removed_hook);
    device_added_hook = scm_make_hook(scm_from_int(2));
    scm_c_define("pustule-device-added-hook", device_added_hook);
    device_removed_hook = scm_make_hook(scm_from_int(1));
    scm_c_define("pustule-device-removed-hook", device_removed_hook);
}

M pulse.c => pulse.c +80 -16
@@ 38,26 38,51 @@ void state_callback(pa_context* ctx, void* userdata) {
        // Get infos about all current inputs
        pa_context_get_sink_input_info_list(ctx, input_info_callback, NULL);

        // Subscribe to new inputs events
        pa_context_set_subscribe_callback(ctx, input_event_callback, NULL);
        pa_context_subscribe(ctx, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL);
        // Get infos about all current devices
        pa_context_get_sink_info_list(ctx, device_info_callback, NULL);

        // Subscribe to new events
        pa_context_set_subscribe_callback(ctx, event_callback, NULL);
        pa_context_subscribe(ctx, PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
    }
}

/* Event handler -- dispatch Scheme events */
void event_callback(pa_context* ctx, pa_subscription_event_type_t t, uint32_t idx, void* userdata) {
    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
        // Input added: query info about it
        pa_context_get_sink_input_info(ctx, idx, input_info_callback, NULL);
    if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
        // This event is about an input!

        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
            // Input added: query info about it
            pa_context_get_sink_input_info(ctx, idx, input_info_callback, NULL);
        }
        else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            // Input removed: directly call the corresponding callback
            pustule_event_t* event = malloc(sizeof(pustule_event_t));
            if (!event) abort();

            event->type = INPUT_REMOVED;
            event->input_event.index = idx;
            pustule_handle_event(event);
        }

    }
    else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
        // Input removed: directly call the corresponding callback
        pustule_event_t* event = malloc(sizeof(pustule_event_t));
        if (!event) abort();

        event->type = INPUT_REMOVED;
        event->input_event.index = idx;
        pustule_handle_event(event);
    else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
        // This event is about a device!

        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
            // Device added: query info about it
            pa_context_get_sink_info_by_index(ctx, idx, device_info_callback, NULL);
        }
        else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            // Device removed: directly call the corresponding callback
            pustule_event_t* event = malloc(sizeof(pustule_event_t));
            if (!event) abort();

            event->type = DEVICE_REMOVED;
            event->device_event.index = idx;
            pustule_handle_event(event);
        }
    }
}



@@ 86,7 111,30 @@ void input_info_callback(pa_context* ctx, const pa_sink_input_info* i, int eol, 
    pustule_handle_event(event);
}

/* Query info about an input */
/* New device available (or infos about an existing device): defer a call to the "device added" callback */
void device_info_callback(pa_context* ctx, const pa_sink_info* i, int eol, void* userdata) {
    if (!i) return;

    pustule_event_t* event = malloc(sizeof(pustule_event_t));
    if (!event) abort();
    pustule_device_event_t* dev = &event->device_event;

    event->type = DEVICE_ADDED;
    dev->index = i->index;
    dev->name = strdup(i->name);
    dev->description = strdup(i->description);
    dev->mute = i->mute;
    dev->volume = i->volume;

    dev->proplist = NULL;
    if (i->proplist)
        dev->proplist = pa_proplist_copy(i->proplist);

    /* Event callback */
    pustule_handle_event(event);
}

/* Query info about an input (for its volume) */
static void input_volume_info_callback(pa_context* ctx, const pa_sink_input_info* i, int eol, void* userdata) {
    if (!i) return;
    pustule_volume_t* vol = (pustule_volume_t*) userdata;


@@ 95,9 143,25 @@ static void input_volume_info_callback(pa_context* ctx, const pa_sink_input_info
    pthread_cond_broadcast(&(vol->cond));
    pthread_mutex_unlock(&(vol->mutex));
}
void input_get_info(uint32_t idx, pustule_volume_t* vol) {
void input_get_volume(uint32_t idx, pustule_volume_t* vol) {
    pa_threaded_mainloop_lock(paloop);
    pa_operation* op = pa_context_get_sink_input_info(pactx, idx, input_volume_info_callback, (void*) vol);
    pa_operation_unref(op);
    pa_threaded_mainloop_unlock(paloop);
}

/* Query info about a device (for its volume) */
static void device_volume_info_callback(pa_context* ctx, const pa_sink_info* i, int eol, void* userdata) {
    if (!i) return;
    pustule_volume_t* vol = (pustule_volume_t*) userdata;
    pthread_mutex_lock(&(vol->mutex));
    vol->volume = ((double) pa_cvolume_avg(&(i->volume))) / PA_VOLUME_NORM;
    pthread_cond_broadcast(&(vol->cond));
    pthread_mutex_unlock(&(vol->mutex));
}
void device_get_volume(uint32_t idx, pustule_volume_t* vol) {
    pa_threaded_mainloop_lock(paloop);
    pa_operation* op = pa_context_get_sink_info_by_index(pactx, idx, device_volume_info_callback, (void*) vol);
    pa_operation_unref(op);
    pa_threaded_mainloop_unlock(paloop);
}

M pustule.h => pustule.h +20 -2
@@ 39,11 39,22 @@ typedef struct {
} pustule_input_event_t;

typedef struct {
    uint32_t index;
    char* name;
    char* description;
    int mute;
    pa_cvolume volume;
    pa_proplist* proplist;
} pustule_device_event_t;

typedef struct {
    enum {
        INPUT_ADDED, INPUT_REMOVED,
        DEVICE_ADDED, DEVICE_REMOVED,
    } type;
    union {
        pustule_input_event_t input_event;
        pustule_device_event_t device_event;
    };
} pustule_event_t;



@@ 56,18 67,23 @@ typedef struct {
/* PulseAudio callbacks */
void event_callback(pa_context* ctx, pa_subscription_event_type_t t, uint32_t idx, void* userdata);
void input_info_callback(pa_context* ctx, const pa_sink_input_info* i, int eol, void* userdata);
void device_info_callback(pa_context* ctx, const pa_sink_info* i, int eol, void* userdata);
void state_callback(pa_context* ctx, void* userdata);

/* PulseAudio functions */
void input_get_info(uint32_t idx, pustule_volume_t* vol);
void input_get_volume(uint32_t idx, pustule_volume_t* vol);
void device_get_volume(uint32_t idx, pustule_volume_t* vol);

/* Guile functions */
SCM pustule_get_input_volume(SCM idx);
SCM pustule_set_input_volume(SCM idx, SCM volume);
SCM pustule_get_device_volume(SCM idx);
SCM pustule_set_device_volume(SCM idx, SCM volume);

/* Guile helpers */
void pustule_init_guile();
SCM scm_from_input_event(pustule_event_t* event);
SCM scm_from_input_event(pustule_input_event_t* event);
SCM scm_from_device_event(pustule_device_event_t* event);

/* Pustule event loop */
void pustule_run();


@@ 78,5 94,7 @@ extern pa_context* pactx;
extern pa_threaded_mainloop* paloop;
extern SCM input_added_hook;
extern SCM input_removed_hook;
extern SCM device_added_hook;
extern SCM device_removed_hook;

#endif