From c4058583f1b653fb92e3c8465dd01f8bd8df56ad Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Wed, 6 Apr 2022 09:21:55 +0200 Subject: [PATCH] feat: Implement basic context object --- src/lua_libpulse_glib/context.c | 151 +++++++++++++++++++++++++++++ src/lua_libpulse_glib/context.h | 40 ++++++++ src/lua_libpulse_glib/pulseaudio.c | 53 ++++++---- src/lua_libpulse_glib/pulseaudio.h | 44 +++++++++ 4 files changed, 270 insertions(+), 18 deletions(-) create mode 100644 src/lua_libpulse_glib/context.c create mode 100644 src/lua_libpulse_glib/context.h create mode 100644 src/lua_libpulse_glib/pulseaudio.h diff --git a/src/lua_libpulse_glib/context.c b/src/lua_libpulse_glib/context.c new file mode 100644 index 0000000..554237b --- /dev/null +++ b/src/lua_libpulse_glib/context.c @@ -0,0 +1,151 @@ + +#include "pulseaudio.h" +#include "context.h" +#include "pulse/context.h" + + +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); + // `lua_call` will pop the function and arguments from the stack, but this callback will likely be called + // multiple times. + // To preseve the values for future calls, we need to duplicate them. + lua_pushvalue(data->L, 1); + lua_pushvalue(data->L, 2); + + pa_context_state_t state = pa_context_get_state(c); + lua_pushinteger(data->L, state); + + lua_call(data->L, 2, 0); +} + + +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); + if (ctx == NULL) { + return luaL_error(L, "failed to create pulseaudio 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"); + } + lgi_ctx->context = ctx; + lgi_ctx->connected = FALSE; + lgi_ctx->state_callback_data = (context_state_callback_data*) calloc(1, sizeof(struct context_state_callback_data)); + + luaL_getmetatable(L, LUA_PA_CONTEXT); + lua_setmetatable(L, -2); + + return 1; +} + + +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) +{ + lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); + + if (ctx->connected == TRUE) { + pa_context_disconnect(ctx->context); + ctx->connected = 0; + } + + if (ctx->state_callback_data != NULL) { + lua_pushstring(L, LUA_PULSEAUDIO); + lua_rawget(L, LUA_REGISTRYINDEX); + + lua_pushstring(L, LUA_PA_REGISTRY); + lua_gettable(L, -2); + luaL_unref(L, -1, ctx->state_callback_data->thread_ref); + free(ctx->state_callback_data); + } + + pa_context_unref(ctx->context); + return 0; +} + + +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; + if (luaL_getmetafield(L, 2, "__name") == LUA_TSTRING) + typearg = lua_tostring(L, -1); + else if (lua_type(L, 2) == LUA_TLIGHTUSERDATA) + typearg = "light userdata"; + else + typearg = luaL_typename(L, 2); + + return luaL_argerror(L, 2, lua_pushfstring(L, "string or nil expected, got %s", typearg)); + } + + luaL_checktype(L, 3, LUA_TFUNCTION); + + lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); + + pa_context_flags_t flags = PA_CONTEXT_NOAUTOSPAWN; + if (nargs > 3) + flags = luaL_checkinteger(L, 4); + + // 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 and arguments to the thread's stack + lua_pushvalue(L, 3); + lua_pushvalue(L, 1); + lua_xmove(L, thread, 2); + + context_state_callback_data* data = calloc(1, sizeof(struct context_state_callback_data)); + data->L = thread; + data->thread_ref = thread_ref; + ctx->state_callback_data = data; + + pa_context_set_state_callback(ctx->context, context_state_callback, data); + + // TODO: Check if I need to create bindings for `pa_spawn_api`. + int ret = pa_context_connect(ctx->context, server, flags, NULL); + if (ret < 0) { + return luaL_error(L, "failed to connect: %s", pa_strerror(ret)); + } + + 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; +} diff --git a/src/lua_libpulse_glib/context.h b/src/lua_libpulse_glib/context.h new file mode 100644 index 0000000..296603f --- /dev/null +++ b/src/lua_libpulse_glib/context.h @@ -0,0 +1,40 @@ +#pragma once + +#include "stdbool.h" +#include "lua.h" +#include "lauxlib.h" +#include "pulse/context.h" +#include "pulse/mainloop-api.h" + +#define LUA_PA_CONTEXT "pulseaudio.context" + + +typedef struct context_state_callback_data { + lua_State* L; + int thread_ref; +} context_state_callback_data; + + +typedef struct lua_pa_context { + pa_context* context; + bool connected; + context_state_callback_data* state_callback_data; +} 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*); + + +static const struct luaL_Reg context_mt [] = { + {"__index", context__index}, + {"__gc", context__gc}, + {"connect", context_connect}, + {NULL, NULL} +}; diff --git a/src/lua_libpulse_glib/pulseaudio.c b/src/lua_libpulse_glib/pulseaudio.c index f023166..05eedb7 100644 --- a/src/lua_libpulse_glib/pulseaudio.c +++ b/src/lua_libpulse_glib/pulseaudio.c @@ -6,6 +6,8 @@ #include #include #include +#include "pulseaudio.h" +#include "context.h" #define LUA_MOD_EXPORT extern #define LUA_PULSEAUDIO "pulseaudio" @@ -13,7 +15,7 @@ #if LUA_VERSION_NUM <= 501 // Shamelessly copied from Lua 5.3 source. -// TODO: Is there any official way to do this in 5.1? +// TODO: What's the official way to do this in 5.1? 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 */ @@ -35,11 +37,6 @@ void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { #endif -typedef struct pulseaudio { - pa_glib_mainloop* mainloop; -} pulseaudio; - - /** * Creates a new PulseAudio object. * @@ -48,7 +45,7 @@ typedef struct pulseaudio { * * @return[type=PulseAudio] */ -static int +int pulseaudio_new(lua_State* L) { GMainContext* ctx = g_main_context_default(); @@ -71,10 +68,23 @@ pulseaudio_new(lua_State* L) } +/** + * Proxies table index operations to our metatable. + */ +int +pulseaudio__index(lua_State* L) +{ + const char* index = luaL_checkstring(L, 2); + luaL_getmetatable(L, LUA_PULSEAUDIO); + lua_getfield(L, -1, index); + return 1; +} + + /** * Free the PulseAudio object */ -static int +int pulseaudio__gc(lua_State* L) { pulseaudio* pa = luaL_checkudata(L, 1, LUA_PULSEAUDIO); @@ -83,20 +93,27 @@ pulseaudio__gc(lua_State* L) } -static const struct luaL_Reg pulseaudio_mt [] = { - {"__gc", pulseaudio__gc}, - {NULL, NULL} -}; - - -static const struct luaL_Reg pulseaudio_lib [] = { - {"new", pulseaudio_new}, - {NULL, NULL} -}; +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) { + // Create a table to store callback refs in, stored in the Lua registry + lua_pushstring(L, LUA_PULSEAUDIO); + lua_newtable(L); + lua_pushstring(L, LUA_PA_REGISTRY); + lua_newtable(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); diff --git a/src/lua_libpulse_glib/pulseaudio.h b/src/lua_libpulse_glib/pulseaudio.h new file mode 100644 index 0000000..dd18fe0 --- /dev/null +++ b/src/lua_libpulse_glib/pulseaudio.h @@ -0,0 +1,44 @@ +#pragma once + +#include "lua.h" +#include "lauxlib.h" +#include + +#ifdef _WIN32 +#define LUA_MOD_EXPORT __declspec(dllexport) +#else +#define LUA_MOD_EXPORT extern +#endif + + +#define LUA_PULSEAUDIO "pulseaudio" +#define LUA_PA_REGISTRY "pulseaudio.registry" + + +typedef struct pulseaudio { + pa_glib_mainloop* mainloop; +} pulseaudio; + + +int +pulseaudio_new(lua_State*); +int +pulseaudio__gc(lua_State*); +int +pulseaudio__index(lua_State*); +int +pulseaudio_new_context(lua_State*); + + +static const struct luaL_Reg pulseaudio_mt [] = { + {"__index", pulseaudio__index}, + {"__gc", pulseaudio__gc}, + {"context", pulseaudio_new_context}, + {NULL, NULL} +}; + + +static const struct luaL_Reg pulseaudio_lib [] = { + {"new", pulseaudio_new}, + {NULL, NULL} +};