/**
* pustule
* Copyright 2014-2020 Thomas Jost <schnouki@schnouki.net>
*
* This file is part of pustule.
*
* pustule is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* pustule 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 General Public License along with
* pustule. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pulse/pulseaudio.h>
#include "log.h"
#include "pustule.h"
/* Global PulseAudio context and main loop */
pa_context* pactx = NULL;
pa_mainloop* paloop = NULL;
/* PulseAudio state changed -- query for infos and subscribe to new events */
void state_callback(pa_context* ctx, void* userdata) {
pa_context_state_t state = pa_context_get_state(ctx);
char* st = "unknown";
if (state == PA_CONTEXT_UNCONNECTED)
st = "unconnected";
else if (state == PA_CONTEXT_CONNECTING)
st = "connecting";
else if (state == PA_CONTEXT_AUTHORIZING)
st = "authorizing";
else if (state == PA_CONTEXT_SETTING_NAME)
st = "setting_name";
else if (state == PA_CONTEXT_READY)
st = "ready";
else if (state == PA_CONTEXT_FAILED)
st = "failed";
else if (state == PA_CONTEXT_TERMINATED)
st = "terminated";
log_debug("State is now %s", st);
if (state == PA_CONTEXT_READY) {
// Get infos about all current inputs
pa_context_get_sink_input_info_list(ctx, input_info_callback, 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 Lua events */
void event_callback(pa_context* ctx, pa_subscription_event_type_t t, uint32_t idx, void* userdata) {
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_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);
}
}
}
/* New input available (or infos about an existing input): defer a call to the "input added" callback */
void input_info_callback(pa_context* ctx, const pa_sink_input_info* i, int eol, void* userdata) {
if (!i) return;
pustule_event_t* event = malloc(sizeof(pustule_event_t));
if (!event) abort();
pustule_input_event_t* iev = &event->input_event;
event->type = INPUT_ADDED;
iev->index = i->index;
iev->name = strdup(i->name);
iev->driver = strdup(i->name);
iev->mute = i->mute;
iev->volume_writable = i->volume_writable;
iev->has_volume = i->has_volume;
iev->volume = i->volume;
iev->proplist = NULL;
if (i->proplist)
iev->proplist = pa_proplist_copy(i->proplist);
/* Event callback */
pustule_handle_event(event);
}
/* 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 or device (for its volume) */
typedef struct {
pa_operation* op;
pustule_volume_callback_t callback;
void* userdata;
} _volume_data_cb_t;
static void input_volume_info_callback(pa_context* ctx, const pa_sink_input_info* i, int eol, void* userdata) {
if (!i) return;
_volume_data_cb_t* data = (_volume_data_cb_t*) userdata;
double volume = ((double) pa_cvolume_avg(&(i->volume))) / PA_VOLUME_NORM;
data->callback(volume, data->userdata);
pa_operation_unref(data->op);
free(data);
}
void input_get_volume(uint32_t idx, pustule_volume_callback_t callback, void* userdata) {
_volume_data_cb_t* data = malloc(sizeof(_volume_data_cb_t));
if (!data)
abort();
data->callback = callback;
data->userdata = userdata;
data->op = pa_context_get_sink_input_info(pactx, idx, input_volume_info_callback, (void*) data);
}
static void device_volume_info_callback(pa_context* ctx, const pa_sink_info* i, int eol, void* userdata) {
if (!i) return;
_volume_data_cb_t* data = (_volume_data_cb_t*) userdata;
double volume = ((double) pa_cvolume_avg(&(i->volume))) / PA_VOLUME_NORM;
data->callback(volume, data->userdata);
pa_operation_unref(data->op);
free(data);
}
void device_get_volume(uint32_t idx, pustule_volume_callback_t callback, void* userdata) {
_volume_data_cb_t* data = malloc(sizeof(_volume_data_cb_t));
if (!data)
abort();
data->callback = callback;
data->userdata = userdata;
data->op = pa_context_get_sink_info_by_index(pactx, idx, device_volume_info_callback, (void*) data);
}
/* Query info about the server */
typedef struct {
pa_operation* op;
pustule_server_info_callback_t callback;
void* userdata;
} _server_info_data_cb_t;
static void server_info_callback(pa_context* ctx, const pa_server_info *i, void* userdata) {
if (!i) return;
_server_info_data_cb_t* data = (_server_info_data_cb_t*) userdata;
pustule_server_info_t* info = malloc(sizeof(pustule_server_info_t));
if (!info)
abort();
info->user_name = i->user_name;
info->host_name = i->host_name;
info->server_version = i->server_version;
info->server_name = i->server_name;
info->default_sink_name = i->default_sink_name;
data->callback(info, data->userdata);
pa_operation_unref(data->op);
free(info);
}
void server_get_info(pustule_server_info_callback_t callback, void* userdata) {
_server_info_data_cb_t* data = malloc(sizeof(_server_info_data_cb_t));
if (!data)
abort();
data->callback = callback;
data->userdata = userdata;
data->op = pa_context_get_server_info(pactx, server_info_callback, (void*) data);
}