docs: make progress on lua api documentation

This commit is contained in:
George Kiagiadakis
2021-05-28 19:22:56 +03:00
parent 7c8b91c94e
commit ec7541e840
6 changed files with 479 additions and 7 deletions

View File

@@ -2,3 +2,121 @@
Core
====
The :ref:`WpCore <core_api>` API is mostly transparent to lua, as the core
object is not exposed to the scripts.
For some functionality, though, the following static functions are exposed.
.. function:: Core.get_info()
Returns a table with information about the core. The table contains
the following fields:
=========== ===========
Field Contains
=========== ===========
cookie The value of :c:func:`wp_core_get_remote_cookie`
name The value of :c:func:`wp_core_get_remote_name`
user_name The value of :c:func:`wp_core_get_remote_user_name`
host_name The value of :c:func:`wp_core_get_remote_host_name`
version The value of :c:func:`wp_core_get_remote_version`
properties The value of :c:func:`wp_core_get_remote_properties`
=========== ===========
:returns: information about the core
:rtype: table
.. function:: Core.idle_add(callback)
Binds :c:func:`wp_core_idle_add_closure`
Schedules to call *callback* the next time that the event loop will be idle
:param function callback: the function to call; the function takes no
arguments and must return true/false, as it is a GSourceFunc:
return true to have the event loop call it again the next time it is idle,
false to stop calling it and remove the associated GSource
:returns: the GSource associated with this idle callback
:rtype: GSource, see :func:`GSource.destroy`
.. function:: Core.timeout_add(timeout_ms, callback)
Binds :c:func:`wp_core_timeout_add_closure`
Schedules to call *callback* after *timeout_ms* milliseconds
:param function callback: the function to call; the function takes no
arguments and must return true/false, as it is a GSourceFunc:
return true to have the event loop call it again periodically every
*timeout_ms* milliseconds, false to stop calling it and remove the
associated GSource
:returns: the GSource associated with this idle callback
:rtype: GSource, see :func:`GSource.destroy`
.. function:: GSource.destroy(self)
For the purpose of working with :func:`Core.idle_add` and
:func:`Core.timeout_add`, the GSource object that is returned by those
functions contains this method.
Call this method to destroy the source, so that the associated callback
is not called again. This can be used to stop a repeating timer callback
or just to abort some idle operation
This method binds *g_source_destroy*
.. function:: Core.sync(callback)
Binds :c:func:`wp_core_sync`
Calls *callback* after synchronizing the transaction state with PipeWire
:param function callback: a function to be called after syncing with PipeWire;
the function takes one argument that will be an error string, if something
went wrong and nil otherwise and returns nothing
.. function:: Core.quit()
Quits the current *wpexec* process
.. note::
This can only be called when the script is running in *wpexec*;
if it is running in the main WirePlumber daemon, it will print
a warning and do nothing
.. function:: Core.require_api(..., callback)
Ensures that the specified API plugins are loaded.
API plugins are plugins that provide some API extensions for use in scripts.
These plugins must always have their name end in "-api" and the names
specified here must not have the "-api" extension.
For instance, the "mixer-api" module provides an API to change volume/mute
controls from scripts, via action signals. It can be used like this:
.. code-block:: lua
Core.require_api("mixer", function(mixer)
-- get the volume of node 35
local volume = mixer:call("get-volume", 35)
-- the return value of "get-volume" is a GVariant(a{sv}),
-- which gets translated to a Lua table
Debug.dump_table(volume)
end)
See also the example in :func:`GObject.call`
.. note::
This can only be called when the script is running in *wpexec*;
if it is running in the main WirePlumber daemon, it will print
a warning and do nothing
:param strings ...: a list of string arguments, which specify the names of
the api plugins to load, if they are not already loaded
:param callback: the function to call after the plugins have been loaded;
this function takes references to the plugins as parameters

View File

