2017-12-14 16:28:17 +03:00

397 lines
10 KiB
C++

/**
* Author: Jason White (https://gist.github.com/jasonwhite/1df6ee4b5039358701d2)
* Author: ch1p
*
* License: Public Domain
*/
#include <stdio.h>
#include <assert.h>
#include <signal.h>
#include <string.h>
#include <pulse/pulseaudio.h>
#include <dbus/dbus.h>
#define DBUS_NAME "com.ch1p.pvm"
class DBus
{
private:
DBusConnection *_connection;
DBusError _error;
public:
DBus()
: _connection(NULL)
{
}
~DBus()
{
if (_connection) {
dbus_connection_close(_connection);
}
}
bool initialize()
{
dbus_error_init(&_error);
_connection = dbus_bus_get(DBUS_BUS_SESSION, &_error);
if (dbus_error_is_set(&_error)) {
fprintf(stderr, "DBus Connection Error (%s)\n", _error.message);
dbus_error_free(&_error);
}
if (!_connection) {
fprintf(stderr, "Failed to initialize dbus connection\n");
return false;
}
dbus_bus_request_name(_connection, DBUS_NAME,
DBUS_NAME_FLAG_REPLACE_EXISTING , &_error);
if (dbus_error_is_set(&_error)) {
fprintf(stderr, "DBus Name Error (%s)\n", _error.message);
dbus_error_free(&_error);
return false;
}
return true;
}
bool notify(char *signal_name)
{
DBusMessage *msg;
dbus_uint32_t serial = 0;
//msg = dbus_message_new_signal("/com/ch1p/Object", DBUS_NAME, "valueChanged");
msg = dbus_message_new_signal("/com/ch1p/Object", DBUS_NAME, signal_name);
if (NULL == msg) {
fprintf(stderr, "DBus: Message Null\n");
return false;
}
if (!dbus_connection_send(_connection, msg, &serial)) {
fprintf(stderr, "DBus send: Out Of Memory!\n");
return false;
}
dbus_connection_flush(_connection);
dbus_message_unref(msg);
return true;
}
};
struct pa_myuserdata {
bool use_dbus;
pa_mainloop_api *mainloop_api;
DBus *dbus;
};
class PulseAudio
{
private:
pa_mainloop* _mainloop;
pa_mainloop_api* _mainloop_api;
pa_context* _context;
pa_signal_event* _signal;
DBus _dbus;
bool _use_dbus;
struct pa_myuserdata _myuserdata;
public:
PulseAudio()
: _mainloop(NULL),
_mainloop_api(NULL),
_context(NULL),
_signal(NULL),
_use_dbus(false)
{
}
/**
* Initializes state and connects to the PulseAudio server.
*/
bool initialize(bool use_dbus)
{
_mainloop = pa_mainloop_new();
if (!_mainloop)
{
fprintf(stderr, "pa_mainloop_new() failed.\n");
return false;
}
_mainloop_api = pa_mainloop_get_api(_mainloop);
if (pa_signal_init(_mainloop_api) != 0)
{
fprintf(stderr, "pa_signal_init() failed\n");
return false;
}
_signal = pa_signal_new(SIGINT, exit_signal_callback, this);
if (!_signal)
{
fprintf(stderr, "pa_signal_new() failed\n");
return false;
}
signal(SIGPIPE, SIG_IGN);
_context = pa_context_new(_mainloop_api, "PulseAudio Test");
if (!_context)
{
fprintf(stderr, "pa_context_new() failed\n");
return false;
}
if (pa_context_connect(_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0)
{
fprintf(stderr, "pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(_context)));
return false;
}
if (use_dbus) {
_use_dbus = true;
if (!_dbus.initialize()) {
return false;
}
}
_myuserdata.use_dbus = _use_dbus;
_myuserdata.mainloop_api = _mainloop_api;
_myuserdata.dbus = &_dbus;
pa_context_set_state_callback(_context, context_state_callback, &_myuserdata);
return true;
}
/**
* Runs the main PulseAudio event loop. Calling quit will cause the event
* loop to exit.
*/
int run()
{
int ret = 1;
if (pa_mainloop_run(_mainloop, &ret) < 0)
{
fprintf(stderr, "pa_mainloop_run() failed.\n");
return ret;
}
return ret;
}
/**
* Exits the main loop with the specified return code.
*/
void quit(int ret = 0)
{
_mainloop_api->quit(_mainloop_api, ret);
}
/**
* Called when the PulseAudio system is to be destroyed.
*/
void destroy()
{
if (_context)
{
pa_context_unref(_context);
_context = NULL;
}
if (_signal)
{
pa_signal_free(_signal);
pa_signal_done();
_signal = NULL;
}
if (_mainloop)
{
pa_mainloop_free(_mainloop);
_mainloop = NULL;
_mainloop_api = NULL;
}
}
~PulseAudio()
{
destroy();
}
private:
/*
* Called on SIGINT.
*/
static void exit_signal_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata)
{
PulseAudio* pa = (PulseAudio*)userdata;
if (pa) pa->quit();
}
/*
* Called whenever the context status changes.
*/
static void context_state_callback(pa_context *c, void *userdata)
{
struct pa_myuserdata *myuserdata = (struct pa_myuserdata *)userdata;
assert(c && myuserdata->mainloop_api);
PulseAudio* pa = (PulseAudio*)(myuserdata->mainloop_api);
switch (pa_context_get_state(c))
{
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
case PA_CONTEXT_READY:
fprintf(stderr, "PulseAudio connection established.\n");
pa_context_get_server_info(c, server_info_callback, userdata);
// Subscribe to sink events from the server. This is how we get
// volume change notifications from the server.
pa_context_set_subscribe_callback(c, subscribe_callback, userdata);
pa_context_subscribe(c, (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE), NULL, NULL);
break;
case PA_CONTEXT_TERMINATED:
pa->quit(0);
fprintf(stderr, "PulseAudio connection terminated.\n");
break;
case PA_CONTEXT_FAILED:
default:
fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(c)));
pa->quit(1);
break;
}
}
/*
* Called when an event we subscribed to occurs.
*/
static void subscribe_callback(pa_context *c,
pa_subscription_event_type_t type, uint32_t idx, void *userdata)
{
unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
//type &= PA_SUBSCRIPTION_EVENT_TYPE_MASK;
pa_operation *op = NULL;
switch (facility)
{
case PA_SUBSCRIPTION_EVENT_SINK:
pa_context_get_sink_info_by_index(c, idx, sink_info_callback, userdata);
break;
case PA_SUBSCRIPTION_EVENT_SOURCE:
pa_context_get_source_info_by_index(c, idx, source_info_callback, userdata);
break;
default:
printf("Unknown event %d\n", facility);
//assert(0); // Got event we aren't expecting.
break;
}
if (op)
pa_operation_unref(op);
}
/*
* Called when the requested sink information is ready.
*/
static void sink_info_callback(pa_context *c, const pa_sink_info *i,
int eol, void *userdata)
{
if (!i) {
return;
}
struct pa_myuserdata *myuserdata = (struct pa_myuserdata *)userdata;
if (myuserdata->use_dbus) {
myuserdata->dbus->notify("sinkChanged");
} else {
float volume = (float)pa_cvolume_avg(&(i->volume)) / (float)PA_VOLUME_NORM;
printf("[sink ] percent volume = %.0f%%%s\n", volume * 100.0f, i->mute ? " (muted)" : "");
}
}
/*
* Called when the requested source information is ready.
*/
static void source_info_callback(pa_context *c, const pa_source_info *i,
int eol, void *userdata)
{
if (!i) {
return;
}
struct pa_myuserdata *myuserdata = (struct pa_myuserdata *)userdata;
if (myuserdata->use_dbus) {
myuserdata->dbus->notify("sourceChanged");
} else {
float volume = (float)pa_cvolume_avg(&(i->volume)) / (float)PA_VOLUME_NORM;
printf("[source] percent volume = %.0f%%%s\n", volume * 100.0f, i->mute ? " (muted)" : "");
}
}
/*
* Called when the requested information on the server is ready. This is
* used to find the default PulseAudio sink.
*/
static void server_info_callback(pa_context *c, const pa_server_info *i,
void *userdata)
{
printf("[info ] default sink name = %s\n", i->default_sink_name);
printf("[info ] default source name = %s\n", i->default_source_name);
pa_context_get_sink_info_by_name(c, i->default_sink_name, sink_info_callback, userdata);
pa_context_get_source_info_by_name(c, i->default_source_name, source_info_callback, userdata);
}
};
void usage(char *name)
{
fprintf(stderr, "Usage:\n"
"%s dbus\n"
"%s stdout\n",
name, name);
exit(1);
}
int main(int argc, char *argv[])
{
if (argc < 2) {
usage(argv[0]);
}
// Check input
bool use_dbus = false;
if (strcmp(argv[1], "dbus") == 0) {
use_dbus = true;
} else if (strcmp(argv[1], "stdout") != 0) {
usage(argv[0]);
}
PulseAudio pa = PulseAudio();
if (!pa.initialize(use_dbus)) {
return 1;
}
int ret = pa.run();
return ret;
}