From bfc322c3adfcab45cf03353f3ba8d5b208a51df5 Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Mon, 16 May 2022 15:59:26 +0200 Subject: [PATCH] feat(context): Implement event subscriptions --- src/lua_libpulse_glib/context.c | 106 +++++++++++++++++++++++++++++++- src/lua_libpulse_glib/context.h | 3 + 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/src/lua_libpulse_glib/context.c b/src/lua_libpulse_glib/context.c index 324a48f..ca28636 100644 --- a/src/lua_libpulse_glib/context.c +++ b/src/lua_libpulse_glib/context.c @@ -1,13 +1,13 @@ #include "context.h" -#include "callback.h" #include "pulseaudio.h" #include #include +#include -/* Calls the user-provided callback with the updates state info. +/* Calls the user-provided callback with the updated state info. */ void context_state_callback(pa_context* c, void* userdata) { context_state_callback_data* data = (context_state_callback_data*) userdata; @@ -26,6 +26,27 @@ void context_state_callback(pa_context* c, void* userdata) { } +/* Calls the user-prodivded event callbacks. + */ +void context_event_callback(pa_context* c, pa_subscription_event_type_t event_type, uint32_t index, void* userdata) { + simple_callback_data* data = (simple_callback_data*) userdata; + lua_State* L = data->L; + + luaL_checktype(L, 1, LUA_TFUNCTION); + luaL_checkudata(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(L, 1); + lua_pushvalue(L, 2); + lua_pushinteger(L, event_type); + lua_pushinteger(L, index); + + lua_call(L, 3, 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. @@ -41,6 +62,7 @@ int context_new(lua_State* L, pa_mainloop_api* pa_api) { 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)); + lgi_ctx->event_callback_data = prepare_lua_callback(L); luaL_getmetatable(L, LUA_PA_CONTEXT); lua_setmetatable(L, -2); @@ -67,6 +89,10 @@ int context__gc(lua_State* L) { free(ctx->state_callback_data); } + if (ctx->event_callback_data != NULL) { + free_lua_callback(ctx->event_callback_data); + } + pa_context_unref(ctx->context); return 0; } @@ -117,6 +143,7 @@ int context_connect(lua_State* L) { ctx->state_callback_data = data; pa_context_set_state_callback(ctx->context, context_state_callback, data); + pa_context_set_subscribe_callback(ctx->context, context_event_callback, ctx->event_callback_data); // TODO: Check if I need to create bindings for `pa_spawn_api`. int ret = pa_context_connect(ctx->context, server, flags, NULL); @@ -179,3 +206,78 @@ int context_set_default_source(lua_State* L) { return 0; } + + +int context_subscribe(lua_State* L) { + lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); + luaL_checktype(L, 2, LUA_TFUNCTION); + + size_t pos = lua_objlen(ctx->event_callback_data->L, 1) + 1; + // Duplicate the callback function, so we can move it over to the other thread + lua_pushvalue(L, 2); + lua_xmove(L, ctx->event_callback_data->L, 1); + lua_rawseti(ctx->event_callback_data->L, 1, pos); + + lua_pushinteger(L, pos); + return 1; +} + + +int context_unsubscribe(lua_State* L) { + lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); + lua_State* thread_L = ctx->event_callback_data->L; + size_t pos = 0; + size_t len = lua_objlen(thread_L, 1); + + if (len == 0) { + return 0; + } + + switch (lua_type(L, 2)) { + case LUA_TNUMBER: { + pos = lua_tointeger(L, 2); + break; + } + case LUA_TFUNCTION: { + bool found = false; + size_t i = 0; + + // Duplicate the function value, so we can move it other to the other thread for comparing + lua_pushvalue(L, -1); + lua_xmove(L, thread_L, 1); + int fn_index = lua_gettop(thread_L); + + lua_pushnil(L); + while (lua_next(thread_L, 1) != 0) { + ++i; + + if (lua_equal(thread_L, -1, fn_index) == 1) { + pos = i; + lua_pop(thread_L, 2); + break; + } + + // Remove the value, but keep the key to continue iterating + lua_pop(thread_L, 1); + } + + if (!found) { + return luaL_error(L, "couldn't find this function in the list of subscriptions"); + } + + break; + } + default: { + return luaL_argerror(L, 2, "expected number or function"); + } + } + + for (; pos < len; ++pos) { + lua_rawgeti(thread_L, 1, pos + 1); + lua_rawseti(thread_L, 1, pos); + } + lua_pushnil(thread_L); + lua_rawseti(thread_L, 1, len); + + return 0; +} diff --git a/src/lua_libpulse_glib/context.h b/src/lua_libpulse_glib/context.h index f79cb80..9c497c1 100644 --- a/src/lua_libpulse_glib/context.h +++ b/src/lua_libpulse_glib/context.h @@ -10,6 +10,8 @@ */ #pragma once +#include "callback.h" + #include #include #include @@ -29,6 +31,7 @@ typedef struct lua_pa_context { pa_context* context; bool connected; context_state_callback_data* state_callback_data; + simple_callback_data* event_callback_data; } lua_pa_context;