@@ -10,7 +10,7 @@ to have a basic understanding of GObject's basic concepts, such as signals and
properties.
Properties
..........
----------
All GObjects have the ability to have `properties`_.
In C we normally use `g_object_get`_ to retrieve them and `g_object_set`_
@@ -37,28 +37,259 @@ Writable properties can also be set in a similar fashion:
mixer["scale"] = "cubic"
Signals
.......
-------
GObjects also have a generic mechanism to deliver events to external callbacks.
These events are called `signals`_
These events are called `signals`_.
To connect to a signal and handle it, you may use the *connect* method:
All lua objects that wrap a GObject contain the following methods:
.. function:: connect(detailed_signal, callback)
.. function:: GObject.connect(self, detailed_signal, callback)
Connects the signal to a callback. When the signal is emitted by the
underlying object, the callback will be executed.
The signature of the callback is expected to match the signature of the
signal, with the first parameter being the object itself.
**Example:**
.. code-block:: lua
-- connects the "bound" signal from WpProxy to a callback
local proxy = function_that_returns_a_wp_proxy()
proxy:connect("bound", function(p, id)
print("Proxy " .. tostring(p) .. " bound to " .. tostring(id))
end)
In this example, the ``p`` variable in the callback is the ``proxy`` object,
while ``id`` is the first parameter of the *"bound"* signal, as documented
in :c:struct:`WpProxy`
:param detailed_signal: the signal name to listen to
(of the form "signal-name::detail")
:param callback: a lua function that will be called when the signal is emitted
.. function:: call(action_signal, ...)
Signals may also be used as a way to have dynamic methods on objects. These
signals are meant to be called by external code and not handled. These signals
are called **action signals**.
You may call an action signal using the *call* method:
.. function:: GObject.call(self, action_signal, ...)
Calls an action signal on this object.
**Example:**
.. code-block:: lua
Core.require_api("default-nodes", "mixer", function(...)
local default_nodes, mixer = ...
-- "get-default-node" and "get-volume" are action signals of the
-- "default-nodes-api" and "mixer-api" plugins respectively
local id = default_nodes:call("get-default-node", "Audio/Sink")
local volume = mixer:call("get-volume", id)
-- the return value of "get-volume" is a GVariant(a{sv}),
-- which gets translated to a Lua table
Debug.dump_table(volume)
end)
:param action_signal: the signal name to call
:param ...: a list of arguments that will be passed to the signal
:returns: the return value of the action signal, if any
Type conversions
----------------
When working with GObject properties and signals, variables need to be
converted from C types to Lua types and vice versa. The following tables
list the type conversions that happen automatically:
C to Lua
^^^^^^^^
Conversion from C to lua is based on the C type.
================================ ===============================================
C Lua
================================ ===============================================
gchar, guchar, gint, guint integer
glong, gulong, gint64, guint64 integer
gfloat, gdouble number
gboolean boolean
gchar * string
gpointer lightuserdata
WpProperties * table (keys: string, values: string)
enum string containing the nickname (short name) of
the enum, or integer if the enum is not
registered with GType
flags integer (as in C)
GVariant * a native type, see below
other GObject, GInterface userdata holding reference to the object
other GBoxed userdata holding reference to the object
================================ ===============================================
Lua to C
^^^^^^^^
Conversion from Lua to C is based on the expected type in C.
============================== ==================================================
Expecting Lua
============================== ==================================================
gchar, guchar, gint, guint, convertible to integer
glong, gulong, gint64, guint64 convertible to integer
gfloat, gdouble convertible to number
gboolean convertible to boolean
gchar * convertible to string
gpointer must be lightuserdata
WpProperties * must be table (keys: string, values: convertible
to string)
enum must be string holding the nickname of the enum,
or convertible to integer
flags convertible to integer
GVariant * see below
other GObject, GInterface must be userdata holding a compatible GObject type
other GBoxed must be userdata holding the same GBoxed type
============================== ==================================================
GVariant to Lua
^^^^^^^^^^^^^^^
============================= =============================================
GVariant Lua
============================= =============================================
NULL or G_VARIANT_TYPE_UNIT nil
G_VARIANT_TYPE_INT16 integer
G_VARIANT_TYPE_INT32 integer
G_VARIANT_TYPE_INT64 integer
G_VARIANT_TYPE_UINT16 integer
G_VARIANT_TYPE_UINT32 integer
G_VARIANT_TYPE_UINT64 integer
G_VARIANT_TYPE_DOUBLE number
G_VARIANT_TYPE_BOOLEAN boolean
G_VARIANT_TYPE_STRING string
G_VARIANT_TYPE_VARIANT converted recursively
G_VARIANT_TYPE_DICTIONARY table (keys & values converted recursively)
G_VARIANT_TYPE_ARRAY table (children converted recursively)
============================= =============================================
Lua to GVariant
^^^^^^^^^^^^^^^
Conversion from Lua to GVariant is based on the lua type and is quite limited.
There is no way to recover an array, for instance, because there is no way
in Lua to tell if a table contains an array or a dictionary. All Lua tables
are converted to dictionaries and integer keys are converted to strings.
========= ================================
Lua GVariant
========= ================================
nil G_VARIANT_TYPE_UNIT
boolean G_VARIANT_TYPE_BOOLEAN
integer G_VARIANT_TYPE_INT64
number G_VARIANT_TYPE_DOUBLE
string G_VARIANT_TYPE_STRING
table G_VARIANT_TYPE_VARDICT (a{sv})
========= ================================
Closures
--------
When a C function is expecting a GClosure, in Lua it is possible to pass
a Lua function directly. The function is then wrapped into a custom GClosure.
When this GClosure is invalidated, the reference to the Lua function is dropped.
Similarly, when the lua engine is stopped, all the GClosures that were
created by this engine are invalidated.
Reference counting
------------------
GObject references in Lua always hold a reference to the underlying GObject.
When moving this reference around to other variables in Lua, the underlying
GObject reference is shared, but Lua reference counts the wrapper "userdata"
object.
.. code-block:: lua
-- creating a new FooObject instance; obj holds the GObject reference
local obj = FooObject()
-- GObject reference is dropped and FooObject is finalized
obj = nil
.. code-block:: lua
-- creating a new FooObject instance; obj holds the GObject reference
local obj = FooObject()
function store_global(o)
-- o is now stored in the global 'obj_global' variable
-- the GObject ref count is still 1
obj_global = o
end
-- obj userdata reference is passed to o, the GObject ref count is still 1
store_global(obj)
-- userdata reference dropped from obj, the GObject is still alive
obj = nil
-- userdata reference dropped from obj_global,
-- the GObject ref is dropped and FooObject is finalized
obj_global = nil
.. note::
When assigning a variable to nil, Lua may not immediately drop
the reference of the underlying object. This is because Lua uses a garbage
collector and goes through all the unreferenced objects to cleanup when
the garbage collector runs.
When a GObject that is already referenced in Lua re-appears somewhere else
through calling some API or because of a callback from C, a new reference is
added on the GObject.
.. code-block:: lua
-- ObjectManager is created in Lua, om holds 1 ref
local om = ObjectManager(...)
om:connect("objects-changed", function (om)
-- om in this scope is a local function argument that was created
-- by the signal's closure marshaller and holds a second reference
-- to the ObjectManager
do_some_stuff()
-- this second reference is dropped when the function goes out of scope
end)
.. danger::
Because Lua variables hold strong references to GObjects, it is dangerous
to create closures that reference such variables, because these closures
may create reference loops and **leak** objects
.. code-block:: lua
local om = ObjectManager(...)
om:connect("objects-changed", function (obj_mgr)
-- using 'om' here instead of the local 'obj_mgr'
-- creates a dangerous reference from the closure to 'om'
for obj in om:iterate() do
do_stuff(obj)
end
end)
-- local userdata reference dropped, but the GClosure that was generated
-- from the above function is still holding a reference and keeps
-- the ObjectManager alive; the GClosure is referenced by the ObjectManager
-- because of the signal connection, so the ObjectManager is leaked
om = nil
.. _GObject: https://developer.gnome.org/gobject/stable/
.. _properties: https://developer.gnome.org/gobject/stable/gobject-properties.html

View File

@@ -0,0 +1,68 @@
.. _lua_introduction:
Introduction
============
`Lua <https://www.lua.org/>`_ is a powerful, efficient, lightweight,
embeddable scripting language.
WirePlumber uses `Lua version 5.3 <https://www.lua.org/versions.html>`_ to
implement its engine. Another, more recent, version may be considered
in the future, but do note that different Lua versions are not API-compatible
and that will likely also affect WirePlumber's Lua API.
There are currently two uses for Lua in WirePlumber:
- To implement the scripting engine
- To implement lua-based :ref:`config files <daemon-configuration>`
This section is only documenting the API of the **scripting engine**
Lua Reference
-------------
If you are not familiar with the Lua language and its API, please refer to
the `Lua 5.3 Reference Manual <https://www.lua.org/manual/5.3/manual.html>`_
Sandbox
-------
WirePlumber's scripting engine sandboxes the lua scripts to a safe environment.
In this environment, the following rules apply:
- Scripts are isolated from one another; global variables in one script
are not visible from another, even though they are actually executed in
the same ``lua_State``
- Tables that hold API methods are not writable. While this may sound strange,
standard Lua allows you to change standard API, for instance
``string.format = rogue_format`` is valid outside the sandbox.
WirePlumber does not allow that.
- The standard Lua API is limited only to safe functions. Functions that
interact with the file system, the lua modules system, the lua state,
the process's state, etc are **not** allowed.
Here is a full list of Lua functions (and API tables) that are exposed:
.. literalinclude:: ../../../lib/wplua/sandbox.lua
:language: lua
:lines: 40-55
- Object methods are not exposed in public tables. To call an object method
you must use the method call syntax of Lua, i.e. ``object:method(params)``
The following, for instance, is **not** valid:
.. code-block:: lua
-- this will cause an exception
local node = ...
Node.send_command(node, "Suspend")
The correct form is this:
.. code-block:: lua
local node = ...
node:send_command("Suspend")

View File

@@ -0,0 +1,51 @@
.. _lua_log_api:
Debug Logging
=============
.. function:: Log.warning(object, message)
Logs a warning message, like :c:macro:`wp_warning_object`
:param GObject object: optional object to associate the message with; you
may skip this and just start with the *message* as the first parameter
:param string message: the warning message to log
.. function:: Log.message(object, message)
Logs a normal message, like :c:macro:`wp_message_object`
:param GObject object: optional object to associate the message with; you
may skip this and just start with the *message* as the first parameter
:param string message: the normal message to log
.. function:: Log.info(object, message)
Logs a info message, like :c:macro:`wp_info_object`
:param GObject object: optional object to associate the message with; you
may skip this and just start with the *message* as the first parameter
:param string message: the info message to log
.. function:: Log.debug(object, message)
Logs a debug message, like :c:macro:`wp_debug_object`
:param GObject object: optional object to associate the message with; you
may skip this and just start with the *message* as the first parameter
:param string message: the debug message to log
.. function:: Log.trace(object, message)
Logs a trace message, like :c:macro:`wp_trace_object`
:param GObject object: optional object to associate the message with; you
may skip this and just start with the *message* as the first parameter
:param string message: the trace message to log
.. function:: Debug.dump_table(t)
Prints a table with all its contents, recursively, to stdout
for debugging purposes
:param table t: any table

View File

@@ -2,6 +2,8 @@
sphinx_files += files(
'lua_core_api.rst',
'lua_gobject.rst',
'lua_introduction.rst',
'lua_log_api.rst',
'lua_object_api.rst',
'lua_object_interest_api.rst',
'lua_object_manager_api.rst',