diff --git a/.busted.lua b/.busted.lua index cfafed4..3aa0614 100644 --- a/.busted.lua +++ b/.busted.lua @@ -1,6 +1,7 @@ return { default = { verbose = true, + helper = "./spec/_helper.lua", lpath = "./src/?.lua;./src/?/init.lua;./src/?/?.lua;./tests/?.lua", }, } diff --git a/.ci/deploy-docs.sh b/.ci/deploy-docs.sh deleted file mode 100755 index 687d2e9..0000000 --- a/.ci/deploy-docs.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -set -ex - -mkdir -p "${BUILD_DIR:-./build}" - -CMAKE_ARGS="" - -if [ -n "$CI" ]; then - CMAKE_ARGS="-DCI=ON" -fi - -sudo enable-lua ${LUA_VERSION:-5.1} - -make -j $(nproc) -C ${SOURCE_DIR:-.} BUILD_DIR=${BUILD_DIR:-./build} LUA_VERSION=${LUA_VERSION:-5.1} LUA=lua doc - -tar -czf doc.tar.gz -C ${BUILD_DIR:-./build}/doc . - -curl -T doc.tar.gz -XPOST --user ${DOCS_CREDENTIALS} https://${DOCS_HOST}/publish/lua-libpulse-glib - -sudo disable-lua ${LUA_VERSION:-5.1} - diff --git a/.ci/deploy-docs.yml b/.ci/deploy-docs.yml deleted file mode 100644 index 3cb49d6..0000000 --- a/.ci/deploy-docs.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -platform: linux - -image_resource: - type: registry-image - source: - repository: registry.local:5000/lua-clib-pulse - tag: latest - -inputs: -- name: repo - -params: - CI: true - SOURCE_DIR: repo - BUILD_DIR: /tmp/build-output - DOCS_HOST: ((doc-host)) - DOCS_CREDENTIALS: "((deploy-user)):((deploy-password))" - -run: - path: repo/.ci/deploy-docs.sh - diff --git a/.ci/pipeline.yml b/.ci/pipeline.yml deleted file mode 100644 index 51048e7..0000000 --- a/.ci/pipeline.yml +++ /dev/null @@ -1,68 +0,0 @@ ---- -resources: -- name: repo - type: git - icon: github - source: - uri: https://git.sclu1034.dev/lucas/lua-libpulse-glib - branch: master - -jobs: -- name: test - plan: - - get: repo - trigger: true - - in_parallel: - - task: run-test-5.1 - file: repo/.ci/test.yml - vars: - lua-version: 5.1 - - task: run-test-5.2 - file: repo/.ci/test.yml - vars: - lua-version: 5.2 - - task: run-test-5.3 - file: repo/.ci/test.yml - vars: - lua-version: 5.3 - # There is no rock for 5.4 LGI, yet. And I don't want to build that from source. - - try: - task: run-test-5.4 - file: repo/.ci/test.yml - vars: - lua-version: 5.4 - -- name: rock - plan: - - get: repo - trigger: true - - in_parallel: - - task: run-rock-5.1 - file: repo/.ci/rock.yml - vars: - lua-version: 5.1 - - task: run-rock-5.2 - file: repo/.ci/rock.yml - vars: - lua-version: 5.2 - - task: run-rock-5.3 - file: repo/.ci/rock.yml - vars: - lua-version: 5.3 - # There is no rock for 5.4 LGI, yet. And I don't want to build that from source. - - try: - task: run-rock-5.4 - file: repo/.ci/rock.yml - vars: - lua-version: 5.4 - -- name: doc - plan: - - get: repo - trigger: true - - task: deploy-docs - file: repo/.ci/deploy-docs.yml - vars: - doc-host: ((doc-host)) - deploy-user: ((deploy-user)) - deploy-password: ((deploy-password)) diff --git a/.ci/rock.sh b/.ci/rock.sh deleted file mode 100755 index 4e863aa..0000000 --- a/.ci/rock.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -set -ex - -sudo enable-lua ${LUA_VERSION:-5.1} -eval "$(luarocks --lua-version ${LUA_VERSION:-5.1} path)" - -luarocks --lua-version ${LUA_VERSION:-5.1} install lgi -luarocks --lua-version ${LUA_VERSION:-5.1} install ldoc -luarocks --lua-version ${LUA_VERSION:-5.1} install lua-discount - -luarocks --lua-version ${LUA_VERSION:-5.1} make rocks/lua-libpulse-glib-scm-1.rockspec - -sudo disable-lua ${LUA_VERSION:-5.1} diff --git a/.ci/rock.yml b/.ci/rock.yml deleted file mode 100644 index e8754b4..0000000 --- a/.ci/rock.yml +++ /dev/null @@ -1,20 +0,0 @@ - ---- -platform: linux - -image_resource: - type: registry-image - source: - repository: registry.local:5000/lua-clib-pulse - tag: latest - -inputs: -- name: repo - -params: - CI: true - LUA_VERSION: ((lua-version)) - -run: - path: .ci/rock.sh - dir: repo diff --git a/.ci/test.sh b/.ci/test.sh deleted file mode 100755 index fd886fb..0000000 --- a/.ci/test.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -set -ex - -mkdir -p "${BUILD_DIR:-./build}" - -CMAKE_ARGS="" - -if [ -n "$CI" ]; then - CMAKE_ARGS="-DCI=ON" -fi - -sudo enable-lua ${LUA_VERSION:-5.1} -eval "$(luarocks --lua-version ${LUA_VERSION:-5.1} path)" - -luarocks --lua-version ${LUA_VERSION:-5.1} install busted -luarocks --lua-version ${LUA_VERSION:-5.1} install lgi - -make -j $(nproc) -C ${SOURCE_DIR:-.} BUILD_DIR=${BUILD_DIR:-./build} LUA_VERSION=${LUA_VERSION:-5.1} LUA=lua build - -sudo disable-lua ${LUA_VERSION:-5.1} diff --git a/.ci/test.yml b/.ci/test.yml deleted file mode 100644 index 0b0dc5d..0000000 --- a/.ci/test.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -platform: linux - -image_resource: - type: registry-image - source: - repository: registry.local:5000/lua-clib-pulse - tag: latest - -inputs: -- name: repo - -params: - CI: true - SOURCE_DIR: repo - BUILD_DIR: /tmp/build-output - LUA_VERSION: ((lua-version)) - -run: - path: repo/.ci/test.sh diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 649fcb2..0000000 --- a/.clang-format +++ /dev/null @@ -1,75 +0,0 @@ ---- -# kak: filetype=yaml -Language: Cpp -BasedOnStyle: LLVM -AlignArrayOfStructures: Left -AlignConsecutiveMacros: AcrossComments -AlignConsecutiveAssignments: None -AlignConsecutiveBitFields: AcrossComments -AlignConsecutiveDeclarations: None -AllowAllArgumentsOnNextLine: false -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortEnumsOnASingleLine: false -AllowShortFunctionsOnASingleLine: None -AlwaysBreakTemplateDeclarations: Yes -AttributeMacros: - - __capability -BinPackArguments: false -BinPackParameters: false -BreakBeforeBinaryOperators: NonAssignment -BreakBeforeInheritanceComma: false -BreakInheritanceList: AfterComma -ColumnLimit: 120 -Cpp11BracedListStyle: false -EmptyLineBeforeAccessModifier: Always -FixNamespaceComments: true -IncludeBlocks: Regroup -IncludeCategories: - - Regex: '^"(llvm|llvm-c|clang|clang-c)/' - Priority: 2 - SortPriority: 0 - CaseSensitive: false - - Regex: '^(<|"(gtest|gmock|isl|json)/)' - Priority: 3 - SortPriority: 0 - CaseSensitive: false - - Regex: '.*' - Priority: 1 - SortPriority: 0 - CaseSensitive: false -IncludeIsMainRegex: '(_test)?$' -IncludeIsMainSourceRegex: '' -IndentExternBlock: NoIndent -IndentWidth: 4 -KeepEmptyLinesAtTheStartOfBlocks: false -MaxEmptyLinesToKeep: 2 -# PackConstructorInitializers: CurrentLine -PointerAlignment: Left -ReferenceAlignment: Right -ReflowComments: true -# SeparateDefinitionBlocks: Leave -SpaceAfterCStyleCast: true -SpaceAfterLogicalNot: false -SpaceAfterTemplateKeyword: false -SpaceBeforeAssignmentOperators: true -SpaceBeforeCaseColon: false -SpaceBeforeCpp11BracedList: false -SpaceBeforeCtorInitializerColon: true -SpaceBeforeInheritanceColon: true -SpaceBeforeParens: ControlStatements -SpaceAroundPointerQualifiers: Default -SpaceBeforeRangeBasedForLoopColon: true -SpaceInEmptyBlock: false -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 1 -SpacesInAngles: Never -SpacesInConditionalStatement: false -SpacesInContainerLiterals: true -SpacesInCStyleCastParentheses: false -SpacesInLineCommentPrefix: - Minimum: 1 - Maximum: -1 -SpacesInParentheses: false -SpacesInSquareBrackets: false -SpaceBeforeSquareBrackets: false -TabWidth: 4 diff --git a/.fdignore b/.fdignore deleted file mode 100644 index e771cd6..0000000 --- a/.fdignore +++ /dev/null @@ -1,2 +0,0 @@ -out/ -.sass-cache/ diff --git a/.gdb_history b/.gdb_history new file mode 100644 index 0000000..922048e --- /dev/null +++ b/.gdb_history @@ -0,0 +1,190 @@ +b pulseaudio__index +b pulseaudio_new_context +r test.lua +r test.lua +b pulseaudio_new_context +b pulseaudio__index +r test.lua +c +c +info lua5.1 +help info +info program +info target +show debug-file-directory +r test.lua +r test.lua +help +help running +target exec +target exec ~/build/lua-5.1.5/src/lua +target exec /home/gerlui/build/lua-5.1.5/src/lua +help readelf +help +apropos symbol +symbol-file /home/gerlui/build/lua-5.1.5/src/lua +b pulseaudio__index +r test.lua +symbol-file /home/gerlui/build/lua-5.1.5/src/lua +b pulseaudio__index +r test.lua +b pulseaudio__index +b pulseaudio_new_context +r test.lua +source ~/.config/gdb/lua-gdb.py +dashboard +apropos arg +symbol-file lua.debug +help symbol-file +r +bpulseaudio_new_context +b pulseaudio_new_context +r +r +b pulseaudio__index +r +b pulseaudio_new_context +b pulseaudio__index +r test.lua +b lua_error +r +b longjmp +b lonjmp +luastack +dashboard +help show +apropos variable +info variables +info tvariables +info locals +info scope +info scope function +info line +info scope line +tui +tui enable +tui disable +dhasboard +dashboard +apropos arg +explore L +dashboard +luastack +luastack : +luastack L +luatraceback +luagetlocal +luagetlocal L +r +r +b pulseaudio__index +b pulseaudio_new_context +r +b lua_error +r +show directories +b pulseaudio__index +b pulseaudio_new_context +b lua_error +r +b pulseaudio_new_context +r +r test.lua +b pulseaudio_new_context +b pulseaudio__index +b lua_error +r +b pulseaudio__index +b pulseaudio_new_context +r +b pulseaudio_new_context +r +b pulseaudio_new_context +r test.lua +b pulseaudio_new_context +r +show directories +show directories +b pulseaudio_new_context +r +b pulseaudio_new_context +r +show directories +b pulseaudio_new_context +r +help +help running +dashboard +s +help running +s +help running +rs +b context_new +help c +c +r +n +n +b context_connect +r test.lua +s +s +s +q +b context_connect +run test.lua +symbol-file lua.debug +s +s +s +s +s +n +n +n +n +n +n +n +s +s +n +n +n +n +n +n +n +n +n +r test.lua +n +n +n +n +n +q +b context__gc +r test.lua +n +help running +dashboard +s +q +b context__gc +r +n +q +b context__gc +r +n +q +symbol-file lua.debug +b context_connect +r +b context_connect +r test.lua +b context_get_server_info +r +q diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..58e945c --- /dev/null +++ b/.gdbinit @@ -0,0 +1,5 @@ +symbol-file -readnow lua.debug +# source ~/.config/gdb/lua-gdb.py +set directories src/lua_libpulse_glib:/home/gerlui/build/lua-5.1.5/src +# set directories src/lua_libpulse_glib +set args test.lua diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ee8cc00 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,79 @@ +name: Lint & Test + +on: + push: + branches: [ master, staging, trying ] + pull_request: + branches: [ master ] + +env: + CI: true + DEBIAN: noninteractive + +jobs: + check: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2.4.0 + - uses: leafo/gh-actions-lua@v8.0.0 + - uses: leafo/gh-actions-luarocks@v4.0.0 + + - name: Install LuaCheck + run: luarocks install luacheck + + - name: Run checks + run: make check LUA_VERSION=${{ matrix.lua_version }} + + test: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + include: + - lua_version: '5.1' + - lua_version: '5.2' + - lua_version: '5.3' + - lua_version: 'luajit' + steps: + - uses: actions/checkout@v2.4.0 + - uses: leafo/gh-actions-lua@v8.0.0 + with: + luaVersion: ${{ matrix.lua_version }} + - uses: leafo/gh-actions-luarocks@v4.0.0 + + - name: Install dependencies + shell: /bin/bash -o errexit -o pipefail -o xtrace {0} + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends gobject-introspection libgirepository1.0-dev libpulse-dev + luarocks install busted + luarocks install lgi + + - name: Run tests + run: make test LUA_VERSION=${{ matrix.lua_version }} + + rock: + runs-on: ubuntu-20.04 + needs: [test, check] + strategy: + fail-fast: false + matrix: + include: + - lua_version: '5.1' + - lua_version: '5.2' + - lua_version: '5.3' + steps: + - uses: actions/checkout@v2.4.0 + - uses: leafo/gh-actions-lua@v8.0.0 + with: + luaVersion: ${{ matrix.lua_version }} + - uses: leafo/gh-actions-luarocks@v4.0.0 + + - name: Install dependencies + shell: /bin/bash -o errexit -o pipefail -o xtrace {0} + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends gobject-introspection libgirepository1.0-dev + + - name: Make rock + run: make rock LUA_VERSION=${{ matrix.lua_version }} diff --git a/.github/workflows/deploy-to-pages.yml b/.github/workflows/deploy-to-pages.yml new file mode 100644 index 0000000..51176b0 --- /dev/null +++ b/.github/workflows/deploy-to-pages.yml @@ -0,0 +1,34 @@ +name: Deploy docs + +on: + push: + branches: ['master'] + +env: + DEBIAN: noninteractive + +jobs: + deploy-docs: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2.4.0 + - uses: leafo/gh-actions-lua@v8.0.0 + - uses: leafo/gh-actions-luarocks@v4.0.0 + - uses: actions/setup-node@v2 + with: + node-version: 'lts/*' + + - name: Install dependencies + run: | + luarocks install ldoc + luarocks install lua-discount + npm install --global sass + + - name: Generate documentation + run: make doc + + - name: Deploy to gh-pages + uses: JamesIves/github-pages-deploy-action@v4.2.5 + with: + branch: gh-pages + folder: out/doc diff --git a/.gitignore b/.gitignore index 4ef1917..a06c9da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,3 @@ out/ - -.gdb_history -.gdbinit -lua.debug - -.cache/clangd -compile_commands.json .sass-cache - -*.rockspec.asc -*.src.rock +.vscode/*.log diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..0b51fe3 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/src/lua_libpulse_glib/**", + "/usr/include/lua5.3", + "/usr/include/glib-2.0", + "/usr/lib/glib-2.0/include", + "/usr/lib/x86_64-linux-gnu/glib-2.0/include", + "/usr/include/gobject-introspection-1.0" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c11", + "cppStandard": "c++14", + "intelliSenseMode": "linux-clang-x64" + } + ], + "version": 4 +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..db3ee7e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "C_Cpp.errorSquiggles": "Enabled", + "files.associations": { + "**/tasks/*.yml": "ansible", + "*.rockspec": "lua", + "*.ld": "lua", + "glib-mainloop.h": "c", + "lua.h": "c" + } +} diff --git a/Makefile b/Makefile index ad3e65f..0484ddf 100644 --- a/Makefile +++ b/Makefile @@ -43,8 +43,6 @@ OBJS = $(shell find src -type f -iname '*.c' | sed 's/\(.*\)\.c$$/$(BUILD_DIR)\/ TARGET = $(BUILD_DIR)/$(PROJECT).so -LUA_CPATH = $(shell echo "$${PWD}/$(BUILD_DIR)/?.so;$${LUA_CPATH}") - ifdef CI CHECK_ARGS ?= --formatter TAP TEST_ARGS ?= --output=TAP @@ -52,67 +50,55 @@ TEST_ARGS ?= --output=TAP CCFLAGS += -Werror endif -bold := $(shell tput bold) -orange := $(shell tput setaf 7) -title := $(bold)$(orange) -reset := $(shell tput sgr0) - -.PHONY: all clean doc doc-content doc-styles install uninstall test check rock - -all: build doc +.PHONY: clean doc doc-content doc-styles install test check rock build: $(TARGET) $(BUILD_DIR)/%.o: %.c @mkdir -p $(shell dirname "$@") - @echo "$(title)$(CC) $< -o $@$(reset)" - @$(CC) -c $(CCFLAGS) $< -o $@ + $(CC) -c $(CCFLAGS) $< -o $@ $(TARGET): $(OBJS) - @echo "$(title)$(CC) -o $@$(reset)" - @$(CC) $(LIBFLAG) -o $@ $(OBJS) $(LIBS) + $(CC) $(LIBFLAG) -o $@ $(OBJS) $(LIBS) -$(BUILD_DIR)/doc/index.html: - @mkdir -p "$(BUILD_DIR)/doc" "$(BUILD_DIR)/src" - @echo "$(title)Preprocess sources$(reset)" - sh tools/process_docs.sh "$(BUILD_DIR)" - @echo "$(title)Generate documentation$(reset)" - ldoc --config=doc/config.ld --dir "$(BUILD_DIR)/doc" --project $(PROJECT) "$(BUILD_DIR)/src" - -$(BUILD_DIR)/doc/ldoc.css: doc/ldoc.scss - @mkdir -p "$(BUILD_DIR)/doc" - @echo "$(title)Generate stylesheet$(reset)" +doc-styles: + @printf "\e[1;97mGenerate stylesheet\e[0m\n" sass doc/ldoc.scss $(BUILD_DIR)/doc/ldoc.css -doc-styles: $(BUILD_DIR)/doc/ldoc.css +doc-content: + @mkdir -p "$(BUILD_DIR)/doc" "$(BUILD_DIR)/src" + @printf "\e[1;97mPreprocess sources\e[0m\n" + sh tools/process_docs.sh "$(BUILD_DIR)" + @printf "\e[1;97mGenerate documentation\e[0m\n" + ldoc --config=doc/config.ld --dir "$(BUILD_DIR)/doc" --project $(PROJECT) "$(BUILD_DIR)/src" -doc-content: $(BUILD_DIR)/doc/index.html - -doc: doc-styles doc-content +doc: doc-content doc-styles +ifdef CI + touch "$(BUILD_DIR)/doc/.nojekyll" +endif clean: rm -r out/ install: build doc - @echo "$(title)Install C library\033[0m" - install -vDm 644 -t $(INSTALL_LIBDIR) $(TARGET) + @printf "\e[1;97mInstall C library\e[0m\n" + xargs install -vDm 644 -t $(INSTALL_LIBDIR)/$(PROJECT) $(TARGET) - @echo "$(title)Install documentation\033[0m" + @printf "\e[1;97mInstall documentation\e[0m\n" install -vd $(INSTALL_DOCDIR) cp -vr $(BUILD_DIR)/doc/* $(INSTALL_DOCDIR) -uninstall: - rm $(INSTALL_LIBDIR)/$(PROJECT).so - rm -r $(INSTALL_DOCDIR) - check: @echo "Nothing to do" -spec: build +test: busted --config-file=.busted.lua --lua=$(LUA) $(TEST_ARGS) -test: build - $(LUA) test.lua - rock: luarocks --local --lua-version $(LUA_VERSION) make rocks/lua-libpulse-glib-scm-1.rockspec + +run: build + env LUA_CPATH="./out/?.so;${LUA_CPATH}" $(LUA) test.lua + +debug: build + env LUA_CPATH="./out/?.so;${LUA_CPATH}" gdb $(LUA) diff --git a/README.adoc b/README.adoc index 03b2813..10ac33a 100644 --- a/README.adoc +++ b/README.adoc @@ -5,6 +5,7 @@ ifdef::env-github,env-browser[] :toc: macro :toclevels: 1 endif::[] +ifdef::env-github[] :branch: master :status: :outfilesuffix: .adoc @@ -14,56 +15,31 @@ endif::[] :note-caption: :paperclip: :tip-caption: :bulb: :warning-caption: :warning: -:url-ci: https://ci.sclu1034.dev/teams/main/pipelines/lua-libpulse-glib -:url-ci-badge: https://ci.sclu1034.dev/api/v1/teams/main/pipelines/lua-libpulse-glib/badge +endif::[] +:url-ci-github: https://github.com/sclu1034/lua-libpulse-glib/actions +:url-ci-badge-github: https://img.shields.io/github/workflow/status/sclu1034/lua-libpulse-glib/Lint%20&%20Test?style=flat-square :url-license-badge: https://img.shields.io/badge/license-GPLv3-brightgreen?style=flat-square :url-luarocks-badge: https://img.shields.io/luarocks/v/sclu1034/lua-libpulse-glib?style=flat-square :url-luarocks-link: https://luarocks.org/modules/sclu1034/lua-libpulse-glib image:{url-license-badge}[License] ifdef::status[] -image:{url-ci-badge}[Build Status (Concourse CI), link={url-ci}] +image:{url-ci-badge-github}[Build Status (GitHub Actions), link={url-ci-github}] endif::[] image:{url-luarocks-badge}[LuaRocks Package, link={url-luarocks-link}] https://freedesktop.org/software/pulseaudio/doxygen/index.html[libpulse] bindings for use with a GLib MainLoop via https://github.com/lgi-devs/lgi/[LGI]. -The project is still in a development state. It hasn't been fully tested yet, and not all functions from libpulse -have a corresponding binding yet. -Please do report anything that's missing or not working correctly. +== Installation -== Quick Start - -Install https://github.com/lgi-devs/lgi[lgi] and _lua_libpulse_glib_ from LuaRocks: +_lua-libpulse-glib_ is available via LuaRocks: [source,shell] ---- -luarocks install lgi -luarocks install --dev lua-libpulse-glib +luarocks install lua-libpulse-glib ---- -[source,lua] ----- -local lgi = require("lgi") -local pulseaudio = require("lua_libpulse_glib") -local ppretty = require("pl.pretty") +When cloning/vendoring the library, the following dependencies are required: -local pa = pulseaudio.new() -local ctx = pa:context("My Test App") - -local loop = lgi.GLib.MainLoop.new() - -ctx:connect(nil, function(state) - if state == 4 then - print("Connection is ready") - - ctx:get_sinks(function(sinks) - ppretty.dump(sinks) - loop:quit() - end) - end -end) - -loop:run() ----- +* https://github.com/lgi-devs/lgi[lgi] diff --git a/bors.toml b/bors.toml new file mode 100644 index 0000000..85d5802 --- /dev/null +++ b/bors.toml @@ -0,0 +1,10 @@ +status = [ + "check", + "test (5.1)", + "test (5.2)", + "test (5.3)", + "test (luajit)", + "rock (5.1)", + "rock (5.2)", + "rock (5.3)", +] diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..30a3490 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,90 @@ +[ + { + "arguments": [ + "/usr/bin/x86_64-linux-gnu-gcc-10", + "-c", + "-fPIC", + "-Wall", + "-g", + "-D_REENTRANT", + "-I/usr/include/gobject-introspection-1.0", + "-I/usr/include/glib-2.0", + "-I/usr/lib/x86_64-linux-gnu/glib-2.0/include", + "-I/usr/include/lua5.1", + "-I/usr/include/lua5.1", + "-I./", + "src/lua_libpulse_glib/context.c", + "-o", + "out/src/lua_libpulse_glib/context.o" + ], + "directory": "/home/gerlui/projects/lgi-pulseaudio", + "file": "/home/gerlui/projects/lgi-pulseaudio/src/lua_libpulse_glib/context.c", + "output": "/home/gerlui/projects/lgi-pulseaudio/out/src/lua_libpulse_glib/context.o" + }, + { + "arguments": [ + "/usr/bin/x86_64-linux-gnu-gcc-10", + "-c", + "-fPIC", + "-Wall", + "-g", + "-D_REENTRANT", + "-I/usr/include/gobject-introspection-1.0", + "-I/usr/include/glib-2.0", + "-I/usr/lib/x86_64-linux-gnu/glib-2.0/include", + "-I/usr/include/lua5.1", + "-I/usr/include/lua5.1", + "-I./", + "src/lua_libpulse_glib/pulseaudio.c", + "-o", + "out/src/lua_libpulse_glib/pulseaudio.o" + ], + "directory": "/home/gerlui/projects/lgi-pulseaudio", + "file": "/home/gerlui/projects/lgi-pulseaudio/src/lua_libpulse_glib/pulseaudio.c", + "output": "/home/gerlui/projects/lgi-pulseaudio/out/src/lua_libpulse_glib/pulseaudio.o" + }, + { + "arguments": [ + "/usr/bin/x86_64-linux-gnu-gcc-10", + "-c", + "-fPIC", + "-Wall", + "-g", + "-D_REENTRANT", + "-I/usr/include/gobject-introspection-1.0", + "-I/usr/include/glib-2.0", + "-I/usr/lib/x86_64-linux-gnu/glib-2.0/include", + "-I/usr/include/lua5.1", + "-I/usr/include/lua5.1", + "-I./", + "src/lua_libpulse_glib/init.c", + "-o", + "out/src/lua_libpulse_glib/init.o" + ], + "directory": "/home/gerlui/projects/lgi-pulseaudio", + "file": "/home/gerlui/projects/lgi-pulseaudio/src/lua_libpulse_glib/init.c", + "output": "/home/gerlui/projects/lgi-pulseaudio/out/src/lua_libpulse_glib/init.o" + }, + { + "arguments": [ + "/usr/bin/x86_64-linux-gnu-gcc-10", + "-c", + "-fPIC", + "-Wall", + "-g", + "-D_REENTRANT", + "-I/usr/include/gobject-introspection-1.0", + "-I/usr/include/glib-2.0", + "-I/usr/lib/x86_64-linux-gnu/glib-2.0/include", + "-I/usr/include/lua5.1", + "-I/usr/include/lua5.1", + "-I./", + "src/lua_libpulse_glib/introspection.c", + "-o", + "out/src/lua_libpulse_glib/introspection.o" + ], + "directory": "/home/gerlui/projects/lgi-pulseaudio", + "file": "/home/gerlui/projects/lgi-pulseaudio/src/lua_libpulse_glib/introspection.c", + "output": "/home/gerlui/projects/lgi-pulseaudio/out/src/lua_libpulse_glib/introspection.o" + } +] diff --git a/doc/config.ld b/doc/config.ld index cdfeb49..c990afc 100644 --- a/doc/config.ld +++ b/doc/config.ld @@ -1,49 +1,13 @@ -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. - - local lgi = require("lgi") - local pulseaudio = require("lua_libpulse_glib") - local ppretty = require("pl.pretty") - - local pa = pulseaudio.new() - local ctx = pa:context("My Test App") - - local loop = lgi.GLib.MainLoop.new() - - ctx:connect(nil, function(state) - if state == 4 then - print("Connection is ready") - - ctx:get_sinks(function(sinks) - ppretty.dump(sinks) - loop:quit() - end) - end - end) - - loop:run() -]] +project = 'lgi-async-extra' +title = 'lgi-async-extra' +description = 'An asynchronous high(er)-level API wrapper for lua-lgi' template = true format = 'discount' pretty = 'lua' --- prettify_files = 'show' +prettify_files = 'show' backtick_references = false wrap = true no_space_before_args = true diff --git a/lua.debug b/lua.debug new file mode 100755 index 0000000..e19e247 Binary files /dev/null and b/lua.debug differ diff --git a/rocks/lua-libpulse-glib-scm-1.rockspec b/rocks/lua-libpulse-glib-scm-1.rockspec index 257547d..5392e06 100644 --- a/rocks/lua-libpulse-glib-scm-1.rockspec +++ b/rocks/lua-libpulse-glib-scm-1.rockspec @@ -6,13 +6,15 @@ source = { } description = { - summary = "Lua bindings for PulseAudio's libpulse, using the GLib Main Loop.", + summary = "An asynchronous high(er)-level API wrapper for LGI", homepage = "https://github.com/sclu1034/lua-libpulse-glib", license = "GPLv3" } dependencies = { - "lua >= 5.1" + "lua >= 5.1", + "lgi", + "async.lua" } build = { diff --git a/spec/.gitkeep b/spec/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/lua_libpulse_glib/callback.c b/src/lua_libpulse_glib/callback.c deleted file mode 100644 index 24a726a..0000000 --- a/src/lua_libpulse_glib/callback.c +++ /dev/null @@ -1,62 +0,0 @@ -#include "callback.h" - -#include "pulseaudio.h" - - -simple_callback_data* prepare_lua_callback(lua_State* L, int callback_index) { - // 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); - - if (callback_index != 0) { - // Copy the callback function to the thread's stack - luaL_checktype(L, callback_index, LUA_TFUNCTION); - lua_pushvalue(L, callback_index); - 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; - - // Clean up the intermediate data from creating the thread - lua_pop(L, 2); - - 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); -} - -// When preparing a thread for this callback, the function must be at index `1`. -// Values on the stack after that will be ignored. This allows adding userdata and other values -// for the purpose of memory management, to keep them alive until the callback has been called. -void success_callback(pa_context* c, int success, void* userdata) { - simple_callback_data* data = (simple_callback_data*) userdata; - lua_State* L = data->L; - - // Copy the callback function to a position from where it can be called. - // There may be other values on the stack for memory management. - lua_pushvalue(L, 1); - lua_pushnil(L); - lua_pushboolean(L, success); - lua_call(L, 2, 0); - - free_lua_callback(data); -} diff --git a/src/lua_libpulse_glib/callback.h b/src/lua_libpulse_glib/callback.h deleted file mode 100644 index 60c90b8..0000000 --- a/src/lua_libpulse_glib/callback.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include -#include - -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()`. -// -// The first `int` is the index to a function on the stack. If this is non-zero, that value will -// be copied to the thread's stack. -simple_callback_data* prepare_lua_callback(lua_State*, int); - - -// 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*); - - -// Simple implementation of `pa_context_success_cb_t` that calls a provided Lua function. -void success_callback(pa_context*, int, void*); diff --git a/src/lua_libpulse_glib/context.c b/src/lua_libpulse_glib/context.c index 69a4cd9..554237b 100644 --- a/src/lua_libpulse_glib/context.c +++ b/src/lua_libpulse_glib/context.c @@ -1,24 +1,20 @@ -#include "context.h" -#include "lua_util.h" #include "pulseaudio.h" - -#include -#include -#include +#include "context.h" +#include "pulse/context.h" -/* Calls the user-provided callback with the updated state info. - */ -void context_state_callback(pa_context* c, void* userdata) { - simple_callback_data* data = (simple_callback_data*) userdata; +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); - // This can't really fail, but for consistency, we keep the error value. - lua_pushnil(data->L); + lua_pushvalue(data->L, 2); pa_context_state_t state = pa_context_get_state(c); lua_pushinteger(data->L, state); @@ -27,39 +23,9 @@ 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_TTABLE); - luaL_checkudata(L, 2, LUA_PA_CONTEXT); - - // Iterate over the list of subscription callbacks and call each one - lua_pushnil(L); - while (lua_next(L, 1) != 0) { - // TODO: Once we do have the "nothing here" value, we need to check for that here. - - // Copy the `self` parameter - lua_pushvalue(L, 2); - lua_pushinteger(L, event_type); - lua_pushinteger(L, index); - - lua_call(L, 3, 0); - } -} - - -void context_subscribe_success_callback(pa_context* _c, int success, void* userdata) { - simple_callback_data* data = (simple_callback_data*) userdata; - if (!success) { - luaL_error(data->L, "Failed to subscribe to events"); - } -} - - -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); @@ -67,31 +33,34 @@ int 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"); } lgi_ctx->context = ctx; lgi_ctx->connected = FALSE; - lgi_ctx->subscribed = FALSE; - lgi_ctx->state_callback_data = prepare_lua_callback(L, 0); - lgi_ctx->event_callback_data = prepare_lua_callback(L, 0); - - // Create the table used to store the subscription callbacks. - lua_newtable(lgi_ctx->event_callback_data->L); + 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); - // Copy the `context` value to the event callback - lua_pushvalue(L, -1); - lua_xmove(L, lgi_ctx->event_callback_data->L, 1); - return 1; } -int context__gc(lua_State* L) { +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) { @@ -100,11 +69,13 @@ int context__gc(lua_State* L) { } if (ctx->state_callback_data != NULL) { - free_lua_callback(ctx->state_callback_data); - } + lua_pushstring(L, LUA_PULSEAUDIO); + lua_rawget(L, LUA_REGISTRYINDEX); - if (ctx->event_callback_data != NULL) { - free_lua_callback(ctx->event_callback_data); + 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); @@ -112,14 +83,16 @@ int 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) @@ -138,13 +111,25 @@ int context_connect(lua_State* L) { if (nargs > 3) flags = luaL_checkinteger(L, 4); - // Make sure the callback function is at a known position in the thread's stack - lua_settop(ctx->state_callback_data->L, 0); - lua_pushvalue(L, 3); - lua_xmove(L, ctx->state_callback_data->L, 1); + // 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); - pa_context_set_state_callback(ctx->context, context_state_callback, ctx->state_callback_data); - pa_context_set_subscribe_callback(ctx->context, context_event_callback, ctx->event_callback_data); + // 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); @@ -156,150 +141,11 @@ int context_connect(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) { +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, 3); - - 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, 3); - - 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; -} - - -int context_subscribe(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - luaL_checktype(L, 2, LUA_TFUNCTION); - - // This call is only effective when the connection state is "ready". - // So we have to do it here, rather than during `context_connect`, - // but we also need to make sure it's only called once. - if (!ctx->subscribed) { - pa_context_subscribe(ctx->context, - PA_SUBSCRIPTION_MASK_ALL, - context_subscribe_success_callback, - ctx->event_callback_data); - } - - size_t pos = lua_rawlen(ctx->event_callback_data->L, 1) + 1; - // Duplicate the callback function, so we can move it over to the other thread - // TODO: Do we actually need to duplicate? - 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_rawlen(thread_L, 1); - - if (len == 0) { - return 0; - } - - // TODO: Handle calling this twice on the same index. - // Given that we use an array to track things, and Lua's way of counting in arrays - // doesn't like `nil`s, we probably need a special "nothing here" value that's not `nil`, - // and signifies an index that has already been unsubscribed. - - // TODO: Simplify things. Supporting just the index is enough. - // Comparing by function is convenient, but also confusing to inexperienced devs. - - 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"); - } - } - - // TODO: As explained above, we need to handle calling unsubscribe twice better. - // Indices should not be re-used but replaced by a special "nothing here" value. - 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 48ccb54..76870b8 100644 --- a/src/lua_libpulse_glib/context.h +++ b/src/lua_libpulse_glib/context.h @@ -1,536 +1,44 @@ -/** 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 lua_libpulse_glib.context - */ #pragma once -#include "callback.h" - -#include -#include -#include -#include -#include +#include "stdbool.h" +#include "lua.h" +#include "lauxlib.h" +#include "pulse/context.h" +#include "pulse/mainloop-api.h" +#include "introspection.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; - bool subscribed; - simple_callback_data* state_callback_data; - simple_callback_data* event_callback_data; + context_state_callback_data* state_callback_data; } lua_pa_context; -int context_new(lua_State*, pa_mainloop_api*); -int context__gc(lua_State*); - -/// Callback Functions -/// @section callbacks - -/** The callback signature for @{Context:subscribe}. - * - * See [here](https://freedesktop.org/software/pulseaudio/doxygen/subscribe.html) how to use the `event_type` - * parameter to determine the event that triggered the callback. - * - * @function event_callback - * @tparam Context context The context that the callback is subscribed to. - * @tparam number event_type A bitflag-ish value from libpulse that represents the event that caused the callback. - * @tparam number index - */ - -/** The callback signature for @{Context:connect}. - * - * @function state_callback - * @tparam Context context The context that the callback is subscribed to. - * @tparam number state An enum that represents the current connection state. - */ +int +context_new(lua_State*, pa_mainloop_api*); +int +context__index(lua_State*); +int +context__gc(lua_State*); +int +context_connect(lua_State*); -/// 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*); - -/** Returns the current connection state. - * - * @function Context:get_state - * @treturn number - */ -int context_get_state(lua_State*); - -/** Registers a callback function as event handler. - * - * This callback will be called whenever the server sends a suitable event. - * - * Any number of callbacks may be registered at the same time, and can be unscubscribed with - * @{Context:unsubscribe}, using the returned subscription ID. - * - * This must called after the connection state changed to "ready" (index `4`). Otherwise - * it will have no effect. - * - * @function Context:subscribe - * @tparam function cb - * @treturn number The subscription ID. - */ -int context_subscribe(lua_State*); - -/** Removes an event handler subscription. - * - * Subscriptions may be removed either by their ID or by their callback function. - * - * @function Context:unsubscribe - * @tparam number|function handler The handler to remove. - */ -int context_unsubscribe(lua_State*); - - -/** Sets the default sink. - * - * @function Context:set_default_sink - * @async - * @tparam string sink Name of the sink to set as default. - * @treturn[opt] string error - * @treturn boolean - */ -int context_set_default_sink(lua_State*); - -/** Sets the default source. - * - * @function Context:set_default_source - * @async - * @tparam string source Name of the source to set as default. - * @treturn[opt] string error - * @treturn boolean - */ -int context_set_default_source(lua_State*); - - -/** 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[] = { +static const struct luaL_Reg context_mt [] = { + {"__index", context__index}, {"__gc", context__gc}, - { NULL, NULL } -}; - - -static const struct luaL_Reg context_f[] = { - {"connect", context_connect }, - { "disconnect", context_disconnect }, - { "subscribe", context_subscribe }, - { "unsubscribe", context_unsubscribe }, - { "get_state", context_get_state }, - { "set_default_sink", context_set_default_sink }, - { "set_default_source", context_set_default_source }, - { "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 } + {"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} }; diff --git a/src/lua_libpulse_glib/convert.c b/src/lua_libpulse_glib/convert.c deleted file mode 100644 index 203ca81..0000000 --- a/src/lua_libpulse_glib/convert.c +++ /dev/null @@ -1,532 +0,0 @@ -#include "convert.h" - -#include "proplist.h" -#include "volume.h" - -#include - - -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); -} diff --git a/src/lua_libpulse_glib/convert.h b/src/lua_libpulse_glib/convert.h deleted file mode 100644 index f4ac1fa..0000000 --- a/src/lua_libpulse_glib/convert.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include -#include - - -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*); diff --git a/src/lua_libpulse_glib/introspection.c b/src/lua_libpulse_glib/introspection.c index a77eb2a..0b4e1bc 100644 --- a/src/lua_libpulse_glib/introspection.c +++ b/src/lua_libpulse_glib/introspection.c @@ -1,30 +1,402 @@ -#include "callback.h" -#include "context.h" -#include "convert.h" -#include "proplist.h" -#include "pulseaudio.h" -#include "volume.h" - -#include #include -#include -#include -#include +#include +#include "introspection.h" +#include "context.h" +#include "pulseaudio.h" -void server_info_callback(pa_context* c, const pa_server_info* info, void* userdata) { - simple_callback_data* data = (simple_callback_data*) userdata; +// TODO: Figure out error handling for callbacks + + +typedef struct server_info_callback_data { + lua_State* L; + int thread_ref; +} server_info_callback_data; + + +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 +volume_to_lua(lua_State* L, const pa_cvolume* volume) +{ + lua_createtable(L, volume->channels, 0); + int table_index = lua_gettop(L); + + for (int i = 0; i < volume->channels; ++i) { + lua_pushinteger(L, i+1); + lua_pushinteger(L, volume->values[i]); + 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 +format_info_to_lua(lua_State* L, const pa_format_info* info) +{ + lua_createtable(L, 0, 6); + int table_index = lua_gettop(L); + + lua_pushstring(L, "encoding"); + lua_pushinteger(L, info->encoding); + lua_settable(L, table_index); + + // TODO: + // lua_pushstring(L, "plist"); + // proplist_to_lua(L, &info->plist); + // lua_settable(L, table_index); +} + + +void +ports_to_lua(lua_State* L, const pa_sink_port_info** list, int n_ports, const pa_sink_port_info* active) +{ + lua_createtable(L, 1, n_ports); + 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 +formats_to_lua(lua_State* L, const pa_format_info** list, int n_formats) +{ + lua_createtable(L, 0, n_formats); + 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"); + lua_pushinteger(L, info->index); + 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); + + // TODO: The proplist needs more advanced handling + // lua_pushstring(L, "proplist"); + // proplist_to_lua(L, &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"); + 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"); + lua_pushinteger(L, info->index); + 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); + + // TODO: The proplist needs more advanced handling + // lua_pushstring(L, "proplist"); + // proplist_to_lua(L, &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"); + 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 +server_info_callback(pa_context* c, const pa_server_info* info, void* userdata) +{ + + server_info_callback_data* data = (server_info_callback_data*) userdata; lua_State* L = data->L; lua_pushnil(L); server_info_to_lua(L, info); lua_call(L, 2, 0); - free_lua_callback(data); + // 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); } -int context_get_server_info(lua_State* L) { +int +context_get_server_info(lua_State* L) +{ lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { @@ -34,7 +406,21 @@ int context_get_server_info(lua_State* L) { return 0; } - simple_callback_data* data = prepare_lua_callback(L, 2); + // 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); + + server_info_callback_data* data = calloc(1, sizeof(struct server_info_callback_data)); + data->L = thread; + data->thread_ref = thread_ref; pa_operation* op = pa_context_get_server_info(ctx->context, server_info_callback, data); if (op == NULL) { @@ -49,42 +435,44 @@ int context_get_server_info(lua_State* L) { } -void sink_info_callback(pa_context* c, const pa_sink_info* info, int eol, void* userdata) { - simple_callback_data* data = (simple_callback_data*) userdata; +typedef struct sink_info_list_callback_data { + lua_State* L; + int thread_ref; +} sink_info_list_callback_data; + + +void +sink_info_list_callback(pa_context* c, const pa_sink_info* info, int eol, void* userdata) +{ + sink_info_list_callback_data* data = (sink_info_list_callback_data*) userdata; lua_State* L = data->L; - if (data->is_list) { - if (!eol) { - int i = lua_rawlen(L, 2); - lua_pushinteger(L, i + 1); - sink_info_to_lua(L, info); - lua_settable(L, 2); - } else { - // Insert the error argument - lua_pushnil(L); - lua_insert(L, -2); - - lua_call(L, 2, 0); - - free_lua_callback(data); - } + if (eol == 0) { + int i = lua_objlen(L, 2); + lua_pushinteger(L, i+1); + sink_info_to_lua(L, info); + lua_settable(L, 2); } else { - if (!eol) { - lua_pushfstring(L, "only one sink info expected, but got multiple"); - lua_call(L, 1, 0); - } else { - lua_pushnil(L); - sink_info_to_lua(L, info); + // Insert the error argument + lua_pushnil(L); + lua_insert(L, -2); - lua_call(L, 2, 0); + lua_call(L, 2, 0); - free_lua_callback(data); - } + 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); } } -int context_get_sink_info_list(lua_State* L) { +int +context_get_sink_info_list(lua_State* L) +{ lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { @@ -94,388 +482,68 @@ int context_get_sink_info_list(lua_State* L) { return 0; } - simple_callback_data* data = prepare_lua_callback(L, 2); - data->is_list = true; - // Create the list to store infos in - lua_newtable(data->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); - pa_operation* op = pa_context_get_sink_info_list(ctx->context, sink_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get sink info list: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } + // Copy the callback function to the thread's stack + lua_pushvalue(L, 2); + lua_xmove(L, thread, 1); + // List to store the values in + lua_newtable(thread); + + sink_info_list_callback_data* data = calloc(1, sizeof(struct sink_info_list_callback_data)); + data->L = thread; + data->thread_ref = thread_ref; + + pa_context_get_sink_info_list(ctx->context, sink_info_list_callback, data); return 0; } -int context_get_sink_info_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - const char* name = luaL_checkstring(L, 2); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 2); - - pa_operation* op = pa_context_get_sink_info_by_name(ctx->context, name, sink_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get sink info by name: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} +typedef struct source_info_list_callback_data { + lua_State* L; + int thread_ref; +} source_info_list_callback_data; -int context_get_sink_info_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Sink index out of bounds. Got %d", index); - } - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 3); - - pa_operation* op = pa_context_get_sink_info_by_index(ctx->context, (uint32_t) index - 1, sink_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get sink info by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_get_sink_info(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_get_sink_info_by_name(L); - } - case LUA_TNUMBER: { - return context_get_sink_info_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -int context_set_sink_volume_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - const char* name = luaL_checkstring(L, 2); - - if (lua_istable(L, 3)) { - pa_cvolume* volume = volume_from_lua(L, 3); - volume_to_lua(L, volume); - pa_xfree((void*) volume); - } else { - lua_pushvalue(L, 3); - } - - volume_t* vol = luaL_checkudata(L, -1, LUA_PA_VOLUME); - - // Move the volume userdata to the callback's thread. It needs to be kept alive - // until the callback has run. - lua_xmove(L, data->L, 1); - - pa_operation* op = pa_context_set_sink_volume_by_name(ctx->context, name, &vol->inner, success_callback, data); - - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set sink volume by name: %s", pa_strerror(error)); - lua_call(L, 1, 0); - } - - return 0; -} - - -int context_set_sink_volume_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Sink index out of bounds. Got %d", index); - } - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - if (lua_istable(L, 3)) { - pa_cvolume* volume = volume_from_lua(L, 3); - volume_to_lua(L, volume); - pa_xfree((void*) volume); - } else { - lua_pushvalue(L, 3); - } - - volume_t* vol = luaL_checkudata(L, -1, LUA_PA_VOLUME); - - // Move the volume userdata to the callback's thread. It needs to be kept alive - // until the callback has run. - lua_xmove(L, data->L, 1); - - pa_operation* op = - pa_context_set_sink_volume_by_index(ctx->context, (uint32_t) index - 1, &vol->inner, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set sink volume by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - } - - return 0; -} - - -int context_set_sink_volume(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_set_sink_volume_by_name(L); - } - case LUA_TNUMBER: { - return context_set_sink_volume_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -int context_set_sink_mute_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - const char* name = luaL_checkstring(L, 2); - int mute = lua_toboolean(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = pa_context_set_sink_mute_by_name(ctx->context, name, mute, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set sink mute by name: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_sink_mute_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Sink index out of bounds. Got %d", index); - } - int mute = lua_toboolean(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = - pa_context_set_sink_mute_by_index(ctx->context, (uint32_t) index - 1, mute, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set sink mute by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_sink_mute(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_set_sink_mute_by_name(L); - } - case LUA_TNUMBER: { - return context_set_sink_mute_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -int context_set_sink_suspended_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - const char* name = luaL_checkstring(L, 2); - int suspended = lua_toboolean(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = pa_context_suspend_sink_by_name(ctx->context, name, suspended, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set sink suspended by name: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_sink_suspended_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Sink index out of bounds. Got %d", index); - } - int suspended = lua_toboolean(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = - pa_context_suspend_sink_by_index(ctx->context, (uint32_t) index - 1, suspended, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set sink suspended by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_sink_suspended(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_set_sink_suspended_by_name(L); - } - case LUA_TNUMBER: { - return context_set_sink_suspended_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -// Can be used both functions that produce a single value or the `_list` versions. -// The `is_list` value on the userdata needs to be set accordingly. -// For lists, the callback will be called multiple times, once for each entry. -// The function that sets up the callback must make sure to push a table on the stack, where the -// values can be collected. -// -// @tparam pa_source_info* info Pointer to the data. -// @tparam boolean eol Indicates whether the last item of the list was reached. -// @tparam simple_callback_data* userdata Expected to be an instance of `simple_callback_data`. -void source_info_callback(pa_context* c, const pa_source_info* info, int eol, void* userdata) { - simple_callback_data* data = (simple_callback_data*) userdata; +void +source_info_list_callback(pa_context* c, const pa_source_info* info, int eol, void* userdata) +{ + source_info_list_callback_data* data = (source_info_list_callback_data*) userdata; lua_State* L = data->L; - if (data->is_list) { - if (!eol) { - int i = lua_rawlen(L, 2); - lua_pushinteger(L, i + 1); - source_info_to_lua(L, info); - lua_settable(L, 2); - } else { - // Insert the error argument - lua_pushnil(L); - lua_insert(L, -2); - - lua_call(L, 2, 0); - - free_lua_callback(data); - } + if (eol == 0) { + int i = lua_objlen(L, 2); + lua_pushinteger(L, i+1); + source_info_to_lua(L, info); + lua_settable(L, 2); } else { - if (!eol) { - lua_pushfstring(L, "only one source info expected, but got multiple"); - lua_call(L, 1, 0); - } else { - lua_pushnil(L); - source_info_to_lua(L, info); + // Insert the error argument + lua_pushnil(L); + lua_insert(L, -2); - lua_call(L, 2, 0); + lua_call(L, 2, 0); - free_lua_callback(data); - } + 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); } } -int context_get_source_info_list(lua_State* L) { +int +context_get_source_info_list(lua_State* L) +{ lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { @@ -485,884 +553,25 @@ int context_get_source_info_list(lua_State* L) { return 0; } - simple_callback_data* data = prepare_lua_callback(L, 2); - data->is_list = true; - // Create the list to store infos in - lua_newtable(data->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); - pa_operation* op = pa_context_get_source_info_list(ctx->context, source_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get source info list: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_get_source_info_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - const char* name = luaL_checkstring(L, 2); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 3); - - pa_operation* op = pa_context_get_source_info_by_name(ctx->context, name, source_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get source info by name: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_get_source_info_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Source index out of bounds. Got %d", index); - } - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 3); - - pa_operation* op = - pa_context_get_source_info_by_index(ctx->context, (uint32_t) index - 1, source_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get source info by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_get_source_info(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_get_source_info_by_name(L); - } - case LUA_TNUMBER: { - return context_get_source_info_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -int context_set_source_volume_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - const char* name = luaL_checkstring(L, 2); - - if (lua_istable(L, 3)) { - pa_cvolume* volume = volume_from_lua(L, 3); - volume_to_lua(L, volume); - pa_xfree((void*) volume); - } else { - lua_pushvalue(L, 3); - } - - volume_t* vol = luaL_checkudata(L, -1, LUA_PA_VOLUME); - - // Move the volume userdata to the callback's thread. It needs to be kept alive - // until the callback has run. - lua_xmove(L, data->L, 1); - - pa_operation* op = pa_context_set_source_volume_by_name(ctx->context, name, &vol->inner, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set source volume by name: %s", pa_strerror(error)); - lua_call(L, 1, 0); - } - - return 0; -} - - -int context_set_source_volume_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Source index out of bounds. Got %d", index); - } - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - if (lua_istable(L, 3)) { - pa_cvolume* volume = volume_from_lua(L, 3); - volume_to_lua(L, volume); - pa_xfree((void*) volume); - } else { - lua_pushvalue(L, 3); - } - - volume_t* vol = luaL_checkudata(L, -1, LUA_PA_VOLUME); - - // Move the volume userdata to the callback's thread. It needs to be kept alive - // until the callback has run. - lua_xmove(L, data->L, 1); - - pa_operation* op = - pa_context_set_source_volume_by_index(ctx->context, (uint32_t) index - 1, &vol->inner, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set source volume by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - } - - return 0; -} - - -int context_set_source_volume(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_set_source_volume_by_name(L); - } - case LUA_TNUMBER: { - return context_set_source_volume_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -int context_set_source_mute_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - const char* name = luaL_checkstring(L, 2); - int mute = lua_toboolean(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = pa_context_set_source_mute_by_name(ctx->context, name, mute, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set source mute by name: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_source_mute_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Source index out of bounds. Got %d", index); - } - int mute = lua_toboolean(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = - pa_context_set_source_mute_by_index(ctx->context, (uint32_t) index - 1, mute, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set source mute by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_source_mute(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_set_source_mute_by_name(L); - } - case LUA_TNUMBER: { - return context_set_source_mute_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -int context_set_source_suspended_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - const char* name = luaL_checkstring(L, 2); - int suspended = lua_toboolean(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = pa_context_suspend_source_by_name(ctx->context, name, suspended, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set source suspended by name: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_source_suspended_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Source index out of bounds. Got %d", index); - } - int suspended = lua_toboolean(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = - pa_context_suspend_source_by_index(ctx->context, (uint32_t) index - 1, suspended, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set source suspended by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_source_suspended(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_set_source_suspended_by_name(L); - } - case LUA_TNUMBER: { - return context_set_source_suspended_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -void sink_input_info_callback(pa_context* c, const pa_sink_input_info* info, int eol, void* userdata) { - simple_callback_data* data = (simple_callback_data*) userdata; - lua_State* L = data->L; - - if (data->is_list) { - if (!eol) { - int i = lua_rawlen(L, 2); - lua_pushinteger(L, i + 1); - sink_input_info_to_lua(L, info); - lua_settable(L, 2); - } else { - // Insert the error argument - lua_pushnil(L); - lua_insert(L, -2); - - lua_call(L, 2, 0); - - free_lua_callback(data); - } - } else { - if (!eol) { - lua_pushfstring(L, "only one sink input info expected, but got multiple"); - lua_call(L, 1, 0); - } else { - lua_pushnil(L); - sink_input_info_to_lua(L, info); - - lua_call(L, 2, 0); - - free_lua_callback(data); - } - } -} - - -int context_get_sink_input_info_list(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 2); - data->is_list = true; - // Create the list to store infos in - lua_newtable(data->L); - - pa_operation* op = pa_context_get_sink_input_info_list(ctx->context, sink_input_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get source info list: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_get_sink_input_info(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Sink input index out of bounds. Got %d", index); - } - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 3); - - pa_operation* op = - pa_context_get_sink_input_info(ctx->context, (uint32_t) index - 1, sink_input_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get sink input info by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_move_sink_input(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_move_sink_input_by_name(L); - } - case LUA_TNUMBER: { - return context_move_sink_input_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -int context_move_sink_input_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer sink_input_index = luaL_checkinteger(L, 2); - if (sink_input_index < 1) { - return luaL_error(L, "Sink input index out of bounds. Got %d", sink_input_index); - } - - lua_Integer sink_index = luaL_checkinteger(L, 3); - if (sink_index < 1) { - return luaL_error(L, "Sink index out of bounds. Got %d", sink_index); - } - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = pa_context_move_sink_input_by_index(ctx->context, - (uint32_t) sink_input_index - 1, - (uint32_t) sink_index - 1, - success_callback, - data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, - "failed to move sink input %d to sink %d: %s", - sink_input_index, - sink_index, - pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_move_sink_input_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer sink_input_index = luaL_checkinteger(L, 2); - if (sink_input_index < 1) { - return luaL_error(L, "Sink input index out of bounds. Got %d", sink_input_index); - } - - const char* sink_name = luaL_checkstring(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = pa_context_move_sink_input_by_name(ctx->context, - (uint32_t) sink_input_index - 1, - sink_name, - success_callback, - data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, - "failed to move sink input %d to sink %s: %s", - sink_input_index, - sink_name, - pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_sink_input_volume(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Sink input index out of bounds. Got %d", index); - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - if (lua_istable(L, 3)) { - pa_cvolume* volume = volume_from_lua(L, 3); - volume_to_lua(L, volume); - pa_xfree((void*) volume); - } else { - lua_pushvalue(L, 3); - } - - volume_t* vol = luaL_checkudata(L, -1, LUA_PA_VOLUME); - - // Move the volume userdata to the callback's thread. It needs to be kept alive - // until the callback has run. - lua_xmove(L, data->L, 1); - - pa_operation* op = - pa_context_set_sink_input_volume(ctx->context, (uint32_t) index - 1, &vol->inner, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set volume for sink input %d: %s", index, pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_sink_input_mute(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Sink input index out of bounds. Got %d", index); - } - bool mute = lua_toboolean(L, 3); - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = pa_context_set_sink_input_mute(ctx->context, (uint32_t) index - 1, mute, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set mute for sink input %d: %s", index, pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_kill_sink_input(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Sink input index out of bounds. Got %d", index); - } - - - simple_callback_data* data = prepare_lua_callback(L, 3); - - pa_operation* op = pa_context_kill_sink_input(ctx->context, (uint32_t) index - 1, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to kill sink input %d: %s", index, pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -void source_output_info_callback(pa_context* c, const pa_source_output_info* info, int eol, void* userdata) { - simple_callback_data* data = (simple_callback_data*) userdata; - lua_State* L = data->L; - - if (data->is_list) { - if (!eol) { - int i = lua_rawlen(L, 2); - lua_pushinteger(L, i + 1); - source_output_info_to_lua(L, info); - lua_settable(L, 2); - } else { - // Insert the error argument - lua_pushnil(L); - lua_insert(L, -2); - - lua_call(L, 2, 0); - - free_lua_callback(data); - } - } else { - if (!eol) { - lua_pushfstring(L, "only one source output info expected, but got multiple"); - lua_call(L, 1, 0); - } else { - lua_pushnil(L); - source_output_info_to_lua(L, info); - - lua_call(L, 2, 0); - - free_lua_callback(data); - } - } -} - - -int context_get_source_output_info_list(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 2); - data->is_list = true; - // Create the list to store infos in - lua_newtable(data->L); - - pa_operation* op = pa_context_get_source_output_info_list(ctx->context, source_output_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get source info list: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_get_source_output_info(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Source output index out of bounds. Got %d", index); - } - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 3); - - pa_operation* op = - pa_context_get_source_output_info(ctx->context, (uint32_t) index - 1, source_output_info_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to get source output info by index: %s", pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_move_source_output(lua_State* L) { - switch (lua_type(L, 2)) { - case LUA_TSTRING: { - return context_move_source_output_by_name(L); - } - case LUA_TNUMBER: { - return context_move_source_output_by_index(L); - } - default: { - lua_pushfstring(L, "expected number or string, got %s", luaL_typename(L, 2)); - return luaL_argerror(L, 2, lua_tostring(L, -1)); - } - } -} - - -int context_move_source_output_by_index(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer source_output_index = luaL_checkinteger(L, 2); - if (source_output_index < 1) { - return luaL_error(L, "Source output index out of bounds. Got %d", source_output_index); - } - - lua_Integer source_index = luaL_checkinteger(L, 3); - if (source_index < 1) { - return luaL_error(L, "Source index out of bounds. Got %d", source_index); - } - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = pa_context_move_source_output_by_index(ctx->context, - (uint32_t) source_output_index - 1, - (uint32_t) source_index - 1, - success_callback, - data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, - "failed to move source output %d to source %d: %s", - source_output_index, - source_index, - pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_move_source_output_by_name(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer source_output_index = luaL_checkinteger(L, 2); - if (source_output_index < 1) { - return luaL_error(L, "Source output index out of bounds. Got %d", source_output_index); - } - - const char* source_name = luaL_checkstring(L, 3); - - if (pa_context_get_state(ctx->context) != PA_CONTEXT_READY) { - lua_pushvalue(L, 2); - lua_pushstring(L, "connection not ready"); - lua_call(L, 1, 0); - return 0; - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = pa_context_move_source_output_by_name(ctx->context, - (uint32_t) source_output_index - 1, - source_name, - success_callback, - data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, - "failed to move source output %d to source %s: %s", - source_output_index, - source_name, - pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_source_output_volume(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Source output index out of bounds. Got %d", index); - } - - simple_callback_data* data = prepare_lua_callback(L, 4); - - if (lua_istable(L, 3)) { - pa_cvolume* volume = volume_from_lua(L, 3); - volume_to_lua(L, volume); - pa_xfree((void*) volume); - } else { - lua_pushvalue(L, 3); - } - - volume_t* vol = luaL_checkudata(L, -1, LUA_PA_VOLUME); - - // Move the volume userdata to the callback's thread. It needs to be kept alive - // until the callback has run. - lua_xmove(L, data->L, 1); - - pa_operation* op = - pa_context_set_source_output_volume(ctx->context, (uint32_t) index - 1, &vol->inner, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set volume for source output %d: %s", index, pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_set_source_output_mute(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Source output index out of bounds. Got %d", index); - } - bool mute = lua_toboolean(L, 3); - - simple_callback_data* data = prepare_lua_callback(L, 4); - - pa_operation* op = - pa_context_set_source_output_mute(ctx->context, (uint32_t) index - 1, mute, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to set mute for source output %d: %s", index, pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } - - return 0; -} - - -int context_kill_source_output(lua_State* L) { - lua_pa_context* ctx = luaL_checkudata(L, 1, LUA_PA_CONTEXT); - lua_Integer index = luaL_checkinteger(L, 2); - if (index < 1) { - return luaL_error(L, "Source output index out of bounds. Got %d", index); - } - - - simple_callback_data* data = prepare_lua_callback(L, 3); - - pa_operation* op = pa_context_kill_source_output(ctx->context, (uint32_t) index - 1, success_callback, data); - if (op == NULL) { - int error = pa_context_errno(ctx->context); - lua_pushvalue(L, 2); - lua_pushfstring(L, "failed to kill source output %d: %s", index, pa_strerror(error)); - lua_call(L, 1, 0); - return 0; - } + // Copy the callback function to the thread's stack + lua_pushvalue(L, 2); + lua_xmove(L, thread, 1); + // List to store the values in + lua_newtable(thread); + + source_info_list_callback_data* data = calloc(1, sizeof(struct source_info_list_callback_data)); + data->L = thread; + data->thread_ref = thread_ref; + + pa_context_get_source_info_list(ctx->context, source_info_list_callback, data); return 0; } diff --git a/src/lua_libpulse_glib/introspection.h b/src/lua_libpulse_glib/introspection.h new file mode 100644 index 0000000..c998d16 --- /dev/null +++ b/src/lua_libpulse_glib/introspection.h @@ -0,0 +1,10 @@ +#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*); diff --git a/src/lua_libpulse_glib/lua_util.h b/src/lua_libpulse_glib/lua_util.h deleted file mode 100644 index 0f05e11..0000000 --- a/src/lua_libpulse_glib/lua_util.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -#define LUA_MOD_EXPORT extern - -#if LUA_VERSION_NUM <= 501 -#define lua_rawlen lua_objlen - -#define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_setfuncs(L, l, 0)) -#define luaL_newlibtable(L, l) (lua_createtable(L, 0, sizeof(l) / sizeof(l[0]))) -#endif - -#if LUA_VERSION_NUM > 501 -#define lua_equal(L, i1, i2) lua_compare(L, i1, i2, LUA_OPEQ) -#endif - -typedef struct luaU_enumfield { - const char* name; - const char* value; -} luaU_enumfield; diff --git a/src/lua_libpulse_glib/proplist.c b/src/lua_libpulse_glib/proplist.c deleted file mode 100644 index 35c6a91..0000000 --- a/src/lua_libpulse_glib/proplist.c +++ /dev/null @@ -1,214 +0,0 @@ -#include "proplist.h" - -#include - -// 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; -} diff --git a/src/lua_libpulse_glib/proplist.h b/src/lua_libpulse_glib/proplist.h deleted file mode 100644 index 4c3d3e2..0000000 --- a/src/lua_libpulse_glib/proplist.h +++ /dev/null @@ -1,318 +0,0 @@ -/** 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 lua_libpulse_glib.proplist - */ -#pragma once - -#include "lua_util.h" - -#include -#include -#include -#include - -#define LUA_PA_PROPLIST "lua_libpulse_glib.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 }, -#if PA_CHECK_VERSION(15, 0, 0) - { "CONTEXT_FORCE_DISABLE_SHM", PA_PROP_CONTEXT_FORCE_DISABLE_SHM }, - { "BLUETOOTH_CODEC", PA_PROP_BLUETOOTH_CODEC }, -#endif - { NULL, NULL } -}; diff --git a/src/lua_libpulse_glib/pulseaudio.c b/src/lua_libpulse_glib/pulseaudio.c index d6b7368..05eedb7 100644 --- a/src/lua_libpulse_glib/pulseaudio.c +++ b/src/lua_libpulse_glib/pulseaudio.c @@ -1,35 +1,53 @@ -#include "pulseaudio.h" +/// libpulse bindings. +// +// @module pulseaudio -#include "context.h" -#include "lua_util.h" -#include "proplist.h" -#include "volume.h" - -#include #include #include +#include #include +#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. -void luaL_setfuncs(lua_State* L, const luaL_Reg* l, int nup) { +// 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 */ - 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)) #endif -int pulseaudio_new(lua_State* L) { + +/** + * 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) +{ 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?"); @@ -37,7 +55,7 @@ int 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"); } @@ -50,10 +68,12 @@ int 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); @@ -61,83 +81,28 @@ int 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)); } -void createlib_volume(lua_State* L) { - luaL_newmetatable(L, LUA_PA_VOLUME); - - luaL_newlib(L, volume_f); - lua_setfield(L, -2, "__index"); - - luaL_setfuncs(L, volume_mt, 0); - - luaL_newlib(L, volume_lib); - - lua_setmetatable(L, -2); -} - - -void createlib_proplist(lua_State* L) { - luaL_newmetatable(L, LUA_PA_PROPLIST); - - luaL_newlib(L, proplist_f); - lua_setfield(L, -2, "__index"); - - luaL_setfuncs(L, proplist_mt, 0); - - luaL_newlib(L, proplist_lib); - - // 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); - - luaL_newlib(L, context_f); - lua_setfield(L, -2, "__index"); - - luaL_setfuncs(L, context_mt, 0); -} - - -void createlib_pulseaudio(lua_State* L) { - luaL_newmetatable(L, LUA_PULSEAUDIO); - - luaL_newlib(L, pulseaudio_f); - lua_setfield(L, -2, "__index"); - - luaL_setfuncs(L, pulseaudio_mt, 0); - - luaL_newlib(L, pulseaudio_lib); -} - - -LUA_MOD_EXPORT int luaopen_lua_libpulse_glib(lua_State* L) { +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); @@ -146,21 +111,12 @@ LUA_MOD_EXPORT int luaopen_lua_libpulse_glib(lua_State* L) { lua_settable(L, -3); lua_rawset(L, LUA_REGISTRYINDEX); - createlib_context(L); - createlib_proplist(L); - createlib_pulseaudio(L); - return 1; -} - - -LUA_MOD_EXPORT int luaopen_lua_libpulse_glib_volume(lua_State* L) { - luaL_newmetatable(L, LUA_PA_VOLUME); - - luaL_newlib(L, volume_f); - lua_setfield(L, -2, "__index"); - - luaL_setfuncs(L, volume_mt, 0); - - luaL_newlib(L, volume_lib); + 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); return 1; } diff --git a/src/lua_libpulse_glib/pulseaudio.h b/src/lua_libpulse_glib/pulseaudio.h index 5e0e5a2..dd18fe0 100644 --- a/src/lua_libpulse_glib/pulseaudio.h +++ b/src/lua_libpulse_glib/pulseaudio.h @@ -1,16 +1,18 @@ -/** Bindings for PulseAudio's libpulse, using the GLib Main Loop. - * - * @module lua_libpulse_glib - */ #pragma once -#include "lauxlib.h" #include "lua.h" - +#include "lauxlib.h" #include -#define LUA_PULSEAUDIO "lua_libpulse_glib" -#define LUA_PA_REGISTRY "lua_libpulse_glib.registry" +#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 { @@ -18,43 +20,25 @@ typedef struct pulseaudio { } pulseaudio; -/** Creates a new PulseAudio object. - * - * @function new - * @return[type=PulseAudio] - */ -int pulseaudio_new(lua_State*); +int +pulseaudio_new(lua_State*); +int +pulseaudio__gc(lua_State*); +int +pulseaudio__index(lua_State*); +int +pulseaudio_new_context(lua_State*); -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[] = { +static const struct luaL_Reg pulseaudio_mt [] = { + {"__index", pulseaudio__index}, {"__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} }; diff --git a/src/lua_libpulse_glib/volume.c b/src/lua_libpulse_glib/volume.c deleted file mode 100644 index d4aecab..0000000 --- a/src/lua_libpulse_glib/volume.c +++ /dev/null @@ -1,343 +0,0 @@ -#include "volume.h" - -#include "lua_util.h" - -#include -#include -#include -#include - - -int volume_new(lua_State* L) { - pa_cvolume* cvol = volume_from_lua(L, 1); - return volume_to_lua(L, cvol); -} - - -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, index)) { - case LUA_TTABLE: { - uint8_t channels = (uint8_t) lua_rawlen(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 = (int) luaL_checkinteger(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"); - } - } -} - - -int volume_from_dB(lua_State* L) { - double value = (double) luaL_checknumber(L, 1); - lua_pushinteger(L, pa_sw_volume_from_dB(value)); - return 1; -} - - -int volume_to_dB(lua_State* L) { - pa_volume_t value = luaL_checkinteger(L, 1); - lua_pushnumber(L, pa_sw_volume_to_dB(value)); - return 1; -} - - -int volume_from_linear(lua_State* L) { - double value = (double) luaL_checknumber(L, 1); - lua_pushinteger(L, pa_sw_volume_from_linear(value)); - return 1; -} - - -int volume_to_linear(lua_State* L) { - pa_volume_t value = luaL_checkinteger(L, 1); - lua_pushnumber(L, pa_sw_volume_to_linear(value)); - return 1; -} diff --git a/src/lua_libpulse_glib/volume.h b/src/lua_libpulse_glib/volume.h deleted file mode 100644 index 95c2870..0000000 --- a/src/lua_libpulse_glib/volume.h +++ /dev/null @@ -1,296 +0,0 @@ -/** 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 lua_libpulse_glib.volume - */ -#ifndef volume_h_INCLUDED -#define volume_h_INCLUDED - -#include -#include -#include - -#define LUA_PA_VOLUME "lua_libpulse_glib.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 - -/** Creates an instance of @{Volume}. - * - * @function new - * @tparam table values An array of channel volumes. - * @treturn Volume - */ -int volume_new(lua_State*); - -/** 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*); - -/** Converts a decibel value to an integer volume value. - * - * This is only valid for software volumes. It does not operate - * on instances of @{Volume}. - * - * @function from_dB - * @tparam number value - * @treturn number - */ -int volume_from_dB(lua_State*); - -/** Converts an integer volume value to a decibel value. - * - * This is only valid for software volumes. It does not operate - * on instances of @{Volume}. - * - * @function to_dB - * @tparam number value - * @treturn number - */ -int volume_to_dB(lua_State*); - -/** Converts a linear factor to an integer volume value. - * - * `0.0` and less is muted, `1.0` is normal volume. - * - * This is only valid for software volumes. It does not operate - * on instances of @{Volume}. - * - * @function from_linear - * @tparam number value - * @treturn number - */ -int volume_from_linear(lua_State*); - -/** Converts an integer volume value to linear factor. - * - * This is only valid for software volumes. It does not operate - * on instances of @{Volume}. - * - * @function to_linear - * @tparam number value - * @treturn number - */ -int volume_to_linear(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[] = { - {"new", volume_new }, - { "from_dB", volume_from_dB }, - { "to_dB", volume_to_dB }, - { "from_linear", volume_from_linear}, - { "to_linear", volume_to_linear }, - { "is_valid", volume_is_valid }, - { NULL, NULL } -}; - - -#endif // volume_h_INCLUDED - diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..2ec9c25 --- /dev/null +++ b/test.lua @@ -0,0 +1,46 @@ +local lgi = require("lgi") +local pulseaudio = require("lua_libpulse_glib") +local ppretty = require("pl.pretty") + +local loop = lgi.GLib.MainLoop.new() + +local pa = pulseaudio.new() +print("pulseaudio: ", pa) +local ctx = pa:context("test") +print("context: ", ctx) + +local function context_state_to_string(state) + return ({ + "unconnected", + "connecting", + "authorizing", + "setting_name", + "ready", + "failed", + "terminated" + })[state + 1] +end + +print("calling connect") +ctx:connect("localhost", function(_, state) + print("pulse connection: ", context_state_to_string(state)) + + if state == 4 then + ctx:get_server_info(function(_, info) + print("server info") + ppretty.dump(info) + end) + + ctx:get_sinks(function(_, list) + print("sinks") + ppretty.dump(list) + end) + + ctx:get_sources(function(_, list) + print("sources") + ppretty.dump(list) + end) + end +end) + +loop:run() diff --git a/tools/process_docs.sh b/tools/process_docs.sh index f6ab73f..9b5fd46 100644 --- a/tools/process_docs.sh +++ b/tools/process_docs.sh @@ -14,7 +14,7 @@ run() { "$@" } -find src -iname '*.lua' -or -iname '*.c' -or -iname '*.h' -not -path '*/internal/*' | while read -r f; do +find src -iname '*.lua' -or -iname '*.c' -not -path '*/internal/*' | while read -r f; do mkdir -p "$(dirname "$OUT/$f")" run "$LUA" ./tools/preprocessor.lua "$f" "$OUT/$f" done