1
Fork 0

feat: Implement introspection for sinks and sources

This provides a way to query the current state for the primary
data structures, as well as common setters.
This commit is contained in:
Lucas Schwiderski 2022-04-22 10:37:48 +02:00
parent f8b2a45740
commit 366e08498e
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
17 changed files with 3607 additions and 327 deletions

View file

@ -1,13 +1,27 @@
project = 'lgi-async-extra'
title = 'lgi-async-extra'
description = 'An asynchronous high(er)-level API wrapper for lua-lgi'
project = 'lua-libpulse-glib'
title = 'lua-libpulse-glib'
description = "Lua bindings for PulseAudio's libpulse, using the GLib Main Loop."
full_description = [[
While libpulse provides different event loop implementations, these bindings focus on the GLib comaptibility only.
Therefore, all asynchronous functions need to be executed inside a running GLib Main Loop.
For now, this loop has to run on GLib's default main context. Custom loop contexts are currently not supported.
The majority of the API is callback-based asynchronous. Callbacks always receive any potential errors as their
first parameter, usually just the error message as string, or `nil` when there was no error. Additional parameters
may either be just a boolean, for operations that don't return data, or the actual queried data.
All numeric indices (such as sink or source indices) are adjusted to be 1-based in typical Lua fashion.
This means that when comparing the output of calls like @{Context:get_sinks} to the output of tools like `pactl`,
indices will be off by one.
]]
template = true
format = 'discount'
pretty = 'lua'
prettify_files = 'show'
-- prettify_files = 'show'
backtick_references = false
wrap = true
no_space_before_args = true

View file

@ -0,0 +1,51 @@
#include "callback.h"
#include "pulseaudio.h"
simple_callback_data* prepare_lua_callback(lua_State* L) {
// Prepare a new thread to run the callback with
lua_pushstring(L, LUA_PULSEAUDIO);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushstring(L, LUA_PA_REGISTRY);
lua_gettable(L, -2);
lua_State* thread = lua_newthread(L);
int thread_ref = luaL_ref(L, -2);
// Copy the callback function to the thread's stack
lua_pushvalue(L, 2);
lua_xmove(L, thread, 1);
simple_callback_data* data = malloc(sizeof(struct simple_callback_data));
data->L = thread;
data->thread_ref = thread_ref;
data->is_list = false;
return data;
}
void free_lua_callback(simple_callback_data* data) {
lua_State* L = data->L;
// Remove thread reference
lua_pushstring(L, LUA_PULSEAUDIO);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushstring(L, LUA_PA_REGISTRY);
lua_gettable(L, -2);
luaL_unref(L, -1, data->thread_ref);
free(data);
}
void success_callback(pa_context* c, int success, void* userdata) {
simple_callback_data* data = (simple_callback_data*) userdata;
lua_State* L = data->L;
lua_pushnil(L);
lua_pushboolean(L, success);
lua_call(L, 2, 0);
free_lua_callback(data);
}

View file

@ -0,0 +1,42 @@
#ifndef callback_h_INCLUDED
#define callback_h_INCLUDED
#include <lua.h>
#include <pulse/context.h>
#include <stdbool.h>
typedef struct simple_callback_data {
lua_State* L;
int thread_ref;
// PulseAudio's introspection callbacks are used for operations that return single values, as well as operations
// that return a list. But the callback itself cannot know in which context it is called, so we have to provide
// that information explicitly.
bool is_list;
} simple_callback_data;
// Prepares a Lua thread that can call a Lua function as
// callback inside a PulseAudio callback.
//
// Assumes that the Lua function to call is at the top of the stack and copies it to the
// new thread.
//
// The thread will be kept in memory by a unique ref in the registry, so the callback has to
// `luaL_unref` that, to mark it for the garbage collector.
//
// The returned callback data needs to be `free()`d at the end of the callback. `free_lua_callback`
// handles both `free()` and `luaL_unref()`.
simple_callback_data* prepare_lua_callback(lua_State* L);
// Removes the thread reference, to allow the thread to be garbage collected, and `free`s
// the callback data.
void free_lua_callback(simple_callback_data* data);
// Simple implementation of `pa_context_success_cb_t` that calls a provided Lua function.
void success_callback(pa_context* c, int success, void* userdata);
#endif // callback_h_INCLUDED

View file

@ -1,12 +1,15 @@
#include "pulseaudio.h"
#include "context.h"
#include "pulse/context.h"
#include "callback.h"
#include "pulseaudio.h"
#include <pulse/context.h>
#include <pulse/error.h>
void
context_state_callback(pa_context* c, void* userdata)
{
/* Calls the user-provided callback with the updates state info.
*/
void context_state_callback(pa_context* c, void* userdata) {
context_state_callback_data* data = (context_state_callback_data*) userdata;
luaL_checktype(data->L, 1, LUA_TFUNCTION);
luaL_checkudata(data->L, 2, LUA_PA_CONTEXT);
@ -23,9 +26,7 @@ context_state_callback(pa_context* c, void* userdata)
}
int
context_new(lua_State* L, pa_mainloop_api* pa_api)
{
int context_new(lua_State* L, pa_mainloop_api* pa_api) {
const char* name = luaL_checkstring(L, -1);
// TODO: libpulse recommends using `new_with_proplist` instead. But I need to figure out that `proplist` first.
pa_context* ctx = pa_context_new(pa_api, name);
@ -33,7 +34,7 @@ context_new(lua_State* L, pa_mainloop_api* pa_api)
return luaL_error(L, "failed to create pulseaudio context");
}
lua_pa_context* lgi_ctx = lua_newuserdata (L, sizeof(lua_pa_context));
lua_pa_context* lgi_ctx = lua_newuserdata(L, sizeof(lua_pa_context));
if (lgi_ctx == NULL) {
return luaL_error(L, "failed to create context userdata");
}
@ -48,19 +49,7 @@ context_new(lua_State* L, pa_mainloop_api* pa_api)
}
int
context__index(lua_State* L)
{
const char* index = luaL_checkstring(L, 2);
luaL_getmetatable(L, LUA_PA_CONTEXT);
lua_getfield(L, -1, index);
return 1;
}
int
context__gc(lua_State* L)
{
int context__gc(lua_State* L) {
lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT);
if (ctx->connected == TRUE) {
@ -83,16 +72,14 @@ context__gc(lua_State* L)
}
int
context_connect(lua_State* L)
{
int context_connect(lua_State* L) {
int nargs = lua_gettop(L);
const char* server = NULL;
if (lua_type(L, 2) == LUA_TSTRING)
server = lua_tostring(L, 2);
else if (lua_type(L, 2) != LUA_TNIL) {
const char *typearg;
const char* typearg;
if (luaL_getmetafield(L, 2, "__name") == LUA_TSTRING)
typearg = lua_tostring(L, -1);
else if (lua_type(L, 2) == LUA_TLIGHTUSERDATA)
@ -141,11 +128,54 @@ context_connect(lua_State* L)
}
int
context_get_state(lua_State* L)
{
int context_disconnect(lua_State* L) {
lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT);
pa_context_disconnect(ctx->context);
return 0;
}
int context_get_state(lua_State* L) {
lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT);
pa_context_state_t state = pa_context_get_state(ctx->context);
lua_pushinteger(L, state);
return 1;
}
int context_set_default_sink(lua_State* L) {
lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT);
const char* name = luaL_checkstring(L, 2);
simple_callback_data* data = prepare_lua_callback(L);
pa_operation* op = pa_context_set_default_sink(ctx->context, name, success_callback, data);
if (op == NULL) {
int error = pa_context_errno(ctx->context);
lua_pushvalue(L, 2);
lua_pushfstring(L, "failed to set default sink: %s", pa_strerror(error));
lua_call(L, 1, 0);
return 0;
}
return 0;
}
int context_set_default_source(lua_State* L) {
lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT);
const char* name = luaL_checkstring(L, 2);
simple_callback_data* data = prepare_lua_callback(L);
pa_operation* op = pa_context_set_default_source(ctx->context, name, success_callback, data);
if (op == NULL) {
int error = pa_context_errno(ctx->context);
lua_pushvalue(L, 2);
lua_pushfstring(L, "failed to set default source: %s", pa_strerror(error));
lua_call(L, 1, 0);
return 0;
}
return 0;
}

View file

@ -1,11 +1,20 @@
/** Bindings for libpulse's connection context.
*
* The connection @{Context} provides introspection calls to query state from the server and various commands to
* change this state.
*
* In many cases, sinks and sources may be addressed by either their name or their numeric index.
* Both can be queried using the `get_(sink|source)_info` or `get_(sink|source)s` calls.
*
* @module pulseaudio.context
*/
#pragma once
#include "stdbool.h"
#include "lua.h"
#include "lauxlib.h"
#include "pulse/context.h"
#include "pulse/mainloop-api.h"
#include "introspection.h"
#include <lauxlib.h>
#include <lua.h>
#include <pulse/context.h>
#include <pulse/mainloop-api.h>
#include <stdbool.h>
#define LUA_PA_CONTEXT "pulseaudio.context"
@ -23,22 +32,428 @@ typedef struct lua_pa_context {
} lua_pa_context;
int
context_new(lua_State*, pa_mainloop_api*);
int
context__index(lua_State*);
int
context__gc(lua_State*);
int
context_connect(lua_State*);
int context_new(lua_State*, pa_mainloop_api*);
int context__gc(lua_State*);
static const struct luaL_Reg context_mt [] = {
{"__index", context__index},
/// Context
/// @type Context
/** Connects the context to the given server address.
*
* If the server address is `nil`, libpulse will attempt to connect to what it considers the default server.
* In most cases, this is the local machine.
*
* The provided callback function will be registered as state callback function, and will be called whenever the
* context's connection state changes.
*
* @function Context:connect
* @async
* @tparam[opt=nil] string server_address The server address.
* @tparam function cb The connection state callback.
* @treturn[opt] string The error message
* @treturn string The state
*/
int context_connect(lua_State*);
/** Disconnects from the server.
*
* @function Context:disconnect
*/
int context_disconnect(lua_State* L);
/** Gets information about the server the context is connected to.
*
* See [pa_server_info](https://freedesktop.org/software/pulseaudio/doxygen/structpa__server__info.html)
* for documentation on the return type.
*
* This will fail when the connection state is anything other than `READY`.
*
* @function Context:get_server_info
* @async
* @tparam function cb
* @return[opt] string The error
* @return table The server info.
*/
int context_get_server_info(lua_State*);
// Sinks
/** Gets information about the given sink.
*
* The sink may be indicated by either its name or its index.
*
* See [pa_sink_info](https://freedesktop.org/software/pulseaudio/doxygen/structpa__sink__info.html)
* for documentation on the return type.
*
* @function Context:get_sink_info
* @async
* @tparam number|string sink The index or name of the sink to query.
* @tparam function cb
* @treturn[opt] string
* @treturn table
*/
int context_get_sink_info(lua_State*);
/** Gets information about all sinks.
*
* This returns the same information as @{Context:get_sink_info} would have returned for every sink
* that's currently registered at the server.
*
* See [pa_sink_info](https://freedesktop.org/software/pulseaudio/doxygen/structpa__sink__info.html)
* for documentation on the return type.
*
* @function Context:get_sinks
* @async
* @tparam function cb
* @treturn[opt] string
* @treturn table
*/
int context_get_sink_info_list(lua_State*);
int context_get_sink_info_by_name(lua_State*);
int context_get_sink_info_by_index(lua_State*);
/** Sets the sink's volume to the given value.
*
* The sink may be indicated by either its name or its index.
*
* @function Context:set_sink_volume
* @async
* @tparam number|string sink The sink to update.
* @tparam Volume volume
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_sink_volume(lua_State*);
int context_set_sink_volume_by_name(lua_State*);
int context_set_sink_volume_by_index(lua_State*);
/** Sets the sink's mute state.
*
* The sink may be indicated by either its name or its index.
*
* @function Context:set_sink_mute
* @async
* @tparam number|string sink The sink to update.
* @tparam boolean mute
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_sink_mute(lua_State*);
int context_set_sink_mute_by_name(lua_State*);
int context_set_sink_mute_by_index(lua_State*);
/** Sets the sink's suspended state.
*
* The sink may be indicated by either its name or its index.
*
* @function Context:set_sink_suspended
* @async
* @tparam number|string sink The sink to update.
* @tparam boolean suspended
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_sink_suspended(lua_State*);
int context_set_sink_suspended_by_name(lua_State*);
int context_set_sink_suspended_by_index(lua_State*);
// Sink Inputs
/** Gets information about the given sink input.
*
* The sink input may be indicated by either its name or its index.
*
* See [pa_sink_input_info](https://freedesktop.org/software/pulseaudio/doxygen/structpa__sink_input__info.html)
* for documentation on the return type.
*
* @function Context:get_sink_input_info
* @async
* @tparam number|string sink input The index or name of the sink input to query.
* @tparam function cb
* @treturn[opt] string
* @treturn table
*/
int context_get_sink_input_info(lua_State*);
/** Gets information about all sink inputs.
*
* This returns the same information as @{Context:get_sink_input_info} would have returned for every sink input
* that's currently registered at the server.
*
* See [pa_sink_input_info](https://freedesktop.org/software/pulseaudio/doxygen/structpa__sink_input__info.html)
* for documentation on the return type.
*
* @function Context:get_sink_inputs
* @async
* @tparam function cb
* @treturn[opt] string
* @treturn table
*/
int context_get_sink_input_info_list(lua_State*);
/** Moves the sink input to a different name.
*
* The target sink may be indicated by either its name or its index.
*
* @function Context:set_sink_suspended
* @async
* @tparam number The sink input to move.
* @tparam number|string sink The sink to update.
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_move_sink_input(lua_State*);
int context_move_sink_input_by_name(lua_State*);
int context_move_sink_input_by_index(lua_State*);
/** Sets the sink input's volume to the given value.
*
* @function Context:set_sink_input_volume
* @async
* @tparam number sink_input The sink input to update.
* @tparam Volume volume
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_sink_input_volume(lua_State*);
/** Sets the sink input's mute state.
*
* @function Context:set_sink_input_mute
* @async
* @tparam number sink_input The sink input to update.
* @tparam boolean mute
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_sink_input_mute(lua_State*);
/** Kills the sink input.
*
* @function Context:kill_sink_input
* @async
* @tparam number sink_input The sink input to kill.
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_kill_sink_input(lua_State*);
// Sources
/** Gets information about the given source.
*
* The source may be indicated by either its name or its index.
*
* See [pa_source_info](https://freedesktop.org/software/pulseaudio/doxygen/structpa__source__info.html)
* for documentation on the return type.
*
* @function Context:get_source_info
* @async
* @tparam number|string source The index or name of the source to query.
* @tparam function cb
* @treturn[opt] string
* @treturn table
*/
int context_get_source_info(lua_State*);
/** Gets information about all sources.
*
* This returns the same information as @{Context:get_source_info} would have returned for every source
* that's currently registered at the server.
*
* See [pa_source_info](https://freedesktop.org/software/pulseaudio/doxygen/structpa__source__info.html)
* for documentation on the return type.
*
* @function Context:get_sources
* @async
* @tparam function cb
* @treturn[opt] string
* @treturn table
*/
int context_get_source_info_list(lua_State*);
int context_get_source_info_by_name(lua_State*);
int context_get_source_info_by_index(lua_State*);
/** Sets the source's volume to the given value.
*
* The source may be indicated by either its name or its index.
*
* @function Context:set_source_volume
* @async
* @tparam number|string source The source to update.
* @tparam Volume volume
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_source_volume(lua_State*);
int context_set_source_volume_by_name(lua_State*);
int context_set_source_volume_by_index(lua_State*);
/** Sets the source's mute state.
*
* The source may be indicated by either its name or its index.
*
* @function Context:set_source_mute
* @async
* @tparam number|string source The source to update.
* @tparam boolean mute
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_source_mute(lua_State*);
int context_set_source_mute_by_name(lua_State*);
int context_set_source_mute_by_index(lua_State*);
/** Sets the source's suspended state.
*
* The source may be indicated by either its name or its index.
*
* @function Context:set_source_suspended
* @async
* @tparam number|string source The source to update.
* @tparam boolean suspended
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_source_suspended(lua_State*);
int context_set_source_suspended_by_name(lua_State*);
int context_set_source_suspended_by_index(lua_State*);
// Source Outputs
/** Gets information about the given source output.
*
* See [pa_source_output_info](https://freedesktop.org/software/pulseaudio/doxygen/structpa__source_output__info.html)
* for documentation on the return type.
*
* @function Context:get_source_output_info
* @async
* @tparam number source_output The index of the source output to query.
* @tparam function cb
* @treturn[opt] string
* @treturn table
*/
int context_get_source_output_info(lua_State*);
/** Gets information about all source outputs.
*
* This returns the same information as @{Context:get_source_output_info} would have returned for every source output
* that's currently registered at the server.
*
* See [pa_source_output_info](https://freedesktop.org/software/pulseaudio/doxygen/structpa__source_output__info.html)
* for documentation on the return type.
*
* @function Context:get_source_outputs
* @async
* @tparam function cb
* @treturn[opt] string
* @treturn table
*/
int context_get_source_output_info_list(lua_State*);
/** Moves the source output to a different name.
*
* The target source may be indicated by either its name or its index.
*
* @function Context:set_source_suspended
* @async
* @tparam number The source output to move.
* @tparam number|string source The source to update.
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_move_source_output(lua_State*);
int context_move_source_output_by_name(lua_State*);
int context_move_source_output_by_index(lua_State*);
/** Sets the source output's volume to the given value.
*
* @function Context:set_source_output_volume
* @async
* @tparam number source_output The source output to update.
* @tparam Volume volume
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_source_output_volume(lua_State*);
/** Sets the source output's mute state.
*
* @function Context:set_source_output_mute
* @async
* @tparam number source_output The source output to update.
* @tparam boolean mute
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_set_source_output_mute(lua_State*);
/** Kills the source output.
*
* @function Context:kill_source_output
* @async
* @tparam number source_output The source output to kill.
* @tparam function cb
* @treturn[opt] string
* @treturn boolean
*/
int context_kill_source_output(lua_State*);
static const struct luaL_Reg context_mt[] = {
{"__gc", context__gc},
{"connect", context_connect},
{"get_server_info", context_get_server_info},
{"get_sinks", context_get_sink_info_list},
{"get_sources", context_get_source_info_list},
{NULL, NULL}
{ NULL, NULL }
};
static const struct luaL_Reg context_f[] = {
{"connect", context_connect },
{ "disconnect", context_disconnect },
{ "get_server_info", context_get_server_info },
{ "get_sinks", context_get_sink_info_list },
{ "get_sink_info", context_get_sink_info },
{ "set_sink_volume", context_set_sink_volume },
{ "set_sink_mute", context_set_sink_mute },
{ "set_sink_suspended", context_set_sink_suspended },
{ "get_sink_inputs", context_get_sink_input_info_list },
{ "get_sink_input_info", context_get_sink_input_info },
{ "set_sink_input_volume", context_set_sink_input_volume },
{ "set_sink_input_mute", context_set_sink_input_mute },
{ "move_sink_input", context_move_sink_input },
{ "kill_sink_input", context_kill_sink_input },
{ "set_source_volume", context_set_source_volume },
{ "set_source_mute", context_set_source_mute },
{ "set_source_suspended", context_set_source_suspended },
{ "get_sources", context_get_source_info_list },
{ "get_source_info", context_get_source_info },
{ "get_source_outputs", context_get_source_output_info_list},
{ "get_source_output_info", context_get_source_output_info },
{ "set_source_output_volume", context_set_source_output_volume },
{ "set_source_output_mute", context_set_source_output_mute },
{ "move_source_output", context_move_source_output },
{ "kill_source_output", context_kill_source_output },
{ NULL, NULL }
};

View file

@ -0,0 +1,532 @@
#include "convert.h"
#include "proplist.h"
#include "volume.h"
#include <pulse/xmalloc.h>
void channel_map_to_lua(lua_State* L, const pa_channel_map* spec) {
lua_createtable(L, spec->channels, 0);
int table_index = lua_gettop(L);
for (int i = 0; i < spec->channels; ++i) {
lua_pushinteger(L, i + 1);
lua_pushinteger(L, spec->map[i]);
lua_settable(L, table_index);
}
}
void sample_spec_to_lua(lua_State* L, const pa_sample_spec* spec) {
lua_createtable(L, 0, 3);
int table_index = lua_gettop(L);
lua_pushstring(L, "rate");
lua_pushinteger(L, spec->rate);
lua_settable(L, table_index);
lua_pushstring(L, "channels");
lua_pushinteger(L, spec->channels);
lua_settable(L, table_index);
lua_pushstring(L, "format");
lua_pushinteger(L, spec->format);
lua_settable(L, table_index);
}
void sink_port_info_to_lua(lua_State* L, const pa_sink_port_info* info) {
lua_createtable(L, 0, 6);
int table_index = lua_gettop(L);
lua_pushstring(L, "name");
lua_pushstring(L, info->name);
lua_settable(L, table_index);
lua_pushstring(L, "description");
lua_pushstring(L, info->description);
lua_settable(L, table_index);
lua_pushstring(L, "priority");
lua_pushinteger(L, info->priority);
lua_settable(L, table_index);
lua_pushstring(L, "available");
lua_pushinteger(L, info->available);
lua_settable(L, table_index);
lua_pushstring(L, "availability_group");
lua_pushstring(L, info->availability_group);
lua_settable(L, table_index);
lua_pushstring(L, "type");
lua_pushinteger(L, info->type);
lua_settable(L, table_index);
}
void source_port_info_to_lua(lua_State* L, const pa_source_port_info* info) {
lua_createtable(L, 0, 6);
int table_index = lua_gettop(L);
lua_pushstring(L, "name");
lua_pushstring(L, info->name);
lua_settable(L, table_index);
lua_pushstring(L, "description");
lua_pushstring(L, info->description);
lua_settable(L, table_index);
lua_pushstring(L, "priority");
lua_pushinteger(L, info->priority);
lua_settable(L, table_index);
lua_pushstring(L, "available");
lua_pushinteger(L, info->available);
lua_settable(L, table_index);
lua_pushstring(L, "availability_group");
lua_pushstring(L, info->availability_group);
lua_settable(L, table_index);
lua_pushstring(L, "type");
lua_pushinteger(L, info->type);
lua_settable(L, table_index);
}
void format_info_to_lua(lua_State* L, const pa_format_info* info) {
lua_createtable(L, 0, 2);
int table_index = lua_gettop(L);
lua_pushstring(L, "encoding");
lua_pushinteger(L, info->encoding);
lua_settable(L, table_index);
lua_pushstring(L, "plist");
proplist_to_lua(L, pa_proplist_copy(info->plist));
lua_settable(L, table_index);
}
void sink_ports_to_lua(lua_State* L, pa_sink_port_info** list, int n_ports, pa_sink_port_info* active) {
lua_createtable(L, n_ports, 1);
int table_index = lua_gettop(L);
for (int i = 0; i < n_ports; ++i) {
lua_pushinteger(L, i + 1);
sink_port_info_to_lua(L, list[i]);
if (list[i] == active) {
lua_pushstring(L, "active");
lua_pushvalue(L, -2);
lua_settable(L, table_index);
}
lua_settable(L, table_index);
}
}
void source_ports_to_lua(lua_State* L, pa_source_port_info** list, int n_ports, pa_source_port_info* active) {
lua_createtable(L, n_ports, 1);
int table_index = lua_gettop(L);
for (int i = 0; i < n_ports; ++i) {
lua_pushinteger(L, i + 1);
source_port_info_to_lua(L, list[i]);
if (list[i] == active) {
lua_pushstring(L, "active");
lua_pushvalue(L, -2);
lua_settable(L, table_index);
}
lua_settable(L, table_index);
}
}
void formats_to_lua(lua_State* L, pa_format_info** list, int n_formats) {
lua_createtable(L, n_formats, 0);
int table_index = lua_gettop(L);
for (int i = 0; i < n_formats; ++i) {
lua_pushinteger(L, i + 1);
format_info_to_lua(L, list[i]);
lua_settable(L, table_index);
}
}
void sink_info_to_lua(lua_State* L, const pa_sink_info* info) {
lua_createtable(L, 0, 24);
int table_index = lua_gettop(L);
lua_pushstring(L, "name");
lua_pushstring(L, info->name);
lua_settable(L, table_index);
lua_pushstring(L, "index");
// Convert C's 0-based index to Lua's 1-base
lua_pushinteger(L, info->index + 1);
lua_settable(L, table_index);
lua_pushstring(L, "description");
lua_pushstring(L, info->description);
lua_settable(L, table_index);
lua_pushstring(L, "sample_spec");
sample_spec_to_lua(L, &info->sample_spec);
lua_settable(L, table_index);
lua_pushstring(L, "channel_map");
channel_map_to_lua(L, &info->channel_map);
lua_settable(L, table_index);
lua_pushstring(L, "owner_module");
lua_pushinteger(L, info->owner_module);
lua_settable(L, table_index);
lua_pushstring(L, "volume");
volume_to_lua(L, &info->volume);
lua_settable(L, table_index);
lua_pushstring(L, "mute");
lua_pushinteger(L, info->mute);
lua_settable(L, table_index);
lua_pushstring(L, "monitor_source");
lua_pushinteger(L, info->monitor_source);
lua_settable(L, table_index);
lua_pushstring(L, "monitor_source_name");
lua_pushstring(L, info->monitor_source_name);
lua_settable(L, table_index);
lua_pushstring(L, "latency");
lua_pushinteger(L, info->latency);
lua_settable(L, table_index);
lua_pushstring(L, "driver");
lua_pushstring(L, info->driver);
lua_settable(L, table_index);
lua_pushstring(L, "flags");
lua_pushinteger(L, info->flags);
lua_settable(L, table_index);
lua_pushstring(L, "proplist");
proplist_to_lua(L, pa_proplist_copy(info->proplist));
lua_settable(L, table_index);
lua_pushstring(L, "base_volume");
lua_pushinteger(L, info->base_volume);
lua_settable(L, table_index);
lua_pushstring(L, "state");
lua_pushinteger(L, info->state);
lua_settable(L, table_index);
lua_pushstring(L, "n_volume_steps");
lua_pushinteger(L, info->n_volume_steps);
lua_settable(L, table_index);
lua_pushstring(L, "card");
lua_pushinteger(L, info->card);
lua_settable(L, table_index);
lua_pushstring(L, "ports");
sink_ports_to_lua(L, info->ports, info->n_ports, info->active_port);
lua_settable(L, table_index);
lua_pushstring(L, "formats");
formats_to_lua(L, info->formats, info->n_formats);
lua_settable(L, table_index);
}
void source_info_to_lua(lua_State* L, const pa_source_info* info) {
lua_createtable(L, 0, 24);
int table_index = lua_gettop(L);
lua_pushstring(L, "name");
lua_pushstring(L, info->name);
lua_settable(L, table_index);
lua_pushstring(L, "index");
// Convert C's 0-based index to Lua's 1-base
lua_pushinteger(L, info->index + 1);
lua_settable(L, table_index);
lua_pushstring(L, "description");
lua_pushstring(L, info->description);
lua_settable(L, table_index);
lua_pushstring(L, "sample_spec");
sample_spec_to_lua(L, &info->sample_spec);
lua_settable(L, table_index);
lua_pushstring(L, "channel_map");
channel_map_to_lua(L, &info->channel_map);
lua_settable(L, table_index);
lua_pushstring(L, "owner_module");
lua_pushinteger(L, info->owner_module);
lua_settable(L, table_index);
lua_pushstring(L, "volume");
volume_to_lua(L, &info->volume);
lua_settable(L, table_index);
lua_pushstring(L, "mute");
lua_pushinteger(L, info->mute);
lua_settable(L, table_index);
lua_pushstring(L, "monitor_of_sink");
lua_pushinteger(L, info->monitor_of_sink);
lua_settable(L, table_index);
lua_pushstring(L, "monitor_of_sink_name");
lua_pushstring(L, info->monitor_of_sink_name);
lua_settable(L, table_index);
lua_pushstring(L, "latency");
lua_pushinteger(L, info->latency);
lua_settable(L, table_index);
lua_pushstring(L, "driver");
lua_pushstring(L, info->driver);
lua_settable(L, table_index);
lua_pushstring(L, "flags");
lua_pushinteger(L, info->flags);
lua_settable(L, table_index);
lua_pushstring(L, "proplist");
proplist_to_lua(L, pa_proplist_copy(info->proplist));
lua_settable(L, table_index);
lua_pushstring(L, "configured_latency");
lua_pushinteger(L, info->configured_latency);
lua_settable(L, table_index);
lua_pushstring(L, "base_volume");
lua_pushinteger(L, info->base_volume);
lua_settable(L, table_index);
lua_pushstring(L, "state");
lua_pushinteger(L, info->state);
lua_settable(L, table_index);
lua_pushstring(L, "n_volume_steps");
lua_pushinteger(L, info->n_volume_steps);
lua_settable(L, table_index);
lua_pushstring(L, "card");
lua_pushinteger(L, info->card);
lua_settable(L, table_index);
lua_pushstring(L, "ports");
source_ports_to_lua(L, info->ports, info->n_ports, info->active_port);
lua_settable(L, table_index);
lua_pushstring(L, "formats");
formats_to_lua(L, info->formats, info->n_formats);
lua_settable(L, table_index);
}
void server_info_to_lua(lua_State* L, const pa_server_info* info) {
lua_createtable(L, 0, 9);
int table_index = lua_gettop(L);
lua_pushstring(L, "user_name");
lua_pushstring(L, info->user_name);
lua_settable(L, table_index);
lua_pushstring(L, "host_name");
lua_pushstring(L, info->host_name);
lua_settable(L, table_index);
lua_pushstring(L, "server_version");
lua_pushstring(L, info->server_version);
lua_settable(L, table_index);
lua_pushstring(L, "server_name");
lua_pushstring(L, info->server_name);
lua_settable(L, table_index);
lua_pushstring(L, "default_sink_name");
lua_pushstring(L, info->default_sink_name);
lua_settable(L, table_index);
lua_pushstring(L, "default_source_name");
lua_pushstring(L, info->default_source_name);
lua_settable(L, table_index);
lua_pushstring(L, "cookie");
lua_pushinteger(L, info->cookie);
lua_settable(L, table_index);
lua_pushstring(L, "sample_spec");
sample_spec_to_lua(L, &info->sample_spec);
lua_settable(L, table_index);
lua_pushstring(L, "channel_map");
channel_map_to_lua(L, &info->channel_map);
lua_settable(L, table_index);
}
void sink_input_info_to_lua(lua_State* L, const pa_sink_input_info* info) {
lua_createtable(L, 0, 15);
int table_index = lua_gettop(L);
lua_pushstring(L, "index");
lua_pushinteger(L, info->index + 1);
lua_settable(L, table_index);
lua_pushstring(L, "name");
lua_pushstring(L, info->name);
lua_settable(L, table_index);
lua_pushstring(L, "owner_module");
lua_pushinteger(L, info->owner_module);
lua_settable(L, table_index);
lua_pushstring(L, "client");
lua_pushinteger(L, info->client);
lua_settable(L, table_index);
lua_pushstring(L, "sink");
lua_pushinteger(L, info->sink);
lua_settable(L, table_index);
lua_pushstring(L, "sample_spec");
sample_spec_to_lua(L, &info->sample_spec);
lua_settable(L, table_index);
lua_pushstring(L, "channel_map");
channel_map_to_lua(L, &info->channel_map);
lua_settable(L, table_index);
lua_pushstring(L, "has_volume");
lua_pushboolean(L, info->has_volume);
lua_settable(L, table_index);
lua_pushstring(L, "volume_writable");
lua_pushboolean(L, info->volume_writable);
lua_settable(L, table_index);
if (info->has_volume) {
lua_pushstring(L, "volume");
volume_to_lua(L, &info->volume);
lua_settable(L, table_index);
}
lua_pushstring(L, "buffer_usec");
lua_pushinteger(L, info->buffer_usec);
lua_settable(L, table_index);
lua_pushstring(L, "sink_usec");
lua_pushinteger(L, info->sink_usec);
lua_settable(L, table_index);
lua_pushstring(L, "resample_method");
lua_pushstring(L, info->resample_method);
lua_settable(L, table_index);
lua_pushstring(L, "driver");
lua_pushstring(L, info->driver);
lua_settable(L, table_index);
lua_pushstring(L, "mute");
lua_pushboolean(L, info->mute);
lua_settable(L, table_index);
lua_pushstring(L, "format");
format_info_to_lua(L, info->format);
lua_settable(L, table_index);
lua_pushstring(L, "proplist");
proplist_to_lua(L, pa_proplist_copy(info->proplist));
lua_settable(L, table_index);
}
void source_output_info_to_lua(lua_State* L, const pa_source_output_info* info) {
lua_createtable(L, 0, 15);
int table_index = lua_gettop(L);
lua_pushstring(L, "index");
lua_pushinteger(L, info->index + 1);
lua_settable(L, table_index);
lua_pushstring(L, "name");
lua_pushstring(L, info->name);
lua_settable(L, table_index);
lua_pushstring(L, "owner_module");
lua_pushinteger(L, info->owner_module);
lua_settable(L, table_index);
lua_pushstring(L, "client");
lua_pushinteger(L, info->client);
lua_settable(L, table_index);
lua_pushstring(L, "source");
lua_pushinteger(L, info->source);
lua_settable(L, table_index);
lua_pushstring(L, "sample_spec");
sample_spec_to_lua(L, &info->sample_spec);
lua_settable(L, table_index);
lua_pushstring(L, "channel_map");
channel_map_to_lua(L, &info->channel_map);
lua_settable(L, table_index);
lua_pushstring(L, "has_volume");
lua_pushboolean(L, info->has_volume);
lua_settable(L, table_index);
lua_pushstring(L, "volume_writable");
lua_pushboolean(L, info->volume_writable);
lua_settable(L, table_index);
if (info->has_volume) {
lua_pushstring(L, "volume");
volume_to_lua(L, &info->volume);
lua_settable(L, table_index);
}
lua_pushstring(L, "buffer_usec");
lua_pushinteger(L, info->buffer_usec);
lua_settable(L, table_index);
lua_pushstring(L, "source_usec");
lua_pushinteger(L, info->source_usec);
lua_settable(L, table_index);
lua_pushstring(L, "resample_method");
lua_pushstring(L, info->resample_method);
lua_settable(L, table_index);
lua_pushstring(L, "driver");
lua_pushstring(L, info->driver);
lua_settable(L, table_index);
lua_pushstring(L, "mute");
lua_pushboolean(L, info->mute);
lua_settable(L, table_index);
lua_pushstring(L, "format");
format_info_to_lua(L, info->format);
lua_settable(L, table_index);
lua_pushstring(L, "proplist");
proplist_to_lua(L, pa_proplist_copy(info->proplist));
lua_settable(L, table_index);
}

View file

@ -0,0 +1,20 @@
#pragma once
#include <lua.h>
#include <pulse/error.h>
#include <pulse/introspect.h>
void channel_map_to_lua(lua_State*, const pa_channel_map*);
void sample_spec_to_lua(lua_State*, const pa_sample_spec*);
void sink_port_info_to_lua(lua_State*, const pa_sink_port_info*);
void source_port_info_to_lua(lua_State*, const pa_source_port_info*);
void format_info_to_lua(lua_State*, const pa_format_info*);
void sink_ports_to_lua(lua_State*, pa_sink_port_info**, int, pa_sink_port_info*);
void source_ports_to_lua(lua_State*, pa_source_port_info**, int, pa_source_port_info*);
void formats_to_lua(lua_State*, pa_format_info**, int);
void sink_info_to_lua(lua_State*, const pa_sink_info*);
void source_info_to_lua(lua_State*, const pa_source_info*);
void server_info_to_lua(lua_State*, const pa_server_info*);
void sink_input_info_to_lua(lua_State*, const pa_sink_input_info*);
void source_output_info_to_lua(lua_State*, const pa_source_output_info*);

File diff suppressed because it is too large Load diff

View file

@ -1,10 +0,0 @@
#pragma once
#include "lua.h"
int
context_get_server_info(lua_State*);
int
context_get_sink_info_list(lua_State*);
int
context_get_source_info_list(lua_State*);

View file

@ -0,0 +1,15 @@
#ifndef lua_util_h_INCLUDED
#define lua_util_h_INCLUDED
#include <lauxlib.h>
#include <lua.h>
#define LUA_MOD_EXPORT extern
typedef struct luaU_enumfield {
const char* name;
const char* value;
} luaU_enumfield;
#endif // lua_util_h_INCLUDED

View file

@ -0,0 +1,214 @@
#include "proplist.h"
#include <pulse/xmalloc.h>
// Creates a Lua userdatum from/for a PulseAudio proplist.
//
// The provided proplist will be `pa_proplist_copy()`ed to pass memory management over
// to the garbage collector.
//
// @return[type=PropList]
int proplist_to_lua(lua_State* L, pa_proplist* pa_plist) {
proplist* plist = lua_newuserdata(L, sizeof(proplist));
if (pa_plist == NULL) {
lua_pushfstring(L, "Failed to allocate proplist userdata");
return lua_error(L);
}
luaL_getmetatable(L, LUA_PA_PROPLIST);
lua_setmetatable(L, -2);
plist->plist = pa_proplist_copy(pa_plist);
return 1;
}
int proplist_new(lua_State* L) {
pa_proplist* pa_plist = pa_proplist_new();
if (pa_plist == NULL) {
lua_pushfstring(L, "Failed to allocate PulseAudio proplist");
return lua_error(L);
}
return proplist_to_lua(L, pa_plist);
}
int proplist_from_string(lua_State* L) {
const char* str = luaL_checkstring(L, 1);
pa_proplist* pa_plist = pa_proplist_from_string(str);
return proplist_to_lua(L, pa_plist);
}
int proplist_key_valid(lua_State* L) {
const char* key = luaL_checkstring(L, 1);
int valid = pa_proplist_key_valid(key);
lua_pushboolean(L, valid);
return 1;
}
int proplist_isempty(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
int empty = pa_proplist_isempty(plist->plist);
lua_pushboolean(L, empty);
return 1;
}
int proplist_tostring_sep(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
const char* sep = luaL_checkstring(L, 2);
const char* str = pa_proplist_to_string_sep(plist->plist, sep);
lua_pushstring(L, str);
pa_xfree((void*) str);
return 1;
}
int proplist_clear(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
pa_proplist_clear(plist->plist);
return 0;
}
int proplist_contains(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
const char* key = luaL_checkstring(L, 2);
if (!pa_proplist_key_valid(key)) {
return luaL_error(L, "invalid key for proplist");
}
int contains = pa_proplist_contains(plist->plist, key);
if (contains < 0) {
return luaL_error(L, "failed to check if proplist contains the key");
} else {
lua_pushboolean(L, contains);
}
return 1;
}
int proplist_copy(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
pa_proplist* other = pa_proplist_copy(plist->plist);
return proplist_to_lua(L, other);
}
// Metatable functions
// Accesses the value at the given string key.
//
// This functions very similar to a regular hash table.
//
// Numeric indices/keys are not supported.
//
// Will return `nil` if there is no value or if it is not valid UTF-8.
// Arbitrary data from `pa_proplist_get()` is currently not supported.
//
// @param[type=string] key The index to access.
// @return[type=string|nil] The data at the given index.
int proplist__index(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
const char* key = luaL_checkstring(L, 2);
if (!pa_proplist_key_valid(key)) {
return luaL_error(L, "invalid key for proplist");
}
if (!pa_proplist_contains(plist->plist, key)) {
lua_pushnil(L);
return 1;
}
const char* value = pa_proplist_gets(plist->plist, key);
if (value == NULL) {
lua_pushnil(L);
} else {
lua_pushstring(L, value);
}
return 1;
}
// Sets or overwrites the value at the given key.
//
// Passing `nil` as value will unset the key.
//
// For both keys and values only strings are supported.
// Numeric indices or binary data values (as in `pa_proplist_set()`) are not available.
//
// @param[type=string] key The index to write to.
// @param[type=string|nil] value The value to write.
int proplist__newindex(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
const char* key = luaL_checkstring(L, 2);
if (lua_isnil(L, 3)) {
if (pa_proplist_unset(plist->plist, key) < 0) {
// TODO: Get last error. Need to get access to the current context for
// this.
return luaL_error(L, "failed to unset key %s", key);
}
} else {
const char* value = luaL_checkstring(L, 3);
if (pa_proplist_sets(plist->plist, key, value) < 0) {
return luaL_error(L, "failed to set value for key %s", key);
}
}
return 0;
}
// Frees the internal proplist.
int proplist__gc(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
pa_proplist_free(plist->plist);
return 0;
}
// Gets the size of the proplist.
int proplist__len(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
unsigned len = pa_proplist_size(plist->plist);
lua_pushinteger(L, len);
return 1;
}
// Compares two proplists for equality.
//
// Proplists are equal if they contain the same keys with the same values.
//
// @param[type=PropList] other The proplist to compare against.
// @return[type=boolean]
int proplist__eq(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
proplist* other = luaL_checkudata(L, 2, LUA_PA_PROPLIST);
int equal = pa_proplist_equal(plist->plist, other->plist);
lua_pushboolean(L, equal);
return 1;
}
// Creates a string representation.
//
// This adds a newline character as separator and as final character.
//
// @return[type=string]
int proplist__tostring(lua_State* L) {
proplist* plist = luaL_checkudata(L, 1, LUA_PA_PROPLIST);
char* str = pa_proplist_to_string(plist->plist);
lua_pushstring(L, str);
pa_xfree((void*) str);
return 1;
}

View file

@ -0,0 +1,316 @@
/** Bindings for PulseAudio's property lists.
*
* Basic operations are mapped to Lua table operations:
*
* - get a value: `plist[key]`
* - set a value: `plist[key] = value`
* - get the size: `#plist`
* - get default string presentation: `tostring(plist)`
* - equality: `plist == other`
*
* Additional operations are exposed as methods, as documented below.
*
* @module pulseaudio.proplist
*/
#pragma once
#include "lua_util.h"
#include <lauxlib.h>
#include <lua.h>
#include <pulse/proplist.h>
#include <stdbool.h>
#define LUA_PA_PROPLIST "pulseaudio.proplist"
typedef struct proplist {
pa_proplist* plist;
} proplist;
/** A list of well-known keys. These will likely be used by other consumers
* of PulseAudio, so should be preferred for interoperatibility.
*
* See [proplist.h](https://freedesktop.org/software/pulseaudio/doxygen/proplist_8h.html) for details on
* these values.
*
* local proplist = require("pulseaudio.proplist")
* proplist.is_key_valid(proplist.MEDIA_NAME) -- true
* print(proplist.MEDIA_NAME) -- media.name
*
* @table pulseaudio.proplist
* @field MEDIA_NAME media.name
* @field MEDIA_TITLE media.title
* @field MEDIA_ARTIST media.artist
* @field MEDIA_COPYRIGHT media.copyright
* @field MEDIA_SOFTWARE media.software
* @field MEDIA_LANGUAGE media.language
* @field MEDIA_FILENAME media.filename
* @field MEDIA_ICON media.icon
* @field MEDIA_ICON_NAME media.icon_name
* @field MEDIA_ROLE media.role
* @field FILTER_WANT filter.want
* @field FILTER_APPLY filter.apply
* @field FILTER_SUPPRESS filter.suppress
* @field EVENT_ID event.id
* @field EVENT_DESCRIPTION event.description
* @field EVENT_MOUSE_X event.mouse.x
* @field EVENT_MOUSE_Y event.mouse.y
* @field EVENT_MOUSE_HPOS event.mouse.hpos
* @field EVENT_MOUSE_VPOS event.mouse.vpos
* @field EVENT_MOUSE_BUTTON event.mouse.button
* @field WINDOW_NAME window.name
* @field WINDOW_ID window.id
* @field WINDOW_ICON window.icon
* @field WINDOW_ICON_NAME window.icon_name
* @field WINDOW_X window.x
* @field WINDOW_Y window.y
* @field WINDOW_WIDTH window.width
* @field WINDOW_HEIGHT window.height
* @field WINDOW_HPOS window.hpos
* @field WINDOW_VPOS window.vpos
* @field WINDOW_DESKTOP window.desktop
* @field WINDOW_X11_DISPLAY window.x11.display
* @field WINDOW_X11_SCREEN window.x11.screen
* @field WINDOW_X11_MONITOR window.x11.monitor
* @field WINDOW_X11_XID window.x11.xid
* @field APPLICATION_NAME application.name
* @field APPLICATION_ID application.id
* @field APPLICATION_VERSION application.version
* @field APPLICATION_ICON application.icon
* @field APPLICATION_ICON_NAME application.icon_name
* @field APPLICATION_LANGUAGE application.language
* @field APPLICATION_PROCESS_ID application.process.id
* @field APPLICATION_PROCESS_BINARY application.process.binary
* @field APPLICATION_PROCESS_USER application.process.user
* @field APPLICATION_PROCESS_HOST application.process.host
* @field APPLICATION_PROCESS_MACHINE_ID application.process.machine_id
* @field APPLICATION_PROCESS_SESSION_ID application.process.session_id
* @field DEVICE_STRING device.string
* @field DEVICE_API device.api
* @field DEVICE_DESCRIPTION device.description
* @field DEVICE_BUS_PATH device.bus_path
* @field DEVICE_SERIAL device.serial
* @field DEVICE_VENDOR_ID device.vendor.id
* @field DEVICE_VENDOR_NAME device.vendor.name
* @field DEVICE_PRODUCT_ID device.product.id
* @field DEVICE_PRODUCT_NAME device.product.name
* @field DEVICE_CLASS device.class
* @field DEVICE_FORM_FACTOR device.form_factor
* @field DEVICE_BUS device.bus
* @field DEVICE_ICON device.icon
* @field DEVICE_ICON_NAME device.icon_name
* @field DEVICE_ACCESS_MODE device.access_mode
* @field DEVICE_MASTER_DEVICE device.master_device
* @field DEVICE_BUFFERING_BUFFER_SIZE device.buffering.buffer_size
* @field DEVICE_BUFFERING_FRAGMENT_SIZE device.buffering.fragment_size
* @field DEVICE_PROFILE_NAME device.profile.name
* @field DEVICE_INTENDED_ROLES device.intended_roles
* @field DEVICE_PROFILE_DESCRIPTION device.profile.description
* @field MODULE_AUTHOR module.author
* @field MODULE_DESCRIPTION module.description
* @field MODULE_USAGE module.usage
* @field MODULE_VERSION module.version
* @field FORMAT_SAMPLE_FORMAT format.sample_format
* @field FORMAT_RATE format.rate
* @field FORMAT_CHANNELS format.channels
* @field FORMAT_CHANNEL_MAP format.channel_map
* @field CONTEXT_FORCE_DISABLE_SHM context.force.disable.shm
* @field BLUETOOTH_CODEC bluetooth.codec
*/
/// Constructor functions.
/// @section constructors
/** Creates a new, empty property list.
*
* @function new
* @return[type=PropList]
*/
int proplist_new(lua_State*);
/** Parses a string into a @{PropList}.
*
* @function from_string
* @param[type=string] str The string to parse.
* @return[type=PropList]
*/
int proplist_from_string(lua_State*);
/// Static functions
/// @section static
/** Checks if the given string is a valid key.
*
* @function is_key_valid
* @param[type=string] key The string to check.
* @return[type=boolean]
*/
int proplist_key_valid(lua_State*);
// Internal functions
int proplist_to_lua(lua_State*, pa_proplist*);
int proplist__index(lua_State*);
int proplist__newindex(lua_State*);
int proplist__gc(lua_State*);
int proplist__len(lua_State*);
int proplist__eq(lua_State*);
int proplist__tostring(lua_State*);
/// Methods
/// @type PropList
/** Checks if the proplist is empty.
*
* @function is_empty
* @return[type=boolean]
*/
int proplist_isempty(lua_State*);
/** Creates a string representation with a custom separator.
*
* @function tostring_sep
* @return[type=string]
*/
int proplist_tostring_sep(lua_State*);
/** Removes all keys from the proplist
*
* @function clear
*/
int proplist_clear(lua_State*);
/** Checks if the proplist contains the given key.
*
* @function contains
* @param[type=string] key The key to check for.
* @return[type=boolean]
*/
int proplist_contains(lua_State*);
/** Duplicates the proplist.
*
* @function copy
* @return[type=proplist]
*/
int proplist_copy(lua_State*);
static const struct luaL_Reg proplist_f[] = {
{"clear", proplist_clear },
{ "contains", proplist_contains },
{ "copy", proplist_copy },
{ "is_empty", proplist_isempty },
{ "tostring_sep", proplist_tostring_sep},
{ NULL, NULL }
};
static const struct luaL_Reg proplist_mt[] = {
{"__gc", proplist__gc },
{ "__len", proplist__len },
{ "__tostring", proplist__tostring},
{ "__eq", proplist__eq },
{ "__index", proplist__index },
{ "__newindex", proplist__newindex},
{ NULL, NULL }
};
static const struct luaL_Reg proplist_lib[] = {
{"new", proplist_new },
{ "is_key_valid", proplist_key_valid },
{ "from_string", proplist_from_string},
{ NULL, NULL }
};
static const struct luaU_enumfield proplist_enum[] = {
{"MEDIA_TITLE", PA_PROP_MEDIA_TITLE },
{ "MEDIA_ARTIST", PA_PROP_MEDIA_ARTIST },
{ "MEDIA_COPYRIGHT", PA_PROP_MEDIA_COPYRIGHT },
{ "MEDIA_SOFTWARE", PA_PROP_MEDIA_SOFTWARE },
{ "MEDIA_LANGUAGE", PA_PROP_MEDIA_LANGUAGE },
{ "MEDIA_FILENAME", PA_PROP_MEDIA_FILENAME },
{ "MEDIA_ICON", PA_PROP_MEDIA_ICON },
{ "MEDIA_ICON_NAME", PA_PROP_MEDIA_ICON_NAME },
{ "MEDIA_ROLE", PA_PROP_MEDIA_ROLE },
{ "FILTER_WANT", PA_PROP_FILTER_WANT },
{ "FILTER_APPLY", PA_PROP_FILTER_APPLY },
{ "FILTER_SUPPRESS", PA_PROP_FILTER_SUPPRESS },
{ "EVENT_ID", PA_PROP_EVENT_ID },
{ "EVENT_DESCRIPTION", PA_PROP_EVENT_DESCRIPTION },
{ "EVENT_MOUSE_X", PA_PROP_EVENT_MOUSE_X },
{ "EVENT_MOUSE_Y", PA_PROP_EVENT_MOUSE_Y },
{ "EVENT_MOUSE_HPOS", PA_PROP_EVENT_MOUSE_HPOS },
{ "EVENT_MOUSE_VPOS", PA_PROP_EVENT_MOUSE_VPOS },
{ "EVENT_MOUSE_BUTTON", PA_PROP_EVENT_MOUSE_BUTTON },
{ "WINDOW_NAME", PA_PROP_WINDOW_NAME },
{ "WINDOW_ID", PA_PROP_WINDOW_ID },
{ "WINDOW_ICON", PA_PROP_WINDOW_ICON },
{ "WINDOW_ICON_NAME", PA_PROP_WINDOW_ICON_NAME },
{ "WINDOW_X", PA_PROP_WINDOW_X },
{ "WINDOW_Y", PA_PROP_WINDOW_Y },
{ "WINDOW_WIDTH", PA_PROP_WINDOW_WIDTH },
{ "WINDOW_HEIGHT", PA_PROP_WINDOW_HEIGHT },
{ "WINDOW_HPOS", PA_PROP_WINDOW_HPOS },
{ "WINDOW_VPOS", PA_PROP_WINDOW_VPOS },
{ "WINDOW_DESKTOP", PA_PROP_WINDOW_DESKTOP },
{ "WINDOW_X11_DISPLAY", PA_PROP_WINDOW_X11_DISPLAY },
{ "WINDOW_X11_SCREEN", PA_PROP_WINDOW_X11_SCREEN },
{ "WINDOW_X11_MONITOR", PA_PROP_WINDOW_X11_MONITOR },
{ "WINDOW_X11_XID", PA_PROP_WINDOW_X11_XID },
{ "APPLICATION_NAME", PA_PROP_APPLICATION_NAME },
{ "APPLICATION_ID", PA_PROP_APPLICATION_ID },
{ "APPLICATION_VERSION", PA_PROP_APPLICATION_VERSION },
{ "APPLICATION_ICON", PA_PROP_APPLICATION_ICON },
{ "APPLICATION_ICON_NAME", PA_PROP_APPLICATION_ICON_NAME },
{ "APPLICATION_LANGUAGE", PA_PROP_APPLICATION_LANGUAGE },
{ "APPLICATION_PROCESS_ID", PA_PROP_APPLICATION_PROCESS_ID },
{ "APPLICATION_PROCESS_BINARY", PA_PROP_APPLICATION_PROCESS_BINARY },
{ "APPLICATION_PROCESS_USER", PA_PROP_APPLICATION_PROCESS_USER },
{ "APPLICATION_PROCESS_HOST", PA_PROP_APPLICATION_PROCESS_HOST },
{ "APPLICATION_PROCESS_MACHINE_ID", PA_PROP_APPLICATION_PROCESS_MACHINE_ID},
{ "APPLICATION_PROCESS_SESSION_ID", PA_PROP_APPLICATION_PROCESS_SESSION_ID},
{ "DEVICE_STRING", PA_PROP_DEVICE_STRING },
{ "DEVICE_API", PA_PROP_DEVICE_API },
{ "DEVICE_DESCRIPTION", PA_PROP_DEVICE_DESCRIPTION },
{ "DEVICE_BUS_PATH", PA_PROP_DEVICE_BUS_PATH },
{ "DEVICE_SERIAL", PA_PROP_DEVICE_SERIAL },
{ "DEVICE_VENDOR_ID", PA_PROP_DEVICE_VENDOR_ID },
{ "DEVICE_VENDOR_NAME", PA_PROP_DEVICE_VENDOR_NAME },
{ "DEVICE_PRODUCT_ID", PA_PROP_DEVICE_PRODUCT_ID },
{ "DEVICE_PRODUCT_NAME", PA_PROP_DEVICE_PRODUCT_NAME },
{ "DEVICE_CLASS", PA_PROP_DEVICE_CLASS },
{ "DEVICE_FORM_FACTOR", PA_PROP_DEVICE_FORM_FACTOR },
{ "DEVICE_BUS", PA_PROP_DEVICE_BUS },
{ "DEVICE_ICON", PA_PROP_DEVICE_ICON },
{ "DEVICE_ICON_NAME", PA_PROP_DEVICE_ICON_NAME },
{ "DEVICE_ACCESS_MODE", PA_PROP_DEVICE_ACCESS_MODE },
{ "DEVICE_MASTER_DEVICE", PA_PROP_DEVICE_MASTER_DEVICE },
{ "DEVICE_BUFFERING_BUFFER_SIZE", PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE },
{ "DEVICE_BUFFERING_FRAGMENT_SIZE", PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE},
{ "DEVICE_PROFILE_NAME", PA_PROP_DEVICE_PROFILE_NAME },
{ "DEVICE_INTENDED_ROLES", PA_PROP_DEVICE_INTENDED_ROLES },
{ "DEVICE_PROFILE_DESCRIPTION", PA_PROP_DEVICE_PROFILE_DESCRIPTION },
{ "MODULE_AUTHOR", PA_PROP_MODULE_AUTHOR },
{ "MODULE_DESCRIPTION", PA_PROP_MODULE_DESCRIPTION },
{ "MODULE_USAGE", PA_PROP_MODULE_USAGE },
{ "MODULE_VERSION", PA_PROP_MODULE_VERSION },
{ "FORMAT_SAMPLE_FORMAT", PA_PROP_FORMAT_SAMPLE_FORMAT },
{ "FORMAT_RATE", PA_PROP_FORMAT_RATE },
{ "FORMAT_CHANNELS", PA_PROP_FORMAT_CHANNELS },
{ "FORMAT_CHANNEL_MAP", PA_PROP_FORMAT_CHANNEL_MAP },
{ "CONTEXT_FORCE_DISABLE_SHM", PA_PROP_CONTEXT_FORCE_DISABLE_SHM },
{ "BLUETOOTH_CODEC", PA_PROP_BLUETOOTH_CODEC },
{ NULL, NULL }
};

View file

@ -1,53 +1,42 @@
/// libpulse bindings.
//
// @module pulseaudio
#include "pulseaudio.h"
#include "context.h"
#include "lua_util.h"
#include "proplist.h"
#include "volume.h"
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <pulse/glib-mainloop.h>
#include "pulseaudio.h"
#include "context.h"
#define LUA_MOD_EXPORT extern
#define LUA_PULSEAUDIO "pulseaudio"
#if LUA_VERSION_NUM <= 501
// Shamelessly copied from Lua 5.3 source.
// TODO: What's the official way to do this in 5.1?
void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {
void luaL_setfuncs(lua_State* L, const luaL_Reg* l, int nup) {
luaL_checkstack(L, nup, "too many upvalues");
for (; l->name != NULL; l++) { /* fill the table with given functions */
if (l->func == NULL) /* place holder? */
for (; l->name != NULL; l++) { /* fill the table with given functions */
if (l->func == NULL) /* place holder? */
lua_pushboolean(L, 0);
else {
int i;
for (i = 0; i < nup; i++) /* copy upvalues to the top */
for (i = 0; i < nup; i++) /* copy upvalues to the top */
lua_pushvalue(L, -nup);
lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
}
lua_setfield(L, -(nup + 2), l->name);
}
lua_pop(L, nup); /* remove upvalues */
lua_pop(L, nup); /* remove upvalues */
}
#define luaL_newlib(L,l) (luaL_register(L,LUA_PULSEAUDIO,l))
#define luaL_newlib(L, l) (luaL_register(L, LUA_PULSEAUDIO, l))
#endif
/**
* Creates a new PulseAudio object.
*
* The API requires a GLib Main Context internally. Currently, only the default context
* is supported.
*
* @return[type=PulseAudio]
*/
int
pulseaudio_new(lua_State* L)
{
int pulseaudio_new(lua_State* L) {
GMainContext* ctx = g_main_context_default();
if (ctx == NULL) {
lua_pushfstring(L, "Failed to accquire default GLib Main Context. Are we running in a Main Loop?");
@ -55,7 +44,7 @@ pulseaudio_new(lua_State* L)
return 0;
}
pulseaudio* pa = lua_newuserdata (L, sizeof(pulseaudio));
pulseaudio* pa = lua_newuserdata(L, sizeof(pulseaudio));
if (!pa) {
return luaL_error(L, "failed to create pulseaudio userdata");
}
@ -68,12 +57,10 @@ pulseaudio_new(lua_State* L)
}
/**
/*
* Proxies table index operations to our metatable.
*/
int
pulseaudio__index(lua_State* L)
{
int pulseaudio__index(lua_State* L) {
const char* index = luaL_checkstring(L, 2);
luaL_getmetatable(L, LUA_PULSEAUDIO);
lua_getfield(L, -1, index);
@ -81,28 +68,98 @@ pulseaudio__index(lua_State* L)
}
/**
/*
* Free the PulseAudio object
*/
int
pulseaudio__gc(lua_State* L)
{
int pulseaudio__gc(lua_State* L) {
pulseaudio* pa = luaL_checkudata(L, 1, LUA_PULSEAUDIO);
pa_glib_mainloop_free(pa->mainloop);
return 0;
}
int
pulseaudio_new_context(lua_State* L)
{
int pulseaudio_new_context(lua_State* L) {
pulseaudio* pa = luaL_checkudata(L, 1, LUA_PULSEAUDIO);
return context_new(L, pa_glib_mainloop_get_api(pa->mainloop));
}
LUA_MOD_EXPORT int luaopen_lua_libpulse_glib(lua_State* L)
{
void createlib_volume(lua_State* L) {
luaL_newmetatable(L, LUA_PA_VOLUME);
lua_createtable(L, 0, sizeof volume_f / sizeof volume_f[0]);
luaL_setfuncs(L, volume_f, 0);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, volume_mt, 0);
#if LUA_VERSION_NUM <= 501
luaL_register(L, LUA_PA_VOLUME, volume_lib);
#else
luaL_newlib(L, volume_lib);
#endif
lua_setmetatable(L, -2);
}
void createlib_proplist(lua_State* L) {
luaL_newmetatable(L, LUA_PA_PROPLIST);
lua_createtable(L, 0, sizeof proplist_f / sizeof proplist_f[0]);
luaL_setfuncs(L, proplist_f, 0);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, proplist_mt, 0);
#if LUA_VERSION_NUM <= 501
luaL_register(L, LUA_PA_PROPLIST, proplist_lib);
#else
luaL_newlib(L, proplist_lib);
#endif
// Create a metatable with an `__index` table for read-only enum fields.
lua_createtable(L, 0, 1);
lua_createtable(L, 0, sizeof proplist_enum / sizeof proplist_enum[0]);
for (int i = 0; proplist_enum[i].name != NULL; ++i) {
lua_pushstring(L, proplist_enum[i].value);
lua_setfield(L, -2, proplist_enum[i].name);
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
void createlib_context(lua_State* L) {
luaL_newmetatable(L, LUA_PA_CONTEXT);
lua_createtable(L, 0, sizeof context_f / sizeof context_f[0]);
luaL_setfuncs(L, context_f, 0);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, context_mt, 0);
}
void createlib_pulseaudio(lua_State* L) {
luaL_newmetatable(L, LUA_PULSEAUDIO);
lua_createtable(L, 0, sizeof pulseaudio_f / sizeof pulseaudio_f[0]);
luaL_setfuncs(L, pulseaudio_f, 0);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, pulseaudio_mt, 0);
#if LUA_VERSION_NUM <= 501
luaL_register(L, LUA_PULSEAUDIO, pulseaudio_lib);
#else
luaL_newlib(L, pulseaudio_lib);
#endif
}
LUA_MOD_EXPORT int luaopen_lua_libpulse_glib(lua_State* L) {
// Create a table to store callback refs in, stored in the Lua registry
lua_pushstring(L, LUA_PULSEAUDIO);
lua_newtable(L);
@ -111,12 +168,9 @@ LUA_MOD_EXPORT int luaopen_lua_libpulse_glib(lua_State* L)
lua_settable(L, -3);
lua_rawset(L, LUA_REGISTRYINDEX);
luaL_newmetatable(L, LUA_PA_CONTEXT);
luaL_setfuncs(L, context_mt, 0);
luaL_newmetatable(L, LUA_PULSEAUDIO);
luaL_setfuncs(L, pulseaudio_mt, 0);
luaL_newlib(L, pulseaudio_lib);
createlib_volume(L);
createlib_context(L);
createlib_proplist(L);
createlib_pulseaudio(L);
return 1;
}

View file

@ -1,17 +1,15 @@
/** Bindings for PulseAudio's libpulse, using the GLib Main Loop.
*
* @module pulseaudio
*/
#pragma once
#include "lua.h"
#include "lauxlib.h"
#include "lua.h"
#include <pulse/glib-mainloop.h>
#ifdef _WIN32
#define LUA_MOD_EXPORT __declspec(dllexport)
#else
#define LUA_MOD_EXPORT extern
#endif
#define LUA_PULSEAUDIO "pulseaudio"
#define LUA_PULSEAUDIO "pulseaudio"
#define LUA_PA_REGISTRY "pulseaudio.registry"
@ -20,25 +18,43 @@ typedef struct pulseaudio {
} pulseaudio;
int
pulseaudio_new(lua_State*);
int
pulseaudio__gc(lua_State*);
int
pulseaudio__index(lua_State*);
int
pulseaudio_new_context(lua_State*);
/** Creates a new PulseAudio object.
*
* @function new
* @return[type=PulseAudio]
*/
int pulseaudio_new(lua_State*);
static const struct luaL_Reg pulseaudio_mt [] = {
{"__index", pulseaudio__index},
int pulseaudio__gc(lua_State*);
/// PulseAudio API
/// @type PulseAudio
/** Creates a new PulseAudio context
*
* @function context
* @tparam string name The application name.
* @return[type=Context]
*/
int pulseaudio_new_context(lua_State*);
static const struct luaL_Reg pulseaudio_mt[] = {
{"__gc", pulseaudio__gc},
{ NULL, NULL }
};
static const struct luaL_Reg pulseaudio_f[] = {
{"context", pulseaudio_new_context},
{NULL, NULL}
{ NULL, NULL }
};
static const struct luaL_Reg pulseaudio_lib [] = {
static const struct luaL_Reg pulseaudio_lib[] = {
{"new", pulseaudio_new},
{NULL, NULL}
{ NULL, NULL }
};

View file

@ -0,0 +1,307 @@
#include "volume.h"
#include <pulse/xmalloc.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
int volume_to_lua(lua_State* L, const pa_cvolume* pa_volume) {
volume_t* volume = lua_newuserdata(L, sizeof(volume_t));
if (pa_volume == NULL) {
lua_pushfstring(L, "Failed to allocate volume userdata");
return lua_error(L);
}
luaL_getmetatable(L, LUA_PA_VOLUME);
lua_setmetatable(L, -2);
volume->inner = *pa_volume;
return 1;
}
// When the value is a plain Lua table, a new `pa_cvolume` is allocated and the caller is
// responsible of freeing it with `pa_xfree`.
// If a userdata is passed instead, memory is owned by Lua and not be freed.
// TODO: Maybe construct userdata from the table, so that memory management is always handled
// by GC. Makes this easier to use. But only do so after the current version has been committed
// once, just so it stays available.
pa_cvolume* volume_from_lua(lua_State* L, int index) {
switch (lua_type(L, 2)) {
case LUA_TTABLE: {
uint8_t channels = (uint8_t) lua_objlen(L, index);
if (channels > PA_CHANNELS_MAX) {
channels = PA_CHANNELS_MAX;
}
pa_cvolume* volume = pa_xnew(pa_cvolume, 1);
volume->channels = channels;
for (int i = 0; i < channels; ++i) {
lua_pushinteger(L, i + 1);
lua_gettable(L, index);
pa_volume_t vol = (pa_volume_t) luaL_checkinteger(L, -1);
volume->values[i] = PA_CLAMP_VOLUME(vol);
lua_pop(L, 1);
}
return volume;
}
case LUA_TUSERDATA: {
volume_t* volume = luaL_checkudata(L, index, LUA_PA_VOLUME);
return &volume->inner;
}
default: {
luaL_argerror(L, index, "expected table or userdata");
return NULL;
}
}
}
int volume__len(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_pushinteger(L, volume->inner.channels);
return 1;
}
int volume__eq(lua_State* L) {
volume_t* left = luaL_checkudata(L, 1, LUA_PA_VOLUME);
volume_t* right = luaL_checkudata(L, 2, LUA_PA_VOLUME);
lua_pushboolean(L, pa_cvolume_equal(&left->inner, &right->inner));
return 1;
}
int volume__index(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
int index = luaL_checkint(L, 2);
luaL_argcheck(L, index >= 1 && index <= (PA_CHANNELS_MAX + 1), 2, "channel index out of bounds");
lua_pushinteger(L, volume->inner.values[index - 1]);
return 1;
}
int volume__newindex(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer index = luaL_checkinteger(L, 2);
lua_Integer value = luaL_checkinteger(L, 3);
luaL_argcheck(L, index >= 1 && index <= (PA_CHANNELS_MAX + 1), 2, "channel index out of bounds");
luaL_argcheck(L, PA_VOLUME_IS_VALID(value), 3, "volume value is invalid");
volume->inner.values[index - 1] = value;
return 0;
}
int volume__tostring(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
char s[PA_CVOLUME_SNPRINT_MAX];
pa_cvolume_snprint(s, PA_CVOLUME_SNPRINT_MAX, &volume->inner);
lua_pushstring(L, s);
return 1;
}
int volume_is_valid(lua_State* L) {
// Lua's `checkudata` throws an error, and catching that is expensive.
// So we have to manually implement the check.
volume_t* volume = lua_touserdata(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, LUA_PA_VOLUME);
bool is_userdata = volume != NULL && lua_getmetatable(L, 1) && lua_rawequal(L, -1, -2);
// Remove the two metatables
lua_pop(L, 2);
lua_pushboolean(L, is_userdata && pa_cvolume_valid(&volume->inner));
return 1;
}
int volume_channels_equal_to(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer value = luaL_checkinteger(L, 2);
luaL_argcheck(L, PA_VOLUME_IS_VALID(value), 2, "volume out of bounds");
lua_pushboolean(L, pa_cvolume_channels_equal_to(&volume->inner, (pa_volume_t) value));
return 1;
}
int volume_is_muted(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_pushboolean(L, pa_cvolume_is_muted(&volume->inner));
return 1;
}
int volume_is_norm(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_pushboolean(L, pa_cvolume_is_norm(&volume->inner));
return 1;
}
int volume_set_channels(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer channels = luaL_checkinteger(L, 2);
lua_Integer value = luaL_checkinteger(L, 3);
luaL_argcheck(L, channels >= 0 && channels <= PA_CHANNELS_MAX, 2, "channel count out of bounds");
luaL_argcheck(L, PA_VOLUME_IS_VALID(value), 3, "volume out of bounds");
pa_cvolume_set(&volume->inner, (unsigned int) channels, (pa_volume_t) value);
return 0;
}
int volume_set(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer index = luaL_checkinteger(L, 2) - 1;
lua_Integer value = luaL_checkinteger(L, 3);
luaL_argcheck(L, index >= 0 && index <= PA_CHANNELS_MAX, 2, "channel index out of bounds");
luaL_argcheck(L, PA_VOLUME_IS_VALID(value), 3, "volume out of bounds");
volume->inner.values[index] = (pa_volume_t) value;
return 0;
}
int volume_get(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer index = luaL_checkinteger(L, 2) - 1;
luaL_argcheck(L, index > 0 && index < volume->inner.channels, 2, "channel index out of bounds");
lua_pushinteger(L, volume->inner.values[index]);
return 1;
}
int volume_reset(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer channels = luaL_checkinteger(L, 2);
luaL_argcheck(L, channels >= 0 && channels <= PA_CHANNELS_MAX, 2, "channel count out of bounds");
pa_cvolume_reset(&volume->inner, channels);
return 0;
}
int volume_mute(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer channels = luaL_checkinteger(L, 2);
luaL_argcheck(L, channels >= 0 && channels <= PA_CHANNELS_MAX, 2, "channel count out of bounds");
pa_cvolume_mute(&volume->inner, channels);
return 0;
}
int volume_avg(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_pushinteger(L, pa_cvolume_avg(&volume->inner));
return 1;
}
int volume_min(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_pushinteger(L, pa_cvolume_min(&volume->inner));
return 1;
}
int volume_max(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_pushinteger(L, pa_cvolume_max(&volume->inner));
return 1;
}
int volume_inc(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer value = luaL_checkinteger(L, 2);
luaL_argcheck(L, PA_VOLUME_IS_VALID(value), 2, "volume out of bounds");
if (!pa_cvolume_inc(&volume->inner, (pa_volume_t) value)) {
return luaL_error(L, "failed to increase volume");
}
return 0;
}
int volume_dec(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer value = luaL_checkinteger(L, 2);
luaL_argcheck(L, PA_VOLUME_IS_VALID(value), 2, "volume out of bounds");
if (!pa_cvolume_dec(&volume->inner, (pa_volume_t) value)) {
return luaL_error(L, "failed to decrease volume");
}
return 0;
}
int volume_scale(lua_State* L) {
volume_t* volume = luaL_checkudata(L, 1, LUA_PA_VOLUME);
lua_Integer value = luaL_checkinteger(L, 2);
luaL_argcheck(L, PA_VOLUME_IS_VALID(value), 2, "volume out of bounds");
if (!pa_cvolume_scale(&volume->inner, (pa_volume_t) value)) {
return luaL_error(L, "failed to scale volume");
}
return 0;
}
int volume_multiply(lua_State* L) {
volume_t* left = luaL_checkudata(L, 1, LUA_PA_VOLUME);
switch (lua_type(L, 2)) {
case LUA_TNUMBER: {
lua_Integer value = luaL_checkinteger(L, 2);
luaL_argcheck(L, PA_VOLUME_IS_VALID(value), 2, "volume out of bounds");
if (!pa_sw_cvolume_multiply_scalar(&left->inner, &left->inner, (pa_volume_t) value)) {
return luaL_error(L, "failed to multiply volume");
}
return 0;
}
case LUA_TUSERDATA: {
volume_t* right = luaL_checkudata(L, 2, LUA_PA_VOLUME);
if (!pa_sw_cvolume_multiply(&left->inner, &left->inner, &right->inner)) {
return luaL_error(L, "failed to multiply volume");
}
return 0;
}
default: {
return luaL_argerror(L, 2, "expected number or userdata");
}
}
}
int volume_divide(lua_State* L) {
volume_t* left = luaL_checkudata(L, 1, LUA_PA_VOLUME);
switch (lua_type(L, 2)) {
case LUA_TNUMBER: {
lua_Integer value = luaL_checkinteger(L, 2);
luaL_argcheck(L, PA_VOLUME_IS_VALID(value), 2, "volume out of bounds");
if (!pa_sw_cvolume_divide_scalar(&left->inner, &left->inner, (pa_volume_t) value)) {
return luaL_error(L, "failed to divide volume");
}
return 0;
}
case LUA_TUSERDATA: {
volume_t* right = luaL_checkudata(L, 2, LUA_PA_VOLUME);
if (!pa_sw_cvolume_divide(&left->inner, &left->inner, &right->inner)) {
return luaL_error(L, "failed to divide volume");
}
return 0;
}
default: {
return luaL_argerror(L, 2, "expected number or userdata");
}
}
}

View file

@ -0,0 +1,237 @@
/** Bindings for libpulse's `pa_cvolume`.
*
* Contrary to libpulse, methods that change the a @{Volume} object generally don't return anything, but
* instead change the instance itself.
*
* @module pulseaudio.volume
*/
#ifndef volume_h_INCLUDED
#define volume_h_INCLUDED
#include <lauxlib.h>
#include <lua.h>
#include <pulse/volume.h>
#define LUA_PA_VOLUME "pulseaudio.volume"
typedef struct volume_t {
pa_cvolume inner;
} volume_t;
/* Creates a Lua userdatum from/for a PulseAudio volume.
*
* Control over the `pa_plist`'s memory has to be taken over by this function,
* to enable integration into Lua's garbage collection.
*
* @function volume_to_lua
* @treturn Volume
*/
int volume_to_lua(lua_State*, const pa_cvolume*);
/* Creates a pa_cvolume from Lua userdata
*/
pa_cvolume* volume_from_lua(lua_State*, int);
/* Implements the `#` length operator.
* This simply proxies to the `.channels` value.
*/
int volume__len(lua_State*);
int volume__eq(lua_State*);
int volume__tostring(lua_State*);
int volume__index(lua_State*);
int volume__newindex(lua_State*);
/// Static Functions
/// @section static
/** Checks whether a value is a valid @{Volume}.
*
* @function is_valid
* @tparam any value The value to check
* @treturn boolean
*/
int volume_is_valid(lua_State*);
/// Volume
/// @type Volume
/** Returns the average volume over all channels.
*
* @function Volume:avg
* @treturn number
*/
int volume_avg(lua_State*);
/** Returns `true` if the volume of all channels is equal to the specified value.
*
* @function Volume:channels_equal_to
* @tparam number value Volume to compare to
* @treturn boolean
*/
int volume_channels_equal_to(lua_State*);
/** Decreases the volume by the given amount.
*
* The proportions between the channels are kept.
*
* @function Volume:dec
* @tparam number value The value to decrease by.
*/
int volume_dec(lua_State*);
/** Divides the volume by the given value.
*
* The value to divide by may either be a scalar, that's applied to all channels,
* or another instance of @{Volume}, which would be applied channel by channel.
*
* It is possible to divide a @{Volume} by itself.
*
* This is only valid for software volumes.
*
* @function Volume:divide
* @tparam number|Volume value The volume to divide by.
*/
int volume_divide(lua_State*);
/** Returns the volume of a single channel.
*
* @function Volume:get
* @tparam number index The channel index
* @treturn number The channel's volume
*/
int volume_get(lua_State*);
/** Increases the volume by the given amount.
*
* The proportions between the channels are kept.
*
* @function Volume:inc
* @tparam number value the value to increase by.
*/
int volume_inc(lua_State*);
/** Returns `true` when all channels are muted.
*
* @function Volume:is_muted
* @treturn boolean
*/
int volume_is_muted(lua_State*);
/** Returns `true` when all channels are on normal level.
*
* @function Volume:is_norm
* @treturn boolean
*/
int volume_is_norm(lua_State*);
/** Returns the maximum volume out of all channels.
*
* @function Volume:max
* @treturn number
*/
int volume_max(lua_State*);
/** Returns the minimum volume out of all channels.
*
* @function Volume:min
* @treturn number
*/
int volume_min(lua_State*);
/** Multiplies the volume by the given amount.
*
* The value to multiply with may either be a scalar, that's applied to all channels,
* or another instance of @{Volume}, which would be applied channel by channel.
*
* It is possible to multiply a @{Volume} by itself.
*
* This is only valid for software volumes.
*
* @function Volume:multiply
* @tparam number|Volume value
*/
int volume_multiply(lua_State*);
/** Mutes all channels.
*
* @function Volume:mute
*/
int volume_mute(lua_State*);
/** Resets all channels to normal volume.
*
* @function Volume:reset
*/
int volume_reset(lua_State*);
/** Scales all channels to the passed amount.
*
* This adjust all channel volumes so that the maximum between them equals the given value, while
* keeping proportions between channels the same.
*
* @function Volume:scale
* @tparam number value The value to scale to.
*/
int volume_scale(lua_State*);
/** Sets a channel to the given value.
*
* @function Volume:set
* @tparam number index The channel index.
* @tparam number value The volume to set to.
*/
int volume_set(lua_State*);
/** Sets a number of channels to the given volume value.
*
* @function Volume:set_channels
* @tparam number channels The number of channels to set
* @tparam number value The volume to set to.
*/
int volume_set_channels(lua_State*);
static const struct luaL_Reg volume_f[] = {
{"avg", volume_avg },
{ "channels_equal_to", volume_channels_equal_to},
{ "dec", volume_dec },
{ "divide", volume_divide },
{ "get", volume_get },
{ "inc", volume_inc },
{ "is_muted", volume_is_muted },
{ "is_norm", volume_is_norm },
{ "is_valid", volume_is_valid },
{ "max", volume_max },
{ "min", volume_min },
{ "multiply", volume_multiply },
{ "mute", volume_mute },
{ "reset", volume_reset },
{ "scale", volume_scale },
{ "set", volume_set },
{ NULL, NULL }
};
// We don't add `__index` here, as it needs to be handled specially,
// to allow indexing both by channel index as well as accessing the methods above.
static const struct luaL_Reg volume_mt[] = {
{"__len", volume__len },
{ "__tostring", volume__tostring},
{ "__eq", volume__eq },
{ "__newindex", volume__newindex},
{ NULL, NULL }
};
static const struct luaL_Reg volume_lib[] = {
{"is_valid", volume_is_valid},
{ NULL, NULL }
};
#endif // volume_h_INCLUDED

View file

@ -14,7 +14,7 @@ run() {
"$@"
}
find src -iname '*.lua' -or -iname '*.c' -not -path '*/internal/*' | while read -r f; do
find src -iname '*.lua' -or -iname '*.c' -or -iname '*.h' -not -path '*/internal/*' | while read -r f; do
mkdir -p "$(dirname "$OUT/$f")"
run "$LUA" ./tools/preprocessor.lua "$f" "$OUT/$f"
done