07070100000000000081A4000000000000000000000001656CC35F000000DE000000000000000000000000000000000000002100000000wireplumber-0.4.17/.editorconfig  root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[Makefile]
indent_style = tab
indent_size = 8

[*.py]
indent_style = space
indent_size = 4
  07070100000001000081A4000000000000000000000001656CC35F00000033000000000000000000000000000000000000001E00000000wireplumber-0.4.17/.gitignore build/
subprojects/lua-*
subprojects/packagecache/
 07070100000002000081A4000000000000000000000001656CC35F00002122000000000000000000000000000000000000002200000000wireplumber-0.4.17/.gitlab-ci.yml stages:
  - container
  - container_coverity
  - build
  - analysis
  - pages

variables:
  FDO_UPSTREAM_REPO: 'pipewire/wireplumber'
  # change to build against a different tag or branch of pipewire
  PIPEWIRE_HEAD: 'master'

# ci-templates as of Mar 24th 2023
.templates_sha: &templates_sha dd90ac0d7a03b574eb4f18d7358083f0c97825f3

include:
  - project: 'freedesktop/ci-templates'
    ref: *templates_sha
    file: '/templates/fedora.yml'
  - project: 'freedesktop/ci-templates'
    ref: *templates_sha
    file: '/templates/ubuntu.yml'
  - project: 'freedesktop/ci-templates'
    ref: *templates_sha
    file: '/templates/alpine.yml'

.fedora:
  variables:
    # Update this tag when you want to trigger a rebuild
    FDO_DISTRIBUTION_TAG: '2023-03-24.1'
    FDO_DISTRIBUTION_VERSION: '37'
    # findutils: used by the .build script below
    # dbus-devel: required by pipewire
    # dbus-daemon: required by GDBus unit tests
    # pip, doxygen: required for documentation
    # ShellCheck, diffutils: required by the CI
    FDO_DISTRIBUTION_PACKAGES: >-
      findutils
      gcc
      gcc-c++
      git
      meson
      glib2-devel
      gobject-introspection-devel
      dbus-devel
      dbus-daemon
      python3-pip
      doxygen
      ShellCheck
      diffutils
    # install Sphinx and Breathe to generate documentation
    # also install glib2-doc (required to make documentation links to GLib work)
    # manually, to remove the 'tsflags=nodocs' flag that is enabled by default
    # in the fedora docker image
    FDO_DISTRIBUTION_EXEC: >-
      pip3 install lxml Sphinx sphinx-rtd-theme breathe ;
      dnf -y install glib2-doc --setopt='tsflags='

.ubuntu:
  variables:
    # Update this tag when you want to trigger a rebuild
    FDO_DISTRIBUTION_TAG: '2023-03-24.1'
    FDO_DISTRIBUTION_VERSION: '20.04'
    FDO_DISTRIBUTION_PACKAGES: >-
      debhelper-compat
      findutils
      git
      ninja-build
      pkg-config
      python3-pip
      dbus
      libdbus-1-dev
      libglib2.0-dev
      liblua5.3-dev
      libgirepository1.0-dev
      doxygen
      python3-lxml
    FDO_DISTRIBUTION_EXEC: >-
      pip3 install meson

.alpine:
  variables:
    # Update this tag when you want to trigger a rebuild
    FDO_DISTRIBUTION_TAG: '2023-03-24.1'
    FDO_DISTRIBUTION_VERSION: '3.15'
    FDO_DISTRIBUTION_PACKAGES: >-
      dbus
      dbus-dev
      doxygen
      elogind-dev
      findutils
      g++
      gcc
      git
      glib-dev
      gobject-introspection-dev
      lua5.4-dev
      meson
      py3-lxml
      samurai

.coverity:
  variables:
    FDO_REPO_SUFFIX: 'coverity'
    FDO_BASE_IMAGE: registry.freedesktop.org/$FDO_UPSTREAM_REPO/fedora/$FDO_DISTRIBUTION_VERSION:$FDO_DISTRIBUTION_TAG
    FDO_DISTRIBUTION_PACKAGES: >-
      curl
    FDO_DISTRIBUTION_EXEC: >-
      mkdir -p /opt ;
      cd /opt ;
      curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/cxx/linux64
      --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN ;
      tar xf /tmp/cov-analysis-linux64.tgz ;
      mv cov-analysis-linux64-* coverity ;
      rm /tmp/cov-analysis-linux64.tgz
  only:
    variables:
      - $COVERITY

.not_coverity:
  except:
    variables:
      - $COVERITY

.build:
  before_script:
    # setup the environment
    - export BUILD_ID="$CI_JOB_ID"
    - export PREFIX="$PWD/prefix-$BUILD_ID"
    - export PW_BUILD_DIR="$PWD/build-pipewire-$BUILD_ID"
    - |
      if [ -n "$FDO_CI_CONCURRENT" ]; then
         NINJA_ARGS="-j$FDO_CI_CONCURRENT $NINJA_ARGS"
         export NINJA_ARGS
      fi
    # Build pipewire
    # Fedora also ships that, but without the test plugins that we need...
    - git clone --depth=1 --branch="$PIPEWIRE_HEAD"
        https://gitlab.freedesktop.org/pipewire/pipewire.git
    - meson "$PW_BUILD_DIR" pipewire --prefix="$PREFIX"
        -Dpipewire-alsa=disabled -Dpipewire-jack=disabled
        -Dalsa=disabled -Dv4l2=disabled -Djack=disabled -Dbluez5=disabled
        -Dvulkan=disabled -Dgstreamer=disabled -Dsystemd=disabled
        -Ddocs=disabled -Dman=disabled -Dexamples=disabled -Dpw-cat=disabled
        -Dsdl2=disabled -Dsndfile=disabled -Dlibpulse=disabled -Davahi=disabled
        -Decho-cancel-webrtc=disabled -Dsession-managers=[]
        -Dvideotestsrc=enabled -Daudiotestsrc=enabled -Dtest=enabled
    - ninja $NINJA_ARGS -C "$PW_BUILD_DIR" install
    # misc environment only for wireplumber
    - export WP_BUILD_DIR="$PWD/build-wireplumber-$BUILD_ID"
    - export PKG_CONFIG_PATH="$(dirname $(find "$PREFIX" -name 'libpipewire-*.pc')):$PKG_CONFIG_PATH"
  script:
    # Build wireplumber
    - meson "$WP_BUILD_DIR" . --prefix="$PREFIX" $BUILD_OPTIONS
    - cd "$WP_BUILD_DIR"
    - ninja $NINJA_ARGS
    - ninja $NINJA_ARGS test
    - ninja $NINJA_ARGS install
  artifacts:
    name: wireplumber-$CI_COMMIT_SHA
    when: always
    paths:
      - build-*/meson-logs
      - prefix-*

container_fedora:
  extends:
    - .fedora
    - .fdo.container-build@fedora
  stage: container
  variables:
    GIT_STRATEGY: none

container_ubuntu:
  extends:
    - .ubuntu
    - .fdo.container-build@ubuntu
  stage: container
  variables:
    GIT_STRATEGY: none

container_alpine:
  extends:
    - .alpine
    - .fdo.container-build@alpine
  stage: container
  variables:
    GIT_STRATEGY: none

container_coverity:
  extends:
    - .fedora
    - .coverity
    - .fdo.container-build@fedora
  stage: container_coverity
  variables:
    GIT_STRATEGY: none

build_on_fedora_with_docs:
  extends:
    - .fedora
    - .not_coverity
    - .fdo.distribution-image@fedora
    - .build
  stage: build
  variables:
    BUILD_OPTIONS: -Dintrospection=enabled -Ddoc=enabled -Dsystem-lua=false

build_on_fedora_no_docs:
  extends:
    - .fedora
    - .not_coverity
    - .fdo.distribution-image@fedora
    - .build
  stage: build
  variables:
    BUILD_OPTIONS: -Dintrospection=enabled -Ddoc=disabled -Dsystem-lua=false

build_on_ubuntu_with_gir:
  extends:
    - .ubuntu
    - .not_coverity
    - .fdo.distribution-image@ubuntu
    - .build
  stage: build
  variables:
    BUILD_OPTIONS: -Dintrospection=enabled -Ddoc=disabled -Dsystem-lua=true

build_on_ubuntu_no_gir:
  extends:
    - .ubuntu
    - .not_coverity
    - .fdo.distribution-image@ubuntu
    - .build
  stage: build
  variables:
    BUILD_OPTIONS: -Dintrospection=disabled -Ddoc=disabled -Dsystem-lua=true

build_on_alpine:
  extends:
    - .alpine
    - .not_coverity
    - .fdo.distribution-image@alpine
    - .build
  stage: build
  variables:
    BUILD_OPTIONS: -Dintrospection=enabled -Ddoc=disabled -Dsystem-lua=true -Delogind=disabled

build_with_coverity:
  extends:
    - .fedora
    - .coverity
    - .fdo.suffixed-image@fedora
    - .build
  stage: analysis
  script:
    - export PATH=/opt/coverity/bin:$PATH
    - meson "$WP_BUILD_DIR" . --prefix="$PREFIX"
        -Dintrospection=disabled -Ddoc=disabled
    - cov-configure --config coverity_conf.xml
        --comptype gcc --compiler cc --template
        --xml-option=append_arg@C:--ppp_translator
        --xml-option=append_arg@C:"replace/GLIB_(DEPRECATED|AVAILABLE)_ENUMERATOR_IN_\d_\d\d(_FOR\(\w+\)|)\s+=/ ="
    - cov-build --dir cov-int --config coverity_conf.xml ninja $NINJA_ARGS -C "$WP_BUILD_DIR"
    - tar caf wireplumber.tar.gz cov-int
    - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME
        --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL
        --form file=@wireplumber.tar.gz --form version="`git describe --tags`"
        --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID "
  artifacts:
    name: wireplumber-coverity-$CI_COMMIT_SHA
    when: always
    paths:
      - build-*/meson-logs
      - cov-int/build-log.txt

shellcheck:
  extends:
    - .fedora
    - .not_coverity
    - .fdo.distribution-image@fedora
  stage: analysis
  script:
    - shellcheck $(git grep -l "#\!/.*bin/.*sh")

linguas_check:
  extends:
    - .fedora
    - .not_coverity
    - .fdo.distribution-image@fedora
  stage: analysis
  script:
    - cd po
    - cat LINGUAS | sort > LINGUAS.sorted
    - ls *.po | sed s/.po//g | sort > LINGUAS.new
    - diff -u LINGUAS.sorted LINGUAS.new
    - rm -f LINGUAS.*

pages:
  extends:
    - .not_coverity
  stage: pages
  dependencies:
    - build_on_fedora_with_docs
  script:
    - mkdir public
    - cp -R prefix-*/share/doc/wireplumber/html/* public/
  artifacts:
    paths:
      - public
  only:
    - master
  07070100000003000081A4000000000000000000000001656CC35F00000446000000000000000000000000000000000000001B00000000wireplumber-0.4.17/LICENSE    Copyright © 2019-2021 Collabora Ltd.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
  07070100000004000081A4000000000000000000000001656CC35F000001AE000000000000000000000000000000000000001C00000000wireplumber-0.4.17/Makefile   WIREPLUMBER_DEBUG ?= 3

all:
	ninja -C build

install:
	ninja -C build install

uninstall:
	ninja -C build uninstall

clean:
	ninja -C build clean

run: all
	WIREPLUMBER_DEBUG=$(WIREPLUMBER_DEBUG) \
	./wp-uninstalled.sh $(DBG) wireplumber

test:
	meson test -C build

test_valgrind:
	meson test -C build --setup=valgrind

gdb:
	$(MAKE) run DBG=gdb

valgrind:
	G_SLICE=always-malloc \
	$(MAKE) run DBG="valgrind --leak-check=full"
  07070100000005000081A4000000000000000000000001656CC35F00009F16000000000000000000000000000000000000001C00000000wireplumber-0.4.17/NEWS.rst   WirePlumber 0.4.17
~~~~~~~~~~~~~~~~~~

Fixes:

  - Fixed a reference counting issue in the object managers that could cause
    crashes due to memory corruption (#534)

  - Fixed an issue with filters linking to wrong targets, often with two sets
    of links (#536)

  - Fixed a crash in the endpoints policy that would show up when log messages
    were enabled at level 3 or higher

Past releases
~~~~~~~~~~~~~

WirePlumber 0.4.16
..................

Additions:

  - Added a new "sm-objects" script that allows loading objects on demand
    via metadata entries that describe the object to load; this can be used to
    load pipewire modules, such as filters or network sources/sinks, on demand

  - Added a mechanism to override device profile priorities in the configuration,
    mainly as a way to re-prioritize Bluetooth codecs, but this also can be used
    for other devices

  - Added a mechanism in the endpoints policy to allow connecting filters
    between a certain endpoint's virtual sink and the device sink; this is
    specifically intended to allow plugging a filter-chain to act as equalizer
    on the Multimedia endpoint

  - Added wp_core_get_own_bound_id() method in WpCore

Changes:

  - PipeWire 0.3.68 is now required

  - policy-dsp now has the ability to hide hardware nodes behind the DSP sink
    to prevent hardware misuse or damage

  - JSON parsing in Lua now allows keys inside objects to be without quotes

  - Added optional argument in the Lua JSON parse() method to limit recursions,
    making it possible to partially parse a JSON object

  - It is now possible to pass ``nil`` in Lua object constructors that expect an
    optional properties object; previously, omitting the argument was the only
    way to skip the properties

  - The endpoints policy now marks the endpoint nodes as "passive" instead of
    marking their links, adjusting for the behavior change in PipeWire 0.3.68

  - Removed the "passive" property from si-standard-link, since only nodes are
    marked as passive now

Fixes:

  - Fixed the ``wpctl clear-default`` command to completely clear all the
    default nodes state instead of only the last set default

  - Reduced the amount of globals that initially match the interest in the
    object manager

  - Used an idle callback instead of pw_core_sync() in the object manager to
    expose tmp globals

WirePlumber 0.4.15
..................

Additions:

  - A new "DSP policy" module has been added; its purpose is to automatically
    load a filter-chain when a certain hardware device is present, so that
    audio always goes through this software DSP before reaching the device.
    This is mainly to support Apple M1/M2 devices, which require a software
    DSP to be always present

  - WpImplModule now supports loading module arguments directly from a SPA-JSON
    config file; this is mainly to support DSP configuration for Apple M1/M2
    and will likely be reworked for 0.5

  - Added support for automatically combining Bluetooth LE Audio device sets
    (e.g. pairs of earbuds) (!500)

  - Added command line options in ``wpctl`` to display device/node names and
    nicknames instead of descriptions

  - Added zsh completions file for ``wpctl``

  - The device profile selection policy now respects the ``device.profile``
    property if it is set on the device; this is useful to hand-pick a profile
    based on static configuration rules (alsa_monitor.rules)

Changes/Fixes:

  - Linking policy now sends an error to the client before destroying the node,
    if it determines that the node cannot be linked to any target; this fixes
    error reporting on the client side

  - Fixed a crash in suspend-node that could happen when destroying virtual
    sinks that were loaded from another process such as pw-loopback (#467)

  - Virtual machine default period size has been bumped to 1024 (#507)

  - Updated bluez5 default configuration, using ``bluez5.roles`` instead of
    ``bluez5.headset-roles`` now (!498)

  - Disabled Bluetooth autoconnect by default (!514)

  - Removed ``RestrictNamespaces`` option from the systemd services in order to
    allow libcamera to load sandboxed IPA modules (#466)

  - Fixed a JSON encoding bug with empty strings (#471)

  - Lua code can now parse strings without quotes from SPA-JSON

  - Added some missing `\since` annotations and made them show up in the
    generated gobject-introspection file, to help bindings generators

WirePlumber 0.4.14
..................

Additions:

  - Added support for managing Bluetooth-MIDI, complimenting the parts that
    were merged in PipeWire recently (!453)

  - Added a default volume configuration option for streams whose volume
    has never been saved before; that allows starting new streams at a lower
    volume than 100% by default, if desired (!480)

  - Added support for managing link errors and propagating them to the
    client(s) involved. This allows better error handling on the application
    side in case a format cannot be negotiated - useful in video streams
    (see !484, pipewire#2935)

  - snd_aloop devices are now described as being "Loopback" devices
    (pipewire#2214)

  - ALSA nodes in the pro audio profile now get increased graph priority, so
    that they are more likely to become the driver in the graph

  - Added support for disabling libcamera nodes & devices with ``node.disabled``
    and ``device.disabled``, like it works for ALSA and V4L2 (#418)

WirePlumber 0.4.13
..................

Additions:

  - Added bluetooth SCO (HSP/HFP) hardware offload support, together with an
    example script that enables this functionality on the PinePhone

  - Encoded audio (mp3, aac, etc...) can now be passed through, if this mode is
    supported by both the application and the device

  - The v4l2 monitor now also respects the ``node.disabled`` and
    ``device.disabled`` properties inside rules

  - Added "Firefox Developer Edition" to the list of apps that are allowed to
    trigger a bluetooth profile auto-switch (#381)

  - Added support in the portal access script to allow newly plugged cameras
    to be immediately visible to the portal apps

Fixes:

  - Worked around an issue that would prevent streams from properly linking
    when using effects software like EasyEffects and JamesDSP (!450)

  - Fixed destroying pavucontrol-qt monitor streams after the node that was
    being monitored is destroyed (#388)

  - Fixed a crash in the alsa.lua monitor that could happen when a disabled
    device was removed and re-added (#361)

  - Fixed a rare crash in the metadata object (#382)

  - Fixed a bug where a restored node target would override the node target
    set by the application on the node's properties (#335)

Packaging:

  - Added build options to compile wireplumber's library, daemon and tools
    independently

  - Added a build option to disable unit tests that require the dbus daemon

  - Stopped using fakesink/fakesrc in the unit tests to be able to run them
    on default pipewire installations. Compiling the spa ``test`` plugin is no
    longer necessary

  - Added pkg-config and header information in the gir file

WirePlumber 0.4.12
..................

Changes:

  - WirePlumber now maintains a stack of previously configured default nodes and
    prioritizes to one of those when the actively configured default node
    becomes unavailable, before calculating the next default using priorities
    (see !396)

  - Updated bluetooth scripts to support the name changes that happened in
    PipeWire 0.3.59 and also support the experimental Bluetooth LE functionality

  - Changed the naming of bluetooth nodes to not include the profile in it;
    this allows maintaining existing links when switching between a2dp and hfp

  - The default volume for new outputs has changed to be 40% in cubic scale
    (= -24 dB) instead of linear (= 74% cubic / -8 dB) that it was before

  - The default volume for new inputs has changed to be 100% rather than
    following the default for outputs

  - Added ``--version`` flag on the wireplumber executable (#317)

  - Added ``--limit`` flag on ``wpctl set-volume`` to limit the higher volume
    that can be set (useful when incrementing volume with a keyboard shortcut
    that calls into wpctl)

  - The properties of the alsa midi node can now be set in the config files

Fixes:

  - Fixed a crash in lua code that would happen when running in a VM (#303)

  - Fixed a crash that would happen when re-connecting to D-Bus (#305)

  - Fixed a mistake in the code that would cause device reservation not to
    work properly

  - Fixed ``wpctl clear-default`` to accept 0 as a valid setting ID

  - Fixed the logic of choosing the best profile after the active profile
    of a device becomes unavailable (#329)

  - Fixed a regression that would cause PulseAudio "corked" streams to not
    properly link and cause busy loops

  - Fixed an issue parsing spa-json objects that have a nested object as the
    value of their last property

WirePlumber 0.4.11
..................

Changes:

  - The libcamera monitor is now enabled by default, so if the libcamera source
    is enabled in PipeWire, cameras discovered with the libcamera API will be
    available out of the box. This is safe to use alongside V4L2, as long as
    the user does not try to use the same camera over different APIs at the same
    time

  - Libcamera and V4L2 nodes now get assigned a ``priority.session`` number;
    V4L2 nodes get a higher priority by default, so the default camera is going
    to be /dev/video0 over V4L2, unless changed with ``wpctl``

  - Libcamera nodes now get a user-friendly description based on their location
    (ex. built-in front camera). Additionally, V4L2 nodes now have a "(V4L2)"
    string appended to their description in order to be distinguished from
    the libcamera ones

  - 50-alsa-config.lua now has a section where you can set properties that
    will only be applied if WirePlumber is running in a virtual machine. By
    default it now sets ``api.alsa.period-size = 256`` and
    ``api.alsa.headroom = 8192`` (#162, #134)

Fixes:

  - The "enabled" properties in the config files are now "true" by default
    when they are not defined. This fixes backwards compatibility with older
    configuration files (#254)

  - Fixed device name deduplication in the alsa monitor, when device reservation
    is enabled (#241)

  - Reverted a previous fix that makes it possible again to get a glitch when
    changing default nodes while also changing the profile (GNOME Settings).
    The fix was causing other problems and the issue will be addressed
    differently in the future (#279)

  - Fixed an issue that would prevent applications from being moved to a
    recently plugged USB headset (#293)

  - Fixed an issue where wireplumber would automatically link control ports,
    if they are enabled, to audio ports, effectively breaking audio (#294)

  - The policy now always considers the profile of a device that was previously
    selected by the user, if it is available, when deciding which profile to
    activate (#179). This may break certain use cases (see !360)

  - A few documentation fixes

Tools:

  - wpctl now has a ``get-volume`` command for easier scripting of volume controls

  - wpctl now supports relative steps and percentage-based steps in ``set-volume``

  - wpctl now also prints link states

  - wpctl can now ``inspect`` metadata objects without showing critical warnings

Library:

  - A new WpDBus API was added to maintain a single D-Bus connection among
    modules that need one

  - WpCore now has a method to get the virtual machine type, if WirePlumber
    is running in a virtual machine

  - WpSpaDevice now has a ``wp_spa_device_new_managed_object_iterator()`` method

  - WpSpaJson now has a ``wp_spa_json_to_string()`` method that returns a newly
    allocated string with the correct size of the string token

  - WpLink now has a ``WP_LINK_FEATURE_ESTABLISHED`` that allows the caller to
    wait until the link is in the PAUSED or ACTIVE state. This transparently
    now enables watching links for negotiation or allocation errors and failing
    gracefully instead of keeping dead link objects around (#294)

Misc:

  - The Lua subproject was bumped to version 5.4.4

WirePlumber 0.4.10
..................

Changes:

  - Added i18n support to be able to translate some user-visible strings

  - wpctl now supports using ``@DEFAULT_{AUDIO_,VIDEO_,}{SINK,SOURCE}@`` as ID,
    almost like pactl. Additionally, it supports a ``--pid`` flag for changing
    volume and mute state by specifying a process ID, applying the state to all
    nodes of a specific client process

  - The Lua engine now supports loading Lua libraries. These can be placed
    either in the standard Lua libraries path or in the "lib" subdirectory
    of WirePlumber's "scripts" directory and can be loaded with ``require()``

  - The Lua engine's sandbox has been relaxed to allow more functionality
    in scripts (the debug & coroutine libraries and some other previously
    disabled functions)

  - Lua scripts are now wrapped in special WpPlugin objects, allowing them to
    load asynchronously and declare when they have finished their loading

  - Added a new script that provides the same functionality as
    module-fallback-sink from PipeWire, but also takes endpoints into account
    and can be customized more easily. Disabled by default for now to avoid
    conflicts

Policy:

  - Added an optional experimental feature that allows filter-like streams
    (like echo-cancel or filter-node) to match the channel layout of the
    device they connect to, on both sides of the filter; that means that if,
    for instance, a sink has 6 channels and the echo-cancel's source stream
    is linked to that sink, then the virtual sink presented by echo-cancel
    will also be configured to the same 6 channels layout. This feature needs
    to be explicitly enabled in the configuration ("filter.forward-format")

  - filter-like streams (filter-chain and such) no longer follow the default
    sink when it changes, like in PulseAudio

Fixes:

  - The suspend-node script now also suspends nodes that go into the "error"
    state, allowing them to recover from errors without having to restart
    WirePlumber

  - Fixed a crash in mixer-api when setting volume with channelVolumes (#250)

  - logind module now watches only for user state changes, avoiding errors when
    machined is not running

Misc:

  - The configuration files now have comments mentioning which options need to
    be disabled in order to run WirePlumber without D-Bus

  - The configuration files now have properties to enable/disable the monitors
    and other sections, so that it is possible to disable them by dropping in
    a file that just sets the relevant property to false

  - ``setlocale()`` is now called directly instead of relying on ``pw_init()``

  - WpSpaJson received some fixes and is now used internally to parse
    configuration files

  - More applications were added to the bluetooth auto-switch apps whitelist

WirePlumber 0.4.9
.................

Fixes:

  - restore-stream no longer crashes if properties for it are not present
    in the config (#190)

  - spa-json no longer crashes on non-x86 architectures

  - Fixed a potential crash in the bluetooth auto-switch module (#193)

  - Fixed a race condition that would cause Zoom desktop audio sharing to fail
    (#197)

  - Surround sound in some games is now exposed properly (pipewire#876)

  - Fixed a race condition that would cause the default source & sink to not
    be set at startup

  - policy-node now supports the 'target.object' key on streams and metadata

  - Multiple fixes in policy-node that make the logic in some cases behave
    more like PulseAudio (regarding nodes with the dont-reconnect property
    and regarding following the default source/sink)

  - Fixed a bug with parsing unquoted strings in spa-json

Misc:

  - The policy now supports configuring "persistent" device profiles. If a
    device is *manually* set to one of these profiles, then it will not be
    auto-switched to another profile automatically under any circumstances
    (#138, #204)

  - The device-activation module was re-written in lua

  - Brave, Edge, Vivaldi and Telegram were added in the bluetooth auto-switch
    applications list

  - ALSA nodes now use the PCM name to populate node.nick, which is useful
    at least on HDA cards using UCM, where all outputs (analog, hdmi, etc)
    are exposesd as nodes on a single profile

  - An icon name is now set on the properties of bluetooth devices

WirePlumber 0.4.8
.................

Highlights:

  - Added bluetooth profile auto-switching support. Bluetooth headsets will now
    automatically switch to the HSP/HFP profile when making a call and go back
    to the A2DP profile after the call ends (#90)

  - Added an option (enabled by default) to auto-switch to echo-cancel virtual
    device nodes when the echo-cancel module is loaded in pipewire-pulse, if
    there is no other configured default node

Fixes:

  - Fixed a regression that prevented nodes from being selected as default when
    using the pro-audio profile (#163)

  - Fixed a regression that caused encoded audio streams to stall (#178)

  - Fixed restoring bluetooth device profiles

Library:

  - A new WpSpaJson API was added as a front-end to spa-json. This is also
    exposed to Lua, so that Lua scripts can natively parse and write data in
    the spa-json format

Misc:

  - wpctl can now list the configured default sources and sinks and has a new
    command that allows clearing those configured defaults, so that wireplumber
    goes back to choosing the default nodes based on node priorities

  - The restore-stream script now has its own configuration file in
    main.lua.d/40-stream-defaults.lua and has independent options for
    restoring properties and target nodes

  - The restore-stream script now supports rule-based configuration to disable
    restoring volume properties and/or target nodes for specific streams,
    useful for applications that misbehave when we restore those (see #169)

  - policy-endpoint now assigns the "Default" role to any stream that does not
    have a role, so that it can be linked to a pre-configured endpoint

  - The route-settings-api module was dropped in favor of dealing with json
    natively in Lua, now that the API exists

WirePlumber 0.4.7
.................

Fixes:

  - Fixed a regression in 0.4.6 that caused the selection of the default audio
    sources and sinks to be delayed until some event, which effectively caused
    losing audio output in many circumstances (#148, #150, #151, #153)

  - Fixed a regression in 0.4.6 that caused the echo-cancellation pipewire
    module (and possibly others) to not work

  - A default sink or source is now not selected if there is no available route
    for it (#145)

  - Fixed an issue where some clients would wait for a bit while seeking (#146)

  - Fixed audio capture in the endpoints-based policy

  - Fixed an issue that would cause certain lua scripts to error out with older
    configuration files (#158)

WirePlumber 0.4.6
.................

Changes:

  - Fixed a lot of race condition bugs that would cause strange crashes or
    many log messages being printed when streaming clients would connect and
    disconnect very fast (#128, #78, ...)

  - Improved the logic for selecting a default target device (#74)

  - Fixed switching to headphones when the wired headphones are plugged in (#98)

  - Fixed an issue where ``udevadm trigger`` would break wireplumber (#93)

  - Fixed an issue where switching profiles of a device could kill client nodes

  - Fixed briefly switching output to a secondary device when switching device
    profiles (#85)

  - Fixed ``wpctl status`` showing default device selections when dealing with
    module-loopback virtual sinks and sources (#130)

  - WirePlumber now ignores hidden files from the config directory (#104)

  - Fixed an interoperability issue with jackdbus (pipewire#1846)

  - Fixed an issue where pulseaudio tcp clients would not have permissions to
    connect to PipeWire (pipewire#1863)

  - Fixed a crash in the journald logger with NULL debug messages (#124)

  - Enabled real-time priority for the bluetooth nodes to run in RT (#132)

  - Made the default stream volume configurable

  - Scripts are now also looked up in $XDG_CONFIG_HOME/wireplumber/scripts

  - Updated documentation on configuring WirePlumber and fixed some more
    documentation issues (#68)

  - Added support for using strings as log level selectors in WIREPLUMBER_DEBUG

WirePlumber 0.4.5
.................

Fixes:

  - Fixed a crash that could happen after a node linking error (#76)

  - Fixed a bug that would cause capture streams to link to monitor ports
    of loopback nodes instead of linking to their capture ports

  - Fixed a needless wait that would happen on applications using the pipewire
    ALSA plugin (#92)

  - Fixed an issue that would cause endless rescan loops in policy-node and
    could potentially also cause other strange behaviors in case pavucontrol
    or another monitoring utility was open while the policy was rescanning (#77)

  - Fixed the endpoints-based policy that broke in recent versions and improved
    its codebase to share more code and be more in-line with policy-node

  - The semicolon character is now escaped properly in state files (#82)

  - When a player requests encoded audio passthrough, the policy now prefers
    linking to a device that supports that instead of trying to link to the
    default device and potentially failing (#75)

  - Miscellaneous robustness fixes in policy-node

API:

  - Added WpFactory, a binding for pw_factory proxies. This allows object
    managers to query factories that are loaded in the pipewire daemon

  - The file-monitor-api plugin can now watch files for changes in addition
    to directories

WirePlumber 0.4.4
.................

Highlights:

  - Implemented linking nodes in passthrough mode, which enables encoded
    iec958 / dsd audio passthrough

  - Streams are now sent an error if it was not possible to link them to
    a target (#63)

  - When linking nodes where at least one of them has an unpositioned channel
    layout, the other one is not reconfigured to match the channel layout;
    it is instead linked with a best effort port matching logic

  - Output route switches automatically to the latest one that has become
    available (#69)

  - Policy now respects the 'node.exclusive' and 'node.passive' properties

  - Many other minor policy fixes for a smoother desktop usage experience

API:

  - Fixed an issue with the ``LocalModule()`` constructor not accepting ``nil``
    as well as the properties table properly

  - Added ``WpClient.send_error()``, ``WpSpaPod.fixate()`` and
    ``WpSpaPod.filter()`` (both in C and Lua)

Misc:

  - Bumped meson version requirement to 0.56 to be able to use
    ``meson.project_{source,build}_root()`` and ease integration with pipewire's
    build system as a subproject

  - wireplumber.service is now an alias to pipewire-session-manager.service

  - Loading the logind module no longer fails if it was not found on the system;
    there is only a message printed in the output

  - The logind module can now be compiled with elogind (#71)

  - Improvements in wp-uninstalled.sh, mostly to ease its integration with
    pipewire's build system when wireplumber is build as a subproject

  - The format of audio nodes is now selected using the same algorithm as in
    media-session

  - Fixed a nasty segfault that appeared in 0.4.3 due to a typo (#72)

  - Fixed a re-entrancy issue in the wplua runtime (#73)

WirePlumber 0.4.3
.................

Fixes:

  - Implemented logind integration to start the bluez monitor only on the
    WirePlumber instance that is running on the active seat; this fixes a bunch
    of startup warnings and the disappearance of HSP/HFP nodes after login (#54)

  - WirePlumber is now launched with GIO_USE_VFS=local to avoid strange D-Bus
    interference when the user session is restarted, which previously resulted
    in WirePlumber being terminated with SIGTERM and never recovering (#48)

  - WirePlumber now survives a restart of the D-Bus service, reconnecting to
    the bus and reclaiming the bus services that it needs (#55)

  - Implemented route-settings metadata, which fixes storing volume for
    the "System Sounds" in GNOME (#51)

  - Monitor sources can now be selected as the default source (#60)

  - Refactored some policy logic to allow linking to monitors; the policy now
    also respects "stream.capture.sink" property of streams which declares
    that the stream wants to be linked to a monitor (#66)

  - Policy now cleans up 'target.node' metadata so that streams get to follow
    the default source/sink again after the default was changed to match the
    stream's currently configured target (#65)

  - Fixed configuring virtual sources (#57)

  - Device monitors now do not crash if a SPA plugin is missing; instead, they
    print a warning to help users identify what they need to install (!214)

  - Fixed certain "proxy activation failed" warnings (#44)

  - iec958 codec configuration is now saved and restored properly (!228)

  - Fixed some logging issues with the latest version of pipewire (!227, !232)

  - Policy now respects the "node.link-group" property, which fixes issues
    with filter-chain and other virtual sources & sinks (#47)

  - Access policy now grants full permissions to flatpak "Manager" apps (#59)

Policy:

  - Added support for 'no-dsp' mode, which allows streaming audio using the
    format of the device instead of the standard float 32-bit planar format (!225)

Library:

  - WpImplMetadata is now implemented using pw_impl_metadata instead of
    using its own implementation (#52)

  - Added support for custom object property IDs in WpSpaPod (#53)

Misc:

  - Added a script to load the libcamera monitor (!231)

  - Added option to disable building unit tests (!209)

  - WirePlumber will now fail to start with a warning if pipewire-media-session
    is also running in the system (#56)

  - The bluez monitor configuration was updated to match the latest one in
    pipewire-media-session (!224)

WirePlumber 0.4.2
.................

Highlights:

  - Requires PipeWire 0.3.32 or later at runtime

  - Configuration files are now installed in $PREFIX/share/wireplumber, along
    with scripts, following the paradigm of PipeWire

  - State files are now stored in $XDG_STATE_HOME instead of $XDG_CONFIG_HOME

  - Added new ``file-monitor-api`` module, which allows Lua scripts to watch
    the filesystem for changes, using inotify

  - Added monitor for MIDI devices

  - Added a ``system-lua-version`` meson option that allows distributors to
    choose which Lua version to build against (``auto``, ``5.3`` or ``5.4``)

  - wpipc has been removed and split out to a separate project,
    https://git.automotivelinux.org/src/pipewire-ic-ipc/

Library:

  - A new ``WpImplModule`` class has been added; this allows loading a PipeWire
    module in the WirePlumber process space, keeping a handle that can be
    used to unload that module later. This is useful for loading filters,
    network sources/sinks, etc...

  - State files can now store keys that contain certain GKeyFile-reserved
    characters, such as ``[``, ``]``, ``=`` and space; this fixes storing
    stream volume state for streams using PipeWire's ALSA compatibility PCM
    plugin

  - ``WpProperties`` now uses a boxed ``WpPropertiesItem`` type in its iterators
    so that these iterators can be used with g-i bindings

  - Added API to lookup configuration and script files from multiple places
    in the filesystem

Lua:

  - A ``LocalModule`` API has been added to reflect the functionality offered
    by ``WpImplModule`` in C

  - The ``Node`` API now has a complete set of methods to reflect the methods
    of ``WpNode``

  - Added ``Port.get_direction()``

  - Added ``not-equals`` to the possible constraint verbs

  - ``Debug.dump_table`` now sorts keys before printing the table

Misc:

  - Tests no longer accidentally create files in $HOME; all transient
    files that are used for testing are now created in the build directory,
    except for sockets which are created in ``/tmp`` due to the 108-character
    limitation in socket paths

  - Tests that require optional SPA plugins are now skipped if those SPA plugins
    are not installed

  - Added a nice summary output at the end of meson configuration

  - Documented the Lua ObjectManager / Interest / Constraint APIs

  - Fixed some memory leaks

WirePlumber 0.4.1
.................

Bug fix release to go with PipeWire 0.3.31.
Please update to this version if you are using PipeWire >= 0.3.31.

Highlights:

  - WirePlumber now supports Lua 5.4. You may compile it either with Lua 5.3
    or 5.4, without any changes in behavior. The internal Lua subproject has
    also been upgraded to Lua 5.4, so any builds with ``-Dsystem-lua=false``
    will use Lua 5.4 by default

Fixes:

  - Fixed filtering of pw_metadata objects, which broke with PipeWire 0.3.31

  - Fixed a potential livelock condition in si-audio-adapter/endpoint where
    the code would wait forever for a node's ports to appear in the graph

  - Fixed granting access to camera device nodes in flatpak clients connecting
    through the camera portal

  - Fixed a lot of issues found by the coverity static analyzer

  - Fixed certain race conditions in the wpipc library

  - Fixed compilation with GCC older than v8.1

Scripts:

  - Added a policy script that matches nodes to specific devices based on the
    "media.role" of the nodes and the "device.intended-roles" of the devices

Build system:

  - Bumped GLib requirement to 2.62, as the code was already using 2.62 API

  - Added support for building WirePlumber as a PipeWire subproject

  - Doxygen version requirement has been relaxed to accept v1.8

  - The CI now also verifies that the build works on Ubuntu 20.04 LTS
    and tries multiple builds with different build options

WirePlumber 0.4.0
.................

This is the first stable release of the 0.4.x series, which is expected to be
an API & ABI stable release series to go along with PipeWire 0.3.x. It is
a fundamental goal of this series to maintain compatibility with
pipewire-media-session, making WirePlumber suitable for a desktop PulseAudio &
JACK replacement setup, while supporting other setups as well (ex. automotive)
by making use of its brand new Lua scripting engine, which allows making
customizations easily.

Highlights:

  - Re-implemented the default-routes module in lua, using the same logic
    as the one that pipewire-media-session uses. This fixes a number of issues
    related to volume controls on alsa devices.

  - Implemented a restore-stream lua script, based on the restore-stream
    module from media-session. This allows storing stream volumes and targets
    and restoring them when the stream re-connects

  - Added support for handling dont-remix streams and streams that are not
    autoconnected. Fixes ``pw-cat -p --target=0`` and the gnome-control-center
    channel test

  - Device names are now sanitized in the same way as in pipewire-media-session

  - Disabled endpoints in the default configuration. Using endpoints does
    not provide the best experience on desktop systems yet

  - Fixed a regression introduced in 0.3.96 that would not allow streams to be
    relinked on their endpoints after having been corked by the policy

Library:

  - Some API methods were changed to adhere to the programming practices
    followed elsewhere in the codebase and to be future-proof. Also added
    paddings on public structures so that from this point on, the 0.4.x series
    is going to be API & ABI stable

  - lua: added WpState and wp_metadata_set() bindings and improved
    WpObject.activate() to report errors

  - ObjectManager: added support for declaring interest on all kinds of
    properties of global objects. Previously it was only possible to declare
    interest on pipewire global properties

Misc:

  - daemon & wpexec: changed the exit codes to follow the standardized codes
    defined in sysexits.h

  - wpexec now forces the log level to be >= 1 so that lua runtime errors can be
    printed on the terminal

  - Fixed issues with gobject-introspection data that were introduced by the
    switch to doxygen

  - Fixed a build issue where wp-gtkdoc.h would not be generated in time
    for the gobject-introspection target to build

  - Added a valgrind test setup in meson, use with ``meson test --setup=valgrind``

  - Many memory leak and stability fixes

  - Updated more documentation pages

WirePlumber 0.3.96
..................

Second pre-release (RC2) of WirePlumber 0.4.0.

Highlights:

  - The policy now configures streams for channel upmixing/downmixing

  - Some issues in the policy have been fixed, related to:

    - plugging a new higher priority device while audio is playing
    - pavucontrol creating links to other stream nodes for level monitoring
    - some race condition that could happen at startup

  - Proxy object errors are now handled; this fixes memory leaks of invalid
    links and generally makes things more robust

  - The systemd service units now conflict with pipewire-media-session.service

  - Session & EndpointLink objects have been removed from the API; these were
    not in use after recent refactoring, so they have been removed in order to
    avoid carrying them in the ABI

  - The documentation system has switched to use *Doxygen* & *Sphinx*; some
    documentation has also been updated and some Lua API documentation has
    been introduced

WirePlumber 0.3.95
..................

First pre-release (RC1) of WirePlumber 0.4.0.

Highlights:

  - Lua scripting engine. All the session management logic is now scripted
    and there is also the ability to run scripts standalone with ``wpexec``
    (see tests/examples).

  - Compatibility with the latest PipeWire (0.3.26+ required). Also, most
    features and behavioral logic of pipewire-media-session 0.3.26 are
    available, making WirePlumber suitable for a desktop PulseAudio & JACK
    replacement setup.

  - Compatibility with embedded system policies, like the one on AGL, has been
    restored and is fully configurable.

  - The design of endpoints has been simplified. We now associate endpoints
    with use cases (roles) instead of physical devices. This removes the need
    for "endpoint stream" objects, allows more logic to be scripted in lua
    and makes the graph simpler. It is also possible to run without endpoints
    at all, matching the behavior of pipewire-media-session and pulseaudio.

  - Configuration is now done using a pipewire-style json .conf file plus lua
    files. Most of the options go in the lua files, while pipewire context
    properties, spa_libs and pipewire modules are configured in the json file.

  - Systemd unit files have been added and are the recommended way to run
    wireplumber. Templated unit files are also available, which allow running
    multiple instances of wireplumber with a specific configuration each.

WirePlumber 0.3.0
.................

The desktop-ready release!

Changes since 0.2.96:

  - Changed how the device endpoints & nodes are named
    to make them look better in JACK graph tools, such as qjackctl.
    JACK tools use the ':' character as a separator to distinguish the node
    name from the port name (since there are no actual nodes in JACK) and
    having ':' in our node names made the graph look strange in JACK

  - Fixed an issue with parsing wireplumber.conf that could cause
    out-of-bounds memory access

  - Fixed some pw_proxy object leaks that would show up in the log

  - Fixed more issues with unlinking the stream volume (si-convert) node
    from the ALSA sink node and suspending the both;
    This now also works with PipeWire 0.3.5 and 0.3.6, so it is possible
    to use these PipeWire versions with WirePlumber without disabling streams
    on audio sinks.

WirePlumber 0.2.96
..................

Second pre-release (RC2) of WirePlumber 0.3.0

Changes since 0.2.95:

  - Quite some work went into fixing bugs related to the ``ReserveDevice1``
    D-Bus API. It is now possible to start a JACK server before or after
    WirePlumber and WirePlumber will automatically stop using the device that
    JACK opens, while at the same time it will enable the special "JACK device"
    that allows PipeWire to interface with JACK

  - Fixed a number of issues that did not previously allow using the spa
    bluez5 device with WirePlumber. Now it is possible to at least use the
    A2DP sink (output to bluetooth speakers) without major issues

  - On the API level, ``WpCore`` was changed to allow having multiple instances
    that share the same ``pw_context``. This is useful to have multiple
    connections to PipeWire, while sharing the context infrastructure

  - ``WpCore`` also gained support for retrieving server info & properties
    and ``wpctl status`` now also prints info about the server & all clients

  - ``module-monitor`` was modified to allow loading multiple monitor instances
    with one instance of the module itself

  - Audio nodes are now configured with the sample rate that is defined
    globally in ``pipewire.conf`` with ``set-prop default.clock.rate <rate>``

  - Policy now respects the ``node.autoconnect`` property; additionally, it is
    now possible to specify endpoint ids in the ``node.target`` property of nodes
    (so endpoint ids are accepted in the ``PIPEWIRE_NODE`` environment variable,
    and in the ``path`` property of the pipewire gstreamer elements)

  - Fixed an issue where links between the si-convert audioconvert nodes and
    the actual device nodes would stay active forever; they are now declared
    as "passive" links, which allows the nodes to suspend. This requires
    changes to PipeWire that were commited after 0.3.6; when using WirePlumber
    with 0.3.5 or 0.3.6, it is recommended to disable streams on audio sinks
    by commenting out the ``streams = "audio-sink.streams"`` lines in the
    .endpoint configuration files

  - ``wireplumber.conf`` now accepts comments to be present inside blocks and
    at the end of valid configuration lines

  - Improved documentation and restructured the default configuration to be
    more readable and sensible

  - Fixed issues that prevented using WirePlumber with GLib < 2.60;
    2.58 is now the actual minimum requirement

WirePlumber 0.2.95
..................

First pre-release of WirePlumber 0.3.0.

This is the first release that targets desktop use-cases. It aims to be
fully compatible with ``pipewire-media-session``, while at the same time it
adds a couple of features that ``pipewire-media-session`` lacks, such as:

  - It makes use of session, endpoint and endpoint-stream objects
    to orchestrate the graph

  - It is configurable:

    - It supports configuration of endpoints, so that their properties
      (such as their name) can be overriden

    - It also supports declaring priorities on endpoints, so that there
      are sane defaults on the first start

    - It supports partial configuration of linking policy

    - It supports creating static node and device objects at startup,
      also driven by configuration files

  - It has the concept of session default endpoints, which can be changed
    with ``wpctl`` and are stored in XDG_CONFIG_DIR, so the user may change
    at runtime the target device of new links in a persistent way

  - It supports volume & mute controls on audio endpoints, which can be
    set with ``wpctl``

  - Last but not least, it is extensible

Also note that this release currently breaks compatibility with AGL, since
the policy management engine received a major refactoring to enable more
use-cases, and has been focusing on desktop support ever since.
Policy features specific to AGL and other embedded systems are expected
to come back in a 0.3.x point release.

WirePlumber 0.2.0
.................

As shipped in AGL Itchy Icefish 9.0.0 and Happy Halibut 8.0.5

WirePlumber 0.1.2
.................

As shipped in AGL Happy Halibut 8.0.2

WirePlumber 0.1.1
.................

As shipped in AGL Happy Halibut 8.0.1

WirePlumber 0.1.0
.................

First release of WirePlumber, as shipped in AGL Happy Halibut 8.0.0
  07070100000006000081A4000000000000000000000001656CC35F000005B6000000000000000000000000000000000000001E00000000wireplumber-0.4.17/README.rst WirePlumber
===========

.. image:: https://gitlab.freedesktop.org/pipewire/wireplumber/badges/master/pipeline.svg
   :alt: Pipeline status

.. image:: https://scan.coverity.com/projects/21488/badge.svg
   :alt: Coverity Scan Build Status

.. image:: https://img.shields.io/tokei/lines/gitlab.freedesktop.org/pipewire/wireplumber
   :alt: Lines of code

.. image:: https://img.shields.io/badge/license-MIT-green
   :alt: License

.. image:: https://img.shields.io/badge/dynamic/json?color=informational&label=tag&query=%24%5B0%5D.name&url=https%3A%2F%2Fgitlab.freedesktop.org%2Fapi%2Fv4%2Fprojects%2F2941%2Frepository%2Ftags
   :alt: Tag

WirePlumber is a modular session / policy manager for
`PipeWire <https://pipewire.org>`_ and a GObject-based high-level library
that wraps PipeWire's API, providing convenience for writing the daemon's
modules as well as external tools for managing PipeWire.

The WirePlumber daemon implements the session & policy management service.
It follows a modular design, having plugins that implement the actual
management functionality.

The WirePlumber Library provides API that allows you to extend the WirePlumber
daemon, to write management or status tools for PipeWire
(apps that don't do actual media streaming) and to write custom session managers
for embedded devices.

Documentation
-------------

The latest version of the documentation is available online
`here <https://pipewire.pages.freedesktop.org/wireplumber/>`_
  07070100000007000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001800000000wireplumber-0.4.17/docs   07070100000008000081A4000000000000000000000001656CC35F0001B7F7000000000000000000000000000000000000002400000000wireplumber-0.4.17/docs/Doxyfile.in   # Doxyfile 1.9.1

# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
#
# All text after a double hash (##) is considered a comment and is placed in
# front of the TAG it is preceding.
#
# All text after a single hash (#) is considered a comment and will be ignored.
# The format is:
# TAG = value [value, ...]
# For lists, items can also be appended using:
# TAG += value [value, ...]
# Values that contain spaces should be placed between quotes (\" \").

#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------

# This tag specifies the encoding used for all characters in the configuration
# file that follow. The default is UTF-8 which is also the encoding used for all
# text before the first occurrence of this tag. Doxygen uses libiconv (or the
# iconv built into libc) for the transcoding. See
# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
# The default value is: UTF-8.

DOXYFILE_ENCODING      = UTF-8

# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
# double-quotes, unless you are using Doxywizard) that should identify the
# project for which the documentation is generated. This name is used in the
# title of most generated pages and in a few other places.
# The default value is: My Project.

PROJECT_NAME           = WirePlumber

# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER         =

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.

PROJECT_BRIEF          =

# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
# the logo to the output directory.

PROJECT_LOGO           =

# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
# into which the generated documentation will be written. If a relative path is
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.

OUTPUT_DIRECTORY       = @OUTPUT_DIR@

# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
# will distribute the generated files over these directories. Enabling this
# option can be useful when feeding doxygen a huge amount of source files, where
# putting all generated files in the same directory would otherwise causes
# performance problems for the file system.
# The default value is: NO.

CREATE_SUBDIRS         = NO

# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
# characters to appear in the names of generated files. If set to NO, non-ASCII
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
# U+3044.
# The default value is: NO.

ALLOW_UNICODE_NAMES    = NO

# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
# Ukrainian and Vietnamese.
# The default value is: English.

OUTPUT_LANGUAGE        = English

# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
# descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this.
# The default value is: YES.

BRIEF_MEMBER_DESC      = YES

# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
# description of a member or function before the detailed description
#
# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
# brief descriptions will be completely suppressed.
# The default value is: YES.

REPEAT_BRIEF           = YES

# This tag implements a quasi-intelligent brief description abbreviator that is
# used to form the text in various listings. Each string in this list, if found
# as the leading text of the brief description, will be stripped from the text
# and the result, after processing the whole list, is used as the annotated
# text. Otherwise, the brief description is used as-is. If left blank, the
# following values are used ($name is automatically replaced with the name of
# the entity):The $name class, The $name widget, The $name file, is, provides,
# specifies, contains, represents, a, an and the.

ABBREVIATE_BRIEF       = "The $name class" \
                         "The $name widget" \
                         "The $name file" \
                         is \
                         provides \
                         specifies \
                         contains \
                         represents \
                         a \
                         an \
                         the

# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
# doxygen will generate a detailed section even if there is only a brief
# description.
# The default value is: NO.

ALWAYS_DETAILED_SEC    = NO

# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
# inherited members of a class in the documentation of that class as if those
# members were ordinary class members. Constructors, destructors and assignment
# operators of the base classes will not be shown.
# The default value is: NO.

INLINE_INHERITED_MEMB  = NO

# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
# before files name in the file list and in the header files. If set to NO the
# shortest path that makes the file name unique will be used
# The default value is: YES.

FULL_PATH_NAMES        = YES

# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
# Stripping is only done if one of the specified strings matches the left-hand
# part of the path. The tag can be used to show relative paths in the file list.
# If left blank the directory from which doxygen is run is used as the path to
# strip.
#
# Note that you can specify absolute paths here, but also relative paths, which
# will be relative from the directory where doxygen is started.
# This tag requires that the tag FULL_PATH_NAMES is set to YES.

STRIP_FROM_PATH        =

# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
# path mentioned in the documentation of a class, which tells the reader which
# header file to include in order to use a class. If left blank only the name of
# the header file containing the class definition is used. Otherwise one should
# specify the list of include paths that are normally passed to the compiler
# using the -I flag.

STRIP_FROM_INC_PATH    =

# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
# less readable) file names. This can be useful is your file systems doesn't
# support long names like on DOS, Mac, or CD-ROM.
# The default value is: NO.

SHORT_NAMES            = NO

# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
# first line (until the first dot) of a Javadoc-style comment as the brief
# description. If set to NO, the Javadoc-style will behave just like regular Qt-
# style comments (thus requiring an explicit @brief command for a brief
# description.)
# The default value is: NO.

JAVADOC_AUTOBRIEF      = NO

# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
# such as
# /***************
# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
# Javadoc-style will behave just like regular comments and it will not be
# interpreted by doxygen.
# The default value is: NO.

JAVADOC_BANNER         = NO

# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
# line (until the first dot) of a Qt-style comment as the brief description. If
# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
# requiring an explicit \brief command for a brief description.)
# The default value is: NO.

QT_AUTOBRIEF           = NO

# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
# a brief description. This used to be the default behavior. The new default is
# to treat a multi-line C++ comment block as a detailed description. Set this
# tag to YES if you prefer the old behavior instead.
#
# Note that setting this tag to YES also means that rational rose comments are
# not recognized any more.
# The default value is: NO.

MULTILINE_CPP_IS_BRIEF = NO

# By default Python docstrings are displayed as preformatted text and doxygen's
# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
# doxygen's special commands can be used and the contents of the docstring
# documentation blocks is shown as doxygen documentation.
# The default value is: YES.

PYTHON_DOCSTRING       = YES

# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
# documentation from any documented member that it re-implements.
# The default value is: YES.

INHERIT_DOCS           = YES

# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
# page for each member. If set to NO, the documentation of a member will be part
# of the file/class/namespace that contains it.
# The default value is: NO.

SEPARATE_MEMBER_PAGES  = NO

# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
# uses this value to replace tabs by spaces in code fragments.
# Minimum value: 1, maximum value: 16, default value: 4.

TAB_SIZE               = 8

# This tag can be used to specify a number of aliases that act as commands in
# the documentation. An alias has the form:
# name=value
# For example adding
# "sideeffect=@par Side Effects:\n"
# will allow you to put the command \sideeffect (or @sideeffect) in the
# documentation, which will result in a user-defined paragraph with heading
# "Side Effects:". You can put \n's in the value part of an alias to insert
# newlines (in the resulting output). You can put ^^ in the value part of an
# alias to insert a newline as if a physical newline was in the original file.
# When you need a literal { or } or , in the value part of an alias you have to
# escape them by means of a backslash (\), this can lead to conflicts with the
# commands \{ and \} for these it is advised to use the version @{ and @} or use
# a double escape (\\{ and \\})

ALIASES                = \
  "rst=\verbatim embed:rst:leading-asterisk" \
  "endrst=\endverbatim" \
  "gsignals=\b GObject \b Signals" \
  "gproperties=\b GObject \b Properties" \
  "gproperty{4}=<dl><dt>\1</dt><dd><p>\4</p><table><tr><td>`\2`</td><td>`\3`</td></tr></table></dd></dl>"

# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
# members will be omitted, etc.
# The default value is: NO.

OPTIMIZE_OUTPUT_FOR_C  = NO

# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
# Python sources only. Doxygen will then generate output that is more tailored
# for that language. For instance, namespaces will be presented as packages,
# qualified scopes will look different, etc.
# The default value is: NO.

OPTIMIZE_OUTPUT_JAVA   = NO

# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
# sources. Doxygen will then generate output that is tailored for Fortran.
# The default value is: NO.

OPTIMIZE_FOR_FORTRAN   = NO

# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
# sources. Doxygen will then generate output that is tailored for VHDL.
# The default value is: NO.

OPTIMIZE_OUTPUT_VHDL   = NO

# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
# sources only. Doxygen will then generate output that is more tailored for that
# language. For instance, namespaces will be presented as modules, types will be
# separated into more groups, etc.
# The default value is: NO.

OPTIMIZE_OUTPUT_SLICE  = NO

# Doxygen selects the parser to use depending on the extension of the files it
# parses. With this tag you can assign which parser to use for a given
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files). For instance to make doxygen treat .inc files
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
# use: inc=Fortran f=C.
#
# Note: For files without extension you can use no_extension as a placeholder.
#
# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
# the files are not read by doxygen. When specifying no_extension you should add
# * to the FILE_PATTERNS.
#
# Note see also the list of default file extension mappings.

EXTENSION_MAPPING      =

# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
# documentation. See https://daringfireball.net/projects/markdown/ for details.
# The output of markdown processing is further processed by doxygen, so you can
# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
# case of backward compatibilities issues.
# The default value is: YES.

MARKDOWN_SUPPORT       = YES

# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
# Note: This feature currently applies only to Markdown headings.
# Minimum value: 0, maximum value: 99, default value: 5.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.

TOC_INCLUDE_HEADINGS   = 5

# When enabled doxygen tries to link words that correspond to documented
# classes, or namespaces to their corresponding documentation. Such a link can
# be prevented in individual cases by putting a % sign in front of the word or
# globally by setting AUTOLINK_SUPPORT to NO.
# The default value is: YES.

AUTOLINK_SUPPORT       = YES

# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
# to include (a tag file for) the STL sources as input, then you should set this
# tag to YES in order to let doxygen match functions declarations and
# definitions whose arguments contain STL classes (e.g. func(std::string);
# versus func(std::string) {}). This also make the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
# The default value is: NO.

BUILTIN_STL_SUPPORT    = NO

# If you use Microsoft's C++/CLI language, you should set this option to YES to
# enable parsing support.
# The default value is: NO.

CPP_CLI_SUPPORT        = NO

# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
# will parse them like normal C++ but will assume all classes use public instead
# of private inheritance when no explicit protection keyword is present.
# The default value is: NO.

SIP_SUPPORT            = NO

# For Microsoft's IDL there are propget and propput attributes to indicate
# getter and setter methods for a property. Setting this option to YES will make
# doxygen to replace the get and set methods by a property in the documentation.
# This will only work if the methods are indeed getting or setting a simple
# type. If this is not the case, or you want to show the methods anyway, you
# should set this option to NO.
# The default value is: YES.

IDL_PROPERTY_SUPPORT   = YES

# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
# tag is set to YES then doxygen will reuse the documentation of the first
# member in the group (if any) for the other members of the group. By default
# all members of a group must be documented explicitly.
# The default value is: NO.

DISTRIBUTE_GROUP_DOC   = NO

# If one adds a struct or class to a group and this option is enabled, then also
# any nested class or struct is added to the same group. By default this option
# is disabled and one has to add nested compounds explicitly via \ingroup.
# The default value is: NO.

GROUP_NESTED_COMPOUNDS = NO

# Set the SUBGROUPING tag to YES to allow class member groups of the same type
# (for instance a group of public functions) to be put as a subgroup of that
# type (e.g. under the Public Functions section). Set it to NO to prevent
# subgrouping. Alternatively, this can be done per class using the
# \nosubgrouping command.
# The default value is: YES.

SUBGROUPING            = YES

# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
# are shown inside the group in which they are included (e.g. using \ingroup)
# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
# and RTF).
#
# Note that this feature does not work in combination with
# SEPARATE_MEMBER_PAGES.
# The default value is: NO.

INLINE_GROUPED_CLASSES = NO

# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
# with only public data fields or simple typedef fields will be shown inline in
# the documentation of the scope in which they are defined (i.e. file,
# namespace, or group documentation), provided this scope is documented. If set
# to NO, structs, classes, and unions are shown on a separate page (for HTML and
# Man pages) or section (for LaTeX and RTF).
# The default value is: NO.

INLINE_SIMPLE_STRUCTS  = NO

# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
# enum is documented as struct, union, or enum with the name of the typedef. So
# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
# with name TypeT. When disabled the typedef will appear as a member of a file,
# namespace, or class. And the struct will be named TypeS. This can typically be
# useful for C code in case the coding convention dictates that all compound
# types are typedef'ed and only the typedef is referenced, never the tag name.
# The default value is: NO.

TYPEDEF_HIDES_STRUCT   = NO

# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
# cache is used to resolve symbols given their name and scope. Since this can be
# an expensive process and often the same symbol appears multiple times in the
# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
# doxygen will become slower. If the cache is too large, memory is wasted. The
# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
# symbols. At the end of a run doxygen will report the cache usage and suggest
# the optimal cache size from a speed point of view.
# Minimum value: 0, maximum value: 9, default value: 0.

LOOKUP_CACHE_SIZE      = 0

# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
# during processing. When set to 0 doxygen will based this on the number of
# cores available in the system. You can set it explicitly to a value larger
# than 0 to get more control over the balance between CPU load and processing
# speed. At this moment only the input processing can be done using multiple
# threads. Since this is still an experimental feature the default is set to 1,
# which efficively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 32, default value: 1.

NUM_PROC_THREADS       = 1

#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------

# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
# documentation are documented, even if no documentation was available. Private
# class members and static file members will be hidden unless the
# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
# Note: This will also disable the warnings about undocumented members that are
# normally produced when WARNINGS is set to YES.
# The default value is: NO.

EXTRACT_ALL            = NO

# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
# be included in the documentation.
# The default value is: NO.

EXTRACT_PRIVATE        = NO

# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
# methods of a class will be included in the documentation.
# The default value is: NO.

EXTRACT_PRIV_VIRTUAL   = NO

# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
# scope will be included in the documentation.
# The default value is: NO.

EXTRACT_PACKAGE        = NO

# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
# included in the documentation.
# The default value is: NO.

EXTRACT_STATIC         = NO

# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
# locally in source files will be included in the documentation. If set to NO,
# only classes defined in header files are included. Does not have any effect
# for Java sources.
# The default value is: YES.

EXTRACT_LOCAL_CLASSES  = NO

# This flag is only useful for Objective-C code. If set to YES, local methods,
# which are defined in the implementation section but not in the interface are
# included in the documentation. If set to NO, only methods in the interface are
# included.
# The default value is: NO.

EXTRACT_LOCAL_METHODS  = NO

# If this flag is set to YES, the members of anonymous namespaces will be
# extracted and appear in the documentation as a namespace called
# 'anonymous_namespace{file}', where file will be replaced with the base name of
# the file that contains the anonymous namespace. By default anonymous namespace
# are hidden.
# The default value is: NO.

EXTRACT_ANON_NSPACES   = NO

# If this flag is set to YES, the name of an unnamed parameter in a declaration
# will be determined by the corresponding definition. By default unnamed
# parameters remain unnamed in the output.
# The default value is: YES.

RESOLVE_UNNAMED_PARAMS = YES

# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
# undocumented members inside documented classes or files. If set to NO these
# members will be included in the various overviews, but no documentation
# section is generated. This option has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.

HIDE_UNDOC_MEMBERS     = YES

# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set
# to NO, these classes will be included in the various overviews. This option
# has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.

HIDE_UNDOC_CLASSES     = YES

# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
# declarations. If set to NO, these declarations will be included in the
# documentation.
# The default value is: NO.

HIDE_FRIEND_COMPOUNDS  = NO

# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
# documentation blocks found inside the body of a function. If set to NO, these
# blocks will be appended to the function's detailed documentation block.
# The default value is: NO.

HIDE_IN_BODY_DOCS      = NO

# The INTERNAL_DOCS tag determines if documentation that is typed after a
# \internal command is included. If the tag is set to NO then the documentation
# will be excluded. Set it to YES to include the internal documentation.
# The default value is: NO.

INTERNAL_DOCS          = NO

# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
# able to match the capabilities of the underlying filesystem. In case the
# filesystem is case sensitive (i.e. it supports files in the same directory
# whose names only differ in casing), the option must be set to YES to properly
# deal with such files in case they appear in the input. For filesystems that
# are not case sensitive the option should be be set to NO to properly deal with
# output files written for symbols that only differ in casing, such as for two
# classes, one named CLASS and the other named Class, and to also support
# references to files without having to specify the exact matching casing. On
# Windows (including Cygwin) and MacOS, users should typically set this option
# to NO, whereas on Linux or other Unix flavors it should typically be set to
# YES.
# The default value is: system dependent.

CASE_SENSE_NAMES       = YES

# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
# their full class and namespace scopes in the documentation. If set to YES, the
# scope will be hidden.
# The default value is: NO.

HIDE_SCOPE_NAMES       = NO

# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
# append additional text to a page's title, such as Class Reference. If set to
# YES the compound reference will be hidden.
# The default value is: NO.

HIDE_COMPOUND_REFERENCE= NO

# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
# the files that are included by a file in the documentation of that file.
# The default value is: YES.

SHOW_INCLUDE_FILES     = NO

# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
# grouped member an include statement to the documentation, telling the reader
# which file to include in order to use the member.
# The default value is: NO.

SHOW_GROUPED_MEMB_INC  = NO

# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
# files with double quotes in the documentation rather than with sharp brackets.
# The default value is: NO.

FORCE_LOCAL_INCLUDES   = NO

# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
# documentation for inline members.
# The default value is: YES.

INLINE_INFO            = YES

# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
# (detailed) documentation of file and class members alphabetically by member
# name. If set to NO, the members will appear in declaration order.
# The default value is: YES.

SORT_MEMBER_DOCS       = YES

# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
# descriptions of file, namespace and class members alphabetically by member
# name. If set to NO, the members will appear in declaration order. Note that
# this will also influence the order of the classes in the class list.
# The default value is: NO.

SORT_BRIEF_DOCS        = NO

# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
# (brief and detailed) documentation of class members so that constructors and
# destructors are listed first. If set to NO the constructors will appear in the
# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
# member documentation.
# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
# detailed member documentation.
# The default value is: NO.

SORT_MEMBERS_CTORS_1ST = NO

# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
# of group names into alphabetical order. If set to NO the group names will
# appear in their defined order.
# The default value is: NO.

SORT_GROUP_NAMES       = NO

# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
# fully-qualified names, including namespaces. If set to NO, the class list will
# be sorted only by class name, not including the namespace part.
# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
# Note: This option applies only to the class list, not to the alphabetical
# list.
# The default value is: NO.

SORT_BY_SCOPE_NAME     = NO

# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
# type resolution of all parameters of a function it will reject a match between
# the prototype and the implementation of a member function even if there is
# only one candidate or it is obvious which candidate to choose by doing a
# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
# accept a match between prototype and implementation in such cases.
# The default value is: NO.

STRICT_PROTO_MATCHING  = NO

# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
# list. This list is created by putting \todo commands in the documentation.
# The default value is: YES.

GENERATE_TODOLIST      = YES

# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
# list. This list is created by putting \test commands in the documentation.
# The default value is: YES.

GENERATE_TESTLIST      = YES

# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
# list. This list is created by putting \bug commands in the documentation.
# The default value is: YES.

GENERATE_BUGLIST       = YES

# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
# the deprecated list. This list is created by putting \deprecated commands in
# the documentation.
# The default value is: YES.

GENERATE_DEPRECATEDLIST= YES

# The ENABLED_SECTIONS tag can be used to enable conditional documentation
# sections, marked by \if <section_label> ... \endif and \cond <section_label>
# ... \endcond blocks.

ENABLED_SECTIONS       =

# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
# initial value of a variable or macro / define can have for it to appear in the
# documentation. If the initializer consists of more lines than specified here
# it will be hidden. Use a value of 0 to hide initializers completely. The
# appearance of the value of individual variables and macros / defines can be
# controlled using \showinitializer or \hideinitializer command in the
# documentation regardless of this setting.
# Minimum value: 0, maximum value: 10000, default value: 30.

MAX_INITIALIZER_LINES  = 30

# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
# the bottom of the documentation of classes and structs. If set to YES, the
# list will mention the files that were used to generate the documentation.
# The default value is: YES.

SHOW_USED_FILES        = YES

# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
# will remove the Files entry from the Quick Index and from the Folder Tree View
# (if specified).
# The default value is: YES.

SHOW_FILES             = YES

# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
# page. This will remove the Namespaces entry from the Quick Index and from the
# Folder Tree View (if specified).
# The default value is: YES.

SHOW_NAMESPACES        = YES

# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# doxygen should invoke to get the current version for each file (typically from
# the version control system). Doxygen will invoke the program by executing (via
# popen()) the command command input-file, where command is the value of the
# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
# by doxygen. Whatever the program writes to standard output is used as the file
# version. For an example see the documentation.

FILE_VERSION_FILTER    =

# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
# by doxygen. The layout file controls the global structure of the generated
# output files in an output format independent way. To create the layout file
# that represents doxygen's defaults, run doxygen with the -l option. You can
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
# will be used as the name of the layout file.
#
# Note that if you run doxygen from a directory containing a file called
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
# tag is left empty.

LAYOUT_FILE            =

# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
# the reference definitions. This must be a list of .bib files. The .bib
# extension is automatically appended if omitted. This requires the bibtex tool
# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
# For LaTeX the style of the bibliography can be controlled using
# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
# search path. See also \cite for info how to create references.

CITE_BIB_FILES         =

#---------------------------------------------------------------------------
# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------

# The QUIET tag can be used to turn on/off the messages that are generated to
# standard output by doxygen. If QUIET is set to YES this implies that the
# messages are off.
# The default value is: NO.

QUIET                  = YES

# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
# this implies that the warnings are on.
#
# Tip: Turn warnings on while writing the documentation.
# The default value is: YES.

WARNINGS               = YES

# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: YES.

WARN_IF_UNDOCUMENTED   = YES

# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some parameters
# in a documented function, or documenting parameters that don't exist or using
# markup commands wrongly.
# The default value is: YES.

WARN_IF_DOC_ERROR      = YES

# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return
# value. If set to NO, doxygen will only warn about wrong or incomplete
# parameter documentation, but not about the absence of documentation. If
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
# The default value is: NO.

WARN_NO_PARAMDOC       = NO

# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
# at the end of the doxygen process doxygen will return with a non-zero status.
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
# The default value is: NO.

WARN_AS_ERROR          = NO

# The WARN_FORMAT tag determines the format of the warning messages that doxygen
# can produce. The string should contain the $file, $line, and $text tags, which
# will be replaced by the file and line number from which the warning originated
# and the warning text. Optionally the format may contain $version, which will
# be replaced by the version of the file (if it could be obtained via
# FILE_VERSION_FILTER)
# The default value is: $file:$line: $text.

WARN_FORMAT            = "$file:$line: $text"

# The WARN_LOGFILE tag can be used to specify a file to which warning and error
# messages should be written. If left blank the output is written to standard
# error (stderr).

WARN_LOGFILE           =

#---------------------------------------------------------------------------
# Configuration options related to the input files
#---------------------------------------------------------------------------

# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

INPUT                  = @INPUT@

# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
# documentation (see:
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
# The default value is: UTF-8.

INPUT_ENCODING         = UTF-8

# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# read by doxygen.
#
# Note the list of default checked file patterns might differ from the list of
# default file extension mappings.
#
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,
# *.ucf, *.qsf and *.ice.

FILE_PATTERNS          =

# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
# The default value is: NO.

RECURSIVE              = NO

# The EXCLUDE tag can be used to specify files and/or directories that should be
# excluded from the INPUT source files. This way you can easily exclude a
# subdirectory from a directory tree whose root is specified with the INPUT tag.
#
# Note that relative paths are relative to the directory from which doxygen is
# run.

EXCLUDE                =

# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
# from the input.
# The default value is: NO.

EXCLUDE_SYMLINKS       = NO

# If the value of the INPUT tag contains directories, you can use the
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
# certain files from those directories.
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*

EXCLUDE_PATTERNS       =

# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*

EXCLUDE_SYMBOLS        =

# The EXAMPLE_PATH tag can be used to specify one or more files or directories
# that contain example code fragments that are included (see the \include
# command).

EXAMPLE_PATH           =

# If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
# *.h) to filter out the source-files in the directories. If left blank all
# files are included.

EXAMPLE_PATTERNS       = *

# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
# searched for input files to be used with the \include or \dontinclude commands
# irrespective of the value of the RECURSIVE tag.
# The default value is: NO.

EXAMPLE_RECURSIVE      = NO

# The IMAGE_PATH tag can be used to specify one or more files or directories
# that contain images that are to be included in the documentation (see the
# \image command).

IMAGE_PATH             =

# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
# by executing (via popen()) the command:
#
# <filter> <input-file>
#
# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
# name of an input file. Doxygen will then use the output that the filter
# program writes to standard output. If FILTER_PATTERNS is specified, this tag
# will be ignored.
#
# Note that the filter must not add or remove lines; it is applied before the
# code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.

INPUT_FILTER           =

# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
# basis. Doxygen will compare the file name with each pattern and apply the
# filter if there is a match. The filters are a list of the form: pattern=filter
# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
# patterns match the file name, INPUT_FILTER is applied.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.

FILTER_PATTERNS        =

# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER) will also be used to filter the input files that are used for
# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
# The default value is: NO.

FILTER_SOURCE_FILES    = NO

# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
# it is also possible to disable source filtering for a specific pattern using
# *.ext= (so without naming a filter).
# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.

FILTER_SOURCE_PATTERNS =

# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
# is part of the input, its contents will be placed on the main page
# (index.html). This can be useful if you have a project on for instance GitHub
# and want to reuse the introduction page also for the doxygen output.

USE_MDFILE_AS_MAINPAGE =

#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------

# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
# generated. Documented entities will be cross-referenced with these sources.
#
# Note: To get rid of all source code in the generated output, make sure that
# also VERBATIM_HEADERS is set to NO.
# The default value is: NO.

SOURCE_BROWSER         = NO

# Setting the INLINE_SOURCES tag to YES will include the body of functions,
# classes and enums directly into the documentation.
# The default value is: NO.

INLINE_SOURCES         = NO

# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
# special comment blocks from generated source code fragments. Normal C, C++ and
# Fortran comments will always remain visible.
# The default value is: YES.

STRIP_CODE_COMMENTS    = YES

# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
# entity all documented functions referencing it will be listed.
# The default value is: NO.

REFERENCED_BY_RELATION = NO

# If the REFERENCES_RELATION tag is set to YES then for each documented function
# all documented entities called/used by that function will be listed.
# The default value is: NO.

REFERENCES_RELATION    = NO

# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
# to YES then the hyperlinks from functions in REFERENCES_RELATION and
# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
# link to the documentation.
# The default value is: YES.

REFERENCES_LINK_SOURCE = YES

# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
# source code will show a tooltip with additional information such as prototype,
# brief description and links to the definition and documentation. Since this
# will make the HTML file larger and loading of large files a bit slower, you
# can opt to disable this feature.
# The default value is: YES.
# This tag requires that the tag SOURCE_BROWSER is set to YES.

SOURCE_TOOLTIPS        = YES

# If the USE_HTAGS tag is set to YES then the references to source code will
# point to the HTML generated by the htags(1) tool instead of doxygen built-in
# source browser. The htags tool is part of GNU's global source tagging system
# (see https://www.gnu.org/software/global/global.html). You will need version
# 4.8.6 or higher.
#
# To use it do the following:
# - Install the latest version of global
# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
# - Make sure the INPUT points to the root of the source tree
# - Run doxygen as normal
#
# Doxygen will invoke htags (and that will in turn invoke gtags), so these
# tools must be available from the command line (i.e. in the search path).
#
# The result: instead of the source browser generated by doxygen, the links to
# source code will now point to the output of htags.
# The default value is: NO.
# This tag requires that the tag SOURCE_BROWSER is set to YES.

USE_HTAGS              = NO

# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
# verbatim copy of the header file for each class for which an include is
# specified. Set to NO to disable this.
# See also: Section \class.
# The default value is: YES.

VERBATIM_HEADERS       = YES

#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------

# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
# compounds will be generated. Enable this if the project contains a lot of
# classes, structs, unions or interfaces.
# The default value is: YES.

ALPHABETICAL_INDEX     = YES

# In case all classes in a project start with a common prefix, all classes will
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
# can be used to specify a prefix (or a list of prefixes) that should be ignored
# while generating the index headers.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.

IGNORE_PREFIX          =

#---------------------------------------------------------------------------
# Configuration options related to the HTML output
#---------------------------------------------------------------------------

# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
# The default value is: YES.

GENERATE_HTML          = NO

# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: html.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_OUTPUT            = html

# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
# generated HTML page (for example: .htm, .php, .asp).
# The default value is: .html.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_FILE_EXTENSION    = .html

# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
# each generated HTML page. If the tag is left blank doxygen will generate a
# standard header.
#
# To get valid HTML the header file that includes any scripts and style sheets
# that doxygen needs, which is dependent on the configuration options used (e.g.
# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
# default header using
# doxygen -w html new_header.html new_footer.html new_stylesheet.css
# YourConfigFile
# and then modify the file new_header.html. See also section "Doxygen usage"
# for information on how to generate the default header that doxygen normally
# uses.
# Note: The header is subject to change so you typically have to regenerate the
# default header when upgrading to a newer version of doxygen. For a description
# of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_HEADER            =

# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
# generated HTML page. If the tag is left blank doxygen will generate a standard
# footer. See HTML_HEADER for more information on how to generate a default
# footer and what special commands can be used inside the footer. See also
# section "Doxygen usage" for information on how to generate the default footer
# that doxygen normally uses.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_FOOTER            =

# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
# sheet that is used by each HTML page. It can be used to fine-tune the look of
# the HTML output. If left blank doxygen will generate a default style sheet.
# See also section "Doxygen usage" for information on how to generate the style
# sheet that doxygen normally uses.
# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
# it is more robust and this tag (HTML_STYLESHEET) will in the future become
# obsolete.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_STYLESHEET        =

# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# cascading style sheets that are included after the standard style sheets
# created by doxygen. Using this option one can overrule certain style aspects.
# This is preferred over using HTML_STYLESHEET since it does not replace the
# standard style sheet and is therefore more robust against future updates.
# Doxygen will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list). For an example see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_EXTRA_STYLESHEET  =

# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
# that these files will be copied to the base HTML output directory. Use the
# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
# files will be copied as-is; there are no commands or markers available.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_EXTRA_FILES       =

# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
# this color. Hue is specified as an angle on a colorwheel, see
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
# purple, and 360 is red again.
# Minimum value: 0, maximum value: 359, default value: 220.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_COLORSTYLE_HUE    = 220

# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
# in the HTML output. For a value of 0 the output will use grayscales only. A
# value of 255 will produce the most vivid colors.
# Minimum value: 0, maximum value: 255, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_COLORSTYLE_SAT    = 100

# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
# luminance component of the colors in the HTML output. Values below 100
# gradually make the output lighter, whereas values above 100 make the output
# darker. The value divided by 100 is the actual gamma applied, so 80 represents
# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
# change the gamma.
# Minimum value: 40, maximum value: 240, default value: 80.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_COLORSTYLE_GAMMA  = 80

# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
# page will contain the date and time when the page was generated. Setting this
# to YES can help to show when doxygen was last run and thus if the
# documentation is up to date.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_TIMESTAMP         = NO

# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
# documentation will contain a main index with vertical navigation menus that
# are dynamically created via JavaScript. If disabled, the navigation index will
# consists of multiple levels of tabs that are statically embedded in every HTML
# page. Disable this option to support browsers that do not have JavaScript,
# like the Qt help browser.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_DYNAMIC_MENUS     = YES

# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
# documentation will contain sections that can be hidden and shown after the
# page has loaded.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_DYNAMIC_SECTIONS  = NO

# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
# shown in the various tree structured indices initially; the user can expand
# and collapse entries dynamically later on. Doxygen will expand the tree to
# such a level that at most the specified number of entries are visible (unless
# a fully collapsed tree already exceeds this amount). So setting the number of
# entries 1 will produce a full collapsed tree by default. 0 is a special value
# representing an infinite number of entries and will result in a full expanded
# tree by default.
# Minimum value: 0, maximum value: 9999, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_INDEX_NUM_ENTRIES = 100

# If the GENERATE_DOCSET tag is set to YES, additional index files will be
# generated that can be used as input for Apple's Xcode 3 integrated development
# environment (see:
# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
# create a documentation set, doxygen will generate a Makefile in the HTML
# output directory. Running make will produce the docset in that directory and
# running make install will install the docset in
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
# genXcode/_index.html for more information.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_DOCSET        = NO

# This tag determines the name of the docset feed. A documentation feed provides
# an umbrella under which multiple documentation sets from a single provider
# (such as a company or product suite) can be grouped.
# The default value is: Doxygen generated docs.
# This tag requires that the tag GENERATE_DOCSET is set to YES.

DOCSET_FEEDNAME        = "Doxygen generated docs"

# This tag specifies a string that should uniquely identify the documentation
# set bundle. This should be a reverse domain-name style string, e.g.
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_DOCSET is set to YES.

DOCSET_BUNDLE_ID       = org.doxygen.Project

# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
# the documentation publisher. This should be a reverse domain-name style
# string, e.g. com.mycompany.MyDocSet.documentation.
# The default value is: org.doxygen.Publisher.
# This tag requires that the tag GENERATE_DOCSET is set to YES.

DOCSET_PUBLISHER_ID    = org.doxygen.Publisher

# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
# The default value is: Publisher.
# This tag requires that the tag GENERATE_DOCSET is set to YES.

DOCSET_PUBLISHER_NAME  = Publisher

# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
# (see:
# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.
#
# The HTML Help Workshop contains a compiler that can convert all HTML output
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
# files are now used as the Windows 98 help format, and will replace the old
# Windows help format (.hlp) on all Windows platforms in the future. Compressed
# HTML files also contain an index, a table of contents, and you can search for
# words in the documentation. The HTML workshop also contains a viewer for
# compressed HTML files.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_HTMLHELP      = NO

# The CHM_FILE tag can be used to specify the file name of the resulting .chm
# file. You can add a path in front of the file if the result should not be
# written to the html output directory.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

CHM_FILE               =

# The HHC_LOCATION tag can be used to specify the location (absolute path
# including file name) of the HTML help compiler (hhc.exe). If non-empty,
# doxygen will try to run the HTML help compiler on the generated index.hhp.
# The file has to be specified with full path.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

HHC_LOCATION           =

# The GENERATE_CHI flag controls if a separate .chi index file is generated
# (YES) or that it should be included in the main .chm file (NO).
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

GENERATE_CHI           = NO

# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
# and project file content.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

CHM_INDEX_ENCODING     =

# The BINARY_TOC flag controls whether a binary table of contents is generated
# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
# enables the Previous and Next buttons.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

BINARY_TOC             = NO

# The TOC_EXPAND flag can be set to YES to add extra items for group members to
# the table of contents of the HTML help documentation and to the tree view.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

TOC_EXPAND             = NO

# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
# (.qch) of the generated HTML documentation.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_QHP           = NO

# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
# the file name of the resulting .qch file. The path specified is relative to
# the HTML output folder.
# This tag requires that the tag GENERATE_QHP is set to YES.

QCH_FILE               =

# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
# Project output. For more information please see Qt Help Project / Namespace
# (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_NAMESPACE          = org.doxygen.Project

# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
# Help Project output. For more information please see Qt Help Project / Virtual
# Folders (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
# The default value is: doc.
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_VIRTUAL_FOLDER     = doc

# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
# filter to add. For more information please see Qt Help Project / Custom
# Filters (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_CUST_FILTER_NAME   =

# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
# custom filter to add. For more information please see Qt Help Project / Custom
# Filters (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_CUST_FILTER_ATTRS  =

# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
# project's filter section matches. Qt Help Project / Filter Attributes (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_SECT_FILTER_ATTRS  =

# The QHG_LOCATION tag can be used to specify the location (absolute path
# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
# run qhelpgenerator on the generated .qhp file.
# This tag requires that the tag GENERATE_QHP is set to YES.

QHG_LOCATION           =

# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
# generated, together with the HTML files, they form an Eclipse help plugin. To
# install this plugin and make it available under the help contents menu in
# Eclipse, the contents of the directory containing the HTML and XML files needs
# to be copied into the plugins directory of eclipse. The name of the directory
# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
# After copying Eclipse needs to be restarted before the help appears.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_ECLIPSEHELP   = NO

# A unique identifier for the Eclipse help plugin. When installing the plugin
# the directory name containing the HTML and XML files should also have this
# name. Each documentation set should have its own identifier.
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.

ECLIPSE_DOC_ID         = org.doxygen.Project

# If you want full control over the layout of the generated HTML pages it might
# be necessary to disable the index and replace it with your own. The
# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
# of each HTML page. A value of NO enables the index and the value YES disables
# it. Since the tabs in the index contain the same information as the navigation
# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

DISABLE_INDEX          = NO

# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information. If the tag
# value is set to YES, a side panel will be generated containing a tree-like
# index structure (just like the one that is generated for HTML Help). For this
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
# (i.e. any modern browser). Windows users are probably better off using the
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
# further fine-tune the look of the index. As an example, the default style
# sheet generated by doxygen has an example that shows how to put an image at
# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
# the same information as the tab index, you could consider setting
# DISABLE_INDEX to YES when enabling this option.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_TREEVIEW      = NO

# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
# doxygen will group on one line in the generated HTML documentation.
#
# Note that a value of 0 will completely suppress the enum values from appearing
# in the overview section.
# Minimum value: 0, maximum value: 20, default value: 4.
# This tag requires that the tag GENERATE_HTML is set to YES.

ENUM_VALUES_PER_LINE   = 4

# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
# to set the initial width (in pixels) of the frame in which the tree is shown.
# Minimum value: 0, maximum value: 1500, default value: 250.
# This tag requires that the tag GENERATE_HTML is set to YES.

TREEVIEW_WIDTH         = 250

# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
# external symbols imported via tag files in a separate window.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

EXT_LINKS_IN_WINDOW    = NO

# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
# the HTML output. These images will generally look nicer at scaled resolutions.
# Possible values are: png (the default) and svg (looks nicer but requires the
# pdf2svg or inkscape tool).
# The default value is: png.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_FORMULA_FORMAT    = png

# Use this tag to change the font size of LaTeX formulas included as images in
# the HTML documentation. When you change the font size after a successful
# doxygen run you need to manually remove any form_*.png images from the HTML
# output directory to force them to be regenerated.
# Minimum value: 8, maximum value: 50, default value: 10.
# This tag requires that the tag GENERATE_HTML is set to YES.

FORMULA_FONTSIZE       = 10

# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
# generated for formulas are transparent PNGs. Transparent PNGs are not
# supported properly for IE 6.0, but are supported on all modern browsers.
#
# Note that when changing this option you need to delete any form_*.png files in
# the HTML output directory before the changes have effect.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.

FORMULA_TRANSPARENT    = YES

# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.

FORMULA_MACROFILE      =

# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
# https://www.mathjax.org) which uses client side JavaScript for the rendering
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
# installed or if you want to formulas look prettier in the HTML output. When
# enabled you may also need to install MathJax separately and configure the path
# to it using the MATHJAX_RELPATH option.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

USE_MATHJAX            = NO

# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. See the MathJax site (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility), NativeMML (i.e. MathML) and SVG.
# The default value is: HTML-CSS.
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_FORMAT         = HTML-CSS

# When MathJax is enabled you need to specify the location relative to the HTML
# output directory using the MATHJAX_RELPATH option. The destination directory
# should contain the MathJax.js script. For instance, if the mathjax directory
# is located at the same level as the HTML output directory, then
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_RELPATH        =

# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_EXTENSIONS     =

# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
# of code that will be used on startup of the MathJax code. See the MathJax site
# (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
# example see the documentation.
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_CODEFILE       =

# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
# the HTML output. The underlying search engine uses javascript and DHTML and
# should work on any modern browser. Note that when using HTML help
# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
# there is already a search function so this one should typically be disabled.
# For large projects the javascript based search engine can be slow, then
# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
# search using the keyboard; to jump to the search box use <access key> + S
# (what the <access key> is depends on the OS and browser, but it is typically
# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
# key> to jump into the search results window, the results can be navigated
# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
# the search. The filter options can be selected when the cursor is inside the
# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
# to select a filter and <Enter> or <escape> to activate or cancel the filter
# option.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.

SEARCHENGINE           = YES

# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
# implemented using a web server instead of a web client using JavaScript. There
# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
# setting. When disabled, doxygen will generate a PHP script for searching and
# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
# and searching needs to be provided by external tools. See the section
# "External Indexing and Searching" for details.
# The default value is: NO.
# This tag requires that the tag SEARCHENGINE is set to YES.

SERVER_BASED_SEARCH    = NO

# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
# script for searching. Instead the search results are written to an XML file
# which needs to be processed by an external indexer. Doxygen will invoke an
# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
# search results.
#
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see:
# https://xapian.org/).
#
# See the section "External Indexing and Searching" for details.
# The default value is: NO.
# This tag requires that the tag SEARCHENGINE is set to YES.

EXTERNAL_SEARCH        = NO

# The SEARCHENGINE_URL should point to a search engine hosted by a web server
# which will return the search results when EXTERNAL_SEARCH is enabled.
#
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see:
# https://xapian.org/). See the section "External Indexing and Searching" for
# details.
# This tag requires that the tag SEARCHENGINE is set to YES.

SEARCHENGINE_URL       =

# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
# search data is written to a file for indexing by an external tool. With the
# SEARCHDATA_FILE tag the name of this file can be specified.
# The default file is: searchdata.xml.
# This tag requires that the tag SEARCHENGINE is set to YES.

SEARCHDATA_FILE        = searchdata.xml

# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
# projects and redirect the results back to the right project.
# This tag requires that the tag SEARCHENGINE is set to YES.

EXTERNAL_SEARCH_ID     =

# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
# projects other than the one defined by this configuration file, but that are
# all added to the same external search index. Each project needs to have a
# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
# to a relative location where the documentation can be found. The format is:
# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
# This tag requires that the tag SEARCHENGINE is set to YES.

EXTRA_SEARCH_MAPPINGS  =

#---------------------------------------------------------------------------
# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------

# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.

GENERATE_LATEX         = NO

# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: latex.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_OUTPUT           = latex

# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked.
#
# Note that when not enabling USE_PDFLATEX the default is latex when enabling
# USE_PDFLATEX the default is pdflatex and when in the later case latex is
# chosen this is overwritten by pdflatex. For specific output languages the
# default can have been set differently, this depends on the implementation of
# the output language.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_CMD_NAME         =

# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
# index for LaTeX.
# Note: This tag is used in the Makefile / make.bat.
# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
# (.tex).
# The default file is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.

MAKEINDEX_CMD_NAME     = makeindex

# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
# generate index for LaTeX. In case there is no backslash (\) as first character
# it will be automatically added in the LaTeX code.
# Note: This tag is used in the generated output file (.tex).
# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
# The default value is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_MAKEINDEX_CMD    = makeindex

# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.

COMPACT_LATEX          = NO

# The PAPER_TYPE tag can be used to set the paper type that is used by the
# printer.
# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
# 14 inches) and executive (7.25 x 10.5 inches).
# The default value is: a4.
# This tag requires that the tag GENERATE_LATEX is set to YES.

PAPER_TYPE             = a4

# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
# that should be included in the LaTeX output. The package can be specified just
# by its name or with the correct syntax as to be used with the LaTeX
# \usepackage command. To get the times font for instance you can specify :
# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
# To use the option intlimits with the amsmath package you can specify:
# EXTRA_PACKAGES=[intlimits]{amsmath}
# If left blank no extra packages will be included.
# This tag requires that the tag GENERATE_LATEX is set to YES.

EXTRA_PACKAGES         =

# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
# generated LaTeX document. The header should contain everything until the first
# chapter. If it is left blank doxygen will generate a standard header. See
# section "Doxygen usage" for information on how to let doxygen write the
# default header to a separate file.
#
# Note: Only use a user-defined header if you know what you are doing! The
# following commands have a special meaning inside the header: $title,
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
# string, for the replacement values of the other commands the user is referred
# to HTML_HEADER.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_HEADER           =

# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
# generated LaTeX document. The footer should contain everything after the last
# chapter. If it is left blank doxygen will generate a standard footer. See
# LATEX_HEADER for more information on how to generate a default footer and what
# special commands can be used inside the footer.
#
# Note: Only use a user-defined footer if you know what you are doing!
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_FOOTER           =

# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# LaTeX style sheets that are included after the standard style sheets created
# by doxygen. Using this option one can overrule certain style aspects. Doxygen
# will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list).
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_EXTRA_STYLESHEET =

# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the LATEX_OUTPUT output
# directory. Note that the files will be copied as-is; there are no commands or
# markers available.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_EXTRA_FILES      =

# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
# contain links (just like the HTML output) instead of page references. This
# makes the output suitable for online browsing using a PDF viewer.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.

PDF_HYPERLINKS         = YES

# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
# files. Set this option to YES, to get a higher quality PDF documentation.
#
# See also section LATEX_CMD_NAME for selecting the engine.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.

USE_PDFLATEX           = YES

# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
# command to the generated LaTeX files. This will instruct LaTeX to keep running
# if errors occur, instead of asking the user for help. This option is also used
# when generating formulas in HTML.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_BATCHMODE        = NO

# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
# index chapters (such as File Index, Compound Index, etc.) in the output.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_HIDE_INDICES     = NO

# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
# bibliography, e.g. plainnat, or ieeetr. See
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
# The default value is: plain.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_BIB_STYLE        = plain

# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
# page will contain the date and time when the page was generated. Setting this
# to NO can help when comparing the output of multiple runs.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_TIMESTAMP        = NO

# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
# path from which the emoji images will be read. If a relative path is entered,
# it will be relative to the LATEX_OUTPUT directory. If left blank the
# LATEX_OUTPUT directory will be used.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_EMOJI_DIRECTORY  =

#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------

# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
# RTF output is optimized for Word 97 and may not look too pretty with other RTF
# readers/editors.
# The default value is: NO.

GENERATE_RTF           = NO

# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: rtf.
# This tag requires that the tag GENERATE_RTF is set to YES.

RTF_OUTPUT             = rtf

# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.

COMPACT_RTF            = NO

# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
# contain hyperlink fields. The RTF file will contain links (just like the HTML
# output) instead of page references. This makes the output suitable for online
# browsing using Word or some other Word compatible readers that support those
# fields.
#
# Note: WordPad (write) and others do not support links.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.

RTF_HYPERLINKS         = NO

# Load stylesheet definitions from file. Syntax is similar to doxygen's
# configuration file, i.e. a series of assignments. You only have to provide
# replacements, missing definitions are set to their default value.
#
# See also section "Doxygen usage" for information on how to generate the
# default style sheet that doxygen normally uses.
# This tag requires that the tag GENERATE_RTF is set to YES.

RTF_STYLESHEET_FILE    =

# Set optional variables used in the generation of an RTF document. Syntax is
# similar to doxygen's configuration file. A template extensions file can be
# generated using doxygen -e rtf extensionFile.
# This tag requires that the tag GENERATE_RTF is set to YES.

RTF_EXTENSIONS_FILE    =

#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------

# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
# classes and files.
# The default value is: NO.

GENERATE_MAN           = NO

# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it. A directory man3 will be created inside the directory specified by
# MAN_OUTPUT.
# The default directory is: man.
# This tag requires that the tag GENERATE_MAN is set to YES.

MAN_OUTPUT             = man

# The MAN_EXTENSION tag determines the extension that is added to the generated
# man pages. In case the manual section does not start with a number, the number
# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
# optional.
# The default value is: .3.
# This tag requires that the tag GENERATE_MAN is set to YES.

MAN_EXTENSION          = .3

# The MAN_SUBDIR tag determines the name of the directory created within
# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
# MAN_EXTENSION with the initial . removed.
# This tag requires that the tag GENERATE_MAN is set to YES.

MAN_SUBDIR             =

# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
# will generate one additional man file for each entity documented in the real
# man page(s). These additional files only source the real man page, but without
# them the man command would be unable to find the correct page.
# The default value is: NO.
# This tag requires that the tag GENERATE_MAN is set to YES.

MAN_LINKS              = NO

#---------------------------------------------------------------------------
# Configuration options related to the XML output
#---------------------------------------------------------------------------

# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
# captures the structure of the code including all documentation.
# The default value is: NO.

GENERATE_XML           = YES

# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: xml.
# This tag requires that the tag GENERATE_XML is set to YES.

XML_OUTPUT             = xml

# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
# listings (including syntax highlighting and cross-referencing information) to
# the XML output. Note that enabling this will significantly increase the size
# of the XML output.
# The default value is: YES.
# This tag requires that the tag GENERATE_XML is set to YES.

XML_PROGRAMLISTING     = NO

# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
# namespace members in file scope as well, matching the HTML output.
# The default value is: NO.
# This tag requires that the tag GENERATE_XML is set to YES.

XML_NS_MEMB_FILE_SCOPE = NO

#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------

# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
# that can be used to generate PDF.
# The default value is: NO.

GENERATE_DOCBOOK       = NO

# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
# front of it.
# The default directory is: docbook.
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.

DOCBOOK_OUTPUT         = docbook

#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------

# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
# the structure of the code including all documentation. Note that this feature
# is still experimental and incomplete at the moment.
# The default value is: NO.

GENERATE_AUTOGEN_DEF   = NO

#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------

# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
# file that captures the structure of the code including all documentation.
#
# Note that this feature is still experimental and incomplete at the moment.
# The default value is: NO.

GENERATE_PERLMOD       = NO

# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
# output from the Perl module output.
# The default value is: NO.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.

PERLMOD_LATEX          = NO

# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
# formatted so it can be parsed by a human reader. This is useful if you want to
# understand what is going on. On the other hand, if this tag is set to NO, the
# size of the Perl module output will be much smaller and Perl will parse it
# just the same.
# The default value is: YES.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.

PERLMOD_PRETTY         = YES

# The names of the make variables in the generated doxyrules.make file are
# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
# so different doxyrules.make files included by the same Makefile don't
# overwrite each other's variables.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.

PERLMOD_MAKEVAR_PREFIX =

#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------

# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
# C-preprocessor directives found in the sources and include files.
# The default value is: YES.

ENABLE_PREPROCESSING   = YES

# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
# in the source code. If set to NO, only conditional compilation will be
# performed. Macro expansion can be done in a controlled way by setting
# EXPAND_ONLY_PREDEF to YES.
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

MACRO_EXPANSION        = YES

# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
# the macro expansion is limited to the macros specified with the PREDEFINED and
# EXPAND_AS_DEFINED tags.
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

EXPAND_ONLY_PREDEF     = YES

# If the SEARCH_INCLUDES tag is set to YES, the include files in the
# INCLUDE_PATH will be searched if a #include is found.
# The default value is: YES.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

SEARCH_INCLUDES        = YES

# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.

INCLUDE_PATH           =

# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the
# directories. If left blank, the patterns specified with FILE_PATTERNS will be
# used.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

INCLUDE_FILE_PATTERNS  =

# The PREDEFINED tag can be used to specify one or more macro names that are
# defined before the preprocessor is started (similar to the -D option of e.g.
# gcc). The argument of the tag is a list of macros of the form: name or
# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
# is assumed. To prevent a macro definition from being undefined via #undef or
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

PREDEFINED             = DOXYGEN_DOCUMENTATION_BUILD \
                         DOXYGEN_SHOULD_SKIP_THIS \
                         G_BEGIN_DECLS= \
                         G_END_DECLS= \
                         WP_API= \
                         WP_PRIVATE_API= \
                         WP_PLUGIN_EXPORT= \
                         WP_PADDING(...)= \
                         G_GNUC_CONST= \
                         G_GNUC_PRINTF(...)= \
                         G_GNUC_NULL_TERMINATED= \
                         G_DEFINE_TYPE_WITH_CODE(...)= \
                         G_DEFINE_ABSTRACT_TYPE(...)= \
                         G_DEFINE_TYPE(...)= \
                         G_DEFINE_BOXED_TYPE(...)= \
                         G_DEFINE_INTERFACE(...)= \
                         G_DEFINE_QUARK(...)= \
                         G_DEFINE_TYPE_WITH_PRIVATE(...)= \
                         G_DEFINE_AUTOPTR_CLEANUP_FUNC(...)= \
                         G_DECLARE_FINAL_TYPE(...)= \
                         G_DECLARE_DERIVABLE_TYPE(...)= \
                         G_DECLARE_INTERFACE(...)= \
                         GQuark=

# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
# macro definition that is found in the sources will be used. Use the PREDEFINED
# tag if you want to use a different macro definition that overrules the
# definition found in the source code.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

EXPAND_AS_DEFINED      =

# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
# remove all references to function-like macros that are alone on a line, have
# an all uppercase name, and do not end with a semicolon. Such function macros
# are typically used for boiler-plate code, and will confuse the parser if not
# removed.
# The default value is: YES.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

SKIP_FUNCTION_MACROS   = YES

#---------------------------------------------------------------------------
# Configuration options related to external references
#---------------------------------------------------------------------------

# The TAGFILES tag can be used to specify one or more tag files. For each tag
# file the location of the external documentation should be added. The format of
# a tag file without this location is as follows:
# TAGFILES = file1 file2 ...
# Adding location for the tag files is done as follows:
# TAGFILES = file1=loc1 "file2 = loc2" ...
# where loc1 and loc2 can be relative or absolute paths or URLs. See the
# section "Linking to external documentation" for more information about the use
# of tag files.
# Note: Each tag file must have a unique name (where the name does NOT include
# the path). If a tag file is not located in the directory in which doxygen is
# run, you must also specify the path to the tagfile here.

TAGFILES               =

# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
# tag file that is based on the input files it reads. See section "Linking to
# external documentation" for more information about the usage of tag files.

GENERATE_TAGFILE       =

# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
# the class index. If set to NO, only the inherited external classes will be
# listed.
# The default value is: NO.

ALLEXTERNALS           = NO

# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
# in the modules index. If set to NO, only the current project's groups will be
# listed.
# The default value is: YES.

EXTERNAL_GROUPS        = YES

# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
# the related pages index. If set to NO, only the current project's pages will
# be listed.
# The default value is: YES.

EXTERNAL_PAGES         = YES

#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------

# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
# If left empty dia is assumed to be found in the default search path.

DIA_PATH               =

# If set to YES the inheritance and collaboration graphs will hide inheritance
# and usage relations if the target is undocumented or is not a class.
# The default value is: YES.

HIDE_UNDOC_RELATIONS   = YES

# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
# available from the path. This tool is part of Graphviz (see:
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# Bell Labs. The other options in this section have no effect if this option is
# set to NO
# The default value is: YES.

HAVE_DOT               = YES

# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
# to run in parallel. When set to 0 doxygen will base this on the number of
# processors available in the system. You can set it explicitly to a value
# larger than 0 to get control over the balance between CPU load and processing
# speed.
# Minimum value: 0, maximum value: 32, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_NUM_THREADS        = 0

# When you want a differently looking font in the dot files that doxygen
# generates you can specify the font name using DOT_FONTNAME. You need to make
# sure dot is able to find the font, which can be done by putting it in a
# standard location or by setting the DOTFONTPATH environment variable or by
# setting DOT_FONTPATH to the directory containing the font.
# The default value is: Helvetica.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_FONTNAME           = Helvetica

# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
# dot graphs.
# Minimum value: 4, maximum value: 24, default value: 10.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_FONTSIZE           = 10

# By default doxygen will tell dot to use the default font as specified with
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# the path where dot can find it using this tag.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_FONTPATH           =

# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
# each documented class showing the direct and indirect inheritance relations.
# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

CLASS_GRAPH            = YES

# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
# graph for each documented class showing the direct and indirect implementation
# dependencies (inheritance, containment, and class references variables) of the
# class with other documented classes.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

COLLABORATION_GRAPH    = YES

# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
# groups, showing the direct groups dependencies.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

GROUP_GRAPHS           = YES

# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
# collaboration diagrams in a style similar to the OMG's Unified Modeling
# Language.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

UML_LOOK               = NO

# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
# class node. If there are many fields or methods and many nodes the graph may
# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
# number of items for each type to make the size more manageable. Set this to 0
# for no limit. Note that the threshold may be exceeded by 50% before the limit
# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
# but if the number exceeds 15, the total amount of fields shown is limited to
# 10.
# Minimum value: 0, maximum value: 100, default value: 10.
# This tag requires that the tag UML_LOOK is set to YES.

UML_LIMIT_NUM_FIELDS   = 10

# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
# tag is set to YES, doxygen will add type and arguments for attributes and
# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
# will not generate fields with class member information in the UML graphs. The
# class diagrams will look similar to the default class diagrams but using UML
# notation for the relationships.
# Possible values are: NO, YES and NONE.
# The default value is: NO.
# This tag requires that the tag UML_LOOK is set to YES.

DOT_UML_DETAILS        = NO

# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
# to display on a single line. If the actual line length exceeds this threshold
# significantly it will wrapped across multiple lines. Some heuristics are apply
# to avoid ugly line breaks.
# Minimum value: 0, maximum value: 1000, default value: 17.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_WRAP_THRESHOLD     = 17

# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
# collaboration graphs will show the relations between templates and their
# instances.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

TEMPLATE_RELATIONS     = NO

# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
# YES then doxygen will generate a graph for each documented file showing the
# direct and indirect include dependencies of the file with other documented
# files.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

INCLUDE_GRAPH          = YES

# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
# set to YES then doxygen will generate a graph for each documented file showing
# the direct and indirect include dependencies of the file with other documented
# files.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

INCLUDED_BY_GRAPH      = YES

# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
# dependency graph for every global function or class method.
#
# Note that enabling this option will significantly increase the time of a run.
# So in most cases it will be better to enable call graphs for selected
# functions only using the \callgraph command. Disabling a call graph can be
# accomplished by means of the command \hidecallgraph.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

CALL_GRAPH             = NO

# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
# dependency graph for every global function or class method.
#
# Note that enabling this option will significantly increase the time of a run.
# So in most cases it will be better to enable caller graphs for selected
# functions only using the \callergraph command. Disabling a caller graph can be
# accomplished by means of the command \hidecallergraph.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

CALLER_GRAPH           = NO

# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
# hierarchy of all classes instead of a textual one.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

GRAPHICAL_HIERARCHY    = YES

# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
# dependencies a directory has on other directories in a graphical way. The
# dependency relations are determined by the #include relations between the
# files in the directories.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

DIRECTORY_GRAPH        = YES

# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. For an explanation of the image formats see the section
# output formats in the documentation of the dot tool (Graphviz (see:
# http://www.graphviz.org/)).
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
# png:gdiplus:gdiplus.
# The default value is: png.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_IMAGE_FORMAT       = png

# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
# enable generation of interactive SVG images that allow zooming and panning.
#
# Note that this requires a modern browser other than Internet Explorer. Tested
# and working are Firefox, Chrome, Safari, and Opera.
# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
# the SVG files visible. Older versions of IE do not have SVG support.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

INTERACTIVE_SVG        = NO

# The DOT_PATH tag can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_PATH               =

# The DOTFILE_DIRS tag can be used to specify one or more directories that
# contain dot files that are included in the documentation (see the \dotfile
# command).
# This tag requires that the tag HAVE_DOT is set to YES.

DOTFILE_DIRS           =

# The MSCFILE_DIRS tag can be used to specify one or more directories that
# contain msc files that are included in the documentation (see the \mscfile
# command).

MSCFILE_DIRS           =

# The DIAFILE_DIRS tag can be used to specify one or more directories that
# contain dia files that are included in the documentation (see the \diafile
# command).

DIAFILE_DIRS           =

# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
# path where java can find the plantuml.jar file. If left blank, it is assumed
# PlantUML is not used or called during a preprocessing step. Doxygen will
# generate a warning when it encounters a \startuml command in this case and
# will not generate output for the diagram.

PLANTUML_JAR_PATH      =

# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
# configuration file for plantuml.

PLANTUML_CFG_FILE      =

# When using plantuml, the specified paths are searched for files specified by
# the !include statement in a plantuml block.

PLANTUML_INCLUDE_PATH  =

# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
# that will be shown in the graph. If the number of nodes in a graph becomes
# larger than this value, doxygen will truncate the graph, which is visualized
# by representing a node as a red box. Note that doxygen if the number of direct
# children of the root node in a graph is already larger than
# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
# Minimum value: 0, maximum value: 10000, default value: 50.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_GRAPH_MAX_NODES    = 50

# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
# generated by dot. A depth value of 3 means that only nodes reachable from the
# root by following a path via at most 3 edges will be shown. Nodes that lay
# further from the root node will be omitted. Note that setting this option to 1
# or 2 may greatly reduce the computation time needed for large code bases. Also
# note that the size of a graph can be further restricted by
# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
# Minimum value: 0, maximum value: 1000, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.

MAX_DOT_GRAPH_DEPTH    = 0

# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not seem
# to support this out of the box.
#
# Warning: Depending on the platform used, enabling this option may lead to
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
# read).
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_TRANSPARENT        = NO

# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
# this, this feature is disabled by default.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_MULTI_TARGETS      = NO

# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
# explaining the meaning of the various boxes and arrows in the dot generated
# graphs.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

GENERATE_LEGEND        = YES

# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
# files that are used to generate the various graphs.
#
# Note: This setting is not only used for dot files but also for msc and
# plantuml temporary files.
# The default value is: YES.

DOT_CLEANUP            = YES
 07070100000009000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002000000000wireplumber-0.4.17/docs/_static   0707010000000A000081A4000000000000000000000001656CC35F0000033D000000000000000000000000000000000000002B00000000wireplumber-0.4.17/docs/_static/custom.css    /* expand to full window width */
.wy-nav-content {
  max-width: none;
}

/* style function names */
.rst-content .sig-name .n {
  color: #000;
  padding-left: 0.3em;
}

/* style parenthesis in function signatures */
.rst-content .sig-paren {
  padding: 0.3em;
}

/* space between pointer symbols and variable names:
   ex: WpObject *<-padding->self */
.rst-content .sig span.p+span.n {
  padding-left: 0.3em;
}

/* space between macro names and their definitions:
   ex: FOO_TYPE_BAR<-padding->(foo_bar_get_type()) */
.rst-content .sig-name+.pre {
  padding-left: 0.5em;
}

/* hack for gobject properties, which are marked as cpp signatures */
.rst-content .sig.sig-object.cpp {
  padding-left: 0.3em;
}

div.graphviz {
  padding-top: 1em;
  padding-bottom: 1em;
}

/* space between badges */
img+img {
  padding-left: 0.5em;
}
   0707010000000B000081A4000000000000000000000001656CC35F00000406000000000000000000000000000000000000002300000000wireplumber-0.4.17/docs/conf.py.in    # -- Project information -----------------------------------------------------

project = 'WirePlumber'
copyright = '2021, Collabora'
author = 'Collabora'
release = '@VERSION@'
version = '@VERSION@'

# -- Breathe configuration ---------------------------------------------------

extensions = [
  'breathe',
  'sphinx_rtd_theme',
  'sphinx.ext.graphviz',
]

breathe_projects = {
  "WirePlumber": "@OUTDIR@/wp/xml",
}
breathe_default_project = "WirePlumber"
breathe_default_members = ('members', 'undoc-members')

breathe_domain_by_extension = {
    "h" : "c",
    "c" : "c",
}
breathe_show_define_initializer = True
breathe_show_enumvalue_initializer = True

# -- Options for HTML output -------------------------------------------------

html_theme = "sphinx_rtd_theme"

html_theme_options = {
  "display_version": True,
  "prev_next_buttons_location": "both",
  "style_external_links": True,
}

html_static_path = ['@SRCDIR@/_static']
html_css_files = ['custom.css']

graphviz_output_format = "svg"

pygments_style = "friendly"
  0707010000000C000081A4000000000000000000000001656CC35F00003953000000000000000000000000000000000000002A00000000wireplumber-0.4.17/docs/gen-api-gtkdoc.py #!/usr/bin/env python
#
#  Copyright 2015 The Geany contributors
#  Copyright 2021 Collabora Ltd.
#    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.

import os
import sys
import re
from lxml import etree
from optparse import OptionParser


def normalize_text(s):
    r"""
    Normalizes whitespace in text.
    >>> normalize_text("asd xxx")
    'asd xxx'
    >>> normalize_text(" asd\nxxx  ")
    'asd xxx'
    """
    return s.replace("\n", " ").strip()


CXX_NAMESPACE_RE = re.compile(r'[_a-zA-Z][_0-9a-zA-Z]*::')
def fix_definition(s):
    """
    Removes C++ name qualifications from some definitions.
    For example:
    >>> fix_definition("bool flag")
    'bool flag'
    >>> fix_definition("bool FooBar::flag")
    'bool flag'
    >>> fix_definition("void(* _GeanyObjectClass::project_open) (GKeyFile *keyfile)")
    'void(* project_open) (GKeyFile *keyfile)'
    """
    return CXX_NAMESPACE_RE.sub(r"", s)


class AtDoc(object):
    def __init__(self):
        self.retval = None
        self.since = ""
        self.annot = []

    def cb(self, type, str):
        if (type == "param"):
            words = str.split(" ", 2)
            self.annot = []
        elif (type == "return"):
            self.annot = []
        elif (type == "since"):
            self.since = str.rstrip()
        elif (type == "see"):
            return "See " + str
        elif type in ("a", "c") and str in ("NULL", "TRUE", "FALSE"):
            return "%" + str
        elif (type == "a"):
            return "@" + str
        else:
            return str

        return ""


class DoxygenProcess(object):
    def __init__(self):
        self.at = None

    # http://stackoverflow.com/questions/4624062/get-all-text-inside-a-tag-in-lxml
    @staticmethod
    def stringify_children(node):
        from lxml.etree import tostring
        from itertools import chain
        parts = ([node.text] +
                 list(chain(*([c.text, tostring(c).decode("utf-8"), c.tail] for c in node.getchildren()))) +
                 [node.tail])
        # filter removes possible Nones in texts and tails
        return "".join(filter(None, parts))

    def get_program_listing(self, xml):
        from lxml.etree import tostring
        arr = ["", "|[<!-- language=\"C\" -->"]
        for l in xml.getchildren():
            if (l.tag == "codeline"):
                # a codeline is of the form
                # <highlight class="normal">GeanyDocument<sp/>*doc<sp/>=<sp/>...;</highlight>
                # <sp/> tags must be replaced with spaces, then just use the text
                h = l.find("highlight")
                if h is not None:
                    html = tostring(h).decode("utf-8")
                    html = html.replace("<sp/>", " ")
                    arr.append("  " + tostring(etree.HTML(html), method="text").decode("utf-8"))
        arr.append("]|")
        return "\n".join(arr)

    def join_annot(self):
        s = " ".join(map(lambda x: "(%s)" % x, self.at.annot))
        return s + ": " if s else ""

    def process_element(self, xml):
        self.at = AtDoc()
        s = self.__process_element(xml)
        return s

    def get_extra(self):
        return self.join_annot()

    def get_return(self):
        return self.at.retval

    def get_since(self):
        return self.at.since

    def __process_element(self, xml):
        s = ""

        if xml.text and re.search('\S', xml.text):
            s += xml.text
        for n in xml.getchildren():
            if n.tag == "emphasis":
                s += self.at.cb("a", self.__process_element(n))
            if n.tag == "computeroutput":
                s += self.at.cb("c", self.__process_element(n))
            if n.tag == "itemizedlist":
                s += "\n" + self.__process_element(n)
            if n.tag == "listitem":
                s += " - " + self.__process_element(n)
            if n.tag == "para":
                p = self.__process_element(n)
                if re.search('\S', p):
                    s += p + "\n"
            if n.tag == "ref":
                s += n.text if n.text else ""
            if n.tag == "simplesect":
                ss = self.at.cb(n.get("kind"), self.__process_element(n))
                s += ss + "\n" if ss else ""
            if n.tag == "programlisting":
                s += self.get_program_listing(n)
            if n.tag == "xrefsect":
                s += self.__process_element(n)
            if n.tag == "xreftitle":
                s += self.__process_element(n) + ": "
            if n.tag == "xrefdescription":
                s += self.__process_element(n)
            if n.tag == "ulink":
                s += self.__process_element(n)
            if n.tag == "linebreak":
                s += "\n"
            if n.tag == "ndash":
                s += "--"
                # workaround for doxygen bug #646002
            if n.tag == "htmlonly":
                s += ""
            if n.tail:
                if re.search('\S', n.tail):
                  s += n.tail
            if n.tag.startswith("param"):
                pass  # parameters are handled separately in DoxyFunction::from_memberdef()
        return s


class DoxyMember(object):
    def __init__(self, name, brief, extra=""):
        self.name       = name
        self.brief      = brief
        self.extra      = extra


class DoxyElement(object):

    def __init__(self, name, definition, **kwargs):
        self.name       = name
        self.definition = definition
        self.brief      = kwargs.get('brief', "")
        self.detail     = kwargs.get('detail', "")
        self.members    = kwargs.get('members', [])
        self.since      = kwargs.get('since', "")
        self.extra      = kwargs.get('extra', "")
        self.retval     = kwargs.get('retval', None)

    def is_documented(self):
        return (normalize_text(self.brief) != "" or
                normalize_text(self.detail) != "" or
                normalize_text(self.since) != "")

    def add_brief(self, xml):
        proc = DoxygenProcess()
        self.brief = proc.process_element(xml)
        self.extra += proc.get_extra()

    def add_detail(self, xml):
        proc = DoxygenProcess()
        self.detail = proc.process_element(xml)
        self.extra += proc.get_extra()
        self.since = proc.get_since()

    def add_member(self, xml):
        name = xml.find("name").text
        proc = DoxygenProcess()
        brief = proc.process_element(xml.find("briefdescription"))
        # optional doxygen command output appears within <detaileddescription />
        proc.process_element(xml.find("detaileddescription"))
        self.members.append(DoxyMember(name, normalize_text(brief), proc.get_extra()))

    def add_param(self, xml):
        name = xml.find("parameternamelist").find("parametername").text
        proc = DoxygenProcess()
        brief = proc.process_element(xml.find("parameterdescription"))
        self.members.append(DoxyMember(name, normalize_text(brief), proc.get_extra()))

    def add_return(self, xml):
        proc = DoxygenProcess()
        brief = proc.process_element(xml)
        self.retval = DoxyMember("ret", normalize_text(brief), proc.get_extra())

    def to_gtkdoc(self):
        s = []
        s.append("/**")
        s.append(" * %s: %s" % (self.name, self.extra))
        for p in self.members:
            s.append(" * @%s: %s %s" % (p.name, p.extra, p.brief))
        s.append(" *")
        s.append(" * %s" % self.brief.replace("\n", "\n * "))
        s.append(" *")
        s.append(" * %s" % self.detail.replace("\n", "\n * "))
        s.append(" *")
        if self.retval:
            s.append(" * Returns: %s %s" % (self.retval.extra, self.retval.brief))
        if self.since:
            s.append(" *")
            s.append(" * Since: %s" % self.since)
        s.append(" */")
        s.append("")
        return "\n".join(s)


class DoxyTypedef(DoxyElement):
    @staticmethod
    def from_memberdef(xml):
        name = xml.find("name").text
        d = normalize_text(xml.find("definition").text)
        d += ";"
        return DoxyTypedef(name, d)


class DoxyEnum(DoxyElement):
    @staticmethod
    def from_memberdef(xml):
        name = xml.find("name").text
        d = "typedef enum {\n"
        for member in xml.findall("enumvalue"):
            v = member.find("initializer")
            d += "\t%s%s,\n" % (member.find("name").text, " "+v.text if v is not None else "")
        d += "} %s;\n" % name

        e = DoxyEnum(name, d)
        e.add_brief(xml.find("briefdescription"))
        e.add_detail(xml.find("detaileddescription"))
        for p in xml.findall("enumvalue"):
            e.add_member(p)
        return e


class DoxyStruct(DoxyElement):
    @staticmethod
    def from_compounddef(xml, typedefs=[]):
        name = xml.find("compoundname").text
        d = "struct %s {\n" % name
        memberdefs = xml.xpath(".//sectiondef[@kind='public-attrib']/memberdef")
        for p in memberdefs:
            # workaround for struct members. g-ir-scanner can't properly map struct members
            # (beginning with struct GeanyFoo) to the typedef and assigns a generic type for them
            # thus we fix that up here and enforce usage of the typedef. These are written
            # out first, before any struct definition, for this reason
            # Exception: there are no typedefs for GeanyFooPrivate so skip those. Their exact
            # type isn't needed anyway
            s = fix_definition(p.find("definition").text).lstrip()
            proc = DoxygenProcess()
            brief = proc.process_element(p.find("briefdescription"))
            private = (normalize_text(brief) == "")
            words = s.split()
            if (words[0] == "struct"):
                if not (words[1].endswith("Private") or words[1].endswith("Private*")):
                    s = " ".join(words[1:])
            d += "\t/*< %s >*/\n\t%s;\n" % ("private" if private else "public", s)

        d += "};\n"
        e = DoxyStruct(name, d)
        e.add_brief(xml.find("briefdescription"))
        e.add_detail(xml.find("detaileddescription"))
        for p in memberdefs:
            e.add_member(p)
        return e


class DoxyFunction(DoxyElement):
    @staticmethod
    def from_memberdef(xml):
        name = xml.find("name").text
        d = normalize_text(xml.find("definition").text)
        if ((xml.find("argsstring").text) is not None):
            d += " " + xml.find("argsstring").text + ";"
            d = normalize_text(d)

        e = DoxyFunction(name, d)
        e.add_brief(xml.find("briefdescription"))
        e.add_detail(xml.find("detaileddescription"))
        for p in xml.xpath(".//detaileddescription/*/parameterlist[@kind='param']/parameteritem"):
            e.add_param(p)
        x = xml.xpath(".//detaileddescription/*/simplesect[@kind='return']")
        if (len(x) > 0):
            e.add_return(x[0])
        return e


class DoxyVariable(DoxyElement):
    @staticmethod
    def from_memberdef(xml):
        name = xml.find("name").text
        d = normalize_text(xml.find("definition").text)
        d += ";"
        e = DoxyVariable(name, d)
        e.add_brief(xml.find("briefdescription"))
        t = xml.find("type")
        if t is not None:
            typestr = "".join(t.itertext()).strip()
            if typestr.startswith("const "):
                typestr = typestr[6:]
            e.type = "("+typestr+") "
        else:
            e.type = ""
        v = xml.find("initializer")
        e.value = v.text.replace("=","").replace("\n","") if v is not None else ""
        return e
    def to_gtkdoc(self):
        s = super().to_gtkdoc()
        # need this to get g-ir-scanner to recognize this as a constant
        s += "#define "+self.name+" ("+self.type+self.value+")\n"
        s += "#undef "+self.name+"\n"
        return s


def main(args):
    xml_dir = None
    outfile = None

    parser = OptionParser(usage="usage: %prog [options] XML_DIR")
    parser.add_option("-o", "--output", metavar="FILE", help="Write output to FILE",
                      action="store", dest="outfile")
    opts, args = parser.parse_args(args[1:])

    xml_dir = args[0]

    if not (os.path.exists(xml_dir)):
        sys.stderr.write("invalid xml directory\n")
        return 1

    symbols = []
    transform = etree.XSLT(etree.parse(os.path.join(xml_dir, "combine.xslt")))
    doc = etree.parse(os.path.join(xml_dir, "index.xml"))
    root = transform(doc)
    h_files = root.xpath(".//compounddef[@kind='file']/compoundname[substring(.,string-length(.)-1)='.h']/..")

    for f in h_files:
        for n0 in f.xpath(".//*/memberdef[@kind='enum']"):
            e = DoxyEnum.from_memberdef(n0)
            symbols.append(e)

    for n0 in root.xpath(".//compounddef[@kind='struct']"):
        e = DoxyStruct.from_compounddef(n0)
        symbols.append(e)

    for n0 in root.xpath(".//compounddef[@kind='group']"):
        for n1 in n0.xpath(".//*/memberdef[@kind='function']"):
            e = DoxyFunction.from_memberdef(n1)
            symbols.append(e)
        for n1 in n0.xpath(".//*/memberdef[@kind='variable']"):
            e = DoxyVariable.from_memberdef(n1)
            symbols.append(e)

    if (opts.outfile):
        try:
            outfile = open(opts.outfile, "w+")
        except OSError as err:
            sys.stderr.write("failed to open \"%s\" for writing (%s)\n" % (opts.outfile, err.strerror))
            return 1
    else:
        outfile = sys.stdout

    try:
        outfile.write("/*\n * Automatically generated file - do not edit\n */\n\n")

        for e in filter(lambda x: x.is_documented(), symbols):
            outfile.write("\n")
            outfile.write(e.to_gtkdoc())
            outfile.write("\n")

    except BrokenPipeError:
        # probably piped to head or tail
        return 0

    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))
 0707010000000D000081A4000000000000000000000001656CC35F0000126E000000000000000000000000000000000000002400000000wireplumber-0.4.17/docs/meson.build   # Find dependencies
pymod = import('python')

python_doc = pymod.find_installation(
  'python3',
  modules: ['sphinx', 'sphinx_rtd_theme', 'breathe', 'sphinx.ext.graphviz'],
  required: get_option('doc')
)
summary({'Python 3 Sphinx related modules': python_doc.found()},
    bool_yn: true,
    section: 'For documentation'
)

python_gir = pymod.find_installation(
  'python3',
  modules: ['lxml'],
  required: get_option('introspection')
)
summary({'Python 3 lxml module': python_gir.found()},
    bool_yn: true,
    section: 'For introspection'
)

if get_option('doc').enabled() or get_option('introspection').enabled()
  doxygen_p = find_program('doxygen', version: '>= 1.8.0', required: true)
elif get_option('doc').auto() or get_option('introspection').auto()
  doxygen_p = find_program('doxygen', version: '>= 1.8.0', required: false)
else
  doxygen_p = disabler()
endif
summary({'Doxygen': doxygen_p.found()}, bool_yn: true, section: 'For introspection')
summary({'Doxygen': doxygen_p.found()}, bool_yn: true, section: 'For documentation')

sphinx_p = find_program('sphinx-build',
    version: '>= 2.1.0', required: get_option('doc'))
summary({'sphinx-build': sphinx_p.found()}, bool_yn: true, section: 'For documentation')
gir_p = find_program('g-ir-scanner', required: get_option('introspection'))
summary({'g-ir-scanner': gir_p.found()}, bool_yn: true, section: 'For introspection')

build_doc = python_doc.found() and doxygen_p.found() and sphinx_p.found()
build_gir = python_gir.found() and doxygen_p.found() and gir_p.found()

# Run doxygen (common for docs and g-i)

if build_doc or build_gir
  doxy_wp_conf_data = configuration_data()
  doxy_wp_conf_data.set('OUTPUT_DIR', meson.current_build_dir() / 'wp')
  doxy_wp_conf_data.set('INPUT', meson.current_source_dir() / '..' / 'lib' / 'wp')
  doxyfile_wp = configure_file(
    input: 'Doxyfile.in',
    output: 'Doxyfile-wp',
    configuration: doxy_wp_conf_data
  )

  doxyxml_wp_depfiles = [wp_lib_sources, wp_lib_headers]
  doxyxml_wp = custom_target('doxyxml_wp',
    command: [doxygen_p, doxyfile_wp],
    depend_files: doxyxml_wp_depfiles,
    output: 'wp',
    build_by_default: true,
  )
endif

# Build documentation

if build_doc
  sphinx_files = files(
    '_static'/'custom.css',
    meson.current_source_dir()/'..'/'README.rst',
    meson.current_source_dir()/'..'/'NEWS.rst',
  )
  subdir('rst')

  sphinx_conf_data = configuration_data()
  sphinx_conf_data.set('SRCDIR', meson.current_source_dir())
  sphinx_conf_data.set('OUTDIR', meson.current_build_dir())
  sphinx_conf_data.set('VERSION', meson.project_version())
  sphinx_conf = configure_file(
    input: 'conf.py.in',
    output: 'conf.py',
    configuration: sphinx_conf_data
  )

  custom_target('doc',
    command: [sphinx_p,
      '-q',                       # quiet
      '-E',                       # rebuild from scratch
      '-j', 'auto',               # parallel build
      '-d', '@PRIVATE_DIR@',      # doctrees dir
      '-c', '@OUTDIR@',           # conf.py dir
      '@CURRENT_SOURCE_DIR@/rst', # source dir
      '@OUTPUT@',                 # output dir
    ],
    depend_files: [
      sphinx_conf, sphinx_files,
      doxyfile_wp, doxyxml_wp_depfiles,
    ],
    depends: [doxyxml_wp],
    output: 'html',
    install: true,
    install_dir: get_option('datadir') / 'doc' / 'wireplumber',
    build_by_default: true,
  )
endif

# Build GObject introspection

if build_gir
  wp_gtkdoc_h = custom_target('wp_gtkdoc_h',
    command: [python_gir,
        '@CURRENT_SOURCE_DIR@/gen-api-gtkdoc.py',
        '-o', '@OUTPUT@',
        '@OUTDIR@/wp/xml',
    ],
    depends: [doxyxml_wp],
    depend_files: [doxyxml_wp_depfiles, 'gen-api-gtkdoc.py'],
    output: 'wp-gtkdoc.h',
    build_by_default: true,
  )

  # A dummy library dependency to force meson to make the gir target
  # depend on wp-gtkdoc.h, because generate_gir() doesn't add dependencies
  # on its sources (meson bug)
  # fixed in 0.59 by https://github.com/mesonbuild/meson/pull/8805
  dummy_c = custom_target('dummy_c',
    command: ['echo', 'int dummy(void) { return 0; }'],
    capture: true,
    output: 'dummy.c',
    build_by_default: true,
  )
  libdummy = library('dummy',
    wp_gtkdoc_h, dummy_c,
    install: false,
    soversion: '0',
    version: '0.0.0',
  )
  dummy_dep = declare_dependency(link_with: libdummy)

  gnome.generate_gir(wp_lib,
    dependencies: [wp_dep, dummy_dep],
    namespace: 'Wp',
    nsversion: wireplumber_api_version,
    export_packages: 'wireplumber-0.4',
    header: 'wp/wp.h',
    sources: [wpenums_h, wp_gtkdoc_h, wp_lib_headers],
    include_directories: [wpenums_include_dir],
    includes: ['GLib-2.0', 'GObject-2.0', 'Gio-2.0'],
    install: true,
  )
endif
  0707010000000E000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001C00000000wireplumber-0.4.17/docs/rst   0707010000000F000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002200000000wireplumber-0.4.17/docs/rst/c_api 07070100000010000081A4000000000000000000000001656CC35F000003A0000000000000000000000000000000000000002600000000wireplumber-0.4.17/docs/rst/c_api.rst  .. _library_root:

C API Documentation
===================

.. toctree::
   :maxdepth: 1

   c_api/wp_api.rst
   c_api/core_api.rst
   c_api/object_api.rst
   c_api/obj_manager_api.rst
   c_api/obj_interest_api.rst
   c_api/iterator_api.rst
   c_api/transitions_api.rst
   c_api/wperror_api.rst
   c_api/log_api.rst
   c_api/proxy_api.rst
   c_api/pipewire_object_api.rst
   c_api/global_proxy_api.rst
   c_api/node_api.rst
   c_api/port_api.rst
   c_api/link_api.rst
   c_api/device_api.rst
   c_api/client_api.rst
   c_api/metadata_api.rst
   c_api/endpoint_api.rst
   c_api/spa_device_api.rst
   c_api/impl_node_api.rst
   c_api/impl_module_api.rst
   c_api/properties_api.rst
   c_api/spa_type_api.rst
   c_api/spa_json_api.rst
   c_api/spa_pod_api.rst
   c_api/plugin_api.rst
   c_api/component_loader_api.rst
   c_api/session_item_api.rst
   c_api/si_interfaces_api.rst
   c_api/si_factory_api.rst
   c_api/state_api.rst
07070100000011000081A4000000000000000000000001656CC35F0000018D000000000000000000000000000000000000003100000000wireplumber-0.4.17/docs/rst/c_api/client_api.rst  .. _client_api:

PipeWire Client
===============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpGlobalProxy;
      WpGlobalProxy -> WpClient;
      GInterface -> WpPipewireObject;
      WpPipewireObject -> WpClient;
   }

.. doxygenstruct:: WpClient

.. doxygengroup:: wpclient
   :content-only:
   07070100000012000081A4000000000000000000000001656CC35F00000172000000000000000000000000000000000000003B00000000wireplumber-0.4.17/docs/rst/c_api/component_loader_api.rst    .. _component_loader_api:

Component Loader
================
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpPlugin;
      WpPlugin -> WpComponentLoader;
   }

.. doxygenstruct:: WpComponentLoader

.. doxygenstruct:: _WpComponentLoaderClass

.. doxygengroup:: wpcomponentloader
   :content-only:
  07070100000013000081A4000000000000000000000001656CC35F000000C9000000000000000000000000000000000000002F00000000wireplumber-0.4.17/docs/rst/c_api/core_api.rst    .. _core_api:

Core
====
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpCore;
   }

.. doxygenstruct:: WpCore

.. doxygengroup:: wpcore
   :content-only:
   07070100000014000081A4000000000000000000000001656CC35F0000018D000000000000000000000000000000000000003100000000wireplumber-0.4.17/docs/rst/c_api/device_api.rst  .. _device_api:

PipeWire Device
===============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpGlobalProxy;
      WpGlobalProxy -> WpDevice;
      GInterface -> WpPipewireObject;
      WpPipewireObject -> WpDevice;
   }

.. doxygenstruct:: WpDevice

.. doxygengroup:: wpdevice
   :content-only:
   07070100000015000081A4000000000000000000000001656CC35F000001E2000000000000000000000000000000000000003300000000wireplumber-0.4.17/docs/rst/c_api/endpoint_api.rst    .. _endpoint_api:

PipeWire Endpoint
=================
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpGlobalProxy;
      WpGlobalProxy -> WpEndpoint;
      GInterface -> WpPipewireObject;
      WpPipewireObject -> WpEndpoint;
      WpEndpoint -> WpImplEndpoint;
   }

.. doxygenstruct:: WpEndpoint

.. doxygenstruct:: WpImplEndpoint

.. doxygengroup:: wpendpoint
   :content-only:
  07070100000016000081A4000000000000000000000001656CC35F00000174000000000000000000000000000000000000003700000000wireplumber-0.4.17/docs/rst/c_api/global_proxy_api.rst    .. _global_proxy_api:

PipeWire Global Object Proxy
============================
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpGlobalProxy;
   }

.. doxygenstruct:: WpGlobalProxy

.. doxygenstruct:: _WpGlobalProxyClass

.. doxygengroup:: wpglobalproxy
   :content-only:
07070100000017000081A4000000000000000000000001656CC35F00000102000000000000000000000000000000000000003600000000wireplumber-0.4.17/docs/rst/c_api/impl_module_api.rst .. _impl_module_api:

Local Modules
=============

.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpImplModule;
   }

.. doxygenstruct:: WpImplModule
   :members:

.. doxygengroup:: wpimplmodule
   :content-only:
  07070100000018000081A4000000000000000000000001656CC35F00000177000000000000000000000000000000000000003400000000wireplumber-0.4.17/docs/rst/c_api/impl_node_api.rst   .. _impl_node_api:

Local Nodes
===========
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpImplNode;
      GInterface -> WpPipewireObject;
      WpPipewireObject -> WpImplNode;
   }

.. doxygenstruct:: WpImplNode
   :members:

.. doxygengroup:: wpimplnode
   :content-only:
 07070100000019000081A4000000000000000000000001656CC35F00000107000000000000000000000000000000000000003300000000wireplumber-0.4.17/docs/rst/c_api/iterator_api.rst    .. _iterator_api:

Iterator
========
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GBoxed -> WpIterator;
   }

.. doxygenstruct:: WpIterator

.. doxygenstruct:: _WpIteratorMethods

.. doxygengroup:: wpiterator
   :content-only:
 0707010000001A000081A4000000000000000000000001656CC35F0000017F000000000000000000000000000000000000002F00000000wireplumber-0.4.17/docs/rst/c_api/link_api.rst    .. _link_api:

PipeWire Link
=============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpGlobalProxy;
      WpGlobalProxy -> WpLink;
      GInterface -> WpPipewireObject;
      WpPipewireObject -> WpLink;
   }

.. doxygenstruct:: WpLink

.. doxygengroup:: wplink
   :content-only:
 0707010000001B000081A4000000000000000000000001656CC35F00000054000000000000000000000000000000000000002E00000000wireplumber-0.4.17/docs/rst/c_api/log_api.rst .. _log_api:

Debug Logging
=============
.. doxygengroup:: wplog
   :content-only:
0707010000001C000081A4000000000000000000000001656CC35F0000030E000000000000000000000000000000000000002E00000000wireplumber-0.4.17/docs/rst/c_api/meson.build # you need to add here any files you add to the api directory as well
sphinx_files += files(
  'client_api.rst',
  'component_loader_api.rst',
  'core_api.rst',
  'device_api.rst',
  'endpoint_api.rst',
  'global_proxy_api.rst',
  'impl_module_api.rst',
  'impl_node_api.rst',
  'iterator_api.rst',
  'link_api.rst',
  'log_api.rst',
  'metadata_api.rst',
  'node_api.rst',
  'obj_interest_api.rst',
  'obj_manager_api.rst',
  'object_api.rst',
  'pipewire_object_api.rst',
  'plugin_api.rst',
  'port_api.rst',
  'properties_api.rst',
  'proxy_api.rst',
  'session_item_api.rst',
  'si_factory_api.rst',
  'si_interfaces_api.rst',
  'spa_device_api.rst',
  'spa_pod_api.rst',
  'spa_type_api.rst',
  'state_api.rst',
  'transitions_api.rst',
  'wp_api.rst',
  'wperror_api.rst',
)
  0707010000001D000081A4000000000000000000000001656CC35F00000195000000000000000000000000000000000000003300000000wireplumber-0.4.17/docs/rst/c_api/metadata_api.rst    .. _metadata_api:

PipeWire Metadata
=================
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpGlobalProxy;
      WpGlobalProxy -> WpMetadata;
      WpMetadata-> WpImplMetadata;
   }

.. doxygenstruct:: WpMetadata

.. doxygenstruct:: WpImplMetadata

.. doxygengroup:: wpmetadata
   :content-only:
   0707010000001E000081A4000000000000000000000001656CC35F0000017F000000000000000000000000000000000000002F00000000wireplumber-0.4.17/docs/rst/c_api/node_api.rst    .. _node_api:

PipeWire Node
=============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpGlobalProxy;
      WpGlobalProxy -> WpNode;
      GInterface -> WpPipewireObject;
      WpPipewireObject -> WpNode;
   }

.. doxygenstruct:: WpNode

.. doxygengroup:: wpnode
   :content-only:
 0707010000001F000081A4000000000000000000000001656CC35F00000104000000000000000000000000000000000000003700000000wireplumber-0.4.17/docs/rst/c_api/obj_interest_api.rst    .. _obj_interest_api:

Object Interest
===============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GBoxed -> WpObjectInterest;
   }

.. doxygenstruct:: WpObjectInterest

.. doxygengroup:: wpobjectinterest
   :content-only:
07070100000020000081A4000000000000000000000001656CC35F000000FF000000000000000000000000000000000000003600000000wireplumber-0.4.17/docs/rst/c_api/obj_manager_api.rst .. _obj_manager_api:

Object Manager
==============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObjectManager;
   }

.. doxygenstruct:: WpObjectManager

.. doxygengroup:: wpobjectmanager
   :content-only:
 07070100000021000081A4000000000000000000000001656CC35F000000FC000000000000000000000000000000000000003100000000wireplumber-0.4.17/docs/rst/c_api/object_api.rst  .. _object_api:

WpObject
========
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
   }

.. doxygenstruct:: WpObject

.. doxygenstruct:: _WpObjectClass

.. doxygengroup:: wpobject
   :content-only:
07070100000022000081A4000000000000000000000001656CC35F0000013A000000000000000000000000000000000000003A00000000wireplumber-0.4.17/docs/rst/c_api/pipewire_object_api.rst .. _pipewire_object_api:

PipeWire Object
===============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GInterface -> WpPipewireObject;
   }

.. doxygenstruct:: WpPipewireObject

.. doxygenstruct:: _WpPipewireObjectInterface

.. doxygengroup:: wppipewireobject
   :content-only:
  07070100000023000081A4000000000000000000000001656CC35F00000116000000000000000000000000000000000000003100000000wireplumber-0.4.17/docs/rst/c_api/plugin_api.rst  .. _plugin_api:

Plugins
=======
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpPlugin;
   }

.. doxygenstruct:: WpPlugin

.. doxygenstruct:: _WpPluginClass

.. doxygengroup:: wpplugin
   :content-only:
  07070100000024000081A4000000000000000000000001656CC35F0000017F000000000000000000000000000000000000002F00000000wireplumber-0.4.17/docs/rst/c_api/port_api.rst    .. _port_api:

PipeWire Port
=============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpGlobalProxy;
      WpGlobalProxy -> WpPort;
      GInterface -> WpPipewireObject;
      WpPipewireObject -> WpPort;
   }

.. doxygenstruct:: WpPort

.. doxygengroup:: wpport
   :content-only:
 07070100000025000081A4000000000000000000000001656CC35F00000102000000000000000000000000000000000000003500000000wireplumber-0.4.17/docs/rst/c_api/properties_api.rst  .. _properties_api:

Properties Dictionary
=====================
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GBoxed -> WpProperties;
   }

.. doxygenstruct:: WpProperties

.. doxygengroup:: wpproperties
   :content-only:
  07070100000026000081A4000000000000000000000001656CC35F0000011F000000000000000000000000000000000000003000000000wireplumber-0.4.17/docs/rst/c_api/proxy_api.rst   .. _proxy_api:

PipeWire Proxy
==============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
   }

.. doxygenstruct:: WpProxy

.. doxygenstruct:: _WpProxyClass

.. doxygengroup:: wpproxy
   :content-only:
 07070100000027000081A4000000000000000000000001656CC35F0000013C000000000000000000000000000000000000003700000000wireplumber-0.4.17/docs/rst/c_api/session_item_api.rst    .. _session_item_api:

Session Items
=============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpSessionItem;
   }

.. doxygenstruct:: WpSessionItem

.. doxygenstruct:: _WpSessionItemClass

.. doxygengroup:: wpsessionitem
   :content-only:
07070100000028000081A4000000000000000000000001656CC35F00000126000000000000000000000000000000000000003500000000wireplumber-0.4.17/docs/rst/c_api/si_factory_api.rst  .. _si_factory_api:

Session Items Factory
=====================
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpSiFactory;
   }

.. doxygenstruct:: WpSiFactory

.. doxygenstruct:: _WpSiFactoryClass

.. doxygengroup:: wpsifactory
   :content-only:
  07070100000029000081A4000000000000000000000001656CC35F000002EE000000000000000000000000000000000000003800000000wireplumber-0.4.17/docs/rst/c_api/si_interfaces_api.rst   .. _si_interfaces_api:

Session Items Interfaces
========================
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GInterface -> WpSiEndpoint;
      GInterface -> WpSiAdapter;
      GInterface -> WpSiLinkable;
      GInterface -> WpSiLink;
      GInterface -> WpSiAcquisition;
   }

.. doxygenstruct:: WpSiEndpoint

.. doxygenstruct:: _WpSiEndpointInterface

.. doxygenstruct:: WpSiAdapter

.. doxygenstruct:: _WpSiAdapterInterface

.. doxygenstruct:: WpSiLinkable

.. doxygenstruct:: _WpSiLinkableInterface

.. doxygenstruct:: WpSiLink

.. doxygenstruct:: _WpSiLinkInterface

.. doxygenstruct:: WpSiAcquisition

.. doxygenstruct:: _WpSiAcquisitionInterface

.. doxygengroup:: wpsiinterfaces
   :content-only:
  0707010000002A000081A4000000000000000000000001656CC35F00000120000000000000000000000000000000000000003500000000wireplumber-0.4.17/docs/rst/c_api/spa_device_api.rst  .. _spa_device_api:

Spa Device
==========
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpObject;
      WpObject -> WpProxy;
      WpProxy -> WpSpaDevice;
   }

.. doxygenstruct:: WpSpaDevice

.. doxygengroup:: wpspadevice
   :content-only:
0707010000002B000081A4000000000000000000000001656CC35F00000169000000000000000000000000000000000000003300000000wireplumber-0.4.17/docs/rst/c_api/spa_json_api.rst    .. _spa_json_api:

Spa Json
========
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GBoxed -> WpSpaJson;
      GBoxed -> WpSpaJsonBuilder;
      GBoxed -> WpSpaJsonParser;
   }

.. doxygenstruct:: WpSpaJson

.. doxygenstruct:: WpSpaJsonBuilder

.. doxygenstruct:: WpSpaJsonParser

.. doxygengroup:: wpspajson
   :content-only:
   0707010000002C000081A4000000000000000000000001656CC35F00000181000000000000000000000000000000000000003200000000wireplumber-0.4.17/docs/rst/c_api/spa_pod_api.rst .. _spa_pod_api:

Spa Pod (Plain Old Data)
========================
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GBoxed -> WpSpaPod;
      GBoxed -> WpSpaPodBuilder;
      GBoxed -> WpSpaPodParser;
   }

.. doxygenstruct:: WpSpaPod

.. doxygenstruct:: WpSpaPodBuilder

.. doxygenstruct:: WpSpaPodParser

.. doxygengroup:: wpspapod
   :content-only:
   0707010000002D000081A4000000000000000000000001656CC35F00000059000000000000000000000000000000000000003300000000wireplumber-0.4.17/docs/rst/c_api/spa_type_api.rst    .. _spa_type_api:

Spa Type Information
====================
.. doxygengroup:: wpspatype
   0707010000002E000081A4000000000000000000000001656CC35F000000DF000000000000000000000000000000000000003000000000wireplumber-0.4.17/docs/rst/c_api/state_api.rst   .. _state_api:

State Storage
=============
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpState;
   }

.. doxygenstruct:: WpState

.. doxygengroup:: wpstate
   :content-only:
 0707010000002F000081A4000000000000000000000001656CC35F00000207000000000000000000000000000000000000003600000000wireplumber-0.4.17/docs/rst/c_api/transitions_api.rst .. _transitions_api:

Transitions
===========
.. graphviz::
  :align: center

   digraph inheritance {
      rankdir=LR;
      GObject -> WpTransition;
      GInterface -> GAsyncResult;
      GAsyncResult -> WpTransition;
      WpTransition -> WpFeatureActivationTransition;
   }

.. doxygenstruct:: WpTransition

.. doxygenstruct:: _WpTransitionClass

.. doxygengroup:: wptransition
   :content-only:

.. doxygenstruct:: WpFeatureActivationTransition

.. doxygengroup:: wpfeatureactivationtransition
   :content-only:
 07070100000030000081A4000000000000000000000001656CC35F00000062000000000000000000000000000000000000002D00000000wireplumber-0.4.17/docs/rst/c_api/wp_api.rst  .. _wp_api:

Library Initialization
======================
.. doxygengroup:: wp
   :content-only:
  07070100000031000081A4000000000000000000000001656CC35F00000056000000000000000000000000000000000000003200000000wireplumber-0.4.17/docs/rst/c_api/wperror_api.rst .. _wperror_api:

Error Codes
===========
.. doxygengroup:: wperror
   :content-only:
  07070100000032000081A4000000000000000000000001656CC35F00000362000000000000000000000000000000000000002A00000000wireplumber-0.4.17/docs/rst/community.rst  .. _community:

Community Resources
===================

Discussion chat room
--------------------

WirePlumber does not have its own discussion room, but we hang out in the
generic PipeWire IRC room, which is bridged to Matrix. Join us:

  - via IRC: **#pipewire** on `irc.oftc.net <https://www.oftc.net/>`_
  - via Matrix: `#pipewire:matrix.org <https://matrix.to/#/#pipewire:matrix.org>`_

Mailing list
------------

For discussions related to PipeWire **development**, there is
`the pipewire-devel mailing list <https://lists.freedesktop.org/mailman/listinfo/pipewire-devel>`_

Reporting issues
----------------

If you have found an issue with WirePlumber and wish to report it, please
`create a ticket on GitLab <https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues>`_

Please always check that your issue is not already reported before reporting it.
  07070100000033000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002A00000000wireplumber-0.4.17/docs/rst/configuration 07070100000034000081A4000000000000000000000001656CC35F0000049E000000000000000000000000000000000000002E00000000wireplumber-0.4.17/docs/rst/configuration.rst  .. _configuration:

Configuration
=============

WirePlumber is a heavily modular daemon. By itself, it doesn't do anything
except load the configured modules. All the rest of the logic is implemented
inside those modules.

Modular design ensures that it is possible to swap the implementation of
specific functionality without having to re-implement the rest of it, allowing
flexibility on target-sensitive parts, such as policy management and
making use of non-standard hardware.

At startup, WirePlumber first reads its **main** configuration file.
This file configures the operation context (properties of the daemon,
modules to be loaded, etc). This file may also specify additional, secondary
configuration files which will be loaded as well at the time of parsing the
main file.

All files and modules are specified relative to their standard search locations,
which are documented later in this chapter.

.. toctree::
   :maxdepth: 1

   configuration/locations.rst
   configuration/main.rst
   configuration/config_lua.rst
   configuration/multi_instance.rst
   configuration/alsa.rst
   configuration/bluetooth.rst
   configuration/policy.rst
   configuration/access.rst
  07070100000035000081A4000000000000000000000001656CC35F0000065B000000000000000000000000000000000000003500000000wireplumber-0.4.17/docs/rst/configuration/access.rst  .. _config_access:

Access configuration
====================

main.lua.d/50-default-access-config.lua
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Using a similar format as the :ref:`ALSA monitor <config_alsa>`, this
configuration file is charged to configure the client objects created by
PipeWire.

* *default_access.properties*

  A Lua object that contains generic client configuration properties in the
  for of key pairs.

  Example:

  .. code-block:: lua

    default_access.properties = {
      ["enable-flatpak-portal"] = true,
    }

  The above example sets to ``true`` the ``enable-flatpak-portal`` property.

  The list of valid properties are:

  .. code-block:: lua

    ["enable-flatpak-portal"] = true,

  Whether to enable the flatpak portal or not.

* *default_access.rules*

  This is a Lua array that can contain objects with rules for a client object.
  Those Lua objects have 2 properties. Similar to the
  :ref:`ALSA configuration <config_alsa>`, the first property is ``matches``,
  which allow users to define rules to match a client object.
  The second property is ``default_permissions``, and it is used to set
  permissions on the matched client object.

  Example:

  .. code-block:: lua

    {
      matches = {
        {
          { "pipewire.access", "=", "flatpak" },
        },
      },
      default_permissions = "rx",
    }

  This grants read and execute permissions to all clients that have the
  ``pipewire.access`` property set to ``flatpak``.

  Possible permissions are any combination of ``r``, ``w`` and ``x`` for read,
  write and execute; or ``all`` for all kind of permissions.

 07070100000036000081A4000000000000000000000001656CC35F00003F83000000000000000000000000000000000000003300000000wireplumber-0.4.17/docs/rst/configuration/alsa.rst    .. _config_alsa:

ALSA configuration
==================

Modifying the default configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ALSA devices are created and managed by the session manager with the *alsa.lua*
monitor script. In the default configuration, this script is loaded by
``main.lua.d/30-alsa-monitor.lua``, which also specifies an ``alsa_monitor``
global table that can be filled in with properties and rules in subsequent
config files. By default, these are filled in ``main.lua.d/50-alsa-config.lua``.

The ``alsa_monitor`` global table has 2 sub-tables:

* *alsa_monitor.properties*

  This is a simple Lua table that has key value pairs used as properties.

  Example:

  .. code-block:: lua

    alsa_monitor.properties = {
      ["alsa.jack-device"] = false,
      ["alsa.reserve"] = true,
    }

  The above example will configure the ALSA monitor to not enable the JACK
  device, and do ALSA device reservation using the mentioned DBus interface.

  A list of valid properties are:

  .. code-block:: lua

    ["alsa.jack-device"] = false

  Creates a JACK device if set to ``true``. This is not enabled by default because
  it requires that the PipeWire JACK replacement libraries are not used by the
  session manager, in order to be able to connect to the real JACK server.

  .. code-block:: lua

    ["alsa.reserve"] = true

  Reserve ALSA devices via *org.freedesktop.ReserveDevice1* on D-Bus.

  .. code-block:: lua

    ["alsa.reserve.priority"] = -20

  The used ALSA device reservation priority.

  .. code-block:: lua

    ["alsa.reserve.application-name"] = "WirePlumber"

  The used ALSA device reservation application name.


* *alsa_monitor.rules*

  This is a Lua array that can contain objects with rules for a device or node.
  Those objects have 2 properties. The first one is ``matches``, which allow
  users to define rules to match a device or node. The second property is
  ``apply_properties``, and it is used to apply properties on the matched object.

  Example:

  .. code-block:: lua

    alsa_monitor.rules = {
        matches = {
          {
            { "device.name", "matches", "alsa_card.*" },
          },
        },
        apply_properties = {
          ["api.alsa.use-acp"] = true,
        }
    }

  This sets the API ALSA use ACP property to all devices with a name that
  matches the ``alsa_card.*`` pattern.

  The ``matches`` section is an array of arrays. On the first level, the rules
  are ORed together, so any rule match is going to apply the properties. On
  the second level, the rules are merged with AND, so they must all match.

  Example:

  .. code-block:: lua

    matches = {
      {
        { "node.name", "matches", "alsa_input.*" },
        { "alsa.driver_name", "equals", "snd_hda_intel" },
      },
      {
        { "node.name", "matches", "alsa_output.*" },
      },
    },

  This is equivalent to the following logic, in pseudocode:

  .. code-block::

    if ("node.name" MATCHES "alsa_input.*" AND "alsa.driver_name" EQUALS "snd_hda_intel" )
       OR
       ("node.name" MATCHES "alsa_output.*")
    then
       ... apply the properties ...
    end

  As you can notice, the individual rules are themselves also lua arrays. The
  first element is a property name (ex "node.name"), the second element is a
  verb and the third element is an expected value, which depends on the verb.
  Internally, this uses the ``Constraint`` API, which is documented in the
  :ref:`Object Interet API <lua_object_interest_api>` section. All the verbs
  that you can use on ``Constraint`` are also allowed here.

  .. note::

    When using the "matches" verb, the values are not complete regular expressions.
    They are wildcard patterns, which means that '*' matches an arbitrary,
    possibly empty, string and '?' matches an arbitrary character.

  All the possible properties that you can apply to devices and nodes of the
  ALSA monitor are described in the sections below.

Device properties
^^^^^^^^^^^^^^^^^

PipeWire devices correspond to the ALSA cards.
The following properties can be configured on devices created by the monitor:

.. code-block:: lua

  ["api.alsa.use-acp"] = true

Use the ACP (alsa card profile) code to manage the device. This will probe the
device and configure the available profiles, ports and mixer settings. The
code to do this is taken directly from PulseAudio and provides devices that
look and feel exactly like the PulseAudio devices.

.. code-block:: lua

  ["api.alsa.use-ucm"] = true

By default, the UCM configuration is used when it is available for your device.
With this option you can disable this and use the ACP profiles instead.

.. code-block:: lua

  ["api.alsa.soft-mixer"] = false

Setting this option to true will disable the hardware mixer for volume control
and mute. All volume handling will then use software volume and mute, leaving
the hardware mixer untouched. The hardware mixer will still be used to mute
unused audio paths in the device.

.. code-block:: lua

  ["api.alsa.ignore-dB"] = false

Setting this option to true will ignore the decibel setting configured by the
driver. Use this when the driver reports wrong settings.

.. code-block:: lua

  ["device.profile-set"] = "profileset-name"

This option can be used to select a custom profile set name for the device.
Usually this is configured in Udev rules but it can also be specified here.

.. code-block:: lua

  ["device.profile"] = "default profile name"

The default active profile name.

.. code-block:: lua

  ["api.acp.auto-profile"] = false

Automatically select the best profile for the device. Normally this option is
disabled because the session manager will manage the profile of the device.
The session manager can save and load previously selected profiles. Enable
this if your session manager does not handle this feature.

.. code-block:: lua

  ["api.acp.auto-port"] = false

Automatically select the highest priority port that is available. This is by
default disabled because the session manager handles the task of selecting and
restoring ports. It can, for example, restore previously saved volumes. Enable
this here when the session manager does not handle port restore.

.. code-block:: lua

  ["api.acp.probe-rate"] = 48000

Sets the samplerate used for probing the ALSA devices and collecting the profiles
and ports.

.. code-block:: lua

  ["api.acp.pro-channels"] = 64

Sets the number of channels to use when probing the Pro Audio profile. Normally,
the maximum amount of channels will be used but with this setting this can be
reduced, which can make it possible to use other samplerates on some devices.

Some of the other properties that might be configured on devices:

.. code-block:: lua

  ["device.nick"] = "My Device",
  ["device.description"] = "My Device"

``device.description`` will show up in most apps when a device name is shown.

Node Properties
^^^^^^^^^^^^^^^

Nodes are sinks or sources in the PipeWire graph. They correspond to the ALSA
devices. In addition to the generic stream node configuration options, there are
some alsa specific options as well:

.. code-block:: lua

    ["priority.driver"] = 2000

This configures the node driver priority. Nodes with higher priority will be
used as a driver in the graph. Other nodes with lower priority will have to
resample to the driver node when they are joined in the same graph. The default
value is set based on some heuristics.

.. code-block:: lua

    ["priority.session"] = 1200

This configures the priority of the node when selecting a default node.
Higher priority nodes will be more likely candidates as a default node.

.. note::

  By default, sources have a ``priority.session`` value around 1600-2000 and
  sinks have a value around 600-1000. If you are increasing the priority of a
  sink, it is **not advised** to use a value higher than 1500, as it may cause
  a sink's monitor to be selected as a default source.

.. code-block:: lua

    ["node.pause-on-idle"] = false

Pause-on-idle will stop the node when nothing is linked to it anymore.
This is by default false because some devices cause a pop when they are
opened/closed. The node will, normally, pause and suspend after a timeout
(see suspend-node.lua).

.. code-block:: lua

    ["session.suspend-timeout-seconds"] = 5  -- 0 disables suspend

This option configures a different suspend timeout on the node.
By default this is 5 seconds. For some devices (HiFi amplifiers, for example)
it might make sense to set a higher timeout because they might require some
time to restart after being idle.

A value of 0 disables suspend for a node and will leave the ALSA device busy.
The device can then manually be suspended with ``pactl suspend-sink|source``.

**The following properties can be used to configure the format used by the
ALSA device:**

.. code-block:: lua

    ["audio.format"] = "S16LE"

By default, PipeWire will use a 32 bits sample format but a different format
can be set here.

The Audio rate of a device can be set here:

.. code-block:: lua

    ["audio.rate"] = 44100

By default, the ALSA device will be configured with the same samplerate as the
global graph. If this is not supported, or a custom values is set here,
resampling will be used to match the graph rate.

.. code-block:: lua

    ["audio.channels"] = 2
    ["audio.position"] = "FL,FR"

By default the channels and their position are determined by the selected
Device profile. You can override this setting here and optionally swap or
reconfigure the channel positions.

.. code-block:: lua

    ["api.alsa.use-chmap"] = false

Use the channel map as reported by the driver. This is disabled by default
because it is often wrong and the ACP code handles this better.

.. code-block:: lua

    ["api.alsa.disable-mmap"]  = true

PipeWire will by default access the memory of the device using mmap.
This can be disabled and force the usage of the slower read and write access
modes in case the mmap support of the device is not working properly.

.. code-block:: lua

    ["channelmix.normalize"] = true

Makes sure that during such mixing & resampling original 0 dB level is
preserved, so nothing sounds wildly quieter/louder.

.. code-block:: lua

    ["channelmix.mix-lfe"] = true

Creates "center" channel for X.0 recordings from front stereo on X.1 setups and
pushes some low-frequency/bass from "center" from X.1 recordings into front
stereo on X.0 setups.

.. code-block:: lua

    ["monitor.channel-volumes"] = false

By default, the volume of the sink/source does not influence the volume on the
monitor ports. Set this option to true to change this. PulseAudio has
inconsistent behaviour regarding this option, it applies channel-volumes only
when the sink/source is using software volumes.

ALSA buffer properties
^^^^^^^^^^^^^^^^^^^^^^

PipeWire uses a timer to consume and produce samples to/from ALSA devices.
After every timeout, it queries the device hardware pointers of the device and
uses this information to set a new timeout. See also this example program.

By default, PipeWire handles ALSA batch devices differently from non-batch
devices. Batch devices only get their hardware pointers updated after each
hardware interrupt. Non-batch devices get updates independent of the interrupt.
This means that for batch devices we need to set the interrupt at a sufficiently
high frequency (at the cost of CPU usage) while for non-batch devices we want to
set the interrupt frequency as low as possible (to save CPU).

For batch devices we also need to take the extra buffering into account caused
by the delayed updates of the hardware pointers.

Most USB devices are batch devices and will be handled as such by PipeWire by
default.

There are 2 tunable parameters to control the buffering and timeouts in a
device

.. code-block:: lua

    ["api.alsa.period-size"] = 1024

This sets the device interrupt to every period-size samples for non-batch
devices and to half of this for batch devices. For batch devices, the other
half of the period-size is used as extra buffering to compensate for the delayed
update. So, for batch devices, there is an additional period-size/2 delay.
It makes sense to lower the period-size for batch devices to reduce this delay.

.. code-block:: lua

    ["api.alsa.headroom"] = 0

This adds extra delay between the hardware pointers and software pointers.
In most cases this can be set to 0. For very bad devices or emulated devices
(like in a VM) it might be necessary to increase the headroom value.
In summary, this is the overview of buffering and timings:


  ============== ========================================== =========
  Property       Batch                                      Non-Batch
  ============== ========================================== =========
  IRQ Frequency  api.alsa.period-size/2                     api.alsa.period-size
  Extra Delay    api.alsa.headroom + api.alsa.period-size/2 api.alsa.headroom
  ============== ========================================== =========

It is possible to disable the batch device tweaks with:

.. code-block:: lua

    ["api.alsa.disable-batch"] = true

It removes the extra delay added of period-size/2 if the device can support this.
For batch devices it is also a good idea to lower the period-size
(and increase the IRQ frequency) to get smaller batch updates and lower latency.

ALSA extra latency properties
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Extra internal delay in the DAC and ADC converters of the device itself can be
set with the ``latency.internal.*`` properties:

.. code-block:: lua

    ["latency.internal.rate"] = 256
    ["latency.internal.ns"] = 0

You can configure a latency in samples (relative to rate with
``latency.internal.rate``) or in nanoseconds (``latency.internal.ns``).
This value will be added to the total reported latency by the node of the device.

You can use a tool like ``jack_iodelay`` to get the number of samples of
internal latency of your device.

This property is also adjustable at runtime with the ``ProcessLatency`` param.
You will need to find the id of the Node you want to change. For example:
Query the current internal latency of an ALSA node with id 58:

.. code-block:: console

    $ pw-cli e 58 ProcessLatency
    Object: size 80, type Spa:Pod:Object:Param:ProcessLatency (262156), id Spa:Enum:ParamId:ProcessLatency (16)
      Prop: key Spa:Pod:Object:Param:ProcessLatency:quantum (1), flags 00000000
        Float 0.000000
      Prop: key Spa:Pod:Object:Param:ProcessLatency:rate (2), flags 00000000
        Int 0
      Prop: key Spa:Pod:Object:Param:ProcessLatency:ns (3), flags 00000000
        Long 0

Set the internal latency to 256 samples:

.. code-block:: console

    $ pw-cli s 58 ProcessLatency '{ rate = 256 }'
    Object: size 32, type Spa:Pod:Object:Param:ProcessLatency (262156), id Spa:Enum:ParamId:ProcessLatency (16)
      Prop: key Spa:Pod:Object:Param:ProcessLatency:rate (2), flags 00000000
        Int 256
    remote 0 node 58 changed
    remote 0 port 70 changed
    remote 0 port 72 changed
    remote 0 port 74 changed
    remote 0 port 76 changed

Startup tweaks
^^^^^^^^^^^^^^

Some devices need some time before they can report accurate hardware pointer
positions. In those cases, an extra start delay can be added that is used to
compensate for this startup delay:

.. code-block:: lua

    ["api.alsa.start-delay"] = 0

It is unsure when this tunable should be used.

IEC958 (S/PDIF) passthrough
^^^^^^^^^^^^^^^^^^^^^^^^^^^

S/PDIF passthrough will only be enabled when the accepted codecs are configured
on the ALSA device.

This can be done in 3 different ways:

  1. Use pavucontrol and toggle the codecs in the output advanced section.

  2. Modify the ``["iec958.codecs"]`` node property to contain supported codecs.

     Example ``~/.config/wireplumber/main.lua.d/51-alsa-spdif.lua``:

     .. code-block:: lua

       table.insert (alsa_monitor.rules, {
         matches = {
           {
             { "node.name", "matches", "alsa_output.*" },
           },
         },
         apply_properties = {
           ["iec958.codecs"] = "[ PCM DTS AC3 EAC3 TrueHD DTS-HD ]",
         }
       })

  3. Use ``pw-cli s <node-id> Props '{ iec958Codecs : [ PCM ] }'`` to modify
     the codecs at runtime.
 07070100000037000081A4000000000000000000000001656CC35F000010EE000000000000000000000000000000000000003800000000wireplumber-0.4.17/docs/rst/configuration/bluetooth.rst   .. _config_bluetooth:

Bluetooth configuration
=======================

Using the same format as the :ref:`ALSA monitor <config_alsa>`, the
configuration file ``bluetooth.lua.d/50-bluez-config.lua`` is charged
to configure the Bluetooth devices and nodes created by WirePlumber.

* *bluez_monitor.properties*

  A Lua object that contains generic client configuration properties in the
  for of key pairs.

  Example:

  .. code-block:: lua

    bluez_monitor.properties = {
      ["bluez5.enable-msbc"] = true,
    }

  This example will enable the MSBC codec in connected Bluetooth devices that
  support it.

  The list of valid properties are:

  .. code-block:: lua

    ["bluez5.enable-sbc-xq"] = true

  Enables the SBC-XQ codec in connected Blueooth devices that support it

  .. code-block:: lua

    ["bluez5.enable-msbc"] = true

  Enables the MSBC codec in connected Blueooth devices that support it

  .. code-block:: lua

    ["bluez5.enable-hw-volume"] = true

  Enables hardware volume controls in Bluetooth devices that support it

  .. code-block:: lua

    ["bluez5.headset-roles"] = "[ hsp_hs hsp_ag hfp_hf hfp_ag ]"

  Enabled headset roles (default: [ hsp_hs hfp_ag ]), this property only applies
  to native backend. Currently some headsets (Sony WH-1000XM3) are not working
  with both hsp_ag and hfp_ag enabled, disable either hsp_ag or hfp_ag to work
  around it.

  Supported headset roles: ``hsp_hs`` (HSP Headset), ``hsp_ag`` (HSP Audio Gateway),
  ``hfp_hf`` (HFP Hands-Free) and ``hfp_ag`` (HFP Audio Gateway)

  .. code-block:: lua

    ["bluez5.codecs"] = "[ sbc sbc_xq aac ]"

  Enables ``sbc``, ``sbc_zq`` and ``aac`` A2DP codecs.

  Supported codecs: ``sbc``, ``sbc_xq``, ``aac``, ``ldac``, ``aptx``,
  ``aptx_hd``, ``aptx_ll``, ``aptx_ll_duplex``, ``faststream``, ``faststream_duplex``.

  All codecs are supported by default.

  .. code-block:: lua

    ["bluez5.hfphsp-backend"] = "native"

  HFP/HSP backend (default: native). Available values: ``any``, ``none``,
  ``hsphfpd``, ``ofono`` or ``native``.

  .. code-block:: lua

    ["bluez5.default.rate"] = 48000

  The bluetooth default audio rate.

  .. code-block:: lua

    ["bluez5.default.channels"] = 2

  The bluetooth default number of channels.

* *bluez_monitor.rules*

  Like in the :ref:`ALSA configuration <config_alsa>`, this is a Lua array that
  can contain objects with rules for a Bluetooth device or node.
  Those objects have 2 properties. The first one is ``matches``, which allows
  users to define rules to match a Bluetooth device or node.
  The second property is ``apply_properties``, and it is used to apply
  properties on the matched Bluetooth device or node.

  Example:

  .. code-block:: lua

    {
      matches = {
        {
          { "device.name", "matches", "bluez_card.*" },
        },
      },
      apply_properties = {
         ["bluez5.auto-connect"]  = "[ hfp_hf hsp_hs a2dp_sink ]"
      }
    }

  This will set the auto-connect property to ``hfp_hf``, ``hsp_hs`` and ``a2dp_sink``
  on bluetooth devices whose name matches the ``bluez_card.*`` pattern.

  A list of valid properties are:

  .. code-block:: lua

    ["bluez5.auto-connect"] = "[ hfp_hf hsp_hs a2dp_sink ]"

  Auto-connect device profiles on start up or when only partial profiles have
  connected. Disabled by default if the property is not specified.

  Supported values are: ``hfp_hf``, ``hsp_hs``, ``a2dp_sink``, ``hfp_ag``,
  ``hsp_ag`` and ``a2dp_source``.

  .. code-block:: lua

    ["bluez5.hw-volume"] = "[ hfp_ag hsp_ag a2dp_source ]"

  Hardware volume controls (default: ``hfp_ag``, ``hsp_ag``, and ``a2dp_source``)

  Supported values are: ``hfp_hf``, ``hsp_hs``, ``a2dp_sink``, ``hfp_ag``,
  ``hsp_ag`` and ``a2dp_source``.

  .. code-block:: lua

    ["bluez5.a2dp.ldac.quality"] = "auto"

  LDAC encoding quality.

  Available values: ``auto`` (Adaptive Bitrate, default),
  ``hq`` (High Quality, 990/909kbps), ``sq`` (Standard Quality, 660/606kbps) and
  ``mq`` (Mobile use Quality, 330/303kbps).

  .. code-block:: lua

    ["bluez5.a2dp.aac.bitratemode"] = 0

  AAC variable bitrate mode.

  Available values: 0 (cbr, default), 1-5 (quality level).

  .. code-block:: lua

    ["device.profile"] = "a2dp-sink"

  Profile connected first.

  Available values: ``a2dp-sink`` (default) or ``headset-head-unit``.
  07070100000038000081A4000000000000000000000001656CC35F00001B04000000000000000000000000000000000000003900000000wireplumber-0.4.17/docs/rst/configuration/config_lua.rst  .. _config_lua:

Lua Configuration Files
=======================

Lua configuration files are similar to the main configuration file, but they
leverage the Lua language to enable advanced configuration of module arguments
and allow split-file configuration.

There is only one global section that WirePlumber reads from these files: the
**components** table. This table is equivalent to the **context.components**
object on the main configuration file. Its purpose is to list components that
WirePlumber should load on startup.

Every line on the **components** table should be another table that contains
information about the loaded component::

  {
    "component-name",
    type = "component-type",
    args = { additional arguments },
    optional = true/false,
  }

* **component-name**: Should be the name of the component to load
  (ex. *"libwireplumber-module-mixer-api"*).

* **component-type**: Should be the type of the component.
  Valid component types include:

  * ``module``: A WirePlumber shared object module.
  * ``script/lua``: A WirePlumber Lua script.
  * ``pw_module``: A PipeWire shared object module (loaded on WirePlumber,
    not on the PipeWire daemon).

* **args**: Is an optional table that can contain additional arguments to be
  passed down to the module or script. Scripts can retrieve these arguments
  by declaring a line that reads ``local config = ...`` at the top of the script.
  Modules receive these arguments as a GVariant ``a{sv}`` table.

* **optional**: Is a boolean value that specifies whether loading of this
  component is optional. The default value is ``false``. If set to ``true``,
  then WirePlumber will not fail loading if the component is not found.

Split-File Configuration
------------------------

When a Lua configuration file is loaded, the engine also looks for additional
files in a directory that has the same name as the configuration file and a
``.d`` suffix.

A Lua directory can contain a list of Lua configuration files. Those files are
loaded alphabetically by filename so that user can control the order in which
Lua configuration files are executed.

Lua files in the directory are always loaded *after* the configuration file
that is out of the directory. However, it is perfectly valid to not have any
configuration file out of the directory.

Example hierarchy with files both in and out of the directory
(in the order of loading)::

  config.lua
  config.lua.d/00-functions.lua
  config.lua.d/01-alsa.lua
  config.lua.d/10-policy.lua
  config.lua.d/99-misc.lua

Example hierarchy with files only in the directory
(in the order of loading)::

  config.lua.d/00-functions.lua
  config.lua.d/01-alsa.lua
  config.lua.d/10-policy.lua
  config.lua.d/99-misc.lua

Example of a file using alsa_monitor.rules in a split-file configuration:

.. code-block:: lua

  table.insert (alsa_monitor.rules, {
    matches = {
      {
        { "device.name", "matches", "alsa_card.*" },
      },
    },
    apply_properties = {
      ["api.alsa.use-acp"] = true,
    }
  })

Multi-Path Merging
------------------

WirePlumber looks for configuration files in 3 different places, as described
in the :ref:`Locations of files <config_locations>` section. When a split-file
configuration scheme is used, files will be merged from these different locations.

For example, consider these files exist on the filesystem::

  /usr/share/wireplumber/config.lua.d/00-functions.lua
  /usr/share/wireplumber/config.lua.d/01-alsa.lua
  /usr/share/wireplumber/config.lua.d/10-policy.lua
  /usr/share/wireplumber/config.lua.d/99-misc.lua
  ...
  /etc/wireplumber/config.lua.d/01-alsa.lua
  ...
  /home/user/.config/wireplumber/config.lua.d/11-policy-extras.lua

In this case, loading ``config.lua`` will result in loading these files
(in this order)::

  /usr/share/wireplumber/config.lua.d/00-functions.lua
  /etc/wireplumber/config.lua.d/01-alsa.lua
  /usr/share/wireplumber/config.lua.d/10-policy.lua
  /home/user/.config/wireplumber/config.lua.d/11-policy-extras.lua
  /usr/share/wireplumber/config.lua.d/99-misc.lua

This is useful to keep the default configuration in /usr and override it
with host-specific and user-specific parts in /etc and /home respectively.

As an exception to this rule, if the configuration path is overridden with
the ``WIREPLUMBER_CONFIG_DIR`` environment variable, then configuration files
will only be loaded from this path and no merging will happen.

Functions
---------

Because of the nature of these files (they are scripts!), it is more convenient
to manage the **components** table through functions. In the default
configuration files shipped with WirePlumber, there is a file called
``00-functions.lua`` that defines some helper functions to load components.

When loading components through these functions, *duplicate calls are ignored*,
so it is possible to call a function to load a specific component multiple times
and it will only be loaded once.

.. function:: load_module(module, args)

   Loads a WirePlumber shared object module.

   :param string module: the module name, without the "libwireplumber-module-"
      prefix (ex specify "mixer-api" to load "libwireplumber-module-mixer-api")
   :param table args: optional module arguments table

.. function:: load_optional_module(module, args)

   Loads an optional WirePlumber shared object module. Optional in this case
   means that if the module is not present on the filesystem, it will be ignored.

   :param string module: the module name, without the "libwireplumber-module-"
      prefix (ex specify "mixer-api" to load "libwireplumber-module-mixer-api")
   :param table args: optional module arguments table

.. function:: load_pw_module(module)

   Loads a PipeWire shared object module

   :param string module: the module name, without the "libpipewire-module-"
      prefix (ex specify "adapter" to load "libpipewire-module-adapter")

.. function:: load_script(script, args)

   Loads a Lua script (a functionality script, not a lua configuration file)

   :param string script: the script's filename (ex. "policy-node.lua")
   :param table args: optional script arguments table

.. function:: load_monitor(monitor, args)

   Loads a Lua monitor script. Monitors are scripts found in the ``monitors/``
   directory and their purpose is to monitor and load devices.

   :param string monitor: the scripts's name without the directory or the .lua
      extension (ex. "alsa" will load "monitors/alsa.lua")
   :param table args: optional script arguments table

.. function:: load_access(access, args)

   Loads a Lua access script. Access scripts are ones found in the ``access/``
   directory and their purpose is to manage application permissions.

   :param string access: the scripts's name without the directory or the .lua
      extension (ex. "flatpak" will load "access/access-flatpak.lua")
   :param table args: optional script arguments table
07070100000039000081A4000000000000000000000001656CC35F00000F7E000000000000000000000000000000000000003800000000wireplumber-0.4.17/docs/rst/configuration/locations.rst   .. _config_locations:

Locations of files
==================

Location of configuration files
-------------------------------

WirePlumber's default locations of its configuration files are determined at
compile time by the build system. Typically, those end up being
``$XDG_CONFIG_DIR/wireplumber``, ``/etc/wireplumber``, and
``/usr/share/wireplumber``, in that order of priority.

In more detail, the latter two are controlled by the ``--sysconfdir`` and ``--datadir``
meson options. When those are set to an absolute path, such as ``/etc``, the
location of the configuration files is set to be ``$sysconfdir/wireplumber``.
When set to a relative path, such as ``etc``, then the installation prefix (``--prefix``)
is prepended to the path: ``$prefix/$sysconfdir/wireplumber``

The three locations are intended for custom user configuration,
host-specific configuration and distribution-provided configuration,
respectively. At runtime, WirePlumber will search the directories
for the highest-priority directory to contain the needed configuration file.
This allows a user or system administrator to easily override the distribution
provided configuration files by placing an equally named file in the respective
directory.

It is also possible to override the configuration directory by setting the
``WIREPLUMBER_CONFIG_DIR`` environment variable::

  WIREPLUMBER_CONFIG_DIR=src/config wireplumber

If ``WIREPLUMBER_CONFIG_DIR`` is set, the default locations are ignored and
configuration files are *only* looked up in this directory.


Location of scripts
-------------------

WirePlumber's default locations of its scripts are the same ones as for the
configuration files, but with the ``scripts`` directory appended.
Typically, these end up being ``$XDG_CONFIG_DIR/wireplumber/scripts``,
``/etc/wireplumber/scripts``, and ``/usr/share/wireplumber/scripts``,
in that order of priority.

The three locations are intended for custom user scripts,
host-specific scripts and distribution-provided scripts, respectively.
At runtime, WirePlumber will search the directories for the highest-priority
directory to contain the needed script.

It is also possible to override the scripts directory by setting the
``WIREPLUMBER_DATA_DIR`` environment variable::

  WIREPLUMBER_DATA_DIR=src wireplumber

The "data" directory is a somewhat more generic path that may be used for
other kinds of data files in the future. For scripts, WirePlumber still expects
to find a ``scripts`` subdirectory in this "data" directory, so in the above
example the scripts would be in ``src/scripts``.

If ``WIREPLUMBER_DATA_DIR`` is set, the default locations are ignored and
scripts are *only* looked up in this directory.

Location of modules
-------------------

WirePlumber modules
^^^^^^^^^^^^^^^^^^^

Like with configuration files, WirePlumber's default location of its modules is
determined at compile time by the build system. Typically, it ends up being
``/usr/lib/wireplumber-0.4`` (or ``/usr/lib/<arch-triplet>/wireplumber-0.4`` on
multiarch systems)

In more detail, this is controlled by the ``--libdir`` meson option. When
this is set to an absolute path, such as ``/lib``, the location of the
modules is set to be ``$libdir/wireplumber-$abi_version``. When this is set
to a relative path, such as ``lib``, then the installation prefix (``--prefix``)
is prepended to the path: ``$prefix/$libdir/wireplumber-$abi_version``.

It is possible to override this directory at runtime by setting the
``WIREPLUMBER_MODULE_DIR`` environment variable::

  WIREPLUMBER_MODULE_DIR=build/modules wireplumber

PipeWire and SPA modules
^^^^^^^^^^^^^^^^^^^^^^^^

PipeWire and SPA modules are not loaded from the same location as WirePlumber's
modules. They are loaded from the location that PipeWire loads them.

It is also possible to override these locations by using environment variables:
``SPA_PLUGIN_DIR`` and ``PIPEWIRE_MODULE_DIR``. For more details, refer to
PipeWire's documentation.
  0707010000003A000081A4000000000000000000000001656CC35F00001141000000000000000000000000000000000000003300000000wireplumber-0.4.17/docs/rst/configuration/main.rst    .. _config_main:

Main configuration file
=======================

The main configuration file is by default called ``wireplumber.conf``. This can
be changed on the command line by passing the ``--config-file`` or ``-c`` option::

  wireplumber --config-file=bluetooth.conf

The ``--config-file`` option is useful to run multiple instances of wireplumber
that do separate tasks each. For more information on this subject, see the
:ref:`Multiple Instances <config_multi_instance>` section.

The format of this configuration file is the variant of JSON that is also
used in PipeWire configuration files. Note that this is subject to change
in the future.

All sections are essentially JSON objects. Lines starting with *#* are treated
as comments and ignored. The list of all possible section JSON objects are:

* *context.properties*

  Used to define properties to configure the PipeWire context and some modules.

  Example::

    context.properties = {
      application.name = WirePlumber
      log.level = 2
    }

  This sets the daemon's name to *WirePlumber* and the log level to *2*, which
  only displays errors and warnings. See the
  :ref:`Debug Logging <logging>` section for more details.

* *context.spa-libs*

  Used to find spa factory names. It maps a spa factory name regular expression
  to a library name that should contain that factory. The object property names
  are the regular expression, and the object property values are the actual
  library name::

    <factory-name regex> = <library-name>

  Example::

    context.spa-libs = {
      api.alsa.*      = alsa/libspa-alsa
      audio.convert.* = audioconvert/libspa-audioconvert
    }

  In this example, we instruct wireplumber to only any *api.alsa.** factory name
  from the *libspa-alsa* library, and also any *audio.convert.** factory name
  from the *libspa-audioconvert* library.

* *context.modules*

  Used to load PipeWire modules. This does not affect the PipeWire daemon by any
  means. It exists simply to allow loading *libpipewire* modules in the PipeWire
  core that runs inside WirePlumber. This is usually useful to load PipeWire
  protocol extensions, so that you can export custom objects to PipeWire and
  other clients.

  Users can also pass key-value pairs if the specific module has arguments, and
  a combination of 2 flags: ``ifexists`` flag is given, the module is ignored when
  not found; if ``nofail`` is given, module initialization failures are ignored::

    {
      name = <module-name>
      [ args = { <key> = <value> ... } ]
      [ flags = [ [ ifexists ] [ nofail ] ]
    }

  Example::

    context.modules = [
      { name = libpipewire-module-adapter }
      {
        name = libpipewire-module-metadata,
        flags = [ ifexists ]
      }
    ]

  The above example loads both PipeWire adapter and metadata modules. The
  metadata module will be ignored if not found because of its ``ifexists`` flag.

* *context.components*

  Used to load WirePlumber components. Components can be either WirePlumber
  modules written in C, WirePlumber scripts or other configuration
  files::

    { name = <component-name>, type = <component-type> }

  Valid component types include:

  * ``module``: A WirePlumber shared object module
  * ``script/lua``: A WirePlumber Lua script
    (requires ``libwireplumber-module-lua-scripting``)
  * ``config/lua``: A WirePlumber Lua configuration file
    (requires ``libwireplumber-module-lua-scripting``)

  Example::

    context.components = [
      { name = libwireplumber-module-lua-scripting, type = module }
      { name = main.lua, type = config/lua }
    ]

  This will load the WirePlumber lua-scripting module, dynamically, and then
  it will also load any components specified in the ``main.lua`` file.

  .. note::

    When loading lua configuration files, WirePlumber will also look for
    additional files in the directory suffixed with ``.d`` and will load
    all of them as well. For example, loading ``example.lua`` will also load
    any ``.lua`` files under ``example.lua.d/``. In addition, the presence of the
    main file is optional, so it is valid to specify ``example.lua`` in the
    component name, while ``example.lua`` doesn't exist, but ``example.lua.d/``
    exists instead and has ``.lua`` files to load.

    For more information about lua configuration files, see the
    :ref:`Lua configuration files <config_lua>` section.

   0707010000003B000081A4000000000000000000000001656CC35F000000ED000000000000000000000000000000000000003600000000wireplumber-0.4.17/docs/rst/configuration/meson.build # you need to add here any files you add to the toc directory as well
sphinx_files += files(
  'locations.rst',
  'main.rst',
  'config_lua.rst',
  'multi_instance.rst',
  'alsa.rst',
  'bluetooth.rst',
  'policy.rst',
  'access.rst',
)
   0707010000003C000081A4000000000000000000000001656CC35F00000700000000000000000000000000000000000000003D00000000wireplumber-0.4.17/docs/rst/configuration/multi_instance.rst  .. _config_multi_instance:

Running multiple instances
==========================

WirePlumber has the ability to run either as a single instance daemon or as
multiple instances, meaning that there can be multiple processes, each one
doing a different task.

In the default configuration, both setups are supported. The default is to run
in single-instance mode.

In single-instance mode, WirePlumber reads ``wireplumber.conf``, which is the
default configuration file, and from there it loads ``main.lua``, ``policy.lua``
and ``bluetooth.lua``, which are lua configuration files (deployed as directories)
that enable all the relevant functionality.

In multi-instance mode, WirePlumber is meant to be started with the
``--config-file`` command line option 3 times:

.. code-block:: console

  $ wireplumber --config-file=main.conf
  $ wireplumber --config-file=policy.conf
  $ wireplumber --config-file=bluetooth.conf

That loads one process which reads ``main.conf``, which then loads ``main.lua``
and enables core functionality. Then another process that reads ``policy.conf``,
which then loads ``policy.lua`` and enables policy functionality... and so on.

To make this easier to work with, a template systemd unit is provided, which is
meant to be started with the name of the main configuration file as a
template argument:

.. code-block:: console

  $ systemctl --user disable wireplumber # disable the single instance

  $ systemctl --user enable wireplumber@main
  $ systemctl --user enable wireplumber@policy
  $ systemctl --user enable wireplumber@bluetooth

It is obviously possible to start as many instances as desired, with manually
crafted configuration files, as long as it is ensured that these instances
serve a different purpose and they do not conflict with each other.
0707010000003D000081A4000000000000000000000001656CC35F00000CDC000000000000000000000000000000000000003500000000wireplumber-0.4.17/docs/rst/configuration/policy.rst  .. _config_policy:

Policy Configuration
====================

policy.lua.d/10-default-policy.lua
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This file contains generic default policy properties that can be configured.

* *default_policy.policy*

  This is a Lua object that contains several properties that change the
  behavior of the default WirePlumber policy.

  Example:

  .. code-block:: lua

    default_policy.policy = {
      ["move"] = true,
    }

  The above example will set the ``move`` policy property to ``true``.

  The list of supported properties are:

  .. code-block:: lua

    ["move"] = true

  Moves session items when metadata ``target.node`` changes.

  .. code-block:: lua

    ["follow"] = true

  Moves session items to the default device when it has changed.

  .. code-block:: lua

    ["audio.no-dsp"] = false

  Set to ``true`` to disable channel splitting & merging on nodes and enable
  passthrough of audio in the same format as the format of the device. Note that
  this breaks JACK support; it is generally not recommended.

  .. code-block:: lua

    ["duck.level"] = 0.3

  How much to lower the volume of lower priority streams when ducking. Note that
  this is a linear volume modifier (not cubic as in PulseAudio).

policy.lua.d/50-endpoints-config.lua
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  Endpoints are objects that can group multiple clients into different groups or
  roles. This is useful if a user wants to apply specific actions when a client
  is connected to a particular role/endpoint. This configuration file allows
  users to configure those endpoints and their actions.

* *default_policy.policy.roles*

  This is a Lua array with objects defining the actions of each role.

  Example:

  .. code-block:: lua

    default_policy.policy.roles = {
      ["Multimedia"] = {
        ["alias"] = { "Movie", "Music", "Game" },
        ["priority"] = 10,
        ["action.default"] = "mix",
      }
      ["Notification"] = {
        ["priority"] = 20,
        ["action.default"] = "duck",
        ["action.Notification"] = "mix",
      }
    }

  The above example defines actions for both ``Multimedia`` and ``Notification``
  roles. Since the Notification role has more priority than the Multimedia
  role, when a client connects to the Notification endpoint, it will ``duck``
  the volume of all Multimedia clients. If Multiple Notification clients want
  to play audio, only the Notifications audio will be mixed.

  Possible values of actions are: ``mix`` (Mixes audio),
  ``duck`` (Mixes and lowers the audio volume) or ``cork`` (Pauses audio).

* *default_policy.policy.endpoints*

  This is a Lua array with objects defining the endpoints that the user wants
  to create.

  Example:

  .. code-block:: lua

    default_policy.endpoints = {
      ["endpoint.multimedia"] = {
        ["media.class"] = "Audio/Sink",
        ["role"] = "Multimedia",
      }
    },
    ["endpoint.notifications"] = {
      ["media.class"] = "Audio/Sink",
      ["role"] = "Notification",
    }

  This example creates 2 endpoints, with names ``endpoint.multimedia`` and
  ``endpoint.notifications``; and assigned roles ``Multimedia`` and ``Notification``
  respectively. Both endpoints have ``Audio/Sink`` media class, and so are only
  used for playback.
0707010000003E000081A4000000000000000000000001656CC35F00000A19000000000000000000000000000000000000002D00000000wireplumber-0.4.17/docs/rst/contributing.rst   .. _contributing:

Contributing to WirePlumber
===========================

Coding style
------------

WirePlumber uses the `GNOME C Coding Style`_ with K&R brace placement
and 2-space indentation, similar to `GStreamer's Coding Style`_.
When in doubt, just follow the example of the existing code.

WirePlumber ships with a `editorconfig <https://editorconfig.org/>`_ file.
If your code editor / IDE supports this, it should automatically set up
the indentation settings.

.. important::

   When submitting changes for review, please ensure that the coding style
   of the changes respects the coding style of the project.

.. _GNOME C Coding Style: https://developer.gnome.org/programming-guidelines/unstable/c-coding-style.html.en
.. _GStreamer's Coding Style: https://gstreamer.freedesktop.org/documentation/frequently-asked-questions/developing.html#what-is-the-coding-style-for-gstreamer-code

Tests
-----

See :ref:`testing`

Running in gdb / valgrind / etc...
----------------------------------

The Makefile included with WirePlumber supports the ``gdb`` and ``valgrind``
targets. So, instead of ``make run`` you can do ``make gdb`` or
``make valgrind`` to do some debugging.

You can also run in any other wrapper by setting the ``DBG`` variable
on ``make run``. For example:

.. code:: console

   $ make run DBG="strace"

You may also use ``wp-uninstalled.sh`` directly as a wrapper to set up the
environment and then execute any command. For example:

.. code:: console

   $ ./wp-uninstalled.sh gdb --args wpctl status

Merge requests
--------------

In order to submit changes to the project, you should create a merge request.
To do this,

  1. fork the project on https://gitlab.freedesktop.org/pipewire/wireplumber
  2. clone the forked project on your computer
  3. make changes in a new git branch
  4. rebase your changes on top of the latest ``master`` of the main repository
  5. push that branch on the forked repository
  6. follow the link shown by ``git push`` to create the merge request
     (or alternatively, visit your forked repository on gitlab and
     create it from there)

.. important::

   While creating the merge request, it is important to enable the
   **allow commits from members who can merge to the target branch** option
   so that maintainers are able to rebase your branch, since WirePlumber uses
   a fast-forward merge policy.

For more detailed information, check out `gitlab's manual on merge requests`_

.. _gitlab's manual on merge requests: https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html
   0707010000003F000081A4000000000000000000000001656CC35F00001287000000000000000000000000000000000000002F00000000wireplumber-0.4.17/docs/rst/daemon-logging.rst     .. _logging:

Debug Logging
=============

Getting debug messages on the command line is a matter of setting the
``WIREPLUMBER_DEBUG`` environment variable. The generic syntax is:

.. code::

   WIREPLUMBER_DEBUG=level:category1,category2,...

``level`` can be one of ``CEWMIDT`` or a numerical log level as listed below.
In either case it defines the minimum debug level to show:

  0. critical warnings and fatal errors (``C`` & ``E`` in the log)
  1. warnings (``W``)
  2. normal messages (``M``)
  3. informational messages (``I``)
  4. debug messages (``D``)
  5. trace messages (``T``)

``category1,category2,...`` is an *optional* comma-separated list of debug
categories to show. Any categories not listed here will *not* appear in the log.
If no categories are specified, then all messages are printed.

Categories support
`glob style patterns <https://developer.gnome.org/glib/stable/glib-Glob-style-pattern-matching.html>`_
containing ``*`` and ``?``, for convenience.

Well known categories include:

  - **wireplumber**: messages from the wireplumber daemon
  - **pw**: messages from libpipewire & spa plugins
  - **wp-***: messages from libwireplumber
  - **wp-core**: messages from *WpCore*
  - **wp-proxy**: messages from *WpProxy*
  - ... and so on ...
  - **m-***: messages from wireplumber modules
  - **m-default-profile**: messages from *libwireplumber-module-default-profile*
  - **m-default-routes**: messages from *libwireplumber-module-default-routes*
  - ... and so on ...
  - **script/***: messages from scripts
  - **script/policy-node**: messages from the *policy-node.lua* script
  - ... and so on ...

Examples
--------

Show all messages:

.. code::

   WIREPLUMBER_DEBUG=T

Show all messages up to the *debug* level (E, C, W, M, I & D), excluding *trace*:

.. code::

   WIREPLUMBER_DEBUG=D

Show all messages up to the *message* level (E, C, W & M),
excluding *info*, *debug* & *trace*
(this is also the default when ``WIREPLUMBER_DEBUG`` is omitted):

.. code::

   WIREPLUMBER_DEBUG=2

Show all messages from the wireplumber library:

.. code::

   WIREPLUMBER_DEBUG=T:wp-*

Show all messages from ``wp-registry``, libpipewire and all modules:

.. code::

   WIREPLUMBER_DEBUG=T:wp-registry,pw,m-*

Relationship with the GLib log handler & G_MESSAGES_DEBUG
---------------------------------------------------------

Older versions of WirePlumber used to use ``G_MESSAGES_DEBUG`` to control their
log output, which is the environment variable that affects GLib's default
log handler.

As of WirePlumber 0.3, ``G_MESSAGES_DEBUG`` is no longer used, since
libwireplumber replaces the default log handler.

If you are writing your own application based on libwireplumber, you can choose
if you want to replace this log handler using the flags passed to
:c:func:`wp_init`.

Relationship with the PipeWire log handler & PIPEWIRE_DEBUG
-----------------------------------------------------------

libpipewire uses the ``PIPEWIRE_DEBUG`` environment variable, with a similar syntax.
WirePlumber replaces the log handler of libpipewire with its own, rendering
``PIPEWIRE_DEBUG`` useless. Instead, you should use ``WIREPLUMBER_DEBUG`` and the
``pw`` category to control log messages from libpipewire & its plugins.

If you are writing your own application based on libwireplumber, you can choose
if you want to replace this log handler using the flags passed to
:c:func:`wp_init`.

Mapping of PipeWire debug levels to WirePlumber
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Both WirePlumber and PipeWire support 6 levels of debug logging, from 0 to 5

PipeWire uses a slightly different semantic for the first 3 levels:

=====  ========  ===========
Level  PipeWire  WirePlumber
=====  ========  ===========
0      no log    Critical / fatal Errors
1      Errors    Warnings
2      Warnings  Messages
=====  ========  ===========

When PipeWire log messages are printed by the WirePlumber log handler, the
level number stays the same and the semantic changes. PipeWire's errors are
printed in the ``W`` category and PipeWire's warnings are printed in the
``M`` category.

In WirePlumber's (actually GLib's) semantics, this feels more appropriate
because:

  - GLib's errors are fatal (``abort()`` is called)
  - GLib's critical warnings are assertion failures (i.e. programming mistakes,
    not runtime errors)
  - PipeWire's errors are neither fatal, nor programming mistakes; they are
    just bad situations that are not meant to happen
  - GLib's warnings are exactly that: bad runtime situations that are not meant
    to happen, so mapping PipeWire errors to GLib warnings makes sense
  - The **Messages** log level does not exist in PipeWire, so it can be used to
    fill the gap for PipeWire warnings
 07070100000040000081A4000000000000000000000001656CC35F000001CB000000000000000000000000000000000000002600000000wireplumber-0.4.17/docs/rst/index.rst .. include:: ../../README.rst

Table of Contents
=================

.. toctree::
   :maxdepth: 2
   :caption: The WirePlumber Daemon

   installing-wireplumber.rst
   running-wireplumber-daemon.rst
   configuration.rst
   daemon-logging.rst

.. toctree::
   :maxdepth: 2
   :caption: The WirePlumber Library

   c_api.rst
   lua_api.rst

.. toctree::
   :maxdepth: 2
   :caption: Resources

   contributing.rst
   testing.rst
   community.rst
   releases.rst
 07070100000041000081A4000000000000000000000001656CC35F00000DB8000000000000000000000000000000000000003700000000wireplumber-0.4.17/docs/rst/installing-wireplumber.rst     .. _installing-wireplumber:

Installing WirePlumber
======================

Dependencies
------------

In order to compile WirePlumber you will need:

* GLib >= 2.62
* PipeWire 0.3 (>= 0.3.43)
* Lua 5.3 or 5.4

Lua is optional in the sense that if it is not found in the system, a bundled
version will be built and linked statically with WirePlumber. This is controlled
by the **system-lua** meson option.

For building gobject-introspection data, you will also need
`doxygen <https://www.doxygen.nl/>`_ and **g-ir-scanner**.
The latter is usually shipped together with the gobject-introspection
development files.

For building documentation, you will also need
`Sphinx <https://pypi.org/project/Sphinx/>`_,
`sphinx-rtd-theme <https://github.com/readthedocs/sphinx_rtd_theme>`_ and
`breathe <https://pypi.org/project/breathe/>`_.
It is recommended to install those using python's **pip** package manager.

Compilation
-----------

WirePlumber uses the `meson build system <https://mesonbuild.com/>`_

To configure the project, you need to first run `meson`.
The basic syntax is shown below:

.. code:: console

   meson [--prefix=/path] [...options...] [build directory] [source directory]

Assuming you want to build in a directory called 'build' inside the source
tree, you can run:

.. code:: console

   $ meson setup --prefix=/usr build
   $ meson compile -C build

Additional options
------------------

.. option:: -Dintrospection=[enabled|disabled|auto]

  Force enable or force disable building gobject-introspection data.

  The default value is **auto**, which means that g-i will be built
  if **doxygen** and **g-ir-scanner** are found and skipped otherwise.

.. option:: -Ddocs=[enabled|disabled|auto]

  Force enable or force disable building documentation.

  The default value is **auto**, which means that documentation will be built
  if **doxygen**, **sphinx** and **breathe** are found and skipped otherwise.

.. option:: -Dmodules=[true|false]

   Build the wireplumber modules.

   The default value is **true**.

.. option:: -Ddaemon=[true|false]

   Build the session manager daemon.

   The default value is **true**.

   Requires **modules** option to be **true**.

.. option:: -Dtools=[true|false]

   Enable or disable building tools.

   The default value is **true**.

   Requires **modules** option to be **true**.

.. option:: -Dsystem-lua=[enabled|disabled|auto]

   Force using lua from the system instead of the bundled one.

   The default value is **auto**, which means that system lua will be used
   if it is found, otherwise the bundled static version will be built silently.

   Use **disabled** to force using the bundled lua.

.. option:: -Dsystemd=[enabled|disabled|auto]

   Enables installing systemd units. The default is **auto**

   **enabled** and **auto** currently mean the same thing.

.. option:: -Dsystemd-system-service=[true|false]

   Enables installing systemd system service file. The default is **false**

.. option:: -Dsystemd-user-service=[true|false]

   Enables installing systemd user service file. The default is **true**

.. option:: -Dsystemd-system-unit-dir=[path]

   Directory for system systemd units.

.. option:: -Dsystemd-user-unit-dir=[path]

   Directory for user systemd units.

Installation
------------

To install, simply run the **install** target with ninja:

.. code:: console

   $ ninja -C build install

To revert the installation, there is also an **uninstall** target:

.. code:: console

   $ ninja -C build uninstall
07070100000042000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002400000000wireplumber-0.4.17/docs/rst/lua_api   07070100000043000081A4000000000000000000000001656CC35F000001F2000000000000000000000000000000000000002800000000wireplumber-0.4.17/docs/rst/lua_api.rst    .. _lua_api:

Lua API Documentation
=====================

.. toctree::
   :maxdepth: 1
   :caption: Contents:

   lua_api/lua_introduction.rst
   lua_api/lua_gobject.rst
   lua_api/lua_core_api.rst
   lua_api/lua_log_api.rst
   lua_api/lua_object_api.rst
   lua_api/lua_object_manager_api.rst
   lua_api/lua_object_interest_api.rst
   lua_api/lua_proxies_api.rst
   lua_api/lua_session_item_api.rst
   lua_api/lua_spa_device_api.rst
   lua_api/lua_spa_pod.rst
   lua_api/lua_local_module_api.rst
  07070100000044000081A4000000000000000000000001656CC35F0000113B000000000000000000000000000000000000003500000000wireplumber-0.4.17/docs/rst/lua_api/lua_core_api.rst   .. _lua_core_api:

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
 07070100000045000081A4000000000000000000000001656CC35F00002B7B000000000000000000000000000000000000003400000000wireplumber-0.4.17/docs/rst/lua_api/lua_gobject.rst   .. _lua_gobject:

GObject Integration
===================

The Lua engine that powers WirePlumber's scripts provides direct integration
with `GObject`_. Most of the objects that you will deal with in the lua scripts
are wrapping GObjects. In order to work with the scripts, you will first need
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`_
to set them.

In WirePlumber's lua engine, these properties are exposed as object members
of the Lua object.

For example:

.. code-block:: lua

   -- read the "bound-id" GObject property from the proxy
   local proxy = function_that_returns_a_wp_proxy()
   local proxy_id = proxy["bound-id"]
   print("Bound ID: " .. proxy_id)

Writable properties can also be set in a similar fashion:

.. code-block:: lua

   -- set the "scale" GObject property to the enum value "cubic"
   local mixer = ...
   mixer["scale"] = "cubic"

Signals
-------

GObjects also have a generic mechanism to deliver events to external callbacks.
These events are called `signals`_.
To connect to a signal and handle it, you may use the *connect* method:

.. 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

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_gobject_lua_to_c:

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
.. _g_object_get: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#g-object-get
.. _g_object_set: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#g-object-set
.. _signals: https://developer.gnome.org/gobject/stable/signal.html
 07070100000046000081A4000000000000000000000001656CC35F000008E3000000000000000000000000000000000000003900000000wireplumber-0.4.17/docs/rst/lua_api/lua_introduction.rst  .. _lua_introduction:

Introduction
============

`Lua <https://www.lua.org/>`_ is a powerful, efficient, lightweight,
embeddable scripting language.

WirePlumber uses `Lua version 5.4 <https://www.lua.org/versions.html>`_ to
implement its engine. For older systems, Lua 5.3 is also supported.

There are currently two uses for Lua in WirePlumber:

  - To implement the scripting engine
  - To implement lua-based :ref:`config files <config_lua>`

This section is only documenting the API of the **scripting engine**. Scripts can be ran with the ``wpexec`` tool.

Example scripts can be found in the `tests/examples` directory of the wireplumber source tree.

Lua Reference
-------------

If you are not familiar with the Lua language and its API, please refer to
the `Lua 5.4 Reference Manual <https://www.lua.org/manual/5.4/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 to a subset of safe functions. For instance,
    functions that interact with the file system (io.*) and the process's state
    (ex.: os.exit) are **not** allowed.

    Here is a full list of Lua functions (and API tables) that are exposed:

    .. literalinclude:: ../../../modules/module-lua-scripting/wplua/sandbox.lua
      :language: lua
      :lines: 27-30

  - 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")
 07070100000047000081A4000000000000000000000001656CC35F00000450000000000000000000000000000000000000003D00000000wireplumber-0.4.17/docs/rst/lua_api/lua_local_module_api.rst   .. _lua_local_module_api:

Local Modules
=============

The `LocalModule` object (which binds the :c:struct:`WpImplModule` C API) provides a way
to load PipeWire modules in the WirePlumber process. Instantiating the object
loads the module, and when the last reference to the returned module object is
dropped, the module is unloaded.

Constructors
~~~~~~~~~~~~

.. function:: LocalModule(name, arguments, properties, [load_args_from_file])

   Loads the named module with the provided arguments and properties (either of
   which can be ``nil``).

   :param string name: the module name, such as ``"libpipewire-module-loopback"``
   :param string arguments: should be either ``nil`` or a string with the desired
        module arguments
   :param table properties: can be ``nil`` or a table that can be
        :ref:`converted <lua_gobject_lua_to_c>` to :c:struct:`WpProperties`
   :param load_args_from_file: (since 0.4.15) optional. if true, arguments param is treated as a file path to load args from
   :returns: a new LocalModule
   :rtype: LocalModule (:c:struct:`WpImplModule`)
   :since: 0.4.2
07070100000048000081A4000000000000000000000001656CC35F00000755000000000000000000000000000000000000003400000000wireplumber-0.4.17/docs/rst/lua_api/lua_log_api.rst   .. _lua_log_api:

Debug Logging
=============

.. function:: Log.warning(object, message)

   Logs a warning message, like :c:macro:`wp_warning_object`

   :param object: optional object to associate the message with; you
      may skip this and just start with the *message* as the first parameter
   :type object: GObject or GBoxed
   :param string message: the warning message to log

.. function:: Log.message(object, message)

   Logs a normal message, like :c:macro:`wp_message_object`

   :param object: optional object to associate the message with; you
      may skip this and just start with the *message* as the first parameter
   :type object: GObject or GBoxed
   :param string message: the normal message to log

.. function:: Log.info(object, message)

   Logs a info message, like :c:macro:`wp_info_object`

   :param object: optional object to associate the message with; you
      may skip this and just start with the *message* as the first parameter
   :type object: GObject or GBoxed
   :param string message: the info message to log

.. function:: Log.debug(object, message)

   Logs a debug message, like :c:macro:`wp_debug_object`

   :param object: optional object to associate the message with; you
      may skip this and just start with the *message* as the first parameter
   :type object: GObject or GBoxed
   :param string message: the debug message to log

.. function:: Log.trace(object, message)

   Logs a trace message, like :c:macro:`wp_trace_object`

   :param object: optional object to associate the message with; you
      may skip this and just start with the *message* as the first parameter
   :type object: GObject or GBoxed
   :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
   07070100000049000081A4000000000000000000000001656CC35F00000028000000000000000000000000000000000000003700000000wireplumber-0.4.17/docs/rst/lua_api/lua_object_api.rst     .. _lua_object_api:

WpObject
========
0707010000004A000081A4000000000000000000000001656CC35F00001A03000000000000000000000000000000000000004000000000wireplumber-0.4.17/docs/rst/lua_api/lua_object_interest_api.rst    .. _lua_object_interest_api:

Object Interest
===============

The Interest object allows you to declare interest in a specific object, or
a set of objects, and filter them. This is used in the
:ref:`ObjectManager <lua_object_manager_api>` but also in other places where
methods allow you to iterate over a set of objects or lookup a specific object.

*Interest* is a binding of :c:struct:`WpObjectInterest`, but it uses a
Lua-idiomatic way of construction instead of mapping directly the C functions,
for convenience.

Construction
~~~~~~~~~~~~

The most common use for *Interest* is in the
:ref:`ObjectManager <lua_object_manager_api>`, where you have to use the
following construction method to create it.

.. function:: Interest(decl)

   :param table decl: an interest declaration
   :returns: the interest
   :rtype: Interest (:c:struct:`WpObjectInterest`)

The interest consists of a GType and an array of constraints. It is constructed
with a table that contains all the constraints, plus the GType in the ``type``
field.

.. code-block:: lua

   local om = ObjectManager {
     Interest {
       type = "node",
       Constraint { "node.name", "matches", "alsa*", type = "pw-global" },
       Constraint { "media.class", "equals", "Audio/Sink", type = "pw-global" },
     }
   }

In the above example, the interest will match all objects of type
:c:struct:`WpNode` that contain the following 2 global properties:

  - "node.name", with a value that begins with the string "alsa"
  - "media.class", with a value that equals exactly the string "Audio/Sink"

When an object method requires an *Interest* as an argument, you may as well
directly pass the declaration table as argument instead of using the
:func:`Interest` constructor function. For instance,

.. code-block:: lua

   local fl_port = node:lookup_port {
     Constraint { "port.name", "equals", "playback_FL" }
   }

In the above example, we lookup a port in a node. The :func:`Node.lookup_port`
method takes an *Interest* as an argument, but we can pass the interest
declaration table directly. Note also that such a method does not require
declaring the ``type`` of the interest, as it has :c:struct:`WpPort` as a
hardcoded default.

The type
........

The type of an interest must be a valid GType, written as a string without the
"Wp" prefix and with the first letter optionally being lowercase. The type may
match any wireplumber object or interface.

Examples:

============= ============
 type string   parsed as
============= ============
node          WpNode
Node          WpNode
device        WpDevice
plugin        WpPlugin
siLink        WpSiLink
SiLink        WpSiLink
properties    WpProperties
============= ============

The Constraint
..............

The *Constraint* is constructed also with a table, like *Interest* itself. This
table must have the following items in this very strict order:

  - a subject string: a string that specifies the name of a property to match
  - a verb string: a string that specifies the match operation
  - an object, if the verb requires it

The verb may be any verb listed in :c:enum:`WpConstraintVerb`, using either
the nickname of the enum or the character value of it.

Allowed verbs:

============ ========= =======================================
nickname     character value
============ ========= =======================================
"equals"     "="       :c:enum:`WP_CONSTRAINT_VERB_EQUALS`
"not-equals" "!"       :c:enum:`WP_CONSTRAINT_VERB_NOT_EQUALS`
"in-list"    "c"       :c:enum:`WP_CONSTRAINT_VERB_IN_LIST`
"in-range"   "~"       :c:enum:`WP_CONSTRAINT_VERB_IN_RANGE`
"matches"    "#"       :c:enum:`WP_CONSTRAINT_VERB_MATCHES`
"is-present" "+"       :c:enum:`WP_CONSTRAINT_VERB_IS_PRESENT`
"is-absent"  "-"       :c:enum:`WP_CONSTRAINT_VERB_IS_ABSENT`
============ ========= =======================================

The values that are expected for each verb are primarily documented in
:c:func:`wp_object_interest_add_constraint`. In Lua, though, native types are
expected instead of GVariant and then they are converted according to the rules
documented in the :ref:`GObject Integration <lua_gobject>` page.

Examples of constraints:

.. code-block:: lua

   Constraint { "node.id", "equals", "42" }
   Constraint { "node.id", "equals", 42 }
   Constraint { "port.physical", "=", true }

   Constraint { "audio.channel", "not-equals", "FL" }

   Constraint { "node.name", "matches", "v4l2_input*" }
   Constraint { "format.dsp", "#", "*mono audio*" }

   -- matches either "default" or "settings" as a possible value for "metadata.name"
   Constraint { "metadata.name", "in-list", "default", "settings" }

   -- matches any priority.session between 0 and 1000, inclusive
   Constraint { "priority.session", "in-range", 0, 1000 }

   -- matches when the object has a "media.role" property
   Constraint { "media.role", "is-present" }

   -- matches when "media.role" is not present in the properties list
   Constraint { "media.role", "is-absent" }

Constraint types
................

PipeWire objects have multiple properties lists, therefore constraints also need
to have a way to specify on which property list they apply. The constraint type
may be any of the types listed in :c:enum:`WpConstraintType` and can be
specified with an additional field in the *Constraint* construction table,
called ``type``.

For instance:

.. code-block:: lua

   Constraint { "node.id", "equals", "42", type = "pw-global" }
   Constraint { "api.alsa.card.name", "matches", "*Intel*", type = "pw" }
   Constraint { "bound-id", "=", 42, type = "gobject" }

Valid types are:

========= ===============================================
type      value
========= ===============================================
pw-global :c:enum:`WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY`
pw        :c:enum:`WP_CONSTRAINT_TYPE_PW_PROPERTY`
gobject   :c:enum:`WP_CONSTRAINT_TYPE_G_PROPERTY`
========= ===============================================

Methods
~~~~~~~

.. function:: Interest.matches(self, obj)

   Binds :c:func:`wp_object_interest_matches`

   Checks if a specific object matches the interest. The object may be a GObject
   or a table convertible to :c:struct:`WpProperties`, if the interest is for
   properties.

   This is rarely useful to use directly on objects, but it may be useful to
   check if a specific table contains key-value pairs that match specific
   constraints, using "properties" as the interest type and passing a table as
   the object.

   :param self: the interest
   :param obj: an object to check for a match
   :type obj: GObject or table
   :returns: whether the object matches the interest
   :rtype: boolean
 0707010000004B000081A4000000000000000000000001656CC35F00000E61000000000000000000000000000000000000003F00000000wireplumber-0.4.17/docs/rst/lua_api/lua_object_manager_api.rst     .. _lua_object_manager_api:

Object Manager
==============

The ObjectManager (binding for :c:struct:`WpObjectManager`) provides a way to
collect a set of objects and be notified when objects that fulfill a certain
set of criteria are created or destroyed.

To start an object manager, you first need to declare interest in a certain
kind of object specifying a set of :ref:`Interests <lua_object_interest_api>`
in the constructor and then you need to activate it by calling
:func:`ObjectManager.activate`

Upon activating an ObjectManager, any pre-existing objects that match the
specified interests will immediately become available to get through
:func:`ObjectManager.iterate` and the :c:struct:`WpObjectManager` "object-added"
signal will be emitted for all of them.

Constructors
~~~~~~~~~~~~

.. function:: ObjectManager(interest_list)

   Constructs a new object manager.

   Combines :c:func:`wp_object_manager_new` and
   :c:func:`wp_object_manager_add_interest_full`

   The argument needs to be a table that contains one or more
   :ref:`Interest <lua_object_interest_api>` objects. The object manager
   will then contain all objects that match any one of the supplied interests.

   Example:

   .. code-block:: lua

      streams_om = ObjectManager {
        -- match stream nodes
        Interest {
          type = "node",
          Constraint { "media.class", "matches", "Stream/*", type = "pw-global" },
        },
        -- and device nodes that are not associated with any routes
        Interest {
          type = "node",
          Constraint { "media.class", "matches", "Audio/*", type = "pw-global" },
          Constraint { "device.routes", "equals", "0", type = "pw" },
        },
        Interest {
          type = "node",
          Constraint { "media.class", "matches", "Audio/*", type = "pw-global" },
          Constraint { "device.routes", "is-absent", type = "pw" },
        },
      }

   The above example will create an ObjectManager that matches all nodes with
   a "media.class" global property that starts with the string "Stream/"
   and additionally all those whose "media.class" starts with "Audio/" and
   they have either a "device.routes" property that equals zero or they
   don't have a "device.routes" property at all.

   :param table interest_list: a list of :ref:`interests <lua_object_interest_api>`
                               to objects
   :returns: a new object manager
   :rtype: ObjectManager (:c:struct:`WpObjectManager`)

Methods
~~~~~~~

.. function:: ObjectManager.activate(self)

   Activates the object manager.
   Binds :c:func:`wp_core_install_object_manager`.

   :param self: the object manager

.. function:: ObjectManager.get_n_objects(self)

    Binds :c:func:`wp_object_manager_get_n_objects`

   :param self: the object manager
   :returns: the number of objects managed by the object manager
   :rtype: integer

.. function:: ObjectManager.iterate(self, interest)

   Binds :c:func:`wp_object_manager_new_filtered_iterator_full`

   :param self: the object manager
   :param interest: an interest to filter objects
   :type interest: :ref:`Interest <lua_object_interest_api>` or nil or none
   :returns: all the managed objects that match the interest
   :rtype: Iterator; the iteration items are of type :ref:`GObject <lua_gobject>`

.. function:: ObjectManager.lookup(self, interest)

   Binds :c:func:`wp_object_manager_lookup`

   :param self: the object manager
   :param interest: the interest to use for the lookup
   :type interest: :ref:`Interest <lua_object_interest_api>` or nil or none
   :returns: the first managed object that matches the interest
   :rtype: :ref:`GObject <lua_gobject>`
   0707010000004C000081A4000000000000000000000001656CC35F00001545000000000000000000000000000000000000003800000000wireplumber-0.4.17/docs/rst/lua_api/lua_proxies_api.rst    .. _lua_proxies_api:

PipeWire Proxies
================

Proxy
.....

Lua objects that bind a :ref:`WpProxy <proxy_api>` contain the following methods:

.. function:: Proxy.get_interface_type(self)

   Binds :c:func:`wp_proxy_get_interface_type`

   :param self: the proxy
   :returns: the proxy type, the proxy type version
   :rtype: string, integer

PipeWire Object
...............

Lua objects that bind a :ref:`WpPipewireObject <pipewire_object_api>`
contain the following methods:

.. function:: PipewireObject.iterate_params(self, param_name)

   Binds :c:func:`wp_pipewire_object_enum_params_sync`

   :param self: the proxy
   :param string param_name: the PipeWire param name to enumerate,
                             ex "Props", "Route"
   :returns: the available parameters
   :rtype: Iterator; the iteration items are :ref:`Spa Pod <lua_spa_pod>` objects

.. function:: PipewireObject.set_param(self, param_name, pod)

   Binds :c:func:`wp_pipewire_object_set_param`

   :param self: the proxy
   :param string param_name: The PipeWire param name to set, ex "Props", "Route"
   :param Pod pod: A Spa Pod object containing the new params

Global Proxy
............

Lua objects that bind a :ref:`WpGlobalProxy <global_proxy_api>`
contain the following methods:

.. function:: GlobalProxy.request_destroy(self)

   Binds :c:func:`wp_global_proxy_request_destroy`

   :param self: the proxy

PipeWire Node
.............

Lua objects that bind a :ref:`WpNode <node_api>` contain the following methods:

.. function:: Node.get_state(self)

   Binds :c:func:`wp_node_get_state`

   :param self: the proxy
   :returns: the current state of the node and an error message, if any
   :rtype: string (:c:enum:`WpNodeState`), string (error message)
   :since: 0.4.2

.. function:: Node.get_n_input_ports(self)

   Binds :c:func:`wp_node_get_n_input_ports`

   :param self: the proxy
   :returns: the current and max numbers of input ports on the node
   :rtype: integer (current), integer (max)
   :since: 0.4.2

.. function:: Node.get_n_output_ports(self)

   Binds :c:func:`wp_node_get_n_output_ports`

   :param self: the proxy
   :returns: the current and max numbers of output ports on the node
   :rtype: integer (current), integer (max)
   :since: 0.4.2

.. function:: Node.get_n_ports(self)

   Binds :c:func:`wp_node_get_n_ports`

   :param self: the proxy
   :returns: the number of ports on the node
   :since: 0.4.2

.. function:: Node.iterate_ports(self, interest)

   Binds :c:func:`wp_node_iterate_ports`

   :param self: the proxy
   :param interest: an interest to filter objects
   :type interest: :ref:`Interest <lua_object_interest_api>` or nil or none
   :returns: all the ports of this node that that match the interest
   :rtype: Iterator; the iteration items are of type :ref:`WpPort <port_api>`
   :since: 0.4.2

.. function:: Node.lookup_port(self, interest)

   Binds :c:func:`wp_node_lookup_port`

   :param self: the proxy
   :param interest: the interest to use for the lookup
   :type interest: :ref:`Interest <lua_object_interest_api>` or nil or none
   :returns: the first port of this node that matches the interest
   :rtype: :ref:`WpPort <port_api>`
   :since: 0.4.2

.. function:: Node.send_command(self, command)

   Binds :c:func:`wp_node_send_command`

   :param self: the proxy
   :param string command: the command to send to the node (ex "Suspend")

PipeWire Port
.............

Lua objects that bind a :ref:`WpPort <port_api>` contain the following methods:

.. function:: Port.get_direction(self)

   Binds :c:func:`wp_port_get_direction`

   :param self: the port
   :returns: the direction of the Port
   :rtype: string (:c:enum:`WpDirection`)
   :since: 0.4.2

PipeWire Client
...............

Lua objects that bind a :ref:`WpClient <client_api>`
contain the following methods:

.. function:: Client.update_permissions(self, perms)

   Binds :c:func:`wp_client_update_permissions`

   Takes a table where the keys are object identifiers and the values are
   permission strings.

   Valid object identifiers are:

   - A number, meaning the bound ID of a proxy
   - The string "any" or the string "all", which sets the default permissions
     for this client

   The permission strings have a chmod-like syntax (ex. "rwx" or "r-xm"), where:

   - "r" means permission to read the object
   - "w" means permission to write data to the object
   - "x" means permission to call methods on the object
   - "m" means permission to set metadata for the object
   - "-" is ignored and can be used to make the string more readable when
     a permission flag is omitted

   **Example:**

   .. code-block:: lua

      client:update_permissions {
        ["all"] = "r-x",
        [35] = "rwxm",
      }

   :param self: the proxy
   :param table perms: the permissions to update for this client

PipeWire Metadata
.................

Lua objects that bind a :ref:`WpMetadata <metadata_api>`
contain the following methods:

.. function:: Metadata.iterate(self, subject)

   Binds :c:func:`wp_metadata_new_iterator`

   :param self: the proxy
   :param integer subject: the subject id
   :returns: an iterator

.. function:: Metadata.find(self, subject, key)

   Binds :c:func:`wp_metadata_find`

   :param self: the proxy
   :param string subject: the subject id
   :param string key: the metadata key to find
   :returns: the value for this metadata key, the type of the value
   :rtype: string, string
   0707010000004D000081A4000000000000000000000001656CC35F000003E0000000000000000000000000000000000000003D00000000wireplumber-0.4.17/docs/rst/lua_api/lua_session_item_api.rst   .. _lua_session_item_api:

Session Item
============

Lua objects that bind a :ref:`WpSessionItem <session_item_api>`
contain the following methods:

.. function:: SessionItem.get_associated_proxy(self, type)

   Binds :c:func:`wp_session_item_get_associated_proxy`

   :param self: the session item
   :param string type: the proxy type name
   :returns: the proxy object or nil

.. function:: SessionItem.reset(self)

   Binds :c:func:`wp_session_item_reset`

   :param self: the session item

.. function:: SessionItem.configure(self, properties)

   Binds :c:func:`wp_session_item_configure`

   :param self: the session item
   :param table properties: The configuration properties
   :returns: true on success, false on failure
   :rtype: boolean

.. function:: SessionItem.register(self)

   Binds :c:func:`wp_session_item_register`

   :param self: the session item

.. function:: SessionItem.remove(self)

   Binds :c:func:`wp_session_item_remove`

   :param self: the session item
0707010000004E000081A4000000000000000000000001656CC35F00000234000000000000000000000000000000000000003B00000000wireplumber-0.4.17/docs/rst/lua_api/lua_spa_device_api.rst     .. _lua_spa_device_api:

Spa Device
==========

.. function:: SpaDevice.get_managed_object(self, id)

   Binds :c:func:`wp_spa_device_get_managed_object`

   :param self: the spa device
   :param integer id: the object id
   :returns: the managed object or nil

.. function:: SpaDevice.store_managed_object(self, id, object)

   Binds :c:func:`wp_spa_device_store_managed_object`

   :param self: the spa device
   :param integer id: the object id
   :param GObject object: a GObject to store or nil to remove the existing
                          stored object
0707010000004F000081A4000000000000000000000001656CC35F00000023000000000000000000000000000000000000003400000000wireplumber-0.4.17/docs/rst/lua_api/lua_spa_pod.rst    .. _lua_spa_pod:

Spa Pod
=======
 07070100000050000081A4000000000000000000000001656CC35F00000198000000000000000000000000000000000000003000000000wireplumber-0.4.17/docs/rst/lua_api/meson.build   # you need to add here any files you add to the toc directory as well
sphinx_files += files(
  'lua_core_api.rst',
  'lua_gobject.rst',
  'lua_local_module_api.rst',
  'lua_introduction.rst',
  'lua_log_api.rst',
  'lua_object_api.rst',
  'lua_object_interest_api.rst',
  'lua_object_manager_api.rst',
  'lua_proxies_api.rst',
  'lua_session_item_api.rst',
  'lua_spa_device_api.rst',
  'lua_spa_pod.rst',
)
07070100000051000081A4000000000000000000000001656CC35F00000188000000000000000000000000000000000000002800000000wireplumber-0.4.17/docs/rst/meson.build   # you need to add here any files you add to the toc directory as well
sphinx_files += files(
  'index.rst',
  'installing-wireplumber.rst',
  'running-wireplumber-daemon.rst',
  'configuration.rst',
  'daemon-logging.rst',
  'contributing.rst',
  'community.rst',
  'testing.rst',
  'releases.rst',
  'c_api.rst',
  'lua_api.rst',
)

subdir('c_api')
subdir('lua_api')
subdir('configuration')
07070100000052000081A4000000000000000000000001656CC35F0000003E000000000000000000000000000000000000002900000000wireplumber-0.4.17/docs/rst/releases.rst  .. _releases:

Releases
========

.. include:: ../../NEWS.rst
  07070100000053000081A4000000000000000000000001656CC35F000012A7000000000000000000000000000000000000003B00000000wireplumber-0.4.17/docs/rst/running-wireplumber-daemon.rst     .. _running-wireplumber-daemon:

Running the WirePlumber daemon
==============================

Systemd
-------

WirePlumber comes with a systemd unit, ``wireplumber.service``, which should
be enabled on your user session:

.. code:: console

   $ systemctl --user --now enable wireplumber

.. note::

   On non-systemd systems, you just need to ensure that wireplumber is started
   after pipewire.

Run from the PipeWire source tree
---------------------------------

PipeWire's build system comes with an option to build WirePlumber together
with PipeWire and allows executing them together without installing either of
them.

To make this work, configure PipeWire with the
``-Dsession-managers="[ 'wireplumber' ]"`` option on the meson command line.

When compiling PipeWire, the build system will now also clone and compile
WirePlumber as a subproject.

To execute the whole stack without installing, simply execute ``make run``
after compiling.

Synopsis:

.. code:: console

   $ meson -Dsession-managers="[ 'wireplumber' ]" build
   $ ninja -C build
   $ make run

Run independently or without installing
---------------------------------------

If you wish to debug WirePlumber, it may be useful to run it separately from
PipeWire or run it directly from the source tree without installing.
To do so:

  1. Ensure that neither *WirePlumber* nor *pipewire-media-session*
     are running or started together with PipeWire

     - If any of those is started by systemd,

       - Stop the relevant systemd service, ``wireplumber.service``
         or ``pipewire-media-session.service``
       - Disable that service as well if you intend to restart PipeWire
         (so that the session manager is not restarted with it)

     - If any of those is started from pipewire.conf,

       - Kill it, in order to stop it temporarily: ``killall wireplumber``
         or ``killall pipewire-media-session``
       - Comment out with ``#`` the relevant ``{ path = "..."  args = "" }``
         line from the ``context.exec`` section in ``pipewire.conf``,
         if you intend to restart PipeWire

  2. Ensure that PipeWire is running

  3. Without stopping PipeWire, run WirePlumber.

     - if it is installed, execute ``wireplumber``
     - if it is **not** installed, execute ``make run`` in the source tree,
       or use the ``wp-uninstalled.sh`` script:

       .. code:: console

          $ ./wp-uninstalled.sh wireplumber

Replacing pipewire-media-session
--------------------------------

Older versions of PipeWire used to be distributed with an example session
manager (pipewire-media-session) that you needed to disable and replace with
WirePlumber.

.. warning::

  These instructions are only relevant to older versions of PipeWire

systemd
^^^^^^^

In most cases, ``pipewire-media-session`` is started by a systemd service unit,
``pipewire-media-session.service``.

To switch to WirePlumber, you will first need to disable that service:

.. code:: console

   $ systemctl --user --now disable pipewire-media-session

... and then, enable and use ``wireplumber.service`` in its place:

.. code:: console

   $ systemctl --user --now enable wireplumber

pipewire.conf
^^^^^^^^^^^^^

On some systems, ``pipewire-media-session`` is not started by systemd, but it
is started by pipewire itself via a configuration option in ``pipewire.conf``

To switch to wireplumber, you will need to edit
**/etc/pipewire/pipewire.conf** in an existing installation or
**src/daemon/pipewire.conf.in** in the PipeWire git tree
and change the appropriate line in the ``exec`` section:

.. code:: diff

   --- /etc/pipewire/pipewire.conf.bak
   +++ /etc/pipewire/pipewire.conf
   @@ -204,7 +204,7 @@ context.exec = [
        # but it is better to start it as a systemd service.
        # Run the session manager with -h for options.
        #
   -    #{ path = "/usr/bin/pipewire-media-session"  args = "" }
   +    { path = "wireplumber"  args = "" }
        #
        # You can optionally start the pulseaudio-server here as well
        # but it is better to start it as a systemd service.

.. code:: diff

   diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in
   index bbafa134..16ef687b 100644
   --- a/src/daemon/pipewire.conf.in
   +++ b/src/daemon/pipewire.conf.in
   @@ -220,7 +220,7 @@ context.exec = [
        # but it is better to start it as a systemd service.
        # Run the session manager with -h for options.
        #
   -    @comment@{ path = "@media_session_path@"  args = "" }
   +    { path = "wireplumber"  args = "" }
        #
        # You can optionally start the pulseaudio-server here as well
        # but it is better to start it as a systemd service.

This setup assumes that WirePlumber is *installed* on the target system.
 07070100000054000081A4000000000000000000000001656CC35F000019FA000000000000000000000000000000000000002800000000wireplumber-0.4.17/docs/rst/testing.rst    .. _testing:

Testing
=======

Automated unit tests
--------------------

WirePlumber has automated tests that you can easily run with:

.. code:: console

   $ meson test -C build

This will automatically compile all test dependencies, so you can be sure
that this always tests your latest changes.

If you wish to run a specific test instead of all of them, you can run:

.. code:: console

   $ meson test -C build test-name

When debugging a single test, you can additionally enable verbose test output
by appending ``-v`` and you can also run the test in gdb by appending ``--gdb``.

For more information on how to use ``meson test``, please refer to
`meson's manual <https://mesonbuild.com/Unit-tests.html>`_

.. important::

   When submitting changes for review, always ensure that all tests pass

Please note that many WirePlumber tests require specific SPA test plugins
to be available in your PipeWire installation. More specifically, PipeWire
needs to be configured with the following options enabled:

.. code:: console

   -Dvideotestsrc=true -Daudiotestsrc=true -Dtest=true

If these SPA plugins are not found in the system, some tests will fail.
This is expected.

WirePlumber examples
--------------------

WirePlumber ships examples in ``test/examples``.
Execute them from the top-level directory with ``wp-uninstalled.sh``:

.. code:: console

   $ ./wp-uninstalled.sh ./build/tests/examples/audiotestsrc-play


Assuming there is no other process actively using ``hw:0,0`` from alsa,
the above example should play a test tone on ``hw:0,0`` without errors.

Native API clients
------------------

pw-cat
^^^^^^

Using the default endpoint:

.. code:: console

   $ wpctl status  # verify the default endpoints
   $ pw-record test.wav
   $ pw-play test.wav


Using a non-default endpoint:

.. code:: console

   $ pw-record --list-targets  # find the node id
   $ pw-record --target <node_id> test.wav
   $ pw-play --list-targets  # find the node id
   $ pw-play --target <node_id> test.wav

or

.. code:: console

   $ wpctl status  # find the capture & playback endpoint ids
   $ pw-record --target <endpoint_id> test.wav
   $ pw-play --target <endpoint_id> test.wav

.. note::

   node ids and endpoint ids can be used interchangeably when specifying
   targets in all use cases

video-play
^^^^^^^^^^

Using the default endpoint:

.. code:: console

   $ cd path/to/pipewire-source-dir
   $ ./build/src/examples/video-play


Using a non-default endpoint:

.. code:: console

   $ wpctl status  # find the endpoint id from the list
   $ cd path/to/pipewire-source-dir
   $ ./build/src/examples/video-play <endpoint_id>

.. tip::

   enable videotestsrc in wireplumber's configuration to have more video
   sources available

PulseAudio compat API clients
-----------------------------

pacat
^^^^^

Using the default endpoint:

.. code:: console

   $ wpctl status  # verify the default endpoints
   $ parecord test.wav
   $ paplay test.wav

pavucontrol
^^^^^^^^^^^

Use the command:

.. code:: console

  $ pavucontrol

* Volume level meters should work
* Changing the volume should work

ALSA compat API clients
-----------------------

aplay / arecord
^^^^^^^^^^^^^^^

.. note::

   unless you have installed PipeWire in the default system prefix
   (``/usr``), the ALSA compat API will not work, unless you copy
   ``libasound_module_pcm_pipewire.so`` in the alsa plugins directory
   (usually ``/usr/<libdir>/alsa-lib/``) and that you add the contents of
   ``pipewire-alsa/conf/50-pipewire.conf`` in your ``~/.asoundrc``
   (or anywhere else, system-wide, where libasound can read it)

Using the default endpoint:

.. code:: console

   $ wpctl status  # verify the default endpoints
   $ arecord -D pipewire -f S16_LE -r 48000 test.wav
   $ aplay -D pipewire test.wav

Using a non-default endpoint:

.. code:: console

   $ wpctl status  # find the capture & playback endpoint ids
   $ PIPEWIRE_NODE=<endpoint_id> arecord -D pipewire -f S16_LE -r 48000 test.wav
   $ PIPEWIRE_NODE=<endpoint_id> aplay -D pipewire test.wav

or

.. code:: console

   $ wpctl status  # find the capture & playback endpoint ids
   $ arecord -D pipewire:NODE=<endpoint_id> -f S16_LE -r 48000 test.wav
   $ aplay -D pipewire:NODE=<endpoint_id> test.wav


JACK compat API clients
-----------------------

qjackctl
^^^^^^^^

.. code:: console

   $ pw-jack qjackctl

* This should correctly connect.
* The "Graph" window should show the PipeWire graph.

jack_simple_client
^^^^^^^^^^^^^^^^^^

.. code:: console

   $ wpctl status  # find the target endpoint id
   $ wpctl inspect <endpoint_id>  # find the node.id
   $ PIPEWIRE_NODE=<node_id> pw-jack jack_simple_client

.. note::

   The JACK layer is not controlled by the session manager, it creates its own
   links; which is why it is required to specify a node id (endpoint id will not
   work)

Device Reservation
------------------

with PulseAudio
^^^^^^^^^^^^^^^

1. With PulseAudio running, start a pulseaudio client.

.. code:: console

   $ gst-launch-1.0 audiotestsrc ! pulsesink

2. Start PipeWire & WirePlumber

   - The device in use by PA will not be available in PW

3. Stop the PA client

   - A few seconds later, WirePlumber should assume control of the device

4. ``wpctl status`` should be able to confirm that the device is available

5. Start a PA client again

   - It should not be able to play; it will just freeze

6. Stop WirePlumber

   - The PA client should immediately start playing

with JACK
^^^^^^^^^

1. Start PipeWire & WirePlumber

   - All devices should be available

2. Start ``jackdbus``

   1. through ``qjackctl``:

      - Enable *Setup* -> *Misc* -> *Enable JACK D-Bus interface*
      - Click *Start* on the main window

   2. or manually:

      - Run ``jackdbus auto``
      - Run ``qdbus org.jackaudio.service /org/jackaudio/Controller org.jackaudio.JackControl.StartServer``

3. Wait a few seconds and run ``wpctl status`` to inspect

   - The devices taken by JACK should no longer be available
   - There should be two *JACK System* endpoints (sink & source)

4. Run an audio client on PipeWire (ex ``pw-play test.wav``)

   - Notice how audio now goes through JACK

5. Stop JACK

   - through ``qjackctl``, click *Stop*
   - or manually: ``qdbus org.jackaudio.service /org/jackaudio/Controller org.jackaudio.JackControl.StopServer``

6. Wait a few seconds and run ``wpctl status`` to inspect

   - The devices that were release by JACK should again be available
   - There should be no *JACK System* endpoint

.. note::

   You may also start WirePlumber *after* starting JACK. It should immediately
   go to the state described in step 3
  07070100000055000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001700000000wireplumber-0.4.17/lib    07070100000056000081A4000000000000000000000001656CC35F0000000D000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/meson.build    subdir('wp')
   07070100000057000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001A00000000wireplumber-0.4.17/lib/wp 07070100000058000081A4000000000000000000000001656CC35F000015C3000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/client.c    /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-client"

#include "client.h"
#include "private/pipewire-object-mixin.h"

/*! \defgroup wpclient WpClient */
/*!
 * \struct WpClient
 *
 * The WpClient class allows accessing the properties and methods of a PipeWire
 * client object (`struct pw_client`). A WpClient is constructed internally
 * when a new client connects to PipeWire and it is made available through the
 * WpObjectManager API.
 */

struct _WpClient
{
  WpGlobalProxy parent;
};

static void wp_client_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface);

G_DEFINE_TYPE_WITH_CODE (WpClient, wp_client, WP_TYPE_GLOBAL_PROXY,
    G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT,
        wp_pw_object_mixin_object_interface_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
        wp_client_pw_object_mixin_priv_interface_init))

static void
wp_client_init (WpClient * self)
{
}

static void
wp_client_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case WP_PW_OBJECT_MIXIN_STEP_BIND:
  case WP_TRANSITION_STEP_ERROR:
    /* base class can handle BIND and ERROR */
    WP_OBJECT_CLASS (wp_client_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO:
    /* just wait, info will be emitted anyway after binding */
    break;
  default:
    g_assert_not_reached ();
  }
}

static const struct pw_client_events client_events = {
  PW_VERSION_CLIENT_EVENTS,
  .info = (HandleEventInfoFunc(client)) wp_pw_object_mixin_handle_event_info,
};

static void
wp_client_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy,
      client, &client_events);
}

static void
wp_client_pw_proxy_destroyed (WpProxy * proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy);

  WP_PROXY_CLASS (wp_client_parent_class)->pw_proxy_destroyed (proxy);
}

static void
wp_client_class_init (WpClientClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->get_property = wp_pw_object_mixin_get_property;

  wpobject_class->get_supported_features =
      wp_pw_object_mixin_get_supported_features;
  wpobject_class->activate_get_next_step =
      wp_pw_object_mixin_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_client_activate_execute_step;

  proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Client;
  proxy_class->pw_iface_version = PW_VERSION_CLIENT;
  proxy_class->pw_proxy_created = wp_client_pw_proxy_created;
  proxy_class->pw_proxy_destroyed = wp_client_pw_proxy_destroyed;

  wp_pw_object_mixin_class_override_properties (object_class);
}

static void
wp_client_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface)
{
  wp_pw_object_mixin_priv_interface_info_init_no_params (iface, client, CLIENT);
}

/*!
 * \brief Send an error to the client
 *
 * \ingroup wpclient
 * \param self the client
 * \param id the global id to report the error on
 * \param res an errno style error code
 * \param message the error message string
 */
void
wp_client_send_error (WpClient * self, guint32 id, int res,
    const gchar * message)
{
  struct pw_client *pwp;

  g_return_if_fail (WP_IS_CLIENT (self));

  pwp = (struct pw_client *) wp_proxy_get_pw_proxy (WP_PROXY (self));
  g_return_if_fail (pwp != NULL);

  pw_client_error (pwp, id, res, message);
}

/*!
 * \brief Update client's permissions on a list of objects.
 *
 * An object id of `-1` can be used to set the default object permissions
 * for this client
 *
 * \ingroup wpclient
 * \param self the client
 * \param n_perm the number of permissions specified in the variable arguments
 * \param ... \a n_perm pairs of guint32 numbers; the first number is the
 *   object id and the second is the permissions that this client should have
 *   on this object
 */
void
wp_client_update_permissions (WpClient * self, guint n_perm, ...)
{
  va_list args;
  struct pw_permission *perm =
      g_alloca (n_perm * sizeof (struct pw_permission));

  va_start (args, n_perm);
  for (guint i = 0; i < n_perm; i++) {
    perm[i].id = va_arg (args, guint32);
    perm[i].permissions = va_arg (args, guint32);
  }
  va_end (args);

  wp_client_update_permissions_array (self, n_perm, perm);
}

/*!
 * \brief Update client's permissions on a list of objects.
 *
 * An object id of `-1` can be used to set the default object permissions
 * for this client
 *
 * \ingroup wpclient
 * \param self the client
 * \param n_perm the number of permissions specified in the \a permissions array
 * \param permissions (array length=n_perm) (element-type pw_permission): an array
 *    of permissions per object id
 */
void
wp_client_update_permissions_array (WpClient * self,
    guint n_perm, const struct pw_permission *permissions)
{
  struct pw_client *pwp;
  int client_update_permissions_result;

  g_return_if_fail (WP_IS_CLIENT (self));

  pwp = (struct pw_client *) wp_proxy_get_pw_proxy (WP_PROXY (self));
  g_return_if_fail (pwp != NULL);

  client_update_permissions_result = pw_client_update_permissions (
      pwp, n_perm, permissions);
  g_warn_if_fail (client_update_permissions_result >= 0);
}
 07070100000059000081A4000000000000000000000001656CC35F00000324000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/client.h    /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_CLIENT_H__
#define __WIREPLUMBER_CLIENT_H__

#include "global-proxy.h"

G_BEGIN_DECLS

struct pw_permission;

/*!
 * \brief The WpClient GType
 * \ingroup wpclient
 */
#define WP_TYPE_CLIENT (wp_client_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpClient, wp_client, WP, CLIENT, WpGlobalProxy)

WP_API
void wp_client_send_error (WpClient * self, guint32 id, int res,
    const gchar * message);

WP_API
void wp_client_update_permissions (WpClient * self, guint n_perm, ...);

WP_API
void wp_client_update_permissions_array (WpClient * self,
    guint n_perm, const struct pw_permission *permissions);

G_END_DECLS

#endif
0707010000005A000081A4000000000000000000000001656CC35F000014D4000000000000000000000000000000000000002D00000000wireplumber-0.4.17/lib/wp/component-loader.c  /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-comp-loader"

#include "component-loader.h"
#include "wp.h"
#include "private/registry.h"
#include <pipewire/impl.h>

/*! \defgroup wpcomponentloader WpComponentLoader */
/*!
 * \struct WpComponentLoader
 *
 * The component loader is a plugin that provides the ability to load components.
 *
 * Components can be:
 *  - WirePlumber modules (libraries that provide WpPlugin and WpSiFactory objects)
 *  - PipeWire modules (libraries that extend libpipewire)
 *  - Scripts (ex. lua scripts)
 *  - Configuration files
 *
 * The WirePlumber library provides built-in support for loading WirePlumber
 * and PipeWire modules, without a component loader. For other kinds of
 * components, like the lua scripts and config files, a component loader is
 * provided in by external WirePlumber module.
 */

#define WP_MODULE_INIT_SYMBOL "wireplumber__module_init"
typedef gboolean (*WpModuleInitFunc) (WpCore *, GVariant *, GError **);

G_DEFINE_ABSTRACT_TYPE (WpComponentLoader, wp_component_loader, WP_TYPE_PLUGIN)

static void
wp_component_loader_init (WpComponentLoader * self)
{
}

static void
wp_component_loader_class_init (WpComponentLoaderClass * klass)
{
}

static gboolean
load_module (WpCore * core, const gchar * module_name,
    GVariant * args, GError ** error)
{
  g_autofree gchar *module_path = NULL;
  GModule *gmodule;
  gpointer module_init;

  module_path = g_module_build_path (wp_get_module_dir (), module_name);
  gmodule = g_module_open (module_path, G_MODULE_BIND_LOCAL);
  if (!gmodule) {
    g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
        "Failed to open module %s: %s", module_path, g_module_error ());
    return FALSE;
  }

  if (!g_module_symbol (gmodule, WP_MODULE_INIT_SYMBOL, &module_init)) {
    g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
        "Failed to locate symbol " WP_MODULE_INIT_SYMBOL " in %s",
        module_path);
    g_module_close (gmodule);
    return FALSE;
  }

  return ((WpModuleInitFunc) module_init) (core, args, error);
}

static gboolean
load_pw_module (WpCore * core, const gchar * module_name,
    GVariant * args, GError ** error)
{
  const gchar *args_str = NULL;
  if (args) {
    if (g_variant_is_of_type (args, G_VARIANT_TYPE_STRING))
      args_str = g_variant_get_string (args, NULL);

    //TODO if it proves to be useful
    //else if (g_variant_is_of_type (args, G_VARIANT_TYPE_DICTIONARY))
  }

  struct pw_impl_module *module = pw_context_load_module (
      wp_core_get_pw_context (core), module_name, args_str, NULL);
  if (!module) {
    int res = errno;
    g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
        "Failed to load pipewire module: %s", g_strerror (res));
    return FALSE;
  }
  return TRUE;
}

static gboolean
find_component_loader_func (gpointer cl, gpointer type)
{
  if (WP_IS_COMPONENT_LOADER (cl) &&
      (WP_COMPONENT_LOADER_GET_CLASS (cl)->supports_type (
            WP_COMPONENT_LOADER (cl), (const gchar *) type)))
    return TRUE;

  return FALSE;
}

static WpComponentLoader *
wp_component_loader_find (WpCore * core, const gchar * type)
{
  g_return_val_if_fail (WP_IS_CORE (core), NULL);
  GObject *c = wp_registry_find_object (wp_core_get_registry (core),
      (GEqualFunc) find_component_loader_func, type);
  return c ? WP_COMPONENT_LOADER (c) : NULL;
}

static gboolean
wp_component_loader_load (WpComponentLoader * self, const gchar * component,
    const gchar * type, GVariant * args, GError ** error)
{
  g_return_val_if_fail (WP_IS_COMPONENT_LOADER (self), FALSE);
  return WP_COMPONENT_LOADER_GET_CLASS (self)->load (self, component, type,
      args, error);
}

/*!
 * \brief Loads the specified \a component on \a self
 *
 * The \a type will determine which component loader to use. The following types
 * are built-in and will always work without a component loader:
 *  - "module" - Loads a WirePlumber module
 *  - "pw_module" - Loads a PipeWire module
 *
 * \ingroup wpcomponentloader
 * \param self the core
 * \param component the module name or file name
 * \param type the type of the component
 * \param args (transfer floating)(nullable): additional arguments for the component,
 *   usually a dict or a string
 * \param error (out) (optional): return location for errors, or NULL to ignore
 * \returns TRUE if loaded, FALSE if there was an error
 */
gboolean
wp_core_load_component (WpCore * self, const gchar * component,
    const gchar * type, GVariant * args, GError ** error)
{
  g_autoptr (GVariant) args_ref = args ? g_variant_ref_sink (args) : NULL;

  if (!g_strcmp0 (type, "module"))
    return load_module (self, component, args_ref, error);
  else if (!g_strcmp0 (type, "pw_module"))
    return load_pw_module (self, component, args_ref, error);
  else {
    g_autoptr (WpComponentLoader) c = wp_component_loader_find (self, type);
    if (c)
      return wp_component_loader_load (c, component, type, args, error);
    else {
      g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
          "No component loader was found for components of type '%s'", type);
      return FALSE;
    }
  }
}
0707010000005B000081A4000000000000000000000001656CC35F0000037C000000000000000000000000000000000000002D00000000wireplumber-0.4.17/lib/wp/component-loader.h  /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_COMPONENT_LOADER_H__
#define __WIREPLUMBER_COMPONENT_LOADER_H__

#include "plugin.h"

G_BEGIN_DECLS

/*!
 * \brief The WpComponentLoader GType
 * \ingroup wpcomponentloader
 */
#define WP_TYPE_COMPONENT_LOADER (wp_component_loader_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpComponentLoader, wp_component_loader,
                          WP, COMPONENT_LOADER, WpPlugin)

struct _WpComponentLoaderClass
{
  WpPluginClass parent_class;

  gboolean (*supports_type) (WpComponentLoader * self, const gchar * type);

  gboolean (*load) (WpComponentLoader * self, const gchar * component,
      const gchar * type, GVariant * args, GError ** error);

  /*< private >*/
  WP_PADDING(6)
};

G_END_DECLS

#endif
0707010000005C000081A4000000000000000000000001656CC35F000076F5000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/core.c  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-core"

#include "core.h"
#include "wp.h"
#include "private/registry.h"

#include <pipewire/pipewire.h>

#include <spa/utils/result.h>
#include <spa/debug/types.h>
#include <spa/support/cpu.h>

/*
 * Integration between the PipeWire main loop and GMainLoop
 */

#define WP_LOOP_SOURCE(x) ((WpLoopSource *) x)

typedef struct _WpLoopSource WpLoopSource;
struct _WpLoopSource
{
  GSource parent;
  struct pw_loop *loop;
};

static gboolean
wp_loop_source_dispatch (GSource * s, GSourceFunc callback, gpointer user_data)
{
  int result;

  wp_trace_boxed (G_TYPE_SOURCE, s, "entering pw main loop");

  pw_loop_enter (WP_LOOP_SOURCE(s)->loop);
  result = pw_loop_iterate (WP_LOOP_SOURCE(s)->loop, 0);
  pw_loop_leave (WP_LOOP_SOURCE(s)->loop);

  wp_trace_boxed (G_TYPE_SOURCE, s, "leaving pw main loop");

  if (G_UNLIKELY (result < 0))
    wp_warning_boxed (G_TYPE_SOURCE, s,
        "pw_loop_iterate failed: %s", spa_strerror (result));

  return G_SOURCE_CONTINUE;
}

static void
wp_loop_source_finalize (GSource * s)
{
  pw_loop_destroy (WP_LOOP_SOURCE(s)->loop);
}

static GSourceFuncs source_funcs = {
  NULL,
  NULL,
  wp_loop_source_dispatch,
  wp_loop_source_finalize
};

static GSource *
wp_loop_source_new (void)
{
  GSource *s = g_source_new (&source_funcs, sizeof (WpLoopSource));
  WP_LOOP_SOURCE(s)->loop = pw_loop_new (NULL);

  g_source_add_unix_fd (s,
      pw_loop_get_fd (WP_LOOP_SOURCE(s)->loop),
      G_IO_IN | G_IO_ERR | G_IO_HUP);

  return (GSource *) s;
}

/*! \defgroup wpcore WpCore */
/*!
 * \struct WpCore
 *
 * The core is the central object around which everything operates. It is
 * essential to create a WpCore before using any other WirePlumber API.
 *
 * The core object has the following responsibilities:
 *  * it initializes the PipeWire library
 *  * it creates a `pw_context` and allows connecting to the PipeWire server,
 *    creating a local `pw_core`
 *  * it glues the PipeWire library's event loop system with GMainLoop
 *  * it maintains a list of registered objects, which other classes use
 *    to keep objects loaded permanently into memory
 *  * it watches the PipeWire registry and keeps track of remote and local
 *    objects that appear in the registry, making them accessible through
 *    the WpObjectManager API.
 *
 * \gproperties
 *
 * \gproperty{g-main-context, GMainContext *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   A GMainContext to attach to}
 *
 * \gproperty{properties, WpProperties *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The pipewire properties of the pw_core}
 *
 * \gproperty{pw-context, gpointer (struct pw_context *), G_PARAM_READABLE,
 *   The pipewire context}
 *
 * \gproperty{pw-core, gpointer (struct pw_core *), G_PARAM_READABLE,
 *   The pipewire core}
 *
 * \gsignals
 *
 * \par connected
 * \parblock
 * \code
 * void
 * connected_callback (WpCore * self,
 *                     gpointer user_data)
 * \endcode
 * Emitted when the core is successfully connected to the PipeWire server
 * \endparblock
 *
 * \par disconnected
 * \parblock
 * \code
 * void
 * disconnected_callback (WpCore * self,
 *                        gpointer user_data)
 * \endcode
 * Emitted when the core is disconnected from the PipeWire server
 * \endparblock
 */
struct _WpCore
{
  GObject parent;

  /* main loop integration */
  GMainContext *g_main_context;

  /* extra properties */
  WpProperties *properties;

  /* pipewire main objects */
  struct pw_context *pw_context;
  struct pw_core *pw_core;
  struct pw_core_info *info;

  /* pipewire main listeners */
  struct spa_hook core_listener;
  struct spa_hook proxy_core_listener;

  WpRegistry registry;
  GHashTable *async_tasks; // <int seq, GTask*>
};

enum {
  PROP_0,
  PROP_G_MAIN_CONTEXT,
  PROP_PROPERTIES,
  PROP_PW_CONTEXT,
  PROP_PW_CORE,
};

enum {
  SIGNAL_CONNECTED,
  SIGNAL_DISCONNECTED,
  NUM_SIGNALS
};

static guint32 signals[NUM_SIGNALS];

G_DEFINE_TYPE (WpCore, wp_core, G_TYPE_OBJECT)

static void
core_info (void *data, const struct pw_core_info * info)
{
  WpCore *self = WP_CORE (data);
  gboolean new_connection = (self->info == NULL);

  self->info = pw_core_info_update (self->info, info);

  wp_info_object (self, "connected to server: %s, cookie: %u",
      self->info->name, self->info->cookie);

  if (new_connection)
    g_signal_emit (self, signals[SIGNAL_CONNECTED], 0);
}

static void
core_done (void *data, uint32_t id, int seq)
{
  WpCore *self = WP_CORE (data);
  g_autoptr (GTask) task = NULL;

  g_hash_table_steal_extended (self->async_tasks, GINT_TO_POINTER (seq), NULL,
      (gpointer *) &task);
  wp_debug_object (self, "done, seq 0x%x, task " WP_OBJECT_FORMAT,
      seq, WP_OBJECT_ARGS (task));

  if (task)
    g_task_return_boolean (task, TRUE);
}

static gboolean
core_disconnect_async (WpCore * self)
{
  wp_core_disconnect (self);
  return G_SOURCE_REMOVE;
}

static void
core_error (void *data, uint32_t id, int seq, int res, const char *message)
{
  WpCore *self = WP_CORE (data);

  /* protocol socket disconnected; schedule disconnecting our core */
  if (id == 0 && res == -EPIPE) {
    wp_core_idle_add_closure (self, NULL, g_cclosure_new_object (
            G_CALLBACK (core_disconnect_async), G_OBJECT (self)));
  }
}

static const struct pw_core_events core_events = {
  PW_VERSION_CORE_EVENTS,
  .info = core_info,
  .done = core_done,
  .error = core_error,
};

static gboolean
async_tasks_finish (gpointer key, gpointer value, gpointer user_data)
{
  GTask *task = G_TASK (value);
  g_return_val_if_fail (task, FALSE);

  g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
        WP_LIBRARY_ERROR_INVARIANT, "core disconnected");
  return TRUE;
}

static void
proxy_core_destroy (void *data)
{
  WpCore *self = WP_CORE (data);
  g_hash_table_foreach_remove (self->async_tasks, async_tasks_finish, NULL);
  g_clear_pointer (&self->info, pw_core_info_free);
  spa_hook_remove(&self->core_listener);
  spa_hook_remove(&self->proxy_core_listener);
  self->pw_core = NULL;
  wp_debug_object (self, "emit disconnected");
  g_signal_emit (self, signals[SIGNAL_DISCONNECTED], 0);
}

static const struct pw_proxy_events proxy_core_events = {
  PW_VERSION_PROXY_EVENTS,
  .destroy = proxy_core_destroy,
};

static void
wp_core_init (WpCore * self)
{
  wp_registry_init (&self->registry);
  self->async_tasks = g_hash_table_new_full (g_direct_hash, g_direct_equal,
      NULL, g_object_unref);
}

static void
wp_core_constructed (GObject *object)
{
  WpCore *self = WP_CORE (object);
  g_autoptr (GSource) source = NULL;

  /* loop */
  source = wp_loop_source_new ();
  g_source_attach (source, self->g_main_context);

  /* context */
  if (!self->pw_context) {
    struct pw_properties *p = NULL;
    const gchar *str = NULL;

    /* properties are fully stored in the pw_context, no need to keep a copy */
    p = self->properties ?
        wp_properties_unref_and_take_pw_properties (self->properties) : NULL;
    self->properties = NULL;

    self->pw_context = pw_context_new (WP_LOOP_SOURCE(source)->loop, p,
        sizeof (grefcount));
    g_return_if_fail (self->pw_context);

    /* use the same config option as pipewire to set the log level */
    p = (struct pw_properties *) pw_context_get_properties (self->pw_context);
    if (!g_getenv("WIREPLUMBER_DEBUG") &&
        (str = pw_properties_get(p, "log.level")) != NULL)
      wp_log_set_level (str);

    /* Init refcount */
    grefcount *rc = pw_context_get_user_data (self->pw_context);
    g_return_if_fail (rc);
    g_ref_count_init (rc);
  } else {
    /* Increase refcount */
    grefcount *rc = pw_context_get_user_data (self->pw_context);
    g_return_if_fail (rc);
    g_ref_count_inc (rc);
  }

  G_OBJECT_CLASS (wp_core_parent_class)->constructed (object);
}

static void
wp_core_dispose (GObject * obj)
{
  WpCore *self = WP_CORE (obj);

  wp_registry_clear (&self->registry);

  G_OBJECT_CLASS (wp_core_parent_class)->dispose (obj);
}

static void
wp_core_finalize (GObject * obj)
{
  WpCore *self = WP_CORE (obj);
  grefcount *rc = pw_context_get_user_data (self->pw_context);
  g_return_if_fail (rc);

  wp_core_disconnect (self);

  /* Clear pw-context if refcount reaches 0 */
  if (g_ref_count_dec (rc))
    g_clear_pointer (&self->pw_context, pw_context_destroy);

  g_clear_pointer (&self->properties, wp_properties_unref);
  g_clear_pointer (&self->g_main_context, g_main_context_unref);
  g_clear_pointer (&self->async_tasks, g_hash_table_unref);

  wp_debug_object (self, "WpCore destroyed");

  G_OBJECT_CLASS (wp_core_parent_class)->finalize (obj);
}

static void
wp_core_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpCore *self = WP_CORE (object);

  switch (property_id) {
  case PROP_G_MAIN_CONTEXT:
    g_value_set_boxed (value, self->g_main_context);
    break;
  case PROP_PROPERTIES:
    g_value_take_boxed (value, wp_core_get_properties (self));
    break;
  case PROP_PW_CONTEXT:
    g_value_set_pointer (value, self->pw_context);
    break;
  case PROP_PW_CORE:
    g_value_set_pointer (value, self->pw_core);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_core_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpCore *self = WP_CORE (object);

  switch (property_id) {
  case PROP_G_MAIN_CONTEXT:
    self->g_main_context = g_value_dup_boxed (value);
    break;
  case PROP_PROPERTIES:
    self->properties = g_value_dup_boxed (value);
    break;
  case PROP_PW_CONTEXT:
    self->pw_context = g_value_get_pointer (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_core_class_init (WpCoreClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;

  object_class->constructed = wp_core_constructed;
  object_class->dispose = wp_core_dispose;
  object_class->finalize = wp_core_finalize;
  object_class->get_property = wp_core_get_property;
  object_class->set_property = wp_core_set_property;

  g_object_class_install_property (object_class, PROP_G_MAIN_CONTEXT,
      g_param_spec_boxed ("g-main-context", "g-main-context",
          "A GMainContext to attach to", G_TYPE_MAIN_CONTEXT,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PROPERTIES,
      g_param_spec_boxed ("properties", "properties", "Extra properties",
          WP_TYPE_PROPERTIES,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PW_CONTEXT,
      g_param_spec_pointer ("pw-context", "pw-context", "The pipewire context",
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PW_CORE,
      g_param_spec_pointer ("pw-core", "pw-core", "The pipewire core",
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  signals[SIGNAL_CONNECTED] = g_signal_new ("connected",
      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
      G_TYPE_NONE, 0);

  signals[SIGNAL_DISCONNECTED] = g_signal_new ("disconnected",
      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
      G_TYPE_NONE, 0);
}

/*!
 * \brief Creates a new core object
 *
 * \ingroup wpcore
 * \param context (transfer none) (nullable): the GMainContext to use for events
 * \param properties (transfer full) (nullable): additional properties, which are
 *   passed to pw_context_new() and pw_context_connect()
 * \returns (transfer full): a new WpCore
 */
WpCore *
wp_core_new (GMainContext *context, WpProperties * properties)
{
  g_autoptr (WpProperties) props = properties;
  return g_object_new (WP_TYPE_CORE,
      "g-main-context", context,
      "properties", properties,
      "pw-context", NULL,
      NULL);
}

/*!
 * \brief Clones a core with the same context as \a self
 *
 * \ingroup wpcore
 * \param self the core
 * \returns (transfer full): the clone WpCore
 */
WpCore *
wp_core_clone (WpCore * self)
{
  return g_object_new (WP_TYPE_CORE,
      "g-main-context", self->g_main_context,
      "properties", self->properties,
      "pw-context", self->pw_context,
      NULL);
}

/*!
 * \brief Gets the GMainContext of the core
 *
 * \ingroup wpcore
 * \param self the core
 * \returns (transfer none) (nullable): the GMainContext that is in
 *   use by this core for events
 */
GMainContext *
wp_core_get_g_main_context (WpCore * self)
{
  g_return_val_if_fail (WP_IS_CORE (self), NULL);
  return self->g_main_context;
}

/*!
 * \brief Gets the internal PipeWire context of the core
 *
 * \ingroup wpcore
 * \param self the core
 * \returns (transfer none): the internal pw_context object
 */
struct pw_context *
wp_core_get_pw_context (WpCore * self)
{
  g_return_val_if_fail (WP_IS_CORE (self), NULL);
  return self->pw_context;
}

/*!
 * \brief Gets the internal PipeWire core of the core
 *
 * \ingroup wpcore
 * \param self the core
 * \returns (transfer none) (nullable): the internal pw_core object,
 *   or NULL if the core is not connected to PipeWire
 */
struct pw_core *
wp_core_get_pw_core (WpCore * self)
{
  g_return_val_if_fail (WP_IS_CORE (self), NULL);
  return self->pw_core;
}

/*!
 * \brief Gets the virtual machine type of the core
 *
 * \ingroup wpcore
 * \param self the core
 * \returns (transfer full) (nullable): a comma separated string with all the
 *   virtual machine types that this core matches, or NULL if the core is not
 *   running in a virtual machine.
 *
 * \since 0.4.11
 */
gchar *
wp_core_get_vm_type (WpCore *self)
{
  const struct spa_support *support;
  uint32_t n_support;
  struct spa_cpu *spa_cpu;
  uint32_t vm_type;
  gchar *res;
  gboolean first = TRUE;

  struct vm_type_info {
    uint32_t type;
    const gchar *name;
  };

  static struct vm_type_info vm_types[] = {
    {SPA_CPU_VM_OTHER, "other"},
    {SPA_CPU_VM_KVM, "kvm"},
    {SPA_CPU_VM_QEMU, "qemu"},
    {SPA_CPU_VM_BOCHS, "bochs"},
    {SPA_CPU_VM_XEN, "zen"},
    {SPA_CPU_VM_UML, "uml"},
    {SPA_CPU_VM_VMWARE, "vmware"},
    {SPA_CPU_VM_ORACLE, "oracle"},
    {SPA_CPU_VM_MICROSOFT, "microsoft"},
    {SPA_CPU_VM_ZVM, "zvm"},
    {SPA_CPU_VM_PARALLELS, "parallels"},
    {SPA_CPU_VM_BHYVE, "bhyve"},
    {SPA_CPU_VM_QNX, "qnx"},
    {SPA_CPU_VM_ACRN, "acrn"},
    {SPA_CPU_VM_POWERVM, "powervm"},
    {0, NULL},
  };

  g_return_val_if_fail (WP_IS_CORE (self), NULL);
  g_return_val_if_fail (self->pw_context, NULL);

  /* Get spa_cpu */
  support = pw_context_get_support (self->pw_context, &n_support);
  spa_cpu = spa_support_find (support, n_support, SPA_TYPE_INTERFACE_CPU);
  g_return_val_if_fail (spa_cpu, NULL);

  /* Just return NULL if CPU is not a VM */
  vm_type = spa_cpu_get_vm_type (spa_cpu);
  if (vm_type == SPA_CPU_VM_NONE)
    return NULL;

  /* Otherwise return a string with all matching VM types */
  res = g_strdup ("");
  for (guint i = 0; vm_types[i].name; i++) {
    if (vm_type & vm_types[i].type) {
      gchar *tmp = g_strdup_printf ("%s%s%s", res, first ? "": ",",
          vm_types[i].name);
      g_free (res);
      res = tmp;
      first = FALSE;
    }
  }

  return res;
}

/*!
 * \brief Connects this core to the PipeWire server.
 *
 * When connection succeeds, the WpCore \c "connected" signal is emitted.
 *
 * \ingroup wpcore
 * \param self the core
 * \returns TRUE if the core is effectively connected or FALSE if
 *   connection failed
 */
gboolean
wp_core_connect (WpCore *self)
{
  struct pw_properties *p = NULL;

  g_return_val_if_fail (WP_IS_CORE (self), FALSE);

  /* Don't do anything if core is already connected */
  if (self->pw_core)
    return TRUE;

  /* Connect */
  p = self->properties ? wp_properties_to_pw_properties (self->properties) : NULL;
  self->pw_core = pw_context_connect (self->pw_context, p, 0);
  if (!self->pw_core)
    return FALSE;

  /* Add the core listeners */
  pw_core_add_listener (self->pw_core, &self->core_listener, &core_events, self);
  pw_proxy_add_listener((struct pw_proxy*)self->pw_core,
      &self->proxy_core_listener, &proxy_core_events, self);

  /* Add the registry listener */
  wp_registry_attach (&self->registry, self->pw_core);

  return TRUE;
}

/*!
 * \brief Disconnects this core from the PipeWire server.
 *
 * This also effectively destroys all WpCore objects that were created through
 * the registry, destroys the pw_core and finally emits the WpCore
 * \c "disconnected" signal.
 *
 * \ingroup wpcore
 * \param self the core
 */
void
wp_core_disconnect (WpCore *self)
{
  wp_registry_detach (&self->registry);

  /* pw_core_disconnect destroys the core proxy
    and we continue in proxy_core_destroy() */
  if (self->pw_core)
    pw_core_disconnect (self->pw_core);
}

/*!
 * \brief Checks if the core is connected to PipeWire
 *
 * \ingroup wpcore
 * \param self the core
 * \returns TRUE if the core is connected to PipeWire, FALSE otherwise
 */
gboolean
wp_core_is_connected (WpCore * self)
{
  g_return_val_if_fail (WP_IS_CORE (self), FALSE);
  return self->pw_core != NULL;
}

/*!
 * \brief Gets the bound id of the client object that is created as a result
 * of this core being connected to the PipeWire daemon
 *
 * \ingroup wpcore
 * \since 0.4.16
 * \param self the core
 * \returns the bound id of this client
 */
guint32
wp_core_get_own_bound_id (WpCore * self)
{
  struct pw_client *client;

  g_return_val_if_fail (wp_core_is_connected (self), SPA_ID_INVALID);

  client = pw_core_get_client (self->pw_core);
  return pw_proxy_get_bound_id ((struct pw_proxy *) client);
}

/*!
 * \brief Gets the cookie of the core's connected PipeWire instance
 *
 * \ingroup wpcore
 * \param self the core
 * \returns The cookie of the PipeWire instance that \a self is connected to.
 *     The cookie is a unique random number for identifying an instance of
 *     PipeWire
 */
guint32
wp_core_get_remote_cookie (WpCore * self)
{
  g_return_val_if_fail (wp_core_is_connected (self), 0);
  g_return_val_if_fail (self->info, 0);

  return self->info->cookie;
}

/*!
 * \brief Gets the name of the core's connected PipeWire instance
 * \ingroup wpcore
 * \param self the core
 * \returns The name of the PipeWire instance that \a self is connected to
 */
const gchar *
wp_core_get_remote_name (WpCore * self)
{
  g_return_val_if_fail (wp_core_is_connected (self), NULL);
  g_return_val_if_fail (self->info, NULL);

  return self->info->name;
}

/*!
 * \brief Gets the user name of the core's connected PipeWire instance
 * \ingroup wpcore
 * \param self the core
 * \returns The name of the user that started the PipeWire instance that
 *     \a self is connected to
 */
const gchar *
wp_core_get_remote_user_name (WpCore * self)
{
  g_return_val_if_fail (wp_core_is_connected (self), NULL);
  g_return_val_if_fail (self->info, NULL);

  return self->info->user_name;
}

/*!
 * \brief Gets the host name of the core's connected PipeWire instance
 * \ingroup wpcore
 * \param self the core
 * \returns The name of the host where the PipeWire instance that
 *     \a self is connected to is running on
 */
const gchar *
wp_core_get_remote_host_name (WpCore * self)
{
  g_return_val_if_fail (wp_core_is_connected (self), NULL);
  g_return_val_if_fail (self->info, NULL);

  return self->info->host_name;
}

/*!
 * \brief Gets the version of the core's connected PipeWire instance
 * \ingroup wpcore
 * \param self the core
 * \returns The version of the PipeWire instance that \a self is connected to
 */
const gchar *
wp_core_get_remote_version (WpCore * self)
{
  g_return_val_if_fail (wp_core_is_connected (self), NULL);
  g_return_val_if_fail (self->info, NULL);

  return self->info->version;
}

/*!
 * \brief Gets the properties of the core's connected PipeWire instance
 * \ingroup wpcore
 * \param self the core
 * \returns (transfer full): the properties of the PipeWire instance that
 *     \a self is connected to
 */
WpProperties *
wp_core_get_remote_properties (WpCore * self)
{
  g_return_val_if_fail (wp_core_is_connected (self), NULL);
  g_return_val_if_fail (self->info, NULL);

  return wp_properties_new_wrap_dict (self->info->props);
}

/*!
 * \brief Gets the properties of the core
 * \ingroup wpcore
 * \param self the core
 * \returns (transfer full): the properties of \a self
 */
WpProperties *
wp_core_get_properties (WpCore * self)
{
  g_return_val_if_fail (WP_IS_CORE (self), NULL);

  /* pw_core has all the properties of the pw_context,
     plus our updates, passed in pw_context_connect() */
  if (self->pw_core)
    return wp_properties_new_wrap (pw_core_get_properties (self->pw_core));

  /* if there is no connection yet, return the properties of the context */
  else if (!self->properties)
    return wp_properties_new_wrap (pw_context_get_properties (self->pw_context));

  /* ... plus any further updates that we got from wp_core_update_properties() */
  else {
    /* we need to copy here in order to augment with the updates */
    WpProperties *ret =
        wp_properties_new_copy (pw_context_get_properties (self->pw_context));
    wp_properties_update (ret, self->properties);
    return ret;
  }
}

/*!
 * \brief Updates the properties of \a self on the connection, making them
 * appear on the client object that represents this connection.
 *
 * If \a self is not connected yet, these properties are stored and passed to
 * pw_context_connect() when connecting.
 *
 * \ingroup wpcore
 * \param self the core
 * \param updates (transfer full): updates to apply to the properties of
 *    \a self; this does not need to include properties that have not changed
 */
void
wp_core_update_properties (WpCore * self, WpProperties * updates)
{
  g_autoptr (WpProperties) upd = updates;

  g_return_if_fail (WP_IS_CORE (self));
  g_return_if_fail (updates != NULL);

  /* store updates locally so that
    - they persist after disconnection
    - we can pass them to pw_context_connect */
  if (!self->properties)
    self->properties = wp_properties_new_empty ();
  wp_properties_update (self->properties, upd);

  if (self->pw_core)
    pw_core_update_properties (self->pw_core, wp_properties_peek_dict (upd));
}

/*!
 * \brief Adds an idle callback to be called in the same GMainContext as the
 * one used by this core.
 *
 * This is essentially the same as g_idle_add_full(), but it adds the created
 * GSource on the GMainContext used by this core instead of the default context.
 *
 * \ingroup wpcore
 * \param self the core
 * \param source (out) (optional): the source
 * \param function (scope notified): the function to call
 * \param data (closure): data to pass to \a function
 * \param destroy (nullable): a function to destroy \a data
 */
void
wp_core_idle_add (WpCore * self, GSource **source, GSourceFunc function,
    gpointer data, GDestroyNotify destroy)
{
  g_autoptr (GSource) s = NULL;

  g_return_if_fail (WP_IS_CORE (self));

  s = g_idle_source_new ();
  g_source_set_callback (s, function, data, destroy);
  g_source_attach (s, self->g_main_context);

  if (source)
    *source = g_source_ref (s);
}

/*!
 * \brief Adds an idle callback to be called in the same GMainContext as
 * the one used by this core.
 *
 * This is the same as wp_core_idle_add(), but it allows you to specify a
 * GClosure instead of a C callback.
 *
 * \ingroup wpcore
 * \param self the core
 * \param source (out) (optional): the source
 * \param closure the closure to invoke
 */
void
wp_core_idle_add_closure (WpCore * self, GSource **source, GClosure * closure)
{
  g_autoptr (GSource) s = NULL;

  g_return_if_fail (WP_IS_CORE (self));
  g_return_if_fail (closure != NULL);

  s = g_idle_source_new ();
  g_source_set_closure (s, closure);
  g_source_attach (s, self->g_main_context);

  if (source)
    *source = g_source_ref (s);
}

/*!
 * \brief Adds a timeout callback to be called at regular intervals in the same
 * GMainContext as the one used by this core.
 *
 * The function is called repeatedly until it returns FALSE, at which point
 * the timeout is automatically destroyed and the function will not be called
 * again. The first call to the function will be at the end of the first
 * interval.

 * This is essentially the same as g_timeout_add_full(), but it adds
 * the created GSource on the GMainContext used by this core instead of the
 * default context.
 *
 * \ingroup wpcore
 * \param self the core
 * \param source (out) (optional): the source
 * \param timeout_ms the timeout in milliseconds
 * \param function (scope notified): the function to call
 * \param data (closure): data to pass to \a function
 * \param destroy (nullable): a function to destroy \a data
 */
void
wp_core_timeout_add (WpCore * self, GSource **source, guint timeout_ms,
    GSourceFunc function, gpointer data, GDestroyNotify destroy)
{
  g_autoptr (GSource) s = NULL;

  g_return_if_fail (WP_IS_CORE (self));

  s = g_timeout_source_new (timeout_ms);
  g_source_set_callback (s, function, data, destroy);
  g_source_attach (s, self->g_main_context);

  if (source)
    *source = g_source_ref (s);
}

/*!
 * \brief Adds a timeout callback to be called at regular intervals in the same
 * GMainContext as the one used by this core.
 *
 * This is the same as wp_core_timeout_add(), but it allows you to specify a
 * GClosure instead of a C callback.
 *
 * \ingroup wpcore
 * \param self the core
 * \param source (out) (optional): the source
 * \param timeout_ms the timeout in milliseconds
 * \param closure the closure to invoke
 */
void
wp_core_timeout_add_closure (WpCore * self, GSource **source, guint timeout_ms,
    GClosure * closure)
{
  g_autoptr (GSource) s = NULL;

  g_return_if_fail (WP_IS_CORE (self));
  g_return_if_fail (closure != NULL);

  s = g_timeout_source_new (timeout_ms);
  g_source_set_closure (s, closure);
  g_source_attach (s, self->g_main_context);

  if (source)
    *source = g_source_ref (s);
}

/*!
 * \brief Asks the PipeWire server to call the \a callback via an event.
 *
 * Since methods are handled in-order and events are delivered
 * in-order, this can be used as a barrier to ensure all previous
 * methods and the resulting events have been handled.
 *
 * In both success and error cases, \a callback is always called.
 * Use wp_core_sync_finish() from within the \a callback to determine whether
 * the operation completed successfully or if an error occurred.
 *
 * \ingroup wpcore
 * \param self the core
 * \param cancellable (nullable): a GCancellable to cancel the operation
 * \param callback (scope async): a function to call when the operation is done
 * \param user_data (closure): data to pass to \a callback
 * \returns TRUE if the sync operation was started, FALSE if an error
 *   occurred before returning from this function
 */
gboolean
wp_core_sync (WpCore * self, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer user_data)
{
  return wp_core_sync_closure (self, cancellable,
      g_cclosure_new (G_CALLBACK (callback), user_data, NULL));
}

static void
invoke_closure (GObject * obj, GAsyncResult * res, gpointer data)
{
  GClosure *closure = data;
  GValue values[2] = { G_VALUE_INIT, G_VALUE_INIT };
  g_value_init (&values[0], G_TYPE_OBJECT);
  g_value_init (&values[1], G_TYPE_OBJECT);
  g_value_set_object (&values[0], obj);
  g_value_set_object (&values[1], res);
  g_closure_invoke (closure, NULL, 2, values, NULL);
  g_value_unset (&values[0]);
  g_value_unset (&values[1]);
  g_closure_unref (closure);
}

/*!
 * \brief Asks the PipeWire server to invoke the \a closure via an event.
 *
 * Since methods are handled in-order and events are delivered
 * in-order, this can be used as a barrier to ensure all previous
 * methods and the resulting events have been handled.
 *
 * In both success and error cases, \a closure is always invoked.
 * Use wp_core_sync_finish() from within the \a closure to determine whether
 * the operation completed successfully or if an error occurred.
 *
 * \ingroup wpcore
 * \since 0.4.6
 * \param self the core
 * \param cancellable (nullable): a GCancellable to cancel the operation
 * \param closure (transfer floating): a closure to invoke when the operation
 *    is done
 * \returns TRUE if the sync operation was started, FALSE if an error
 *   occurred before returning from this function
 */
gboolean
wp_core_sync_closure (WpCore * self, GCancellable * cancellable,
    GClosure * closure)
{
  g_autoptr (GTask) task = NULL;
  int seq;

  g_return_val_if_fail (WP_IS_CORE (self), FALSE);
  g_return_val_if_fail (closure, FALSE);

  closure = g_closure_ref (closure);
  g_closure_sink (closure);
  if (G_CLOSURE_NEEDS_MARSHAL (closure))
    g_closure_set_marshal (closure, g_cclosure_marshal_VOID__OBJECT);

  task = g_task_new (self, cancellable, invoke_closure, closure);

  if (G_UNLIKELY (!self->pw_core)) {
    g_warn_if_reached ();
    g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
        WP_LIBRARY_ERROR_INVARIANT, "No pipewire core");
    return FALSE;
  }

  seq = pw_core_sync (self->pw_core, 0, 0);
  if (G_UNLIKELY (seq < 0)) {
    g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
        WP_LIBRARY_ERROR_OPERATION_FAILED, "pw_core_sync failed: %s",
        g_strerror (-seq));
    return FALSE;
  }

  wp_debug_object (self, "sync, seq 0x%x, task " WP_OBJECT_FORMAT,
      seq, WP_OBJECT_ARGS (task));

  g_hash_table_insert (self->async_tasks, GINT_TO_POINTER (seq),
      g_steal_pointer (&task));
  return TRUE;
}

/*!
 * \brief This function is meant to be called from within the callback of
 * wp_core_sync() in order to determine the success or failure of the operation.
 *
 * \ingroup wpcore
 * \param self the core
 * \param res a GAsyncResult
 * \param error (out) (optional): the error that occurred, if any
 * \returns TRUE if the operation succeeded, FALSE otherwise
 */
gboolean
wp_core_sync_finish (WpCore * self, GAsyncResult * res, GError ** error)
{
  g_return_val_if_fail (WP_IS_CORE (self), FALSE);
  g_return_val_if_fail (g_task_is_valid (res, self), FALSE);

  return g_task_propagate_boolean (G_TASK (res), error);
}

WpRegistry *
wp_core_get_registry (WpCore * self)
{
  return &self->registry;
}

WpCore *
wp_registry_get_core (WpRegistry * self)
{
  return SPA_CONTAINER_OF (self, WpCore, registry);
}
   0707010000005D000081A4000000000000000000000001656CC35F00000B11000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/core.h  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_CORE_H__
#define __WIREPLUMBER_CORE_H__

#include <gio/gio.h>
#include "defs.h"
#include "properties.h"

G_BEGIN_DECLS

struct pw_context;
struct pw_core;
typedef struct _WpObjectManager WpObjectManager;

/*!
 * \brief The WpCore GType
 * \ingroup wpcore
 */
#define WP_TYPE_CORE (wp_core_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpCore, wp_core, WP, CORE, GObject)

/* Basic */

WP_API
WpCore * wp_core_new (GMainContext *context, WpProperties * properties);

WP_API
WpCore * wp_core_clone (WpCore * self);

WP_API
GMainContext * wp_core_get_g_main_context (WpCore * self);

WP_API
struct pw_context * wp_core_get_pw_context (WpCore * self);

WP_API
struct pw_core * wp_core_get_pw_core (WpCore * self);

WP_API
gchar *wp_core_get_vm_type (WpCore *self);

WP_API
gboolean wp_core_load_component (WpCore * self, const gchar * component,
    const gchar * type, GVariant * args, GError ** error);

/* Connection */

WP_API
gboolean wp_core_connect (WpCore *self);

WP_API
void wp_core_disconnect (WpCore *self);

WP_API
gboolean wp_core_is_connected (WpCore * self);

/* Properties */

WP_API
guint32 wp_core_get_own_bound_id (WpCore * self);

WP_API
guint32 wp_core_get_remote_cookie (WpCore * self);

WP_API
const gchar * wp_core_get_remote_name (WpCore * self);

WP_API
const gchar * wp_core_get_remote_user_name (WpCore * self);

WP_API
const gchar * wp_core_get_remote_host_name (WpCore * self);

WP_API
const gchar * wp_core_get_remote_version (WpCore * self);

WP_API
WpProperties * wp_core_get_remote_properties (WpCore * self);

WP_API
WpProperties * wp_core_get_properties (WpCore * self);

WP_API
void wp_core_update_properties (WpCore * self, WpProperties * updates);

/* Callback */

WP_API
void wp_core_idle_add (WpCore * self, GSource **source, GSourceFunc function,
    gpointer data, GDestroyNotify destroy);

WP_API
void wp_core_idle_add_closure (WpCore * self, GSource **source,
    GClosure * closure);

WP_API
void wp_core_timeout_add (WpCore * self, GSource **source, guint timeout_ms,
    GSourceFunc function, gpointer data, GDestroyNotify destroy);

WP_API
void wp_core_timeout_add_closure (WpCore * self, GSource **source,
    guint timeout_ms, GClosure * closure);

WP_API
gboolean wp_core_sync (WpCore * self, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer user_data);

WP_API
gboolean wp_core_sync_closure (WpCore * self, GCancellable * cancellable,
    GClosure * closure);

WP_API
gboolean wp_core_sync_finish (WpCore * self, GAsyncResult * res,
    GError ** error);

/* Object Manager */

WP_API
void wp_core_install_object_manager (WpCore * self, WpObjectManager * om);

G_END_DECLS

#endif
   0707010000005E000081A4000000000000000000000001656CC35F000024DA000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/dbus.c  /* WirePlumber
 *
 * Copyright © 2022 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-dbus"

#include "private/registry.h"
#include "log.h"
#include "wpenums.h"
#include "dbus.h"

enum {
  STEP_DBUS_ENABLE = WP_TRANSITION_STEP_CUSTOM_START,
};

enum
{
  PROP_DBUS_0,
  PROP_DBUS_BUS_TYPE,
  PROP_DBUS_STATE,
};

/**
 * \since 0.4.11
 */
struct _WpDbus
{
  WpObject parent;

  /* Props */
  GBusType bus_type;
  WpDBusState state;

  GCancellable *cancellable;
  GDBusConnection *connection;
};

static void on_connection_closed (GDBusConnection *connection, gboolean
    remote_peer_vanished, GError *error, gpointer data);

G_DEFINE_TYPE (WpDbus, wp_dbus, WP_TYPE_OBJECT)

static void
wp_dbus_init (WpDbus * self)
{
}

static void
wp_dbus_set_state (WpDbus *self, WpDBusState new_state)
{
  if (self->state != new_state) {
    self->state = new_state;
    g_object_notify (G_OBJECT (self), "state");
  }
}

static void
on_got_bus (GObject * obj, GAsyncResult * res, gpointer data)
{
  WpTransition *transition;
  WpDbus *self;
  g_autoptr (GError) error = NULL;

  if (WP_IS_TRANSITION (data)) {
    // coming from wp_dbus_enable
    transition = WP_TRANSITION (data);
    self = wp_transition_get_source_object (transition);
  } else {
    // coming from on_sync_reconnect
    transition = NULL;
    self = WP_DBUS (data);
  }

  self->connection = g_dbus_connection_new_for_address_finish (res, &error);
  if (!self->connection) {
    if (transition) {
      g_prefix_error (&error, "Failed to connect to bus: ");
      wp_transition_return_error (transition, g_steal_pointer (&error));
    }
    return;
  }

  wp_debug_object (self, "Connected to bus");

  /* set up connection */
  g_signal_connect_object (self->connection, "closed",
      G_CALLBACK (on_connection_closed), self, 0);
  g_dbus_connection_set_exit_on_close (self->connection, FALSE);

  wp_dbus_set_state (self, WP_DBUS_STATE_CONNECTED);

  wp_object_update_features (WP_OBJECT (self), WP_DBUS_FEATURE_ENABLED, 0);
}

static gboolean
do_connect (WpDbus *self, GAsyncReadyCallback callback, gpointer data,
    GError **error)
{
  g_autofree gchar *address = NULL;

  address = g_dbus_address_get_for_bus_sync (self->bus_type, NULL, error);
  if (!address) {
    g_prefix_error (error, "Error acquiring bus address: ");
    return FALSE;
  }

  wp_dbus_set_state (self, WP_DBUS_STATE_CONNECTING);

  wp_debug_object (self, "Connecting to bus: %s", address);
  g_dbus_connection_new_for_address (address,
      G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
      G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
      NULL, self->cancellable, callback, data);
  return TRUE;
}

static void
on_sync_reconnect (WpCore * core, GAsyncResult * res, WpDbus * self)
{
  g_autoptr (GError) error = NULL;

  if (!wp_core_sync_finish (core, res, &error)) {
    wp_warning_object (self, "core sync error: %s", error->message);
    return;
  }

  if (!do_connect (self, on_got_bus, self, &error))
    wp_info_object (self, "Cannot reconnect on sync: %s", error->message);
}

static void
on_connection_closed (GDBusConnection *connection,
    gboolean remote_peer_vanished, GError *error, gpointer data)
{
  WpDbus *self = WP_DBUS (data);
  g_autoptr (WpCore) core = NULL;

  wp_info_object (self, "DBus connection closed: %s", error->message);

  g_clear_object (&self->connection);
  wp_dbus_set_state (self, WP_DBUS_STATE_CLOSED);

  /* try to reconnect on idle if connection was closed */
  core = wp_object_get_core (WP_OBJECT (self));
  if (core && wp_core_is_connected (core)) {
    wp_info_object (self, "Trying to reconnect on sync");
    wp_core_sync_closure (core, NULL, g_cclosure_new_object (
        G_CALLBACK (on_sync_reconnect), G_OBJECT (self)));
  }
}

static void
wp_dbus_enable (WpDbus *self, WpTransition *transition)
{
  g_autoptr (GError) error = NULL;
  if (!do_connect (self, on_got_bus, transition, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }
}

static void
wp_dbus_disable (WpDbus *self)
{
  g_cancellable_cancel (self->cancellable);

  g_clear_object (&self->connection);
  wp_dbus_set_state (self, WP_DBUS_STATE_CLOSED);

  g_clear_object (&self->cancellable);
  self->cancellable = g_cancellable_new ();

  wp_object_update_features (WP_OBJECT (self), 0, WP_DBUS_FEATURE_ENABLED);
}

static WpObjectFeatures
wp_dbus_get_supported_features (WpObject * self)
{
  return WP_DBUS_FEATURE_ENABLED;
}

static guint
wp_dbus_activate_get_next_step (WpObject * object,
     WpFeatureActivationTransition * t, guint step, WpObjectFeatures missing)
{
  switch (step) {
    case WP_TRANSITION_STEP_NONE:
      return STEP_DBUS_ENABLE;
    case STEP_DBUS_ENABLE:
      return WP_TRANSITION_STEP_NONE;
    default:
      return WP_TRANSITION_STEP_ERROR;
  }
}

static void
wp_dbus_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * t, guint step, WpObjectFeatures missing)
{
  WpDbus *self = WP_DBUS (object);
  WpTransition *transition = WP_TRANSITION (t);

  switch (step) {
    case STEP_DBUS_ENABLE:
      wp_dbus_enable (self, transition);
      break;

    case WP_TRANSITION_STEP_ERROR:
      break;

    default:
      g_return_if_reached ();
  }
}

static void
wp_dbus_deactivate (WpObject * object, WpObjectFeatures features)
{
  WpDbus *self = WP_DBUS (object);
  guint current = wp_object_get_active_features (object);

  if (features & current & WP_DBUS_FEATURE_ENABLED)
    wp_dbus_disable (self);
}

static void
wp_dbus_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpDbus *self = WP_DBUS (object);

  switch (property_id) {
  case PROP_DBUS_BUS_TYPE:
    g_value_set_enum (value, self->bus_type);
    break;
  case PROP_DBUS_STATE:
    g_value_set_enum (value, self->state);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_dbus_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpDbus *self = WP_DBUS (object);

  switch (property_id) {
  case PROP_DBUS_BUS_TYPE:
    self->bus_type = g_value_get_enum (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_dbus_class_init (WpDbusClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;

  object_class->set_property = wp_dbus_set_property;
  object_class->get_property = wp_dbus_get_property;

  wpobject_class->get_supported_features = wp_dbus_get_supported_features;
  wpobject_class->activate_get_next_step = wp_dbus_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_dbus_activate_execute_step;
  wpobject_class->deactivate = wp_dbus_deactivate;

  g_object_class_install_property (object_class, PROP_DBUS_BUS_TYPE,
      g_param_spec_enum ("bus-type", "bus-type", "The bus type",
          G_TYPE_BUS_TYPE, G_BUS_TYPE_NONE,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_DBUS_STATE,
      g_param_spec_enum ("state", "state", "The dbus connection state",
          WP_TYPE_DBUS_STATE, WP_DBUS_STATE_CLOSED,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

static gboolean
find_dbus_func (gpointer object, gpointer p)
{
  GBusType *bus_type = p;

  if (!WP_IS_DBUS (object) || !bus_type)
    return FALSE;

  return wp_dbus_get_bus_type (WP_DBUS (object)) == *bus_type;
}

/*!
 * \brief Returns the dbus instance that is associated with the given core and
 * bus type.
 *
 * This method will also create the instance and register it with the core
 * if it had not been created before.
 *
 * \param core the core
 * \param bus_type the bus type
 * \return (transfer full): the dbus instance
 */
WpDbus *
wp_dbus_get_instance (WpCore *core, GBusType bus_type)
{
  WpRegistry *registry;
  WpDbus *dbus;

  g_return_val_if_fail (core, NULL);
  g_return_val_if_fail (
      bus_type != G_BUS_TYPE_NONE && bus_type != G_BUS_TYPE_STARTER, NULL);

  registry = wp_core_get_registry (core);
  dbus = wp_registry_find_object (registry, (GEqualFunc) find_dbus_func,
      &bus_type);
  if (G_UNLIKELY (!dbus)) {
    dbus = g_object_new (WP_TYPE_DBUS,
        "core", core,
        "bus-type", bus_type,
        NULL);
    wp_registry_register_object (registry, g_object_ref (dbus));
  }

  return dbus;
}

/*!
 * \brief Returns the bus type of the dbus object.
 *
 * \param self the DBus object
 * \returns the bus type
 */
GBusType
wp_dbus_get_bus_type (WpDbus *self)
{
  g_return_val_if_fail (WP_IS_DBUS (self), G_BUS_TYPE_NONE);

  return self->bus_type;
}

/*!
 * \brief Returns the dbus connection state of the dbus object.
 *
 * \param self the DBus object
 * \returns the dbus connection state
 */
WpDBusState
wp_dbus_get_state (WpDbus *self)
{
  g_return_val_if_fail (WP_IS_DBUS (self), WP_DBUS_STATE_CLOSED);

  return self->state;
}

/*!
 * \brief Returns the DBus connection object of the dbus object.
 *
 * \param self the DBus object
 * \return (transfer full): the DBus connection object
 */
GDBusConnection *
wp_dbus_get_connection (WpDbus *self)
{
  g_return_val_if_fail (WP_IS_DBUS (self), NULL);

  return self->connection ? g_object_ref (self->connection) : NULL;
}
  0707010000005F000081A4000000000000000000000001656CC35F00000457000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/dbus.h  /* WirePlumber
 *
 * Copyright © 2022 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_DBUS_H__
#define __WIREPLUMBER_DBUS_H__

#include "object.h"

G_BEGIN_DECLS

/* WpDbus */

/*!
 * \brief Flags to be used as WpObjectFeatures for WpDbus.
 * \ingroup wpdbus
 *
 * \since 0.4.11
 */
typedef enum { /*< flags >*/
  /* main features */
  WP_DBUS_FEATURE_ENABLED = (1 << 0),
} WpDbusFeatures;

/*!
 * \brief The state of the dbus connection
 * \ingroup wpdbus
 *
 * \since 0.4.11
 */
typedef enum {
  WP_DBUS_STATE_CLOSED = 0,
  WP_DBUS_STATE_CONNECTING,
  WP_DBUS_STATE_CONNECTED,
} WpDBusState;

/*!
 * \brief The WpDbus GType
 * \ingroup wpdbus
 */
#define WP_TYPE_DBUS (wp_dbus_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpDbus, wp_dbus, WP, DBUS, WpObject)

WP_API
WpDbus *wp_dbus_get_instance (WpCore *core, GBusType bus_type);

WP_API
GBusType wp_dbus_get_bus_type (WpDbus *self);

WP_API
WpDBusState wp_dbus_get_state (WpDbus *self);

WP_API
GDBusConnection *wp_dbus_get_connection (WpDbus *self);

G_END_DECLS

#endif
 07070100000060000081A4000000000000000000000001656CC35F00000335000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/defs.h  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_DEFS_H__
#define __WIREPLUMBER_DEFS_H__

#if defined(__GNUC__)
# define WP_PLUGIN_EXPORT __attribute__ ((visibility ("default")))
# define WP_API_EXPORT extern __attribute__ ((visibility ("default")))
#else
# define WP_PLUGIN_EXPORT
# define WP_API_EXPORT extern
#endif

#define WP_API_IMPORT extern

#ifndef WP_API
# ifdef BUILDING_WP
#  define WP_API WP_API_EXPORT
# else
#  define WP_API WP_API_IMPORT
# endif
#endif

#ifndef WP_PRIVATE_API
# ifdef BUILDING_WP
#  define WP_PRIVATE_API
# else
#  define WP_PRIVATE_API __attribute__ ((deprecated ("Private API")))
# endif
#endif

#define WP_PADDING(n) gpointer _wp_padding[n];

#endif
   07070100000061000081A4000000000000000000000001656CC35F00005410000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/device.c    /* WirePlumber
 *
 * Copyright © 2019-2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-device"

#include "device.h"
#include "node.h"
#include "core.h"
#include "log.h"
#include "error.h"
#include "private/pipewire-object-mixin.h"

#include <pipewire/impl.h>
#include <spa/debug/types.h>
#include <spa/monitor/device.h>
#include <spa/utils/result.h>

/*! \defgroup wpdevice WpDevice */
/*!
 * \struct WpDevice
 *
 * The WpDevice class allows accessing the properties and methods of a
 * PipeWire device object (`struct pw_device`).
 *
 * A WpDevice is constructed internally when a new device appears on the
 * PipeWire registry and it is made available through the WpObjectManager API.
 * Alternatively, a WpDevice can also be constructed using
 * wp_device_new_from_factory(), which creates a new device object
 * on the remote PipeWire server by calling into a factory.
 *
 */

struct _WpDevice
{
  WpGlobalProxy parent;
};

static void wp_device_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface);

G_DEFINE_TYPE_WITH_CODE (WpDevice, wp_device, WP_TYPE_GLOBAL_PROXY,
    G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT,
        wp_pw_object_mixin_object_interface_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
        wp_device_pw_object_mixin_priv_interface_init));

static void
wp_device_init (WpDevice * self)
{
}

static void
wp_device_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case WP_PW_OBJECT_MIXIN_STEP_BIND:
  case WP_TRANSITION_STEP_ERROR:
    /* base class can handle BIND and ERROR */
    WP_OBJECT_CLASS (wp_device_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO:
    /* just wait, info will be emitted anyway after binding */
    break;
  case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS:
    wp_pw_object_mixin_cache_params (object, missing);
    break;
  default:
    g_assert_not_reached ();
  }
}

static void
wp_device_deactivate (WpObject * object, WpObjectFeatures features)
{
  wp_pw_object_mixin_deactivate (object, features);
  WP_OBJECT_CLASS (wp_device_parent_class)->deactivate (object, features);
}

static const struct pw_device_events device_events = {
  PW_VERSION_DEVICE_EVENTS,
  .info = (HandleEventInfoFunc(device)) wp_pw_object_mixin_handle_event_info,
  .param = wp_pw_object_mixin_handle_event_param,
};

static void
wp_device_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy,
      device, &device_events);
}

static void
wp_device_pw_proxy_destroyed (WpProxy * proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy);

  WP_PROXY_CLASS (wp_device_parent_class)->pw_proxy_destroyed (proxy);
}

static void
wp_device_class_init (WpDeviceClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->get_property = wp_pw_object_mixin_get_property;

  wpobject_class->get_supported_features =
      wp_pw_object_mixin_get_supported_features;
  wpobject_class->activate_get_next_step =
      wp_pw_object_mixin_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_device_activate_execute_step;
  wpobject_class->deactivate = wp_device_deactivate;

  proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Device;
  proxy_class->pw_iface_version = PW_VERSION_DEVICE;
  proxy_class->pw_proxy_created = wp_device_pw_proxy_created;
  proxy_class->pw_proxy_destroyed = wp_device_pw_proxy_destroyed;

  wp_pw_object_mixin_class_override_properties (object_class);
}

static gint
wp_device_enum_params (gpointer instance, guint32 id,
    guint32 start, guint32 num, WpSpaPod *filter)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  return pw_device_enum_params (d->iface, 0, id, start, num,
      filter ? wp_spa_pod_get_spa_pod (filter) : NULL);
}

static gint
wp_device_set_param (gpointer instance, guint32 id, guint32 flags,
    WpSpaPod * param)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  g_autoptr (WpSpaPod) p = param;
  return pw_device_set_param (d->iface, id, flags,
      wp_spa_pod_get_spa_pod (p));
}

static void
wp_device_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface)
{
  wp_pw_object_mixin_priv_interface_info_init (iface, device, DEVICE);
  iface->enum_params = wp_device_enum_params;
  iface->set_param = wp_device_set_param;
}

/*!
 * \brief Constructs a device on the PipeWire server by asking the remote
 * factory \a factory_name to create it.
 *
 * Because of the nature of the PipeWire protocol, this operation completes
 * asynchronously at some point in the future. In order to find out when
 * this is done, you should call wp_object_activate(), requesting at least
 * %WP_PROXY_FEATURE_BOUND. When this feature is ready, the device is ready for
 * use on the server. If the device cannot be created, this activation operation
 * will fail.
 *
 * \ingroup wpdevice
 * \param core the wireplumber core
 * \param factory_name the pipewire factory name to construct the device
 * \param properties (nullable) (transfer full): the properties to pass to the
 *   factory
 * \returns (nullable) (transfer full): the new device or %NULL if the core
 *   is not connected and therefore the device cannot be created
 */

WpDevice *
wp_device_new_from_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties)
{
  g_autoptr (WpProperties) props = properties;
  return g_object_new (WP_TYPE_DEVICE,
      "core", core,
      "factory-name", factory_name,
      "global-properties", props,
      NULL);
}

/*! \defgroup wpspadevice WpSpaDevice */

struct _WpSpaDevice
{
  WpProxy parent;
  struct spa_handle *handle;
  struct spa_device *device;
  struct spa_hook listener;
  WpProperties *properties;
  GPtrArray *managed_objs;
};

enum {
  PROP_0,
  PROP_SPA_DEVICE_HANDLE,
  PROP_PROPERTIES,
};

enum
{
  SIGNAL_CREATE_OBJECT,
  SIGNAL_OBJECT_REMOVED,
  SPA_DEVICE_LAST_SIGNAL,
};

static guint spa_device_signals[SPA_DEVICE_LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE (WpSpaDevice, wp_spa_device, WP_TYPE_PROXY)

static void
object_unref_safe (gpointer object)
{
  if (object)
    g_object_unref (object);
}

static void
wp_spa_device_init (WpSpaDevice * self)
{
  self->properties = wp_properties_new_empty ();
  self->managed_objs = g_ptr_array_new_with_free_func (object_unref_safe);
}

static void
wp_spa_device_constructed (GObject *object)
{
  WpSpaDevice *self = WP_SPA_DEVICE (object);
  gint res;

  g_return_if_fail (self->handle);

  /* Get the handle interface */
  res = spa_handle_get_interface (self->handle, SPA_TYPE_INTERFACE_Device,
      (gpointer *) &self->device);
  if (res < 0) {
    wp_warning_object (self,
        "Could not get device interface from SPA handle: %s",
        spa_strerror (res));
    return;
  }

  G_OBJECT_CLASS (wp_spa_device_parent_class)->constructed (object);
}

static void
wp_spa_device_finalize (GObject * object)
{
  WpSpaDevice *self = WP_SPA_DEVICE (object);

  self->device = NULL;
  g_clear_pointer (&self->handle, pw_unload_spa_handle);
  g_clear_pointer (&self->properties, wp_properties_unref);
  g_clear_pointer (&self->managed_objs, g_ptr_array_unref);

  G_OBJECT_CLASS (wp_spa_device_parent_class)->finalize (object);
}

static void
wp_spa_device_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpSpaDevice *self = WP_SPA_DEVICE (object);

  switch (property_id) {
  case PROP_SPA_DEVICE_HANDLE:
    self->handle = g_value_get_pointer (value);
    break;
  case PROP_PROPERTIES: {
    WpProperties *p = g_value_get_boxed (value);
    if (p)
      wp_properties_update (self->properties, p);
    break;
  }
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_spa_device_get_property (GObject * object, guint property_id, GValue * value,
    GParamSpec * pspec)
{
  WpSpaDevice *self = WP_SPA_DEVICE (object);

  switch (property_id) {
  case PROP_SPA_DEVICE_HANDLE:
    g_value_set_pointer (value, self->handle);
    break;
  case PROP_PROPERTIES:
    g_value_take_boxed (value, wp_properties_ref (self->properties));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
spa_device_event_info (void *data, const struct spa_device_info *info)
{
  WpSpaDevice *self = WP_SPA_DEVICE (data);

  /*
   * This is emited syncrhonously at the time we add the listener and
   * before object_info is emited. It gives us additional properties
   * about the device, like the "api.alsa.card.*" ones that are not
   * set by the monitor
   */
  if (info->change_mask & SPA_DEVICE_CHANGE_MASK_PROPS)
    wp_properties_update_from_dict (self->properties, info->props);
}

static void
spa_device_event_event (void *data, const struct spa_event *event)
{
  WpSpaDevice *self = WP_SPA_DEVICE (data);
  g_autoptr (WpSpaPod) pod =
      wp_spa_pod_new_wrap_const ((const struct spa_pod *) event);
  guint32 id = -1;
  const gchar *type = NULL;
  g_autoptr (WpSpaPod) props = NULL;
  g_autoptr (GObject) child = NULL;

  wp_trace_boxed (WP_TYPE_SPA_POD, pod, "device event");

  if (wp_spa_pod_get_object (pod, &type,
          "Object", "i", &id,
          "Props", "?P", &props,
          NULL))
    child = wp_spa_device_get_managed_object (self, id);

  if (child && !g_strcmp0 (type, "ObjectConfig") &&
      WP_IS_PIPEWIRE_OBJECT (child) && props) {
    wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (child), "Props", 0,
        g_steal_pointer (&props));
  }
}

static void
spa_device_event_object_info (void *data, uint32_t id,
    const struct spa_device_object_info *info)
{
  WpSpaDevice *self = WP_SPA_DEVICE (data);

  if (info) {
    const gchar *type;
    g_autoptr (WpProperties) props = NULL;

    type = spa_debug_type_short_name (info->type);
    props = wp_properties_new_wrap_dict (info->props);

    g_signal_emit (self, spa_device_signals[SIGNAL_CREATE_OBJECT], 0,
        id, type, info->factory_name, props);
  }
  else {
    g_signal_emit (self, spa_device_signals[SIGNAL_OBJECT_REMOVED], 0, id);
    wp_spa_device_store_managed_object (self, id, NULL);
  }
}

static const struct spa_device_events spa_device_events = {
  SPA_VERSION_DEVICE_EVENTS,
  .info = spa_device_event_info,
  .event = spa_device_event_event,
  .object_info = spa_device_event_object_info,
};

static WpObjectFeatures
wp_spa_device_get_supported_features (WpObject * object)
{
  return WP_PROXY_FEATURE_BOUND | WP_SPA_DEVICE_FEATURE_ENABLED;
}

enum {
  STEP_EXPORT = WP_TRANSITION_STEP_CUSTOM_START,
  STEP_ADD_DEVICE_LISTENER,
};

static guint
wp_spa_device_activate_get_next_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  if (missing & WP_PROXY_FEATURE_BOUND)
    return STEP_EXPORT;
  else if (missing & WP_SPA_DEVICE_FEATURE_ENABLED)
    return STEP_ADD_DEVICE_LISTENER;
  else
    return WP_TRANSITION_STEP_NONE;
}

static void
wp_spa_device_activate_execute_step (WpObject * object,
      WpFeatureActivationTransition * transition, guint step,
      WpObjectFeatures missing)
{
  WpSpaDevice *self = WP_SPA_DEVICE (object);

  switch (step) {
  case STEP_EXPORT: {
    g_autoptr (WpCore) core = wp_object_get_core (object);
    struct pw_core *pw_core = wp_core_get_pw_core (core);
    g_return_if_fail (pw_core);

    wp_proxy_set_pw_proxy (WP_PROXY (self),
        pw_core_export (pw_core, SPA_TYPE_INTERFACE_Device,
            wp_properties_peek_dict (self->properties),
            self->device, 0));
    break;
  }
  case STEP_ADD_DEVICE_LISTENER: {
    gint res = spa_device_add_listener (self->device, &self->listener,
        &spa_device_events, self);
    if (res < 0)
      wp_transition_return_error (WP_TRANSITION (transition),
          g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
              "failed to activate device: %s", spa_strerror (res)));
    else
      wp_object_update_features (object, WP_SPA_DEVICE_FEATURE_ENABLED, 0);
    break;
  }
  case WP_TRANSITION_STEP_ERROR:
    break;
  default:
    g_assert_not_reached ();
  }
}

static void
wp_spa_device_deactivate (WpObject * object, WpObjectFeatures features)
{
  WP_OBJECT_CLASS (wp_spa_device_parent_class)->deactivate (object, features);

  if (features & WP_SPA_DEVICE_FEATURE_ENABLED) {
    WpSpaDevice *self = WP_SPA_DEVICE (object);
    spa_hook_remove (&self->listener);
    g_ptr_array_set_size (self->managed_objs, 0);
    wp_object_update_features (object, 0, WP_SPA_DEVICE_FEATURE_ENABLED);
  }
}

/*!
 * \struct WpSpaDevice
 *
 * A WpSpaDevice allows running a `spa_device` object locally,
 * loading the implementation from a SPA factory. This is useful to run device
 * monitors inside the session manager and have control over creating the
 * actual nodes that the `spa_device` requests to create.
 *
 * To enable the spa device, call wp_object_activate() requesting
 * WP_SPA_DEVICE_FEATURE_ENABLED.
 *
 * For actual devices (not device monitors) it also possible and desirable
 * to export the device to PipeWire, which can be done by requesting
 * WP_PROXY_FEATURE_BOUND from wp_object_activate(). When exporting, the
 * export should be done before enabling the device, by requesting both
 * features at the same time.
 *
 * \gproperties
 *
 * \gproperty{properties, WpProperties *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   Properties of the spa device}
 *
 * \gproperty{spa-device-handle, gpointer, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The spa device handle}
 *
 * \gsignals
 *
 * \par create-object
 * \parblock
 * \code
 * void
 * create_object_callback (WpSpaDevice * self,
 *                         guint id,
 *                         gchar * type,
 *                         gchar * factory,
 *                         WpProperties * properties,
 *                         gpointer user_data)
 * \endcode
 *
 * This signal is emitted when the device is creating a managed object
 * The handler is expected to actually construct the object using the requested
 * SPA factory and with the given properties. The handler should then store the
 * object with wp_spa_device_store_managed_object. The WpSpaDevice will later
 * unref the reference stored by this function when the managed object is to be
 * destroyed.
 *
 * Parameters:
 * - `id` - the id of the managed object
 * - `type` - the SPA type that the managed object should have
 * - `factory` - the name of the SPA factory to use to construct the managed object
 * - `properties` - additional properties that the managed object should have
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 *
 * \par object-removed
 * \parblock
 * \code
 * void
 * object_removed_callback (WpSpaDevice * self,
 *                          guint id,
 *                          gpointer user_data)
 * \endcode
 *
 * This signal is emitted when the device has deleted a managed object.
 * The handler may optionally release additional resources associated with this
 * object.
 *
 * It is not necessary to call wp_spa_device_store_managed_object() to remove
 * the managed object, as this is done internally after this signal is fired.
 *
 * Parameters:
 * - `id` - the id of the managed object
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 */
static void
wp_spa_device_class_init (WpSpaDeviceClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;

  object_class->constructed = wp_spa_device_constructed;
  object_class->finalize = wp_spa_device_finalize;
  object_class->set_property = wp_spa_device_set_property;
  object_class->get_property = wp_spa_device_get_property;

  wpobject_class->get_supported_features = wp_spa_device_get_supported_features;
  wpobject_class->activate_get_next_step = wp_spa_device_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_spa_device_activate_execute_step;
  wpobject_class->deactivate = wp_spa_device_deactivate;

  g_object_class_install_property (object_class, PROP_SPA_DEVICE_HANDLE,
      g_param_spec_pointer ("spa-device-handle", "spa-device-handle",
          "The spa device handle",
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PROPERTIES,
      g_param_spec_boxed ("properties", "properties",
          "Properties of the device", WP_TYPE_PROPERTIES,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  spa_device_signals[SIGNAL_CREATE_OBJECT] = g_signal_new (
      "create-object", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      0, NULL, NULL, NULL, G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_STRING,
      G_TYPE_STRING, WP_TYPE_PROPERTIES);

  spa_device_signals[SIGNAL_OBJECT_REMOVED] = g_signal_new (
      "object-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
}

/*!
 * \brief Constructs an SPA Device object from an existing device handle.
 *
 * \ingroup wpspadevice
 * \param core the wireplumber core
 * \param spa_device_handle the spa device handle
 * \param properties (nullable) (transfer full): additional properties of the device
 * \returns (transfer full): A new WpSpaDevice
 */
WpSpaDevice *
wp_spa_device_new_wrap (WpCore * core, gpointer spa_device_handle,
    WpProperties * properties)
{
  g_autoptr (WpProperties) props = properties;
  return g_object_new (WP_TYPE_SPA_DEVICE,
      "core", core,
      "spa-device-handle", spa_device_handle,
      "properties", props,
      NULL);
}

/*!
 * \brief Constructs a `SPA_TYPE_INTERFACE_Device` by loading the given SPA
 * \a factory_name.
 *
 * To export this device to the PipeWire server, you need to call
 * wp_object_activate() requesting WP_PROXY_FEATURE_BOUND and
 * wait for the operation to complete.
 *
 * \ingroup wpspadevice
 * \param core the wireplumber core
 * \param factory_name the name of the SPA factory
 * \param properties (nullable) (transfer full): properties to be passed to device
 *    constructor
 * \returns (nullable) (transfer full): A new WpSpaDevice wrapping the
 *   device that was constructed by the factory, or NULL if the factory
 *   does not exist or was unable to construct the device
 */
WpSpaDevice *
wp_spa_device_new_from_spa_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties)
{
  g_autoptr (WpProperties) props = properties;
  struct pw_context *pw_context = wp_core_get_pw_context (core);
  struct spa_handle *handle = NULL;

  g_return_val_if_fail (pw_context != NULL, NULL);

  /* Load the monitor handle */
  handle = pw_context_load_spa_handle (pw_context, factory_name,
      props ? wp_properties_peek_dict (props) : NULL);
  if (!handle) {
    wp_message ("SPA handle '%s' could not be loaded; is it installed?",
        factory_name);
    return NULL;
  }

  return wp_spa_device_new_wrap (core, handle, g_steal_pointer (&props));
}

/*!
 * \brief Gets the properties of this device.
 *
 * \ingroup wpspadevice
 * \param self the spa device
 * \returns (transfer full): the device properties
 */
WpProperties *
wp_spa_device_get_properties (WpSpaDevice * self)
{
  g_return_val_if_fail (WP_IS_SPA_DEVICE (self), NULL);
  return wp_properties_ref (self->properties);
}

/*!
 * \brief Iterates through all the objects managed by this device.
 *
 * \ingroup wpspadevice
 * \param self the spa device
 * \returns (transfer full): a WpIterator that iterates over all the objects
 *   managed by this device
 * \since 0.4.11
 */
WpIterator *
wp_spa_device_new_managed_object_iterator (WpSpaDevice * self)
{
  g_return_val_if_fail (WP_IS_SPA_DEVICE (self), NULL);
  return wp_iterator_new_ptr_array (g_ptr_array_ref (self->managed_objs),
      G_TYPE_OBJECT);
}

/*!
 * \brief Gets one of the objects managed by this device.
 *
 * \ingroup wpspadevice
 * \param self the spa device
 * \param id the (device-internal) id of the object to get
 * \returns (transfer full): the managed object associated with \a id
 */
GObject *
wp_spa_device_get_managed_object (WpSpaDevice * self, guint id)
{
  g_return_val_if_fail (WP_IS_SPA_DEVICE (self), NULL);

  GObject *ret = (id < self->managed_objs->len) ?
      g_ptr_array_index (self->managed_objs, id) : NULL;
  return ret ? g_object_ref (ret) : ret;
}

/*!
 * \brief Stores or removes a managed object into/from a device.
 *
 * \ingroup wpspadevice
 * \param self the spa device
 * \param id the (device-internal) id of the object
 * \param object (transfer full) (nullable): the object to store or NULL to remove
 *   the managed object associated with \a id
 */
void
wp_spa_device_store_managed_object (WpSpaDevice * self, guint id,
    GObject * object)
{
  g_return_if_fail (WP_IS_SPA_DEVICE (self));

  if (id >= self->managed_objs->len)
    g_ptr_array_set_size (self->managed_objs, id + 1);

  /* replace the item at @em id; g_ptr_array_insert is tempting to use here
     instead, but it's wrong because it will not remove the previous item */
  gpointer *ptr = &g_ptr_array_index (self->managed_objs, id);
  if (*ptr)
    g_object_unref (*ptr);
  *ptr = object;
}
07070100000062000081A4000000000000000000000001656CC35F00000687000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/device.h    /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_DEVICE_H__
#define __WIREPLUMBER_DEVICE_H__

#include "global-proxy.h"

G_BEGIN_DECLS

/* WpDevice */

/*!
 * \brief The WpDevice GType
 * \ingroup wpdevice
 */
#define WP_TYPE_DEVICE (wp_device_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpDevice, wp_device, WP, DEVICE, WpGlobalProxy)

WP_API
WpDevice * wp_device_new_from_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties);

/* WpSpaDevice */

/*!
 * \brief Flags to be used as WpObjectFeatures for WpSpaDevice
 * \ingroup wpspadevice
 */
typedef enum { /*< flags >*/
  /*! enables a spa device */
  WP_SPA_DEVICE_FEATURE_ENABLED = (WP_PROXY_FEATURE_CUSTOM_START << 0),
} WpSpaDeviceFeatures;

/*!
 * \brief The WpSpaDevice GType
 * \ingroup wpspadevice
 */
#define WP_TYPE_SPA_DEVICE (wp_spa_device_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpSpaDevice, wp_spa_device, WP, SPA_DEVICE, WpProxy)

WP_API
WpSpaDevice * wp_spa_device_new_wrap (WpCore * core,
    gpointer spa_device_handle, WpProperties * properties);

WP_API
WpSpaDevice * wp_spa_device_new_from_spa_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties);

WP_API
WpProperties * wp_spa_device_get_properties (WpSpaDevice * self);

WP_API
WpIterator * wp_spa_device_new_managed_object_iterator (WpSpaDevice * self);

WP_API
GObject * wp_spa_device_get_managed_object (WpSpaDevice * self, guint id);

WP_API
void wp_spa_device_store_managed_object (WpSpaDevice * self, guint id,
    GObject * object);

G_END_DECLS

#endif
 07070100000063000081A4000000000000000000000001656CC35F000054C2000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/endpoint.c  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-endpoint"

#include "endpoint.h"
#include "node.h"
#include "object-manager.h"
#include "error.h"
#include "log.h"
#include "wpenums.h"
#include "spa-type.h"
#include "si-factory.h"
#include "private/pipewire-object-mixin.h"

#include <pipewire/extensions/session-manager.h>
#include <pipewire/extensions/session-manager/introspect-funcs.h>
#include <spa/utils/result.h>

/*! \defgroup wpendpoint WpEndpoint */
/*!
 * \struct WpEndpoint
 *
 * The WpEndpoint class allows accessing the properties and methods of a
 * PipeWire endpoint object (`struct pw_endpoint` from the session-manager extension).
 *
 * A WpEndpoint is constructed internally when a new endpoint appears on the
 * PipeWire registry and it is made available through the WpObjectManager API.
 *
 * \gproperties
 *
 * \gproperty{name, gchar *, G_PARAM_READABLE, The name of the endpoint}
 *
 * \gproperty{media-class, gchar *, G_PARAM_READABLE,
 *   The media class of the endpoint (ex. "Audio/Sink")}
 *
 * \gproperty{direction, WpDirection, G_PARAM_READABLE,
 *   The direction of the endpoint}
 */

enum {
  PROP_NAME = WP_PW_OBJECT_MIXIN_PROP_CUSTOM_START,
  PROP_MEDIA_CLASS,
  PROP_DIRECTION,
};

typedef struct _WpEndpointPrivate WpEndpointPrivate;
struct _WpEndpointPrivate
{
  gint place_holder;
};

static void wp_endpoint_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface);

G_DEFINE_TYPE_WITH_CODE (WpEndpoint, wp_endpoint, WP_TYPE_GLOBAL_PROXY,
    G_ADD_PRIVATE (WpEndpoint)
    G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT,
        wp_pw_object_mixin_object_interface_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
        wp_endpoint_pw_object_mixin_priv_interface_init))

static void
wp_endpoint_init (WpEndpoint * self)
{
}

static void
wp_endpoint_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object);

  switch (property_id) {
  case PROP_NAME:
    g_value_set_string (value, d->info ?
        ((struct pw_endpoint_info *) d->info)->name : NULL);
    break;
  case PROP_MEDIA_CLASS:
    g_value_set_string (value, d->info ?
        ((struct pw_endpoint_info *) d->info)->media_class : NULL);
    break;
  case PROP_DIRECTION:
    g_value_set_enum (value, d->info ?
        ((struct pw_endpoint_info *) d->info)->direction : 0);
    break;
  default:
    wp_pw_object_mixin_get_property (object, property_id, value, pspec);
    break;
  }
}

static WpObjectFeatures
wp_endpoint_get_supported_features (WpObject * object)
{
  return wp_pw_object_mixin_get_supported_features(object);
}

static void
wp_endpoint_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case WP_PW_OBJECT_MIXIN_STEP_BIND:
  case WP_TRANSITION_STEP_ERROR:
    /* base class can handle BIND and ERROR */
    WP_OBJECT_CLASS (wp_endpoint_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO:
    /* just wait, info will be emitted anyway after binding */
    break;
  case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS:
    wp_pw_object_mixin_cache_params (object, missing);
    break;
  default:
    g_assert_not_reached ();
  }
}

static void
wp_endpoint_deactivate (WpObject * object, WpObjectFeatures features)
{
  wp_pw_object_mixin_deactivate (object, features);
  WP_OBJECT_CLASS (wp_endpoint_parent_class)->deactivate (object, features);
}

static const struct pw_endpoint_events endpoint_events = {
  PW_VERSION_ENDPOINT_EVENTS,
  .info = (HandleEventInfoFunc(endpoint)) wp_pw_object_mixin_handle_event_info,
  .param = wp_pw_object_mixin_handle_event_param,
};

static void
wp_endpoint_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy,
      endpoint, &endpoint_events);
}

static void
wp_endpoint_pw_proxy_destroyed (WpProxy * proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy);

  WP_PROXY_CLASS (wp_endpoint_parent_class)->pw_proxy_destroyed (proxy);
}

static void
wp_endpoint_class_init (WpEndpointClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->get_property = wp_endpoint_get_property;

  wpobject_class->get_supported_features = wp_endpoint_get_supported_features;
  wpobject_class->activate_get_next_step =
      wp_pw_object_mixin_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_endpoint_activate_execute_step;
  wpobject_class->deactivate = wp_endpoint_deactivate;

  proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Endpoint;
  proxy_class->pw_iface_version = PW_VERSION_ENDPOINT;
  proxy_class->pw_proxy_created = wp_endpoint_pw_proxy_created;
  proxy_class->pw_proxy_destroyed = wp_endpoint_pw_proxy_destroyed;

  wp_pw_object_mixin_class_override_properties (object_class);

  g_object_class_install_property (object_class, PROP_NAME,
      g_param_spec_string ("name", "name", "name", NULL,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_MEDIA_CLASS,
      g_param_spec_string ("media-class", "media-class", "media-class", NULL,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_DIRECTION,
      g_param_spec_enum ("direction", "direction", "direction",
          WP_TYPE_DIRECTION, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

static gint
wp_endpoint_enum_params (gpointer instance, guint32 id,
    guint32 start, guint32 num, WpSpaPod *filter)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  return pw_endpoint_enum_params (d->iface, 0, id, start, num,
      filter ? wp_spa_pod_get_spa_pod (filter) : NULL);
}

static gint
wp_endpoint_set_param (gpointer instance, guint32 id, guint32 flags,
    WpSpaPod * param)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  g_autoptr (WpSpaPod) p = param;
  return pw_endpoint_set_param (d->iface, id, flags,
      wp_spa_pod_get_spa_pod (p));
}

static void
wp_endpoint_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface)
{
  wp_pw_object_mixin_priv_interface_info_init (iface, endpoint, ENDPOINT);
  iface->enum_params = wp_endpoint_enum_params;
  iface->set_param = wp_endpoint_set_param;
}

/*!
 * \brief Gets the name of the endpoint
 * \remarks Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 * \ingroup wpendpoint
 * \param self the endpoint
 * \returns the name of the endpoint
 */
const gchar *
wp_endpoint_get_name (WpEndpoint * self)
{
  g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_PIPEWIRE_OBJECT_FEATURE_INFO, NULL);

  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);
  return ((struct pw_endpoint_info *) d->info)->name;
}

/*!
 * \brief Gets the media class of the endpoint (ex. "Audio/Sink")
 * \remarks Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 * \ingroup wpendpoint
 * \param self the endpoint
 * \returns the media class of the endpoint
 */
const gchar *
wp_endpoint_get_media_class (WpEndpoint * self)
{
  g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_PIPEWIRE_OBJECT_FEATURE_INFO, NULL);

  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);
  return ((struct pw_endpoint_info *) d->info)->media_class;
}

/*!
 * \brief Gets the direction of the endpoint
 * \remarks Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 * \ingroup wpendpoint
 * \param self the endpoint
 * \returns the direction of this endpoint
 */
WpDirection
wp_endpoint_get_direction (WpEndpoint * self)
{
  g_return_val_if_fail (WP_IS_ENDPOINT (self), 0);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0);

  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);
  return (WpDirection) ((struct pw_endpoint_info *) d->info)->direction;
}

/* WpImplEndpoint */

enum {
  IMPL_PROP_0,
  IMPL_PROP_ITEM,
};

struct _WpImplEndpoint
{
  WpEndpoint parent;

  struct spa_interface iface;
  struct pw_endpoint_info info;
  WpProperties *immutable_props;

  WpSiEndpoint *item;
};

static void wp_endpoint_impl_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface);

G_DEFINE_TYPE_WITH_CODE (WpImplEndpoint, wp_impl_endpoint, WP_TYPE_ENDPOINT,
    G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
        wp_endpoint_impl_pw_object_mixin_priv_interface_init))

static struct spa_param_info impl_param_info[] = {
  SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE),
  SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ)
};

static int
impl_create_link (void *object, const struct spa_dict *props)
{
  /* not implemented */
  return -EINVAL;
}

static const struct pw_endpoint_methods impl_endpoint = {
  PW_VERSION_ENDPOINT_METHODS,
  .add_listener =
      (ImplAddListenerFunc(endpoint)) wp_pw_object_mixin_impl_add_listener,
  .subscribe_params = wp_pw_object_mixin_impl_subscribe_params,
  .enum_params = wp_pw_object_mixin_impl_enum_params,
  .set_param = wp_pw_object_mixin_impl_set_param,
  .create_link = impl_create_link,
};

static void
wp_impl_endpoint_init (WpImplEndpoint * self)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);

  self->iface = SPA_INTERFACE_INIT (
      PW_TYPE_INTERFACE_Endpoint,
      PW_VERSION_ENDPOINT,
      &impl_endpoint, self);

  d->info = &self->info;
  d->iface = &self->iface;
}

static void
populate_properties (WpImplEndpoint * self)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);

  g_clear_pointer (&d->properties, wp_properties_unref);
  d->properties = wp_si_endpoint_get_properties (self->item);
  if (!d->properties)
    d->properties = wp_properties_new_empty ();
  d->properties = wp_properties_ensure_unique_owner (d->properties);
  wp_properties_update (d->properties, self->immutable_props);

  self->info.props = (struct spa_dict *) wp_properties_peek_dict (d->properties);
}

static void
on_si_endpoint_properties_changed (WpSiEndpoint * item, WpImplEndpoint * self)
{
  populate_properties (self);
  wp_pw_object_mixin_notify_info (self, PW_ENDPOINT_CHANGE_MASK_PROPS);
}

static void
on_node_params_changed (WpNode * node, const gchar *param_name,
    WpImplEndpoint * self)
{
  if (!g_strcmp0 (param_name, "PropInfo") || !g_strcmp0 (param_name, "Props")) {
    guint32 param_id = wp_spa_id_value_number (
        wp_spa_id_value_from_short_name ("Spa:Enum:ParamId", param_name));
    wp_pw_object_mixin_notify_params_changed (self, param_id);
  }
}

static void
wp_impl_endpoint_constructed (GObject * object)
{
  WpImplEndpoint *self = WP_IMPL_ENDPOINT (object);
  g_autoptr (GVariant) info = NULL;
  g_autoptr (GVariantIter) immutable_props = NULL;
  g_autoptr (WpObject) node = NULL;
  const gchar *key, *value;
  guchar direction;

  self->info.version = PW_VERSION_ENDPOINT_INFO;

  info = wp_si_endpoint_get_registration_info (self->item);
  g_variant_get (info, "(ssya{ss})", &self->info.name,
      &self->info.media_class, &direction, &immutable_props);

  self->info.direction = (enum pw_direction) direction;

  /* associate with the session (no session anymore, use -1) */
  self->info.session_id = SPA_ID_INVALID;

  /* construct export properties (these will come back through
    the registry and appear in wp_proxy_get_global_properties) */
  self->immutable_props = wp_properties_new (
      PW_KEY_ENDPOINT_NAME, self->info.name,
      PW_KEY_MEDIA_CLASS, self->info.media_class,
      NULL);
  wp_properties_setf (self->immutable_props, PW_KEY_SESSION_ID,
      "%d", self->info.session_id);

  /* populate immutable (global) properties */
  while (g_variant_iter_next (immutable_props, "{&s&s}", &key, &value))
    wp_properties_set (self->immutable_props, key, value);

  /* populate standard properties */
  populate_properties (self);

  /* subscribe to changes */
  g_signal_connect_object (self->item, "endpoint-properties-changed",
      G_CALLBACK (on_si_endpoint_properties_changed), self, 0);

  /* if the item has a node, proxy its ParamProps */
  node = wp_session_item_get_associated_proxy (
      WP_SESSION_ITEM (self->item), WP_TYPE_NODE);
  if (node && (wp_object_get_active_features (node) &
                  WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS)) {
    self->info.params = impl_param_info;
    self->info.n_params = G_N_ELEMENTS (impl_param_info);

    g_signal_connect_object (node, "params-changed",
        G_CALLBACK (on_node_params_changed), self, 0);

    wp_object_update_features (WP_OBJECT (self),
        WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, 0);
  } else {
    self->info.params = NULL;
    self->info.n_params = 0;
  }

  wp_object_update_features (WP_OBJECT (self),
      WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0);

  G_OBJECT_CLASS (wp_impl_endpoint_parent_class)->constructed (object);
}

static void
wp_impl_endpoint_dispose (GObject * object)
{
  WpImplEndpoint *self = WP_IMPL_ENDPOINT (object);

  g_clear_pointer (&self->immutable_props, wp_properties_unref);
  g_clear_pointer (&self->info.name, g_free);
  g_clear_pointer (&self->info.media_class, g_free);

  wp_object_update_features (WP_OBJECT (self), 0,
      WP_PIPEWIRE_OBJECT_FEATURE_INFO |
      WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS);

  G_OBJECT_CLASS (wp_impl_endpoint_parent_class)->dispose (object);
}

static void
wp_impl_endpoint_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpImplEndpoint *self = WP_IMPL_ENDPOINT (object);

  switch (property_id) {
  case IMPL_PROP_ITEM:
    self->item = g_value_get_object (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_impl_endpoint_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpImplEndpoint *self = WP_IMPL_ENDPOINT (object);

  switch (property_id) {
  case IMPL_PROP_ITEM:
    g_value_set_object (value, self->item);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

enum {
  STEP_ACTIVATE_NODE = WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START + 1,
};

static guint
wp_impl_endpoint_activate_get_next_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  WpImplEndpoint *self = WP_IMPL_ENDPOINT (object);

  /* before anything else, if the item has a node,
     cache its props so that enum_params works */
  if (missing & WP_PIPEWIRE_OBJECT_FEATURES_ALL) {
    g_autoptr (WpObject) node = wp_session_item_get_associated_proxy (
        WP_SESSION_ITEM (self->item), WP_TYPE_NODE);

    if (node && (wp_object_get_supported_features (node) &
                    WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS) &&
               !(wp_object_get_active_features (node) &
                    WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS))
      return STEP_ACTIVATE_NODE;
  }

  return WP_OBJECT_CLASS (wp_impl_endpoint_parent_class)->
      activate_get_next_step (object, transition, step, missing);
}

static void
wp_impl_endpoint_node_activated (WpObject * node,
    GAsyncResult * res, WpTransition * transition)
{
  WpImplEndpoint *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (node, res, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  self->info.params = impl_param_info;
  self->info.n_params = G_N_ELEMENTS (impl_param_info);

  g_signal_connect_object (node, "params-changed",
      G_CALLBACK (on_node_params_changed), self, 0);

  wp_object_update_features (WP_OBJECT (self),
      WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, 0);
  wp_pw_object_mixin_notify_info (self, PW_ENDPOINT_CHANGE_MASK_PARAMS);
}

static void
wp_impl_endpoint_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  WpImplEndpoint *self = WP_IMPL_ENDPOINT (object);

  switch (step) {
  case STEP_ACTIVATE_NODE: {
    g_autoptr (WpObject) node = wp_session_item_get_associated_proxy (
        WP_SESSION_ITEM (self->item), WP_TYPE_NODE);

    wp_object_activate (node,
        WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS,
        NULL, (GAsyncReadyCallback) wp_impl_endpoint_node_activated,
        transition);
    break;
  }
  case WP_PW_OBJECT_MIXIN_STEP_BIND: {
    g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
    struct pw_core *pw_core = wp_core_get_pw_core (core);

    /* no pw_core -> we are not connected */
    if (!pw_core) {
      wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
              WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
              "The WirePlumber core is not connected; "
              "object cannot be exported to PipeWire"));
      return;
    }

    /* bind */
    wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_export (pw_core,
            PW_TYPE_INTERFACE_Endpoint,
            wp_properties_peek_dict (self->immutable_props),
            &self->iface, 0));
    break;
  }
  default:
    WP_OBJECT_CLASS (wp_impl_endpoint_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  }
}

/*!
 * \struct WpImplEndpoint
 *
 * \gproperties
 *
 * \gproperty{item, WpSiEndpoint *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The session item that implements this endpoint}
 */
static void
wp_impl_endpoint_class_init (WpImplEndpointClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->constructed = wp_impl_endpoint_constructed;
  object_class->dispose = wp_impl_endpoint_dispose;
  object_class->set_property = wp_impl_endpoint_set_property;
  object_class->get_property = wp_impl_endpoint_get_property;

  wpobject_class->activate_get_next_step =
      wp_impl_endpoint_activate_get_next_step;
  wpobject_class->activate_execute_step =
      wp_impl_endpoint_activate_execute_step;

  proxy_class->pw_proxy_created = NULL;
  proxy_class->pw_proxy_destroyed = NULL;

  g_object_class_install_property (object_class, IMPL_PROP_ITEM,
      g_param_spec_object ("item", "item", "item", WP_TYPE_SI_ENDPOINT,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}

static GPtrArray *
wp_impl_endpoint_enum_params_sync (gpointer instance, guint32 id,
    guint32 start, guint32 num, WpSpaPod *filter)
{
  WpImplEndpoint *self = WP_IMPL_ENDPOINT (instance);
  g_autoptr (WpPipewireObject) node = wp_session_item_get_associated_proxy (
        WP_SESSION_ITEM (self->item), WP_TYPE_NODE);

  if (!node) {
    wp_warning_object (self, "associated node is no longer available");
    return NULL;
  }

  /* bypass a few things, knowing that the node
     caches params in the mixin param store */
  WpPwObjectMixinData *data = wp_pw_object_mixin_get_data (node);
  GPtrArray *params = wp_pw_object_mixin_get_stored_params (data, id);
  /* TODO filter */

  return params;
}

static gint
wp_impl_endpoint_set_param (gpointer instance, guint32 id, guint32 flags,
    WpSpaPod * param)
{
  WpImplEndpoint *self = WP_IMPL_ENDPOINT (instance);
  g_autoptr (WpPipewireObject) node = wp_session_item_get_associated_proxy (
        WP_SESSION_ITEM (self->item), WP_TYPE_NODE);

  if (!node) {
    wp_warning_object (self, "associated node is no longer available");
    return -EPIPE;
  }

  WpSpaIdValue idval = wp_spa_id_value_from_number ("Spa:Enum:ParamId", id);
  if (!idval) {
    wp_critical_object (self, "invalid param id: %u", id);
    return -EINVAL;
  }

  return wp_pipewire_object_set_param (node, wp_spa_id_value_short_name (idval),
      flags, param) ? 0 : -EIO;
}

#define pw_endpoint_emit(hooks,method,version,...) \
    spa_hook_list_call_simple(hooks, struct pw_endpoint_events, \
        method, version, ##__VA_ARGS__)

static void
wp_impl_endpoint_emit_info (struct spa_hook_list * hooks, gconstpointer info)
{
  pw_endpoint_emit (hooks, info, 0, info);
}

static void
wp_impl_endpoint_emit_param (struct spa_hook_list * hooks, int seq,
      guint32 id, guint32 index, guint32 next, const struct spa_pod *param)
{
  pw_endpoint_emit (hooks, param, 0, seq, id, index, next, param);
}

static void
wp_endpoint_impl_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface)
{
  iface->flags |= WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE;
  iface->enum_params_sync = wp_impl_endpoint_enum_params_sync;
  iface->set_param = wp_impl_endpoint_set_param;
  iface->emit_info = wp_impl_endpoint_emit_info;
  iface->emit_param = wp_impl_endpoint_emit_param;
}

/*!
 * \brief Creates a new endpoint implementation
 *
 * \ingroup wpendpoint
 * \param core the core
 * \param item the session item that implements the endpoint
 * \returns (transfer full): a new WpImplEndpoint
 */
WpImplEndpoint *
wp_impl_endpoint_new (WpCore * core, WpSiEndpoint * item)
{
  g_return_val_if_fail (WP_IS_CORE (core), NULL);

  return g_object_new (WP_TYPE_IMPL_ENDPOINT,
      "core", core,
      "item", item,
      NULL);
}
  07070100000064000081A4000000000000000000000001656CC35F000004C6000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/endpoint.h  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_ENDPOINT_H__
#define __WIREPLUMBER_ENDPOINT_H__

#include "global-proxy.h"
#include "port.h"
#include "iterator.h"
#include "object-interest.h"
#include "si-interfaces.h"

G_BEGIN_DECLS

/*!
 * \brief The WpEndpoint GType
 * \ingroup wpendpoint
 */
#define WP_TYPE_ENDPOINT (wp_endpoint_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpEndpoint, wp_endpoint, WP, ENDPOINT, WpGlobalProxy)

struct _WpEndpointClass
{
  WpGlobalProxyClass parent_class;

  /*< private >*/
  WP_PADDING(4)
};

WP_API
const gchar * wp_endpoint_get_name (WpEndpoint * self);

WP_API
const gchar * wp_endpoint_get_media_class (WpEndpoint * self);

WP_API
WpDirection wp_endpoint_get_direction (WpEndpoint * self);

/*!
 * \brief The WpImplEndpoint GType
 * \ingroup wpendpoint
 */
#define WP_TYPE_IMPL_ENDPOINT (wp_impl_endpoint_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpImplEndpoint, wp_impl_endpoint,
                      WP, IMPL_ENDPOINT, WpEndpoint)

WP_API
WpImplEndpoint * wp_impl_endpoint_new (WpCore * core, WpSiEndpoint * item);

G_END_DECLS

#endif
  07070100000065000081A4000000000000000000000001656CC35F00000110000000000000000000000000000000000000002200000000wireplumber-0.4.17/lib/wp/error.c /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "error.h"

/*! \defgroup wperror Errors */

G_DEFINE_QUARK (wireplumber-library, wp_domain_library);
07070100000066000081A4000000000000000000000001656CC35F000003B2000000000000000000000000000000000000002200000000wireplumber-0.4.17/lib/wp/error.h /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WP_ERROR_H__
#define __WP_ERROR_H__

#include <glib.h>
#include "defs.h"

G_BEGIN_DECLS

/*!
 * \brief A GError domain for errors that occurred within the context of the
 * WirePlumber library.
 * \ingroup wperror
 */
#define WP_DOMAIN_LIBRARY (wp_domain_library_quark ())
WP_API
GQuark wp_domain_library_quark (void);

/*!
 * \brief Error codes that can appear in a GError when the error domain
 * is WP_DOMAIN_LIBRARY
 * \ingroup wperror
 */
typedef enum {
  /*! an invariant check failed; this most likely
   *  indicates a programming error */
  WP_LIBRARY_ERROR_INVARIANT,
  /*! an unexpected/invalid argument was given */
  WP_LIBRARY_ERROR_INVALID_ARGUMENT,
  /*! an operation failed */
  WP_LIBRARY_ERROR_OPERATION_FAILED,
} WpLibraryErrorEnum;

G_END_DECLS

#endif
  07070100000067000081A4000000000000000000000001656CC35F00000D46000000000000000000000000000000000000002400000000wireplumber-0.4.17/lib/wp/factory.c   /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-factory"

#include "factory.h"
#include "private/pipewire-object-mixin.h"

#include "log.h"

/*! \defgroup wpfactory WpFactory */
/*!
 * \struct WpFactory
 *
 * The WpFactory class allows accessing the properties and methods of
 * PipeWire Factory objects (`struct pw_factory`).
 *
 * A WpFactory is constructed internally by wireplumber, when the pipewire 
 * constructed factory objects are reported in by PipeWire registry 
 * and it is made available for wireplumber clients through the 
 * WpObjectManager API.
 *
 * \since 0.4.5
 */

struct _WpFactory
{
  WpGlobalProxy parent;
};

static void wp_factory_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface);


G_DEFINE_TYPE_WITH_CODE (WpFactory, wp_factory, WP_TYPE_GLOBAL_PROXY,
    G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT,
        wp_pw_object_mixin_object_interface_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
        wp_factory_pw_object_mixin_priv_interface_init))

static void wp_factory_init (WpFactory * self)
{
}

static void
wp_factory_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case WP_PW_OBJECT_MIXIN_STEP_BIND:
  case WP_TRANSITION_STEP_ERROR:
    /* base class can handle BIND and ERROR */
    WP_OBJECT_CLASS (wp_factory_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO:
    /* just wait, info will be emitted anyway after binding */
    break;
  default:
    g_assert_not_reached ();
  }
}

static const struct pw_factory_events factory_events = {
  PW_VERSION_FACTORY_EVENTS,
  .info = (HandleEventInfoFunc(factory)) wp_pw_object_mixin_handle_event_info,
};

static void
wp_factory_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy,
      factory, &factory_events);
}

static void
wp_factory_pw_proxy_destroyed (WpProxy * proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy);

  WP_PROXY_CLASS (wp_factory_parent_class)->pw_proxy_destroyed (proxy);
}

static void
wp_factory_class_init (WpFactoryClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->get_property = wp_pw_object_mixin_get_property;

  wpobject_class->get_supported_features =
      wp_pw_object_mixin_get_supported_features;
  wpobject_class->activate_get_next_step =
      wp_pw_object_mixin_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_factory_activate_execute_step;

  proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Factory;
  proxy_class->pw_iface_version = PW_VERSION_FACTORY;
  proxy_class->pw_proxy_created = wp_factory_pw_proxy_created;
  proxy_class->pw_proxy_destroyed = wp_factory_pw_proxy_destroyed;

  wp_pw_object_mixin_class_override_properties (object_class);
}

static void
wp_factory_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface)
{
  wp_pw_object_mixin_priv_interface_info_init_no_params (iface, factory, FACTORY);
}
  07070100000068000081A4000000000000000000000001656CC35F000001F0000000000000000000000000000000000000002400000000wireplumber-0.4.17/lib/wp/factory.h   /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author Ashok Sidipotu <ashok.sidipotuc@ollabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_FACTORY_H__
#define __WIREPLUMBER_FACTORY_H__

#include "global-proxy.h"

G_BEGIN_DECLS

struct pw_factory;

/*!
 * \brief The WpFactory GType
 * \ingroup wpfactory
 */
#define WP_TYPE_FACTORY (wp_factory_get_type ())

WP_API
G_DECLARE_FINAL_TYPE (WpFactory, wp_factory, WP, FACTORY, WpGlobalProxy)

G_END_DECLS

#endif
07070100000069000081A4000000000000000000000001656CC35F00003060000000000000000000000000000000000000002900000000wireplumber-0.4.17/lib/wp/global-proxy.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-global-proxy"

#include "global-proxy.h"
#include "private/registry.h"
#include "core.h"
#include "error.h"

/*! \defgroup wpglobalproxy WpGlobalProxy */
/*!
 * \struct WpGlobalProxy
 *
 * A proxy that represents a PipeWire global object, i.e. an
 * object that is made available through the PipeWire registry.
 *
 * \gproperties
 *
 * \gproperty{global, WpGlobal *, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY,
 *   Internal WpGlobal object}
 *
 * \gproperty{factory-name, gchar *, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY,
 *   The factory name}
 *
 * \gproperty{global-properties, WpProperties *,
 *   G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The pipewire global properties}
 *
 * \gproperty{permissions, guint, G_PARAM_READABLE,
 *   The pipewire global permissions}
 */

typedef struct _WpGlobalProxyPrivate WpGlobalProxyPrivate;
struct _WpGlobalProxyPrivate
{
  WpGlobal *global;
  gchar factory_name[96];
  WpProperties *properties;
};

enum {
  PROP_0,
  PROP_GLOBAL,
  PROP_FACTORY_NAME,
  PROP_GLOBAL_PROPERTIES,
  PROP_PERMISSIONS,
};

G_DEFINE_TYPE_WITH_PRIVATE (WpGlobalProxy, wp_global_proxy, WP_TYPE_PROXY)

static void
wp_global_proxy_init (WpGlobalProxy * self)
{
}

static void
wp_global_proxy_dispose (GObject * object)
{
  WpGlobalProxy *self = WP_GLOBAL_PROXY (object);
  WpGlobalProxyPrivate *priv =
      wp_global_proxy_get_instance_private (self);

  if (priv->global)
    wp_global_rm_flag (priv->global, WP_GLOBAL_FLAG_OWNED_BY_PROXY);

  G_OBJECT_CLASS (wp_global_proxy_parent_class)->dispose (object);
}

static void
wp_global_proxy_finalize (GObject * object)
{
  WpGlobalProxy *self = WP_GLOBAL_PROXY (object);
  WpGlobalProxyPrivate *priv =
      wp_global_proxy_get_instance_private (self);

  g_clear_pointer (&priv->properties, wp_properties_unref);
  g_clear_pointer (&priv->global, wp_global_unref);

  G_OBJECT_CLASS (wp_global_proxy_parent_class)->finalize (object);
}

static void
wp_global_proxy_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpGlobalProxy *self = WP_GLOBAL_PROXY (object);
  WpGlobalProxyPrivate *priv =
      wp_global_proxy_get_instance_private (self);

  switch (property_id) {
  case PROP_GLOBAL:
    priv->global = g_value_dup_boxed (value);
    break;
  case PROP_FACTORY_NAME:
    priv->factory_name[0] = '\0';
    strncpy (priv->factory_name, g_value_get_string (value),
        sizeof (priv->factory_name) - 1);
    break;
  case PROP_GLOBAL_PROPERTIES:
    priv->properties = g_value_dup_boxed (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_global_proxy_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpGlobalProxy *self = WP_GLOBAL_PROXY (object);

  switch (property_id) {
  case PROP_PERMISSIONS:
    g_value_set_uint (value, wp_global_proxy_get_permissions (self));
    break;
  case PROP_GLOBAL_PROPERTIES:
    g_value_take_boxed (value, wp_global_proxy_get_global_properties (self));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static WpObjectFeatures
wp_global_proxy_get_supported_features (WpObject * object)
{
  return WP_PROXY_FEATURE_BOUND;
}

enum {
  STEP_BIND = WP_TRANSITION_STEP_CUSTOM_START,
};

static guint
wp_global_proxy_activate_get_next_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  /* we only support BOUND, so this is the only
     feature that can be in @em missing */
  g_return_val_if_fail (missing == WP_PROXY_FEATURE_BOUND,
      WP_TRANSITION_STEP_ERROR);

  return STEP_BIND;
}

static void
wp_global_proxy_step_bind (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  WpProxyClass *proxy_klass = WP_PROXY_GET_CLASS (WP_PROXY (object));
  WpGlobalProxy *self = WP_GLOBAL_PROXY (object);
  WpGlobalProxyPrivate *priv = wp_global_proxy_get_instance_private (self);

  /* Create the pipewire object if global is NULL */
  if (priv->global == NULL && priv->factory_name[0] != '\0') {
    g_autoptr (WpCore) core = NULL;
    struct pw_core *pw_core = NULL;
    struct pw_proxy * p = NULL;

    core = wp_object_get_core (WP_OBJECT (self));
    if (G_UNLIKELY (!core)) {
        wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
            WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
            "The WirePlumber core is not valid; object cannot be created"));
      return;
    }

    pw_core = wp_core_get_pw_core (core);
    if (G_UNLIKELY (!pw_core)) {
        wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
            WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
            "The WirePlumber core is not connected; object cannot be created"));
      return;
    }

    p = pw_core_create_object (pw_core, priv->factory_name,
        proxy_klass->pw_iface_type, proxy_klass->pw_iface_version,
        priv->properties ?
            wp_properties_peek_dict (priv->properties) : NULL, 0);
    if (G_UNLIKELY (!p)) {
      wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
            WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
            "Failed to create object with given factory name and properties"));
      return;
    }

    wp_proxy_set_pw_proxy (WP_PROXY (self), p);
  }

  /* Bind */
  if (wp_proxy_get_pw_proxy (WP_PROXY (self)) == NULL) {
    if (!wp_global_proxy_bind (self)) {
      wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
          WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
              "global not specified or destroyed; cannot bind proxy"));
    }
  }
}


static void
wp_global_proxy_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case STEP_BIND:
    wp_global_proxy_step_bind (object, transition, step, missing);
    break;
  case WP_TRANSITION_STEP_ERROR:
    break;
  default:
    g_assert_not_reached ();
  }
}

static void
wp_global_proxy_bound (WpProxy * proxy, guint32 global_id)
{
  WpGlobalProxy *self = WP_GLOBAL_PROXY (proxy);
  WpGlobalProxyPrivate *priv =
      wp_global_proxy_get_instance_private (self);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));

  if (!priv->global) {
    wp_registry_prepare_new_global (wp_core_get_registry (core),
        global_id, PW_PERM_ALL, WP_GLOBAL_FLAG_OWNED_BY_PROXY,
        G_TYPE_FROM_INSTANCE (self), self,
        priv->properties ? wp_properties_peek_dict (priv->properties) : NULL,
        &priv->global);
  }
}

static void
wp_global_proxy_destroyed (WpProxy * proxy)
{
  WpGlobalProxy *self = WP_GLOBAL_PROXY (proxy);
  WpGlobalProxyPrivate *priv =
      wp_global_proxy_get_instance_private (self);

  g_clear_pointer (&priv->global, wp_global_unref);
}

static void
wp_global_proxy_class_init (WpGlobalProxyClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->finalize = wp_global_proxy_finalize;
  object_class->dispose = wp_global_proxy_dispose;
  object_class->set_property = wp_global_proxy_set_property;
  object_class->get_property = wp_global_proxy_get_property;

  wpobject_class->get_supported_features =
      wp_global_proxy_get_supported_features;
  wpobject_class->activate_get_next_step =
      wp_global_proxy_activate_get_next_step;
  wpobject_class->activate_execute_step =
      wp_global_proxy_activate_execute_step;

  proxy_class->bound = wp_global_proxy_bound;
  proxy_class->pw_proxy_destroyed = wp_global_proxy_destroyed;

  g_object_class_install_property (object_class, PROP_GLOBAL,
      g_param_spec_boxed ("global", "global", "Internal WpGlobal object",
          wp_global_get_type (),
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_FACTORY_NAME,
      g_param_spec_string ("factory-name", "factory-name",
          "The factory name", "",
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_GLOBAL_PROPERTIES,
      g_param_spec_boxed ("global-properties", "global-properties",
          "The pipewire global properties", WP_TYPE_PROPERTIES,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PERMISSIONS,
      g_param_spec_uint ("permissions", "permissions",
          "The pipewire global permissions", 0, G_MAXUINT, 0,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

/*!
 * \brief Requests the PipeWire server to destroy the object represented by
 * this proxy.
 *
 * If the server allows it, the object will be destroyed and the
 * WpProxy's `pw-proxy-destroyed` signal will be emitted. If the server does
 * not allow it, nothing will happen.
 *
 * This is mostly useful for destroying WpLink objects.
 *
 * \ingroup wpglobalproxy
 * \param self the pipewire global
 */
void
wp_global_proxy_request_destroy (WpGlobalProxy * self)
{
  g_return_if_fail (WP_IS_GLOBAL_PROXY (self));

  WpGlobalProxyPrivate *priv =
      wp_global_proxy_get_instance_private (self);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));

  if (priv->global && core) {
    WpRegistry *reg = wp_core_get_registry (core);
    if (reg->pw_registry)
      pw_registry_destroy (reg->pw_registry, priv->global->id);
  }
}

/*!
 * \brief Gets the permissions of a pipewire global
 * \ingroup wpglobalproxy
 * \param self the pipewire global
 * \returns the permissions that wireplumber has on this object
 */
guint32
wp_global_proxy_get_permissions (WpGlobalProxy * self)
{
  g_return_val_if_fail (WP_IS_GLOBAL_PROXY (self), 0);

  WpGlobalProxyPrivate *priv =
      wp_global_proxy_get_instance_private (self);

  return priv->global ? priv->global->permissions : PW_PERM_ALL;
}

/*!
 * \brief Gets the global properties of a pipewire global
 * \ingroup wpglobalproxy
 * \param self the pipewire global
 * \returns (transfer full): the global (immutable) properties of this
 *   pipewire object
 */
WpProperties *
wp_global_proxy_get_global_properties (WpGlobalProxy * self)
{
  g_return_val_if_fail (WP_IS_GLOBAL_PROXY (self), NULL);

  WpGlobalProxyPrivate *priv =
      wp_global_proxy_get_instance_private (self);

  if (priv->global && priv->global->properties)
    return wp_properties_ref (priv->global->properties);
  return NULL;
}

/*!
 * \brief Binds to the global and creates the underlying `pw_proxy`.
 *
 * This is mostly meant to be called internally. It will create the `pw_proxy`
 * and will activate the WP_PROXY_FEATURE_BOUND feature.
 *
 * This may only be called if there is no `pw_proxy` associated with this
 * object yet.
 *
 * \ingroup wpglobalproxy
 * \param self the pipewire global
 * \returns TRUE on success, FALSE if there is no global to bind to
 */
gboolean
wp_global_proxy_bind (WpGlobalProxy * self)
{
  WpGlobalProxyPrivate *priv;
  struct pw_proxy *p;

  g_return_val_if_fail (WP_IS_GLOBAL_PROXY (self), FALSE);
  g_return_val_if_fail (wp_proxy_get_pw_proxy (WP_PROXY (self)) == NULL, FALSE);

  priv = wp_global_proxy_get_instance_private (self);

  if (!priv->global || !priv->global->proxy)
    return FALSE;

  /*
   * We can bind only if the WpGlobal will unbind this proxy on removal, so
   * assert here that this is so. Why: pw_registry_bind can race with the global
   * id being replaced with a different object on server side, so we must rely
   * on the registry_global messages to know if the object was replaced.  If the
   * race occurs, and a wrong object is going to be bound here, what will then
   * happen is that WpGlobal will dispose this proxy and no problem arises.
   */
  g_return_val_if_fail (priv->global->proxy == self, FALSE);

  p = wp_global_bind (priv->global);
  if (!p)
    return FALSE;

  wp_proxy_set_pw_proxy (WP_PROXY (self), p);
  return TRUE;
}
0707010000006A000081A4000000000000000000000001656CC35F000003BC000000000000000000000000000000000000002900000000wireplumber-0.4.17/lib/wp/global-proxy.h  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_GLOBAL_PROXY_H__
#define __WIREPLUMBER_GLOBAL_PROXY_H__

#include "proxy.h"
#include "properties.h"

G_BEGIN_DECLS

/*!
 * \brief The WpGlobalProxy GType
 * \ingroup wpglobalproxy
 */
#define WP_TYPE_GLOBAL_PROXY (wp_global_proxy_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpGlobalProxy, wp_global_proxy,
                          WP, GLOBAL_PROXY, WpProxy)

struct _WpGlobalProxyClass
{
  WpProxyClass parent_class;

  /*< private >*/
  WP_PADDING(4)
};

WP_API
void wp_global_proxy_request_destroy (WpGlobalProxy * self);

WP_API
guint32 wp_global_proxy_get_permissions (WpGlobalProxy * self);

WP_API
WpProperties * wp_global_proxy_get_global_properties (
    WpGlobalProxy * self);

WP_API
gboolean wp_global_proxy_bind (WpGlobalProxy * self);

G_END_DECLS

#endif
0707010000006B000081A4000000000000000000000001656CC35F000020DE000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/iterator.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-iterator"

#include "iterator.h"
#include <spa/utils/defs.h>

/*! \defgroup wpiterator WpIterator */
/*!
 * \struct WpIterator
 * A generic iterator API
 */
struct _WpIterator
{
  const WpIteratorMethods *methods;
  gpointer user_data;
};

G_DEFINE_BOXED_TYPE (WpIterator, wp_iterator, wp_iterator_ref, wp_iterator_unref)

static gboolean
wp_iterator_default_fold (WpIterator *self, WpIteratorFoldFunc func,
    GValue *item, gpointer data)
{
  GValue next = G_VALUE_INIT;

  wp_iterator_reset (self);

  while (wp_iterator_next (self, &next)) {
    const gboolean res = func (&next, item, data);
    g_value_unset (&next);
    if (!res)
      return FALSE;
  }

  return TRUE;
}

struct foreach_fold_data {
  WpIteratorForeachFunc func;
  gpointer data;
};

static gboolean
foreach_fold_func (const GValue *item, GValue *ret, gpointer data)
{
  struct foreach_fold_data *d = data;
  d->func (item, d->data);
  return TRUE;
}

static gboolean
wp_iterator_default_foreach (WpIterator *self, WpIteratorForeachFunc func,
   gpointer data)
{
  struct foreach_fold_data d = {func, data};
  return wp_iterator_fold (self, foreach_fold_func, NULL, &d);
}

/*!
 * \brief Constructs an iterator that uses the provided \a methods to implement
 * its API.
 *
 * The WpIterator structure is internally allocated with \a user_size additional
 * space at the end. A pointer to this space can be retrieved with
 * wp_iterator_get_user_data() and is available for implementation-specific
 * storage.
 *
 * \ingroup wpiterator
 * \param methods method implementations for the new iterator
 * \param user_size size of the user_data structure to be allocated
 * \returns (transfer full): a new custom iterator
 */
WpIterator *
wp_iterator_new (const WpIteratorMethods *methods, size_t user_size)
{
  WpIterator *self = NULL;

  g_return_val_if_fail (methods, NULL);

  self = g_rc_box_alloc0 (sizeof (WpIterator) + user_size);
  self->methods = methods;
  if (user_size > 0)
    self->user_data = SPA_MEMBER (self, sizeof (WpIterator), void);

  return self;
}

/*!
 * \brief Gets the implementation-specific storage of an iterator
 * \note this only for use by implementations of WpIterator
 *
 * \ingroup wpiterator
 * \param self an iterator object
 * \returns a pointer to the implementation-specific storage area
 */
gpointer
wp_iterator_get_user_data (WpIterator *self)
{
  return self->user_data;
}

/*!
 * \brief Increases the reference count of an iterator
 * \ingroup wpiterator
 * \param self an iterator object
 * \returns (transfer full): \a self with an additional reference count on it
 */
WpIterator *
wp_iterator_ref (WpIterator *self)
{
  return (WpIterator *) g_rc_box_acquire ((gpointer) self);
}

static void
wp_iterator_free (WpIterator *self)
{
  if (self->methods->finalize)
    self->methods->finalize (self);
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 *
 * \ingroup wpiterator
 * \param self (transfer full): an iterator object
 */
void
wp_iterator_unref (WpIterator *self)
{
  g_rc_box_release_full (self, (GDestroyNotify) wp_iterator_free);
}

/*!
 * \brief Resets the iterator so we can iterate again from the beginning.
 *
 * \ingroup wpiterator
 * \param self the iterator
 */
void
wp_iterator_reset (WpIterator *self)
{
  g_return_if_fail (self);
  g_return_if_fail (self->methods->reset);

  self->methods->reset (self);
}

/*!
 * \brief Gets the next item of the iterator.
 *
 * \ingroup wpiterator
 * \param self the iterator
 * \param item (out): the next item of the iterator
 * \returns TRUE if next iterator was obtained, FALSE when the iterator has no
 * more items to iterate through.
 */

gboolean
wp_iterator_next (WpIterator *self, GValue *item)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (self->methods->next, FALSE);

  return self->methods->next (self, item);
}

/*!
 * \brief Fold a function over the items of the iterator.
 *
 * \ingroup wpiterator
 * \param self the iterator
 * \param func (scope call): the fold function
 * \param ret (inout): the accumulator data
 * \param data (closure): the user data
 * \returns TRUE if all the items were processed, FALSE otherwise.
 */
gboolean
wp_iterator_fold (WpIterator *self, WpIteratorFoldFunc func, GValue *ret,
    gpointer data)
{
  g_return_val_if_fail (self, FALSE);

  if (self->methods->fold)
    return self->methods->fold (self, func, ret, data);

  return wp_iterator_default_fold (self, func, ret, data);
}

/*!
 * \brief Iterates over all items of the iterator calling a function.
 *
 * \ingroup wpiterator
 * \param self the iterator
 * \param func (scope call): the foreach function
 * \param data (closure): the user data
 * \returns TRUE if all the items were processed, FALSE otherwise.
 */
gboolean
wp_iterator_foreach (WpIterator *self, WpIteratorForeachFunc func,
   gpointer data)
{
  g_return_val_if_fail (self, FALSE);

  if (self->methods->foreach)
    return self->methods->foreach (self, func, data);

  return wp_iterator_default_foreach (self, func, data);
}

struct ptr_array_iterator_data
{
  GPtrArray *array;
  GType item_type;
  guint index;
  void (*set_value) (GValue *, gpointer);
};

static void
ptr_array_iterator_reset (WpIterator *it)
{
  struct ptr_array_iterator_data *it_data = wp_iterator_get_user_data (it);
  it_data->index = 0;
}

static gboolean
ptr_array_iterator_next (WpIterator *it, GValue *item)
{
  struct ptr_array_iterator_data *it_data = wp_iterator_get_user_data (it);

  while (it_data->index < it_data->array->len) {
    gpointer ptr = g_ptr_array_index (it_data->array, it_data->index++);
    if (!ptr)
      continue;
    g_value_init (item, it_data->item_type);
    it_data->set_value (item, ptr);
    return TRUE;
  }
  return FALSE;
}

static gboolean
ptr_array_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret,
    gpointer data)
{
  struct ptr_array_iterator_data *it_data = wp_iterator_get_user_data (it);
  gpointer *ptr, *base;
  guint len;

  ptr = base = it_data->array->pdata;
  len = it_data->array->len;

  while ((ptr - base) < len) {
    if (*ptr) {
      g_auto (GValue) item = G_VALUE_INIT;
      g_value_init (&item, it_data->item_type);
      it_data->set_value (&item, *ptr);
      if (!func (&item, ret, data))
        return FALSE;
    }
    ptr++;
  }
  return TRUE;
}

static void
ptr_array_iterator_finalize (WpIterator *it)
{
  struct ptr_array_iterator_data *it_data = wp_iterator_get_user_data (it);
  g_ptr_array_unref (it_data->array);
}

static const WpIteratorMethods ptr_array_iterator_methods = {
  .version = WP_ITERATOR_METHODS_VERSION,
  .reset = ptr_array_iterator_reset,
  .next = ptr_array_iterator_next,
  .fold = ptr_array_iterator_fold,
  .finalize = ptr_array_iterator_finalize,
};

/*!
 * \brief Creates an iterator from a pointer array
 *
 * \ingroup wpiterator
 * \param items (element-type gpointer) (transfer full): the items to iterate over
 * \param item_type the type of each item
 * \returns (transfer full): a new iterator that iterates over \a items
 */
WpIterator *
wp_iterator_new_ptr_array (GPtrArray * items, GType item_type)
{
  g_autoptr (WpIterator) it = NULL;
  struct ptr_array_iterator_data *it_data;

  g_return_val_if_fail (items != NULL, NULL);

  it = wp_iterator_new (&ptr_array_iterator_methods,
      sizeof (struct ptr_array_iterator_data));
  it_data = wp_iterator_get_user_data (it);
  it_data->array = items;
  it_data->item_type = item_type;
  it_data->index = 0;

  if (g_type_is_a (item_type, G_TYPE_POINTER))
    it_data->set_value = g_value_set_pointer;
  else if (g_type_is_a (item_type, G_TYPE_BOXED))
    it_data->set_value = (void (*) (GValue *, gpointer)) g_value_set_boxed;
  else if (g_type_is_a (item_type, G_TYPE_OBJECT))
    it_data->set_value = g_value_set_object;
  else if (g_type_is_a (item_type, G_TYPE_INTERFACE))
    it_data->set_value = g_value_set_object;
  else if (g_type_is_a (item_type, G_TYPE_VARIANT))
    it_data->set_value = (void (*) (GValue *, gpointer)) g_value_set_variant;
  else if (g_type_is_a (item_type, G_TYPE_STRING))
    it_data->set_value = (void (*) (GValue *, gpointer)) g_value_set_string;
  else
    g_return_val_if_reached (NULL);

  return g_steal_pointer (&it);
}
  0707010000006C000081A4000000000000000000000001656CC35F000009F1000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/iterator.h  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_ITERATOR_H__
#define __WIREPLUMBER_ITERATOR_H__

#include <gio/gio.h>
#include "defs.h"

G_BEGIN_DECLS

/*!
 * \brief A function to be passed to wp_iterator_fold()
 * \param item the item to fold
 * \param ret the value collecting the result
 * \param data data passed to wp_iterator_fold()
 * \returns TRUE if the fold should continue, FALSE if it should stop.
 * \ingroup wpiterator
 */
typedef gboolean (*WpIteratorFoldFunc) (const GValue *item, GValue *ret,
    gpointer data);

/*!
 * \brief A function that is called by wp_iterator_foreach().
 * \param item the item
 * \param data the data passed to wp_iterator_foreach()
 * \ingroup wpiterator
 */
typedef void (*WpIteratorForeachFunc) (const GValue *item, gpointer data);

/*!
 * \brief The WpIterator GType
 * \ingroup wpiterator
 */
#define WP_TYPE_ITERATOR (wp_iterator_get_type ())
WP_API
GType wp_iterator_get_type (void);

typedef struct _WpIterator WpIterator;
typedef struct _WpIteratorMethods WpIteratorMethods;

/*!
 * \brief The version to set to _WpIteratorMethods::version.
 * This allows future expansion of the struct
 * \ingroup wpiterator
 */
#define  WP_ITERATOR_METHODS_VERSION 0U

struct _WpIteratorMethods
{
  guint32 version;

  void (*reset) (WpIterator *self);
  gboolean (*next) (WpIterator *self, GValue *item);
  gboolean (*fold) (WpIterator *self, WpIteratorFoldFunc func,
      GValue *ret, gpointer data);
  gboolean (*foreach) (WpIterator *self, WpIteratorForeachFunc func,
      gpointer data);
  void (*finalize) (WpIterator *self);
};

/* ref count */

WP_API
WpIterator *wp_iterator_ref (WpIterator *self);

WP_API
void wp_iterator_unref (WpIterator *self);

/* iteration api */

WP_API
void wp_iterator_reset (WpIterator *self);

WP_API
gboolean wp_iterator_next (WpIterator *self, GValue *item);

WP_API
gboolean wp_iterator_fold (WpIterator *self, WpIteratorFoldFunc func,
    GValue *ret, gpointer data);

WP_API
gboolean wp_iterator_foreach (WpIterator *self, WpIteratorForeachFunc func,
    gpointer data);

/* constructors */

WP_API
WpIterator * wp_iterator_new_ptr_array (GPtrArray * items, GType item_type);

WP_API
WpIterator * wp_iterator_new (const WpIteratorMethods * methods,
    size_t user_size);

/* private */

WP_API
gpointer wp_iterator_get_user_data (WpIterator * self);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpIterator, wp_iterator_unref)

G_END_DECLS

#endif
   0707010000006D000081A4000000000000000000000001656CC35F00002390000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/link.c  /* WirePlumber
 *
 * Copyright © 2019-2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-link"

#include "link.h"
#include "wpenums.h"
#include "private/pipewire-object-mixin.h"

/*! \defgroup wplink WpLink */
/*!
 * \struct WpLink
 *
 * The WpLink class allows accessing the properties and methods of a
 * PipeWire link object (`struct pw_link`).
 *
 * A WpLink is constructed internally when a new link appears on the
 * PipeWire registry and it is made available through the WpObjectManager API.
 * Alternatively, a WpLink can also be constructed using
 * wp_link_new_from_factory(), which creates a new link object
 * on the remote PipeWire server by calling into a factory.
 *
 * \gproperties
 *
 * \gproperty{state, WpLinkState, G_PARAM_READABLE, The current state of the link}
 *
 * \gsignals
 *
 * \par state-changed
 * \parblock
 * \code
 * void
 * state_changed_callback (WpLink * self,
 *                         WpLinkState * old_state,
 *                         WpLinkState * new_state,
 *                         gpointer user_data)
 * \endcode
 *
 * Emitted when the link changes state. This is only emitted when
 * WP_PIPEWIRE_OBJECT_FEATURE_INFO is enabled.
 *
 * Parameters:
 * - `old_state` - the old state
 * - `new_state` - the new state
 *
 * Flags: G_SIGNAL_RUN_LAST
 * \endparblock
 */

enum {
  PROP_STATE = WP_PW_OBJECT_MIXIN_PROP_CUSTOM_START,
};

enum {
  SIGNAL_STATE_CHANGED,
  N_SIGNALS,
};

static guint32 signals[N_SIGNALS] = {0};

struct _WpLink
{
  WpGlobalProxy parent;
};

static void wp_link_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface);

G_DEFINE_TYPE_WITH_CODE (WpLink, wp_link, WP_TYPE_GLOBAL_PROXY,
    G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT,
        wp_pw_object_mixin_object_interface_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
        wp_link_pw_object_mixin_priv_interface_init))

static void
wp_link_init (WpLink * self)
{
}

static void
wp_link_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object);

  switch (property_id) {
  case PROP_STATE:
    g_value_set_enum (value, d->info ?
        ((struct pw_link_info *) d->info)->state : 0);
    break;
  default:
    wp_pw_object_mixin_get_property (object, property_id, value, pspec);
    break;
  }
}

static WpObjectFeatures
wp_link_get_supported_features (WpObject * object)
{
  return wp_pw_object_mixin_get_supported_features (object)
      | WP_LINK_FEATURE_ESTABLISHED;
}

enum {
  STEP_WAIT_ESTABLISHED = WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START,
};

static void
wp_link_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case WP_PW_OBJECT_MIXIN_STEP_BIND:
  case WP_TRANSITION_STEP_ERROR:
    /* base class can handle BIND and ERROR */
    WP_OBJECT_CLASS (wp_link_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO:
    /* just wait, info will be emitted anyway after binding */
    break;
  case STEP_WAIT_ESTABLISHED:
    /* just wait again, the state will be changed automatically */
    break;
  default:
    g_assert_not_reached ();
  }
}

static const struct pw_link_events link_events = {
  PW_VERSION_LINK_EVENTS,
  .info = (HandleEventInfoFunc(link)) wp_pw_object_mixin_handle_event_info,
};

static void
wp_link_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy,
      link, &link_events);
}

static void
wp_link_pw_proxy_destroyed (WpProxy * proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy);
  wp_object_update_features (WP_OBJECT (proxy), 0, WP_LINK_FEATURE_ESTABLISHED);

  WP_PROXY_CLASS (wp_link_parent_class)->pw_proxy_destroyed (proxy);
}

static void
wp_link_class_init (WpLinkClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->get_property = wp_link_get_property;

  wpobject_class->get_supported_features = wp_link_get_supported_features;
  wpobject_class->activate_get_next_step =
      wp_pw_object_mixin_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_link_activate_execute_step;

  proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Link;
  proxy_class->pw_iface_version = PW_VERSION_LINK;
  proxy_class->pw_proxy_created = wp_link_pw_proxy_created;
  proxy_class->pw_proxy_destroyed = wp_link_pw_proxy_destroyed;

  wp_pw_object_mixin_class_override_properties (object_class);

  g_object_class_install_property (object_class, PROP_STATE,
      g_param_spec_enum ("state", "state", "state", WP_TYPE_LINK_STATE, 0,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  signals[SIGNAL_STATE_CHANGED] = g_signal_new (
      "state-changed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
      WP_TYPE_LINK_STATE, WP_TYPE_LINK_STATE);

}

static void
wp_link_process_info (gpointer instance, gpointer old_info, gpointer i)
{
  WpObject *object = instance;
  const struct pw_link_info *info = i;

  if (info->change_mask & PW_LINK_CHANGE_MASK_STATE) {
    enum pw_link_state old_state = old_info ?
        ((struct pw_link_info *) old_info)->state : PW_LINK_STATE_INIT;

    g_signal_emit (instance, signals[SIGNAL_STATE_CHANGED], 0,
        old_state, info->state);

    if (info->state >= PW_LINK_STATE_PAUSED && old_state < PW_LINK_STATE_PAUSED)
      wp_object_update_features (object, WP_LINK_FEATURE_ESTABLISHED, 0);
    else if (info->state < PW_LINK_STATE_PAUSED && old_state >= PW_LINK_STATE_PAUSED)
      wp_object_update_features (object, 0, WP_LINK_FEATURE_ESTABLISHED);
  }
}

static void
wp_link_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface)
{
  wp_pw_object_mixin_priv_interface_info_init_no_params (iface, link, LINK);
  iface->process_info = wp_link_process_info;
}

/*!
 * \brief Constructs a link on the PipeWire server by asking the remote factory
 * \a factory_name to create it.
 *
 * Because of the nature of the PipeWire protocol, this operation completes
 * asynchronously at some point in the future. In order to find out when
 * this is done, you should call wp_object_activate(), requesting at least
 * WP_PROXY_FEATURE_BOUND. When this feature is ready, the link is ready for
 * use on the server. If the link cannot be created, this activation operation
 * will fail.
 *
 * \ingroup wplink
 * \param core the wireplumber core
 * \param factory_name the pipewire factory name to construct the link
 * \param properties (nullable) (transfer full): the properties to pass to the
 *   factory
 * \returns (nullable) (transfer full): the new link or NULL if the core
 *   is not connected and therefore the link cannot be created
 */
WpLink *
wp_link_new_from_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties)
{
  g_autoptr (WpProperties) props = properties;
  return g_object_new (WP_TYPE_LINK,
      "core", core,
      "factory-name", factory_name,
      "global-properties", props,
      NULL);
}

/*!
 * \brief Retrieves the ids of the objects that are linked by this link
 *
 * \remark Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 *
 * \ingroup wplink
 * \param self the link
 * \param output_node (out) (optional): the bound id of the output (source) node
 * \param output_port (out) (optional): the bound id of the output (source) port
 * \param input_node (out) (optional): the bound id of the input (sink) node
 * \param input_port (out) (optional): the bound id of the input (sink) port
 */
void
wp_link_get_linked_object_ids (WpLink * self,
    guint32 * output_node, guint32 * output_port,
    guint32 * input_node, guint32 * input_port)
{
  g_return_if_fail (WP_IS_LINK (self));

  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);
  struct pw_link_info *info = d->info;
  g_return_if_fail (info);

  if (output_node)
    *output_node = info->output_node_id;
  if (output_port)
    *output_port = info->output_port_id;
  if (input_node)
    *input_node = info->input_node_id;
  if (input_port)
    *input_port = info->input_port_id;
}

/*!
 * \brief Gets the current state of the link
 * \ingroup wplink
 * \param self the link
 * \param error (out) (optional) (transfer none): the error
 * \returns the current state of the link
 * \since 0.4.11
 */
WpLinkState
wp_link_get_state (WpLink * self, const gchar ** error)
{
  g_return_val_if_fail (WP_IS_LINK (self), WP_LINK_STATE_ERROR);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_PIPEWIRE_OBJECT_FEATURE_INFO, WP_LINK_STATE_ERROR);

  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);
  const struct pw_link_info *info = d->info;

  if (error)
    *error = info->error;
  return (WpLinkState) info->state;
}
0707010000006E000081A4000000000000000000000001656CC35F00000655000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/link.h  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_LINK_H__
#define __WIREPLUMBER_LINK_H__

#include "global-proxy.h"

G_BEGIN_DECLS

/*!
 * \brief The state of the link
 * \ingroup wplink
 *
 * \since 0.4.11
 */
typedef enum {
  WP_LINK_STATE_ERROR = -2,     /*!< the link is in error */
  WP_LINK_STATE_UNLINKED = -1,  /*!< the link is unlinked */
  WP_LINK_STATE_INIT = 0,       /*!< the link is initialized */
  WP_LINK_STATE_NEGOTIATING = 1,  /*!< the link is negotiating formats */
  WP_LINK_STATE_ALLOCATING = 2, /*!< the link is allocating buffers */
  WP_LINK_STATE_PAUSED = 3,     /*!< the link is paused */
  WP_LINK_STATE_ACTIVE = 4,     /*!< the link is active */
} WpLinkState;

/*!
 * \brief An extension of WpProxyFeatures
 * \ingroup wplink
 *
 * \since 0.4.11
 */
typedef enum { /*< flags >*/
  /*! waits until the state of the link is >= PAUSED */
  WP_LINK_FEATURE_ESTABLISHED = (WP_PROXY_FEATURE_CUSTOM_START << 0),
} WpLinkFeatures;

/*!
 * \brief The WpLink GType
 * \ingroup wplink
 */
#define WP_TYPE_LINK (wp_link_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpLink, wp_link, WP, LINK, WpGlobalProxy)

WP_API
WpLink * wp_link_new_from_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties);

WP_API
void wp_link_get_linked_object_ids (WpLink * self,
    guint32 * output_node, guint32 * output_port,
    guint32 * input_node, guint32 * input_port);

WP_API
WpLinkState wp_link_get_state (WpLink * self, const gchar ** error);

G_END_DECLS

#endif
   0707010000006F000081A4000000000000000000000001656CC35F0000571B000000000000000000000000000000000000002000000000wireplumber-0.4.17/lib/wp/log.c   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "log.h"
#include "spa-pod.h"
#include "proxy.h"
#include <pipewire/pipewire.h>
#include <spa/support/log.h>

/*!
 * \defgroup wplog Debug Logging
 * \{
 */
/*!
 * \def WP_LOG_LEVEL_TRACE
 * \brief A custom GLib log level for trace messages (see GLogLevelFlags)
 */
/*!
 * \def WP_OBJECT_FORMAT
 * \brief A format string to print GObjects with WP_OBJECT_ARGS()
 * For example:
 * \code
 * GObject *myobj = ...;
 * wp_debug ("This: " WP_OBJECT_FORMAT " is an object", WP_OBJECT_ARGS (myobj));
 * \endcode
 */
/*!
 * \def WP_OBJECT_ARGS(object)
 * \brief A macro to format an object for printing with WP_OBJECT_FORMAT
 */
/*!
 * \def wp_critical(...)
 * \brief Logs a critical message to the standard log via GLib's logging system.
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_critical_object(object, ...)
 * \brief Logs a critical message to the standard log via GLib's logging system.
 * \param object A GObject associated with the log; this is printed in a special
 *   way to make it easier to track messages from a specific object
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_critical_boxed(type, object, ...)
 * \brief Logs a critical message to the standard log via GLib's logging system.
 * \param type The type of \a object
 * \param object A boxed object associated with the log; this is printed in a
 *   special way to make it easier to track messages from a specific object.
 *   For some object types, contents from the object are also printed (ex WpSpaPod)
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_warning(...)
 * \brief Logs a warning message to the standard log via GLib's logging system.
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_warning_object(object, ...)
 * \brief Logs a warning message to the standard log via GLib's logging system.
 * \param object A GObject associated with the log; this is printed in a special
 *   way to make it easier to track messages from a specific object
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_warning_boxed(type, object, ...)
 * \brief Logs a warning message to the standard log via GLib's logging system.
 * \param type The type of \a object
 * \param object A boxed object associated with the log; this is printed in a
 *   special way to make it easier to track messages from a specific object.
 *   For some object types, contents from the object are also printed (ex WpSpaPod)
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_message(...)
 * \brief Logs a standard message to the standard log via GLib's logging system.
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_message_object(object, ...)
 * \brief Logs a standard message to the standard log via GLib's logging system.
 * \param object A GObject associated with the log; this is printed in a special
 *   way to make it easier to track messages from a specific object
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_message_boxed(type, object, ...)
 * \brief Logs a standard message to the standard log via GLib's logging system.
 * \param type The type of \a object
 * \param object A boxed object associated with the log; this is printed in a
 *   special way to make it easier to track messages from a specific object.
 *   For some object types, contents from the object are also printed (ex WpSpaPod)
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_info(...)
 * \brief Logs a info message to the standard log via GLib's logging system.
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_info_object(object, ...)
 * \brief Logs a info message to the standard log via GLib's logging system.
 * \param object A GObject associated with the log; this is printed in a special
 *   way to make it easier to track messages from a specific object
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_info_boxed(type, object, ...)
 * \brief Logs a info message to the standard log via GLib's logging system.
 * \param type The type of \a object
 * \param object A boxed object associated with the log; this is printed in a
 *   special way to make it easier to track messages from a specific object.
 *   For some object types, contents from the object are also printed (ex WpSpaPod)
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_debug(...)
 * \brief Logs a debug message to the standard log via GLib's logging system.
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_debug_object(object, ...)
 * \brief Logs a debug message to the standard log via GLib's logging system.
 * \param object A GObject associated with the log; this is printed in a special
 *   way to make it easier to track messages from a specific object
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_debug_boxed(type, object, ...)
 * \brief Logs a debug message to the standard log via GLib's logging system.
 * \param type The type of \a object
 * \param object A boxed object associated with the log; this is printed in a
 *   special way to make it easier to track messages from a specific object.
 *   For some object types, contents from the object are also printed (ex WpSpaPod)
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_trace(...)
 * \brief Logs a trace message to the standard log via GLib's logging system.
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_trace_object(object, ...)
 * \brief Logs a trace message to the standard log via GLib's logging system.
 * \param object A GObject associated with the log; this is printed in a special
 *   way to make it easier to track messages from a specific object
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_trace_boxed(type, object, ...)
 * \brief Logs a trace message to the standard log via GLib's logging system.
 * \param type The type of \a object
 * \param object A boxed object associated with the log; this is printed in a
 *   special way to make it easier to track messages from a specific object.
 *   For some object types, contents from the object are also printed (ex WpSpaPod)
 * \param ... A format string, followed by format arguments in printf() style
 */
/*!
 * \def wp_log(level, type, object, ...)
 * \brief The generic form of all the logging macros
 * \remark Don't use this directly, use one of the other logging macros
 */
/*! \} */

static GString *spa_dbg_str = NULL;
#define spa_debug(...) \
({ \
  g_string_append_printf (spa_dbg_str, __VA_ARGS__); \
  g_string_append_c (spa_dbg_str, '\n'); \
})

#include <spa/debug/pod.h>

static gsize initialized = 0;
static gboolean use_color = FALSE;
static gboolean output_is_journal = FALSE;
static GPatternSpec **enabled_categories = NULL;
static gint enabled_level = 4; /* MESSAGE */

struct common_fields
{
  const gchar *log_domain;
  const gchar *file;
  const gchar *line;
  const gchar *func;
  const gchar *message;
  GLogField *message_field;
  gint log_level;
  GType object_type;
  gconstpointer object;
};

/* reference: https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit */
#define COLOR_RED            "\033[1;31m"
#define COLOR_GREEN          "\033[1;32m"
#define COLOR_YELLOW         "\033[1;33m"
#define COLOR_BLUE           "\033[1;34m"
#define COLOR_MAGENTA        "\033[1;35m"
#define COLOR_CYAN           "\033[1;36m"
#define COLOR_BRIGHT_RED     "\033[1;91m"
#define COLOR_BRIGHT_GREEN   "\033[1;92m"
#define COLOR_BRIGHT_YELLOW  "\033[1;93m"
#define COLOR_BRIGHT_BLUE    "\033[1;94m"
#define COLOR_BRIGHT_MAGENTA "\033[1;95m"
#define COLOR_BRIGHT_CYAN    "\033[1;96m"

#define RESET_COLOR          "\033[0m"

/* our palette */
#define DOMAIN_COLOR    COLOR_MAGENTA
#define LOCATION_COLOR  COLOR_BLUE

/* available colors for object printouts (the <Object:0xfoobar>) */
static const gchar *object_colors[] = {
  COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN,
  COLOR_BRIGHT_RED, COLOR_BRIGHT_GREEN, COLOR_BRIGHT_YELLOW,
  COLOR_BRIGHT_MAGENTA, COLOR_BRIGHT_CYAN
};

/*
 * priority numbers are based on GLib's gmessages.c
 * reference: http://man7.org/linux/man-pages/man3/syslog.3.html#DESCRIPTION
 */
static const struct {
  GLogLevelFlags log_level;
  enum spa_log_level spa_level;
  gchar name[6];
  gchar priority[2];
  gchar color[8];
} log_level_info[] = {
  { 0,                   0,                  "U", "5", COLOR_BRIGHT_RED },
  { G_LOG_LEVEL_ERROR,   0,                  "E", "3", COLOR_RED },
  { G_LOG_LEVEL_CRITICAL,0,                  "C", "4", COLOR_BRIGHT_MAGENTA },
  { G_LOG_LEVEL_WARNING, SPA_LOG_LEVEL_ERROR,"W", "4", COLOR_BRIGHT_YELLOW },
  { G_LOG_LEVEL_MESSAGE, SPA_LOG_LEVEL_WARN, "M", "5", COLOR_BRIGHT_GREEN },
  { G_LOG_LEVEL_INFO,    SPA_LOG_LEVEL_INFO, "I", "6", COLOR_GREEN },
  { G_LOG_LEVEL_DEBUG,   SPA_LOG_LEVEL_DEBUG,"D", "7", COLOR_BRIGHT_CYAN },
  { WP_LOG_LEVEL_TRACE,  SPA_LOG_LEVEL_TRACE,"T", "7", COLOR_CYAN },
};

/* map glib's log levels, which are flags in the range (1<<2) to (1<<8),
  to the 1-7 range; first calculate the integer part of log2(log_level)
  to bring it down to 2-8 and substract 1 */
static G_GNUC_CONST inline gint
log_level_index (GLogLevelFlags log_level)
{
  gint logarithm = 0;
  while ((log_level >>= 1) != 0)
    logarithm += 1;
  return (logarithm >= 2 && logarithm <= 8) ? (logarithm - 1) : 0;
}

static G_GNUC_CONST inline gint
level_index_from_spa (gint spa_lvl)
{
  return CLAMP (spa_lvl + 2, 0, (gint) G_N_ELEMENTS (log_level_info) - 1);
}

static G_GNUC_CONST inline gint
level_index_to_spa (gint lvl_index)
{
  return CLAMP (lvl_index - 2, 0, 5);
}

static inline void
write_debug_message (FILE *s, struct common_fields *cf)
{
  gint64 now;
  time_t now_secs;
  struct tm now_tm;
  gchar time_buf[128];

  now = g_get_real_time ();
  now_secs = (time_t) (now / G_USEC_PER_SEC);
  localtime_r (&now_secs, &now_tm);
  strftime (time_buf, sizeof (time_buf), "%H:%M:%S", &now_tm);

  fprintf (s, "%s%s %s.%06d %s%18.18s %s%s:%s:%s:%s %s\n",
      /* level */
      use_color ? log_level_info[cf->log_level].color : "",
      log_level_info[cf->log_level].name,
      /* timestamp */
      time_buf,
      (gint) (now % G_USEC_PER_SEC),
      /* domain */
      use_color ? DOMAIN_COLOR : "",
      cf->log_domain,
      /* file, line, function */
      use_color ? LOCATION_COLOR : "",
      cf->file,
      cf->line,
      cf->func,
      use_color ? RESET_COLOR : "",
      /* message */
      cf->message);
  fflush (s);
}

static inline gchar *
format_message (struct common_fields *cf)
{
  g_autofree gchar *extra_message = NULL;
  g_autofree gchar *extra_object = NULL;
  const gchar *object_color = "";

  if (use_color) {
    guint h = g_direct_hash (cf->object) % G_N_ELEMENTS (object_colors);
    object_color = object_colors[h];
  }

  if (cf->object_type == WP_TYPE_SPA_POD && cf->object && !spa_dbg_str) {
    spa_dbg_str = g_string_new (cf->message);
    g_string_append (spa_dbg_str, ":\n");
    spa_debug_pod (2, NULL, wp_spa_pod_get_spa_pod (cf->object));
    extra_message = g_string_free (spa_dbg_str, FALSE);
    spa_dbg_str = NULL;
  }
  else if (cf->object && g_type_is_a (cf->object_type, WP_TYPE_PROXY) &&
      (wp_object_get_active_features ((WpObject *) cf->object) & WP_PROXY_FEATURE_BOUND)) {
    extra_object = g_strdup_printf (":%u:",
        wp_proxy_get_bound_id ((WpProxy *) cf->object));
  }

  return g_strdup_printf ("%s<%s%s%p>%s %s",
      object_color,
      cf->object_type != 0 ? g_type_name (cf->object_type) : "",
      extra_object ? extra_object : ":",
      cf->object,
      use_color ? RESET_COLOR : "",
      extra_message ? extra_message : cf->message);
}

static inline void
extract_common_fields (struct common_fields *cf, const GLogField *fields,
    gsize n_fields)
{
  for (guint i = 0; i < n_fields; i++) {
    if (g_strcmp0 (fields[i].key, "GLIB_DOMAIN") == 0) {
      cf->log_domain = fields[i].value;
    }
    else if (g_strcmp0 (fields[i].key, "MESSAGE") == 0) {
      cf->message = fields[i].value;
      cf->message_field = (GLogField *) &fields[i];
    }
    else if (g_strcmp0 (fields[i].key, "CODE_FILE") == 0) {
      cf->file = fields[i].value;
    }
    else if (g_strcmp0 (fields[i].key, "CODE_LINE") == 0) {
      cf->line = fields[i].value;
    }
    else if (g_strcmp0 (fields[i].key, "CODE_FUNC") == 0) {
      cf->func = fields[i].value;
    }
    else if (g_strcmp0 (fields[i].key, "WP_OBJECT_TYPE") == 0 &&
        fields[i].length == sizeof (GType)) {
      cf->object_type = *((GType *) fields[i].value);
    }
    else if (g_strcmp0 (fields[i].key, "WP_OBJECT") == 0 &&
        fields[i].length == sizeof (gconstpointer)) {
      cf->object = *((gconstpointer *) fields[i].value);
    }
  }
}

/*!
 * \brief Use this to figure out if a debug message is going to be printed or not,
 * so that you can avoid allocating resources just for debug logging purposes
 * \ingroup wplog
 * \param log_level a log level
 * \returns whether the log level is currently enabled
 */
gboolean
wp_log_level_is_enabled (GLogLevelFlags log_level)
{
  return log_level_index (log_level) <= enabled_level;
}

static gint
level_index_from_string (const char *str)
{
  g_return_val_if_fail (str != NULL, 0);

  for (guint i = 0; i < G_N_ELEMENTS (log_level_info); i++) {
    if (g_str_equal (str, log_level_info[i].name))
      return i;
  }

  return level_index_from_spa (atoi (str));
}

/*!
 * \brief Configures the log level and enabled categories
 * \ingroup wplog
 * \param level_str a log level description string as it would appear in the
 *   WIREPLUMBER_DEBUG environment variable "level:category1,category2"
 */
void
wp_log_set_level (const gchar * level_str)
{
  gint n_tokens = 0;
  gchar **tokens = NULL;
  gchar **categories = NULL;

  /* reset to defaults */
  enabled_level = 4; /* MESSAGE */
  if (enabled_categories) {
    GPatternSpec **pspec = enabled_categories;
    for (; *pspec != NULL; pspec++)
      g_pattern_spec_free (*pspec);
    g_clear_pointer (&enabled_categories, g_free);
  }

  if (level_str && level_str[0] != '\0') {
    /* level:category1,category2 */
    tokens = pw_split_strv (level_str, ":", 2, &n_tokens);

    /* set the log level */
    enabled_level = level_index_from_string (tokens[0]);

    /* enable filtering of debug categories */
    if (n_tokens > 1) {
      categories = pw_split_strv (tokens[1], ",", INT_MAX, &n_tokens);

      /* alloc space to hold the GPatternSpec pointers */
      enabled_categories = g_malloc_n ((n_tokens + 1), sizeof (gpointer));
      if (!enabled_categories)
        g_error ("out of memory");

      for (gint i = 0; i < n_tokens; i++)
        enabled_categories[i] = g_pattern_spec_new (categories[i]);
      enabled_categories[n_tokens] = NULL;
    }
  }

  /* set the log level also on the spa_log */
  wp_spa_log_get_instance()->level = level_index_to_spa (enabled_level);

  if (categories)
    pw_free_strv (categories);
  if (tokens)
    pw_free_strv (tokens);
}

static gboolean
is_category_enabled(const gchar *log_domain)
{
  GPatternSpec **cat = enabled_categories;
  guint len;
  g_autofree gchar *reverse_domain = NULL;

  if (!enabled_categories)
    return true;

  len = strlen (log_domain);
  reverse_domain = g_strreverse (g_strndup (log_domain, len));

  cat = enabled_categories;
  while (*cat && !g_pattern_match (*cat, len, log_domain, reverse_domain))
    cat++;

  /* NULL if we reached the end without matching */
  return (*cat != NULL);
}

/*!
 * \brief WirePlumber's GLogWriterFunc
 *
 * This is installed automatically when you call wp_init() with
 * WP_INIT_SET_GLIB_LOG set in the flags
 * \ingroup wplog
 */
GLogWriterOutput
wp_log_writer_default (GLogLevelFlags log_level,
    const GLogField *fields, gsize n_fields, gpointer user_data)
{
  struct common_fields cf = {0};
  g_autofree gchar *full_message = NULL;

  g_return_val_if_fail (fields != NULL, G_LOG_WRITER_UNHANDLED);
  g_return_val_if_fail (n_fields > 0, G_LOG_WRITER_UNHANDLED);

  /* in the unlikely event that someone messed with stderr... */
  if (G_UNLIKELY (!stderr || fileno (stderr) < 0))
    return G_LOG_WRITER_UNHANDLED;

  /* one-time initialization */
  if (g_once_init_enter (&initialized)) {
    use_color = g_log_writer_supports_color (fileno (stderr));
    output_is_journal = g_log_writer_is_journald (fileno (stderr));
    g_once_init_leave (&initialized, TRUE);
  }

  cf.log_level = log_level_index (log_level);

  /* check if debug level is enabled */
  if (cf.log_level > enabled_level)
    return G_LOG_WRITER_UNHANDLED;

  extract_common_fields (&cf, fields, n_fields);

  if (!cf.log_domain)
    cf.log_domain = "default";

  /* check if debug category is enabled */
  if (!is_category_enabled(cf.log_domain))
    return G_LOG_WRITER_UNHANDLED;

  if (G_UNLIKELY (!cf.message))
    cf.message_field->value = cf.message = "(null)";

  /* format the message to include the object */
  if (cf.object_type) {
    cf.message_field->value = cf.message = full_message =
        format_message (&cf);
  }

  /* write complete field information to the journal if we are logging to it */
  if (output_is_journal &&
      g_log_writer_journald (log_level, fields, n_fields, user_data) == G_LOG_WRITER_HANDLED)
    return G_LOG_WRITER_HANDLED;

  write_debug_message (stderr, &cf);
  return G_LOG_WRITER_HANDLED;
}

/*!
 * \brief Used internally by the debug logging macros. Avoid using it directly.
 * \ingroup wplog
 */
void
wp_log_structured_standard (
    const gchar *log_domain,
    GLogLevelFlags log_level,
    const gchar *file,
    const gchar *line,
    const gchar *func,
    GType object_type,
    gconstpointer object,
    const gchar *message_format,
    ...)
{
  g_autofree gchar *message = NULL;
  GLogField fields[8] = {
    { "PRIORITY", log_level_info[log_level_index (log_level)].priority, -1 },
    { "CODE_FILE", file, -1 },
    { "CODE_LINE", line, -1 },
    { "CODE_FUNC", func, -1 },
    { "MESSAGE", NULL, -1 },
  };
  gsize n_fields = 5;
  va_list args;

  if (log_domain != NULL) {
    fields[n_fields].key = "GLIB_DOMAIN";
    fields[n_fields].value = log_domain;
    fields[n_fields].length = -1;
    n_fields++;
  }

  if (object_type != 0) {
    fields[n_fields].key = "WP_OBJECT_TYPE";
    fields[n_fields].value = &object_type;
    fields[n_fields].length = sizeof (GType);
    n_fields++;
  }

  if (object != NULL) {
    fields[n_fields].key = "WP_OBJECT";
    fields[n_fields].value = &object;
    fields[n_fields].length = sizeof (gconstpointer);
    n_fields++;
  }

  va_start (args, message_format);
  fields[4].value = message = g_strdup_vprintf (message_format, args);
  va_end (args);

  g_log_structured_array (log_level, fields, n_fields);
}

static G_GNUC_PRINTF (7, 0) void
wp_spa_log_logtv (void *object,
    enum spa_log_level level,
    const struct spa_log_topic *topic,
    const char *file,
    int line,
    const char *func,
    const char *fmt,
    va_list args)
{
  g_autofree gchar *message = NULL;
  gchar line_str[11];
  GLogField fields[] = {
    { "PRIORITY", NULL, -1 },
    { "CODE_FILE", file, -1 },
    { "CODE_LINE", line_str, -1 },
    { "CODE_FUNC", func, -1 },
    { "MESSAGE", NULL, -1 },
    { "GLIB_DOMAIN", "pw", -1 },
  };

  gint log_level_idx = level_index_from_spa (level);
  GLogLevelFlags log_level = log_level_info[log_level_idx].log_level;
  fields[0].value = log_level_info[log_level_idx].priority;

  sprintf (line_str, "%d", line);
  fields[4].value = message = g_strdup_vprintf (fmt, args);

  if (topic)
    fields[5].value = topic->topic;

  g_log_structured_array (log_level, fields, SPA_N_ELEMENTS (fields));
}

static G_GNUC_PRINTF (7, 8) void
wp_spa_log_logt (void *object,
    enum spa_log_level level,
    const struct spa_log_topic *topic,
    const char *file,
    int line,
    const char *func,
    const char *fmt, ...)
{
  va_list args;
  va_start (args, fmt);
  wp_spa_log_logtv (object, level, topic, file, line, func, fmt, args);
  va_end (args);
}

static G_GNUC_PRINTF (6, 0) void
wp_spa_log_logv (void *object,
    enum spa_log_level level,
    const char *file,
    int line,
    const char *func,
    const char *fmt,
    va_list args)
{
  wp_spa_log_logtv (object, level, NULL, file, line, func, fmt, args);
}

static G_GNUC_PRINTF (6, 7) void
wp_spa_log_log (void *object,
    enum spa_log_level level,
    const char *file,
    int line,
    const char *func,
    const char *fmt, ...)
{
  va_list args;
  va_start (args, fmt);
  wp_spa_log_logtv (object, level, NULL, file, line, func, fmt, args);
  va_end (args);
}

static void
wp_spa_log_topic_init (void *object, struct spa_log_topic *topic)
{
  if (is_category_enabled(topic->topic)) {
    topic->has_custom_level = false;
  } else {
    topic->has_custom_level = true;
    topic->level = SPA_LOG_LEVEL_NONE;
  }
}

static const struct spa_log_methods wp_spa_log_methods = {
  SPA_VERSION_LOG_METHODS,
  .log = wp_spa_log_log,
  .logv = wp_spa_log_logv,
  .logt = wp_spa_log_logt,
  .logtv = wp_spa_log_logtv,
  .topic_init = wp_spa_log_topic_init,
};

static struct spa_log wp_spa_log = {
  .iface = { SPA_TYPE_INTERFACE_Log, SPA_VERSION_LOG, { &wp_spa_log_methods, NULL } },
  .level = SPA_LOG_LEVEL_WARN,
};

/*!
 * \brief Gets WirePlumber's instance of `spa_log`
 * \ingroup wplog
 * \returns WirePlumber's instance of `spa_log`, which can be used to redirect
 *   PipeWire's log messages to the currently installed GLogWriterFunc.
 *   This is installed automatically when you call wp_init() with
 *   WP_INIT_SET_PW_LOG set in the flags
 */
struct spa_log *
wp_spa_log_get_instance (void)
{
  return &wp_spa_log;
}
 07070100000070000081A4000000000000000000000001656CC35F00000D15000000000000000000000000000000000000002000000000wireplumber-0.4.17/lib/wp/log.h   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_LOG_H__
#define __WIREPLUMBER_LOG_H__

#include <glib-object.h>
#include "defs.h"

G_BEGIN_DECLS

#define WP_LOG_LEVEL_TRACE (1 << G_LOG_LEVEL_USER_SHIFT)

#define WP_OBJECT_FORMAT "<%s:%p>"
#define WP_OBJECT_ARGS(object) \
    (object ? G_OBJECT_TYPE_NAME(object) : "invalid"), object

WP_API
gboolean wp_log_level_is_enabled (GLogLevelFlags log_level) G_GNUC_PURE;

WP_API
void wp_log_set_level (const gchar * level_str);

WP_API
GLogWriterOutput wp_log_writer_default (GLogLevelFlags log_level,
    const GLogField *fields, gsize n_fields, gpointer user_data);

WP_API
void wp_log_structured_standard (const gchar *log_domain,
    GLogLevelFlags log_level, const gchar *file, const gchar *line,
    const gchar *func, GType object_type, gconstpointer object,
    const gchar *message_format, ...) G_GNUC_PRINTF (8, 9);

#define wp_log(level, type, object, ...) \
({ \
  if (G_UNLIKELY (wp_log_level_is_enabled (level))) \
    wp_log_structured_standard (G_LOG_DOMAIN, level, __FILE__, \
        G_STRINGIFY (__LINE__), G_STRFUNC, type, object, __VA_ARGS__); \
})

#define wp_critical(...) \
    wp_log (G_LOG_LEVEL_CRITICAL, 0, NULL, __VA_ARGS__)
#define wp_warning(...) \
    wp_log (G_LOG_LEVEL_WARNING, 0, NULL, __VA_ARGS__)
#define wp_message(...) \
    wp_log (G_LOG_LEVEL_MESSAGE, 0, NULL, __VA_ARGS__)
#define wp_info(...) \
    wp_log (G_LOG_LEVEL_INFO, 0, NULL, __VA_ARGS__)
#define wp_debug(...) \
    wp_log (G_LOG_LEVEL_DEBUG, 0, NULL, __VA_ARGS__)
#define wp_trace(...) \
    wp_log (WP_LOG_LEVEL_TRACE, 0, NULL, __VA_ARGS__)

#define wp_critical_object(object, ...)  \
    wp_log (G_LOG_LEVEL_CRITICAL, (object) ? G_TYPE_FROM_INSTANCE (object) : G_TYPE_NONE, object, __VA_ARGS__)
#define wp_warning_object(object, ...)  \
    wp_log (G_LOG_LEVEL_WARNING, (object) ? G_TYPE_FROM_INSTANCE (object) : G_TYPE_NONE, object, __VA_ARGS__)
#define wp_message_object(object, ...)  \
    wp_log (G_LOG_LEVEL_MESSAGE, (object) ? G_TYPE_FROM_INSTANCE (object) : G_TYPE_NONE, object, __VA_ARGS__)
#define wp_info_object(object, ...)  \
    wp_log (G_LOG_LEVEL_INFO, (object) ? G_TYPE_FROM_INSTANCE (object) : G_TYPE_NONE, object, __VA_ARGS__)
#define wp_debug_object(object, ...)  \
    wp_log (G_LOG_LEVEL_DEBUG, (object) ? G_TYPE_FROM_INSTANCE (object) : G_TYPE_NONE, object, __VA_ARGS__)
#define wp_trace_object(object, ...)  \
    wp_log (WP_LOG_LEVEL_TRACE, (object) ? G_TYPE_FROM_INSTANCE (object) : G_TYPE_NONE, object, __VA_ARGS__)

#define wp_critical_boxed(type, object, ...) \
    wp_log (G_LOG_LEVEL_CRITICAL, type, object, __VA_ARGS__)
#define wp_warning_boxed(type, object, ...) \
    wp_log (G_LOG_LEVEL_WARNING, type, object, __VA_ARGS__)
#define wp_message_boxed(type, object, ...) \
    wp_log (G_LOG_LEVEL_MESSAGE, type, object, __VA_ARGS__)
#define wp_info_boxed(type, object, ...) \
    wp_log (G_LOG_LEVEL_INFO, type, object, __VA_ARGS__)
#define wp_debug_boxed(type, object, ...) \
    wp_log (G_LOG_LEVEL_DEBUG, type, object, __VA_ARGS__)
#define wp_trace_boxed(type, object, ...) \
    wp_log (WP_LOG_LEVEL_TRACE, type, object, __VA_ARGS__)

struct spa_log;

WP_API
struct spa_log * wp_spa_log_get_instance (void);

G_END_DECLS

#endif
   07070100000071000081A4000000000000000000000001656CC35F00000B44000000000000000000000000000000000000002600000000wireplumber-0.4.17/lib/wp/meson.build wp_lib_sources = files(
  'client.c',
  'component-loader.c',
  'core.c',
  'dbus.c',
  'device.c',
  'endpoint.c',
  'error.c',
  'factory.c',
  'global-proxy.c',
  'iterator.c',
  'link.c',
  'log.c',
  'metadata.c',
  'module.c',
  'node.c',
  'object.c',
  'object-interest.c',
  'object-manager.c',
  'plugin.c',
  'port.c',
  'properties.c',
  'proxy.c',
  'proxy-interfaces.c',
  'session-item.c',
  'si-factory.c',
  'si-interfaces.c',
  'spa-json.c',
  'spa-pod.c',
  'spa-type.c',
  'state.c',
  'transition.c',
  'wp.c',
)

wp_lib_priv_sources = files(
  'private/pipewire-object-mixin.c',
)

wp_lib_headers = files(
  'client.h',
  'component-loader.h',
  'core.h',
  'dbus.h',
  'defs.h',
  'device.h',
  'endpoint.h',
  'error.h',
  'global-proxy.h',
  'iterator.h',
  'link.h',
  'log.h',
  'metadata.h',
  'module.h',
  'node.h',
  'object.h',
  'object-interest.h',
  'object-manager.h',
  'plugin.h',
  'port.h',
  'properties.h',
  'proxy.h',
  'proxy-interfaces.h',
  'session-item.h',
  'si-factory.h',
  'si-interfaces.h',
  'spa-json.h',
  'spa-pod.h',
  'spa-type.h',
  'state.h',
  'transition.h',
  'wp.h',
  'factory.h',
)

install_headers(wp_lib_headers,
  install_dir : wireplumber_headers_dir
)

enums = gnome.mkenums_simple('wpenums',
  sources: wp_lib_headers,
  header_prefix: '#include "wp/defs.h"',
  decorator: 'WP_API',
  install_header: true,
  install_dir: wireplumber_headers_dir,
)
wpenums_h = enums[1]
wpenums_c = enums[0]
wp_gen_sources = [wpenums_h]
wpenums_include_dir = include_directories('.')

wpversion_data = configuration_data()
wpversion_data.set('version', meson.project_version())
wpversion_data.set('api_version', wireplumber_api_version)
wpversion = configure_file(
  input : 'wpversion.h.in',
  output : 'wpversion.h',
  configuration : wpversion_data,
  install_dir: wireplumber_headers_dir
)
wp_gen_sources += [wpversion]

wp_lib = library('wireplumber-' + wireplumber_api_version,
  wp_lib_sources, wp_lib_priv_sources, wpenums_c, wpenums_h, wpversion,
  c_args : [
    '-D_GNU_SOURCE',
    '-DG_LOG_USE_STRUCTURED',
    '-DWIREPLUMBER_DEFAULT_MODULE_DIR="@0@"'.format(wireplumber_module_dir),
    '-DWIREPLUMBER_DEFAULT_CONFIG_DIR="@0@"'.format(wireplumber_config_dir),
    '-DWIREPLUMBER_DEFAULT_DATA_DIR="@0@"'.format(wireplumber_data_dir),
    '-DLOCALE_DIR="@0@"'.format(wireplumber_locale_dir),
    '-DBUILDING_WP',
  ],
  install: true,
  include_directories: wp_lib_include_dir,
  dependencies : [gobject_dep, gmodule_dep, gio_dep, pipewire_dep, libintl_dep],
  soversion: wireplumber_so_version,
  version: meson.project_version(),
)

wp_dep = declare_dependency(
  link_with: wp_lib,
  sources: wp_gen_sources,
  include_directories: wp_lib_include_dir,
  dependencies: [gobject_dep, gio_dep]
)

pkgconfig.generate(wp_lib,
  libraries: [gobject_dep, gio_dep],
  subdirs: 'wireplumber-' + wireplumber_api_version
)
07070100000072000081A4000000000000000000000001656CC35F0000518D000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/metadata.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Raghavendra Rao <raghavendra.rao@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-metadata"

#include "metadata.h"
#include "core.h"
#include "log.h"
#include "error.h"
#include "wpenums.h"

#include <pipewire/impl.h>
#include <pipewire/pipewire.h>
#include <pipewire/extensions/metadata.h>

/*! \defgroup wpmetadata WpMetadata */
/*!
 * \struct WpMetadata
 *
 * The WpMetadata class allows accessing the properties and methods of
 * PipeWire metadata object (`struct pw_metadata`).
 *
 * A WpMetadata is constructed internally when a new metadata object appears on the
 * PipeWire registry and it is made available through the WpObjectManager API.
 *
 * \gsignals
 *
 * \par changed
 * \parblock
 * \code
 * void
 * changed_callback (WpMetadata * self,
 *                   guint subject,
 *                   gchar * key,
 *                   gchar * type,
 *                   gchar * value,
 *                   gpointer user_data)
 * \endcode
 * Emited when metadata change
 *
 * Parameters:
 * - `subject` - the metadata subject id
 * - `key` - the metadata key
 * - `type` - the value type
 * - `value` - the metadata value
 *
 * Flags: G_SIGNAL_RUN_LAST
 * \endparblock
 */
enum {
  SIGNAL_CHANGED,
  N_SIGNALS,
};

static guint32 signals[N_SIGNALS] = {0};

/* data structure */

struct item
{
  uint32_t subject;
  gchar *key;
  gchar *type;
  gchar *value;
};

static void
set_item (struct item * item, uint32_t subject, const char * key,
    const char * type, const char * value)
{
  item->subject = subject;
  item->key = g_strdup (key);
  item->type = g_strdup (type);
  item->value = g_strdup (value);
}

static void
clear_item (struct item * item)
{
  g_free (item->key);
  g_free (item->type);
  g_free (item->value);
  spa_zero (*item);
}

static struct item *
find_item (struct pw_array * metadata, uint32_t subject, const char * key)
{
  struct item *item;

  pw_array_for_each (item, metadata) {
    if (item->subject == subject && (key == NULL || !strcmp (item->key, key))) {
      return item;
    }
  }
  return NULL;
}

static int
clear_subject (struct pw_array * metadata, uint32_t subject)
{
  struct item *item;
  uint32_t removed = 0;

  while (true) {
    item = find_item (metadata, subject, NULL);
    if (item == NULL)
      break;
    clear_item (item);
    pw_array_remove (metadata, item);
    removed++;
  }

  return removed;
}

static void
clear_items (struct pw_array * metadata)
{
  struct item *item;

  pw_array_consume (item, metadata) {
    clear_item (item);
    pw_array_remove (metadata, item);
  }
  pw_array_reset (metadata);
}

typedef struct _WpMetadataPrivate WpMetadataPrivate;
struct _WpMetadataPrivate
{
  struct pw_metadata *iface;
  struct spa_hook listener;
  struct pw_array metadata;
  gboolean remove_listener;
};

G_DEFINE_TYPE_WITH_PRIVATE (WpMetadata, wp_metadata, WP_TYPE_GLOBAL_PROXY)

static void
wp_metadata_init (WpMetadata * self)
{
  WpMetadataPrivate *priv = wp_metadata_get_instance_private (self);
  pw_array_init (&priv->metadata, 4096);
}

static void
wp_metadata_finalize (GObject * object)
{
  WpMetadataPrivate *priv =
      wp_metadata_get_instance_private (WP_METADATA (object));

  pw_array_clear (&priv->metadata);

  G_OBJECT_CLASS (wp_metadata_parent_class)->finalize (object);
}

static WpObjectFeatures
wp_metadata_get_supported_features (WpObject * object)
{
  return WP_PROXY_FEATURE_BOUND | WP_METADATA_FEATURE_DATA;
}

enum {
  STEP_BIND = WP_TRANSITION_STEP_CUSTOM_START,
  STEP_CACHE
};

static guint
wp_metadata_activate_get_next_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  g_return_val_if_fail (
      missing & (WP_PROXY_FEATURE_BOUND | WP_METADATA_FEATURE_DATA),
      WP_TRANSITION_STEP_ERROR);

  /* bind if not already bound */
  if (missing & WP_PROXY_FEATURE_BOUND)
    return STEP_BIND;
  else
    return STEP_CACHE;
}

static void
wp_metadata_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case STEP_CACHE:
    /* just wait for initial_sync_done() */
    break;
  default:
    WP_OBJECT_CLASS (wp_metadata_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  }
}

static int
metadata_event_property (void *object, uint32_t subject, const char *key,
    const char *type, const char *value)
{
  WpMetadata *self = WP_METADATA (object);
  WpMetadataPrivate *priv =
      wp_metadata_get_instance_private (WP_METADATA (self));
  struct item *item = NULL;

  if (key == NULL) {
    if (clear_subject (&priv->metadata, subject) > 0) {
      wp_debug_object (self, "remove id:%d", subject);
      g_signal_emit (self, signals[SIGNAL_CHANGED], 0, subject, NULL, NULL,
          NULL);
    }
    return 0;
  }

  item = find_item (&priv->metadata, subject, key);
  if (item == NULL) {
    if (value == NULL)
      return 0;
    item = pw_array_add (&priv->metadata, sizeof (*item));
    if (item == NULL)
      return -errno;
  } else {
    clear_item (item);
  }

  if (value != NULL) {
    if (type == NULL)
      type = "string";
    set_item (item, subject, key, type, value);
    wp_debug_object (self, "add id:%d key:%s type:%s value:%s",
        subject, key, type, value);
  } else {
    type = NULL;
    pw_array_remove (&priv->metadata, item);
    wp_debug_object (self, "remove id:%d key:%s", subject, key);
  }

  g_signal_emit (self, signals[SIGNAL_CHANGED], 0, subject, key, type, value);
  return 0;
}

static const struct pw_metadata_events metadata_events = {
  PW_VERSION_METADATA_EVENTS,
  .property = metadata_event_property,
};

static void
initial_sync_done (WpCore * core, GAsyncResult * res, WpMetadata * self)
{
  g_autoptr (GError) error = NULL;
  if (!wp_core_sync_finish (core, res, &error)) {
    wp_warning_object (self, "core sync error: %s", error->message);
    return;
  }

  wp_object_update_features (WP_OBJECT (self), WP_METADATA_FEATURE_DATA, 0);
}

static void
wp_metadata_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
{
  WpMetadata *self = WP_METADATA (proxy);
  WpMetadataPrivate *priv = wp_metadata_get_instance_private (self);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));

  priv->iface = (struct pw_metadata *) pw_proxy;
  pw_metadata_add_listener (priv->iface, &priv->listener,
      &metadata_events, self);
  priv->remove_listener = TRUE;
  wp_core_sync_closure (core, NULL,
      g_cclosure_new_object ((GCallback) initial_sync_done, G_OBJECT (self)));
}

static void
wp_metadata_pw_proxy_destroyed (WpProxy * proxy)
{
  WpMetadata *self = WP_METADATA (proxy);
  WpMetadataPrivate *priv = wp_metadata_get_instance_private (self);

  if (priv->remove_listener) {
    spa_hook_remove (&priv->listener);
    priv->remove_listener = FALSE;
  }
  clear_items (&priv->metadata);
  wp_object_update_features (WP_OBJECT (self), 0, WP_METADATA_FEATURE_DATA);

  WP_PROXY_CLASS (wp_metadata_parent_class)->pw_proxy_destroyed (proxy);
}

static void
wp_metadata_class_init (WpMetadataClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->finalize = wp_metadata_finalize;

  wpobject_class->get_supported_features = wp_metadata_get_supported_features;
  wpobject_class->activate_get_next_step = wp_metadata_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_metadata_activate_execute_step;

  proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Metadata;
  proxy_class->pw_iface_version = PW_VERSION_METADATA;
  proxy_class->pw_proxy_created = wp_metadata_pw_proxy_created;
  proxy_class->pw_proxy_destroyed = wp_metadata_pw_proxy_destroyed;

  signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 4,
      G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
}

struct metadata_iterator_data
{
  WpMetadata *metadata;
  const struct item *item;
  guint32 subject;
};

static void
metadata_iterator_reset (WpIterator *it)
{
  struct metadata_iterator_data *it_data = wp_iterator_get_user_data (it);
  WpMetadataPrivate *priv =
      wp_metadata_get_instance_private (it_data->metadata);

  it_data->item = pw_array_first (&priv->metadata);
}

static gboolean
metadata_iterator_next (WpIterator *it, GValue *item)
{
  struct metadata_iterator_data *it_data = wp_iterator_get_user_data (it);
  WpMetadataPrivate *priv =
      wp_metadata_get_instance_private (it_data->metadata);

  while (pw_array_check (&priv->metadata, it_data->item)) {
    if ((it_data->subject == PW_ID_ANY ||
            it_data->subject == it_data->item->subject)) {
      g_value_init (item, G_TYPE_POINTER);
      g_value_set_pointer (item, (gpointer) it_data->item);
      it_data->item++;
      return TRUE;
    }
    it_data->item++;
  }
  return FALSE;
}

static gboolean
metadata_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret,
    gpointer data)
{
  struct metadata_iterator_data *it_data = wp_iterator_get_user_data (it);
  WpMetadataPrivate *priv =
      wp_metadata_get_instance_private (it_data->metadata);
  const struct item *i;

  pw_array_for_each (i, &priv->metadata) {
    if ((it_data->subject == PW_ID_ANY ||
            it_data->subject == it_data->item->subject)) {
      g_auto (GValue) item = G_VALUE_INIT;
      g_value_init (&item, G_TYPE_POINTER);
      g_value_set_pointer (&item, (gpointer) i);
      if (!func (&item, ret, data))
        return FALSE;
    }
  }
  return TRUE;
}

static void
metadata_iterator_finalize (WpIterator *it)
{
  struct metadata_iterator_data *it_data = wp_iterator_get_user_data (it);
  g_object_unref (it_data->metadata);
}

static const WpIteratorMethods metadata_iterator_methods = {
  .version = WP_ITERATOR_METHODS_VERSION,
  .reset = metadata_iterator_reset,
  .next = metadata_iterator_next,
  .fold = metadata_iterator_fold,
  .finalize = metadata_iterator_finalize,
};

/*!
 * \brief Iterates over metadata items that matches the given \a subject.
 *
 * If no constraints are specified, the returned iterator iterates over all the
 * stored metadata.
 *
 * Note that this method works on cached metadata. When you change metadata
 * with wp_metadata_set(), this cache will be updated on the next round-trip
 * with the pipewire server.
 *
 * \ingroup wpmetadata
 * \param self a metadata object
 * \param subject the metadata subject id, or -1 (PW_ID_ANY)
 * \returns (transfer full): an iterator that iterates over the found metadata.
 *   Use wp_metadata_iterator_item_extract() to parse the items returned by
 *   this iterator.
 */
WpIterator *
wp_metadata_new_iterator (WpMetadata * self, guint32 subject)
{
  WpMetadataPrivate *priv;
  g_autoptr (WpIterator) it = NULL;
  struct metadata_iterator_data *it_data;

  g_return_val_if_fail (self != NULL, NULL);
  priv = wp_metadata_get_instance_private (self);

  it = wp_iterator_new (&metadata_iterator_methods,
      sizeof (struct metadata_iterator_data));
  it_data = wp_iterator_get_user_data (it);
  it_data->metadata = g_object_ref (self);
  it_data->item = pw_array_first (&priv->metadata);
  it_data->subject = subject;
  return g_steal_pointer (&it);
}

/*!
 * \brief Extracts the metadata subject, key, type and value out of a
 * GValue that was returned from the WpIterator of wp_metadata_find()
 *
 * \ingroup wpmetadata
 * \param item a GValue that was returned from the WpIterator of wp_metadata_find()
 * \param subject (out)(optional): the subject id of the current item
 * \param key (out)(optional)(transfer none): the key of the current item
 * \param type (out)(optional)(transfer none): the type of the current item
 * \param value (out)(optional)(transfer none): the value of the current item
 */
void
wp_metadata_iterator_item_extract (const GValue * item, guint32 * subject,
    const gchar ** key, const gchar ** type, const gchar ** value)
{
  const struct item *i = g_value_get_pointer (item);
  g_return_if_fail (i != NULL);
  if (subject)
    *subject = i->subject;
  if (key)
    *key = i->key;
  if (type)
    *type = i->type;
  if (value)
    *value = i->value;
}

/*!
 * \brief Finds the metadata value given its \a subject and \a key.
 *
 * \ingroup wpmetadata
 * \param self a metadata object
 * \param subject the metadata subject id
 * \param key the metadata key name
 * \param type (out)(optional): the metadata type name
 * \returns the metadata string value, or NULL if not found.
 */
const gchar *
wp_metadata_find (WpMetadata * self, guint32 subject, const gchar * key,
  const gchar ** type)
{
  g_autoptr (WpIterator) it = NULL;
  g_auto (GValue) val = G_VALUE_INIT;
  it = wp_metadata_new_iterator (self, subject);
  for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
    const gchar *k = NULL, *t = NULL, *v = NULL;
    wp_metadata_iterator_item_extract (&val, NULL, &k, &t, &v);
    if (g_strcmp0 (k, key) == 0) {
      if (type)
        *type = t;
      g_value_unset (&val);
      return v;
    }
  }
  return NULL;
}

/*!
 * \brief Sets the metadata associated with the given \a subject and \a key.
 * Use NULL as a value to unset the given \a key and use NULL in both \a key
 * and \a value to remove all metadata associated with the given \a subject.
 *
 * \ingroup wpmetadata
 * \param self the metadata object
 * \param subject the subject id for which this metadata property is being set
 * \param key (nullable): the key to set, or NULL to remove all metadata for
 *   \a subject
 * \param type (nullable): the type of the value; NULL is synonymous to "string"
 * \param value (nullable): the value to set, or NULL to unset the given \a key
 */
void
wp_metadata_set (WpMetadata * self, guint32 subject,
    const gchar * key, const gchar * type, const gchar * value)
{
  WpMetadataPrivate *priv = wp_metadata_get_instance_private (self);
  pw_metadata_set_property (priv->iface, subject, key, type, value);
}

/*!
 * \brief Clears permanently all stored metadata.
 * \ingroup wpmetadata
 * \param self the metadata object
 */
void
wp_metadata_clear (WpMetadata * self)
{
  WpMetadataPrivate *priv = wp_metadata_get_instance_private (self);
  pw_metadata_clear (priv->iface);
}

/*!
 * \struct WpImplMetadata
 * Implementation of the metadata object.
 *
 * Activate this object with at least WP_PROXY_FEATURE_BOUND to export it to
 * PipeWire.
 */
struct _WpImplMetadata
{
  WpMetadata parent;

  gchar *name;
  WpProperties *properties;

  struct pw_impl_metadata *impl;
  struct spa_hook listener;
};

enum {
  PROP_0,
  PROP_NAME,
  PROP_PROPERTIES,
};

G_DEFINE_TYPE (WpImplMetadata, wp_impl_metadata, WP_TYPE_METADATA)

static void
wp_impl_metadata_init (WpImplMetadata * self)
{
}

static const struct pw_impl_metadata_events impl_metadata_events = {
  PW_VERSION_IMPL_METADATA_EVENTS,
  .property = metadata_event_property,
};

static void
wp_impl_metadata_constructed (GObject *object)
{
  WpImplMetadata *self = WP_IMPL_METADATA (object);
  WpMetadataPrivate *priv =
      wp_metadata_get_instance_private (WP_METADATA (self));
  g_autoptr (WpCore) core = NULL;
  struct pw_context *pw_context;
  struct pw_properties *props = NULL;

  core = wp_object_get_core (WP_OBJECT (self));
  g_return_if_fail (core);
  pw_context = wp_core_get_pw_context (core);
  g_return_if_fail (pw_context);

  if (self->properties)
    props = wp_properties_to_pw_properties (self->properties);

  self->impl = pw_context_create_metadata (pw_context, self->name, props , 0);
  g_return_if_fail (self->impl);
  priv->iface = pw_impl_metadata_get_implementation (self->impl);
  g_return_if_fail (priv->iface);

  pw_impl_metadata_add_listener (self->impl, &self->listener,
      &impl_metadata_events, self);

  wp_object_update_features (WP_OBJECT (self), WP_METADATA_FEATURE_DATA, 0);
  G_OBJECT_CLASS (wp_impl_metadata_parent_class)->constructed (object);
}

static void
wp_impl_metadata_finalize (GObject * object)
{
  WpImplMetadata *self = WP_IMPL_METADATA (object);

  spa_hook_remove (&self->listener);
  g_clear_pointer (&self->impl, pw_impl_metadata_destroy);
  g_clear_pointer (&self->properties, wp_properties_unref);
  g_clear_pointer (&self->name, g_free);

  G_OBJECT_CLASS (wp_impl_metadata_parent_class)->finalize (object);
}

static void
wp_impl_metadata_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpImplMetadata *self = WP_IMPL_METADATA (object);

  switch (property_id) {
  case PROP_NAME:
    g_clear_pointer (&self->name, g_free);
    self->name = g_value_dup_string (value);
    break;
  case PROP_PROPERTIES:
    g_clear_pointer (&self->properties, wp_properties_unref);
    self->properties = g_value_dup_boxed (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_impl_metadata_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpImplMetadata *self = WP_IMPL_METADATA (object);

  switch (property_id) {
  case PROP_NAME:
    g_value_set_string (value, self->name);
    break;
  case PROP_PROPERTIES:
    g_value_set_boxed (value, self->properties);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_impl_metadata_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  WpImplMetadata *self = WP_IMPL_METADATA (object);
  WpMetadataPrivate *priv =
      wp_metadata_get_instance_private (WP_METADATA (self));

  switch (step) {
  case STEP_BIND: {
    g_autoptr (WpCore) core = wp_object_get_core (object);
    struct pw_core *pw_core = wp_core_get_pw_core (core);
    const struct pw_properties *props = NULL;

    /* no pw_core -> we are not connected */
    if (!pw_core) {
      wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
              WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
              "The WirePlumber core is not connected; "
              "object cannot be exported to PipeWire"));
      return;
    }

    props = pw_impl_metadata_get_properties (self->impl);
    wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_export (pw_core,
            PW_TYPE_INTERFACE_Metadata, &props->dict, priv->iface, 0)
    );
    break;
  }
  case STEP_CACHE:
    /* never reached because WP_METADATA_FEATURE_DATA is always enabled */
    g_assert_not_reached ();
    break;
  default:
    WP_OBJECT_CLASS (wp_impl_metadata_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  }
}

static void
wp_impl_metadata_class_init (WpImplMetadataClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->constructed = wp_impl_metadata_constructed;
  object_class->finalize = wp_impl_metadata_finalize;
  object_class->set_property = wp_impl_metadata_set_property;
  object_class->get_property = wp_impl_metadata_get_property;

  wpobject_class->activate_execute_step =
      wp_impl_metadata_activate_execute_step;

  /* disable adding a listener for events */
  proxy_class->pw_proxy_created = NULL;

  g_object_class_install_property (object_class, PROP_NAME,
      g_param_spec_string ("name", "name", "The metadata name", "",
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PROPERTIES,
      g_param_spec_boxed ("properties", "properties",
          "The metadata properties", WP_TYPE_PROPERTIES,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}

/*!
 * \brief Creates a new metadata implementation
 * \ingroup wpmetadata
 * \param core the core
 * \returns (transfer full): a new WpImplMetadata
 */
WpImplMetadata *
wp_impl_metadata_new (WpCore * core)
{
  return wp_impl_metadata_new_full (core, NULL, NULL);
}

/*!
 * \brief Creates a new metadata implementation with name and properties
 * \ingroup wpmetadata
 * \param core the core
 * \param name (nullable): the metadata name
 * \param properties (nullable) (transfer full): the metadata properties
 * \returns (transfer full): a new WpImplMetadata
 * \since 0.4.3
 */
WpImplMetadata *
wp_impl_metadata_new_full (WpCore * core, const gchar *name,
    WpProperties *properties)
{
  g_autoptr (WpProperties) props = properties;

  g_return_val_if_fail (WP_IS_CORE (core), NULL);

  return g_object_new (WP_TYPE_IMPL_METADATA,
      "core", core,
      "name", name,
      "properties", props,
      NULL);
}
   07070100000073000081A4000000000000000000000001656CC35F000006FA000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/metadata.h  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Raghavendra Rao <raghavendra.rao@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_METADATA_H__
#define __WIREPLUMBER_METADATA_H__

#include "global-proxy.h"

G_BEGIN_DECLS

/*!
 * \brief An extension of WpProxyFeatures for WpMetadata objects
 * \ingroup wpmetadata
 */
typedef enum { /*< flags >*/
  /*! caches metadata locally */
  WP_METADATA_FEATURE_DATA = (WP_PROXY_FEATURE_CUSTOM_START << 0),
} WpMetadataFeatures;

/*!
 * \brief The WpMetadata GType
 * \ingroup wpmetadata
 */
#define WP_TYPE_METADATA (wp_metadata_get_type ())

WP_API
G_DECLARE_DERIVABLE_TYPE (WpMetadata, wp_metadata, WP, METADATA, WpGlobalProxy)

struct _WpMetadataClass
{
  WpGlobalProxyClass parent_class;

  /*< private >*/
  WP_PADDING(4)
};

WP_API
WpIterator * wp_metadata_new_iterator (WpMetadata * self, guint32 subject);

WP_API
void wp_metadata_iterator_item_extract (const GValue * item, guint32 * subject,
    const gchar ** key, const gchar ** type, const gchar ** value);

WP_API
const gchar * wp_metadata_find (WpMetadata * self, guint32 subject,
    const gchar * key, const gchar ** type);

WP_API
void wp_metadata_set (WpMetadata * self, guint32 subject,
    const gchar * key, const gchar * type, const gchar * value);

WP_API
void wp_metadata_clear (WpMetadata * self);

/*!
 * \brief The WpImplMetadata GType
 * \ingroup wpmetadata
 */
#define WP_TYPE_IMPL_METADATA (wp_impl_metadata_get_type ())

WP_API
G_DECLARE_FINAL_TYPE (WpImplMetadata, wp_impl_metadata, WP, IMPL_METADATA, WpMetadata)

WP_API
WpImplMetadata * wp_impl_metadata_new (WpCore * core);

WP_API
WpImplMetadata * wp_impl_metadata_new_full (WpCore * core, const gchar *name,
    WpProperties *properties);

G_END_DECLS

#endif
  07070100000074000081A4000000000000000000000001656CC35F0000222B000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/module.c    /* WirePlumber
 *
 * Copyright © 2021 Asymptotic
 *    @author Arun Raghavan <arun@asymptotic.io>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-module"

#include <pipewire/impl.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>

#include "module.h"

/*! \defgroup wpimplmodule WpImplModule */
/*!
 * \struct WpImplModule
 * \since 0.4.2
 *
 * Used to load PipeWire modules within the WirePlumber process. This is
 * slightly different from other objects in that the module is not exported to
 * PipeWire, but it may create an export objects itself.
 */

struct _WpImplModule
{
  GObject parent;

  GWeakRef core;
  gchar *name;
  gchar *args;
  WpProperties *props; /* only used during module load */

  struct pw_impl_module *pw_impl_module;
};

G_DEFINE_TYPE (WpImplModule, wp_impl_module, G_TYPE_OBJECT);

enum {
  PROP_0,
  PROP_CORE,
  PROP_NAME,
  PROP_ARGUMENTS,
  PROP_PROPERTIES,
  PROP_PW_IMPL_MODULE,
};

static void
wp_impl_module_init (WpImplModule * self)
{
  g_weak_ref_init (&self->core, NULL);
  self->name = NULL;
  self->args = NULL;
  self->props = NULL;
  self->pw_impl_module = NULL;
}

static void
wp_impl_module_constructed (GObject * object)
{
  WpImplModule *self = WP_IMPL_MODULE (object);
  WpCore *core = g_weak_ref_get (&self->core);
  struct pw_context *context = core ? wp_core_get_pw_context (core) : NULL;
  struct pw_properties *props = NULL;

  if (!core || !context) {
    g_warning ("Tried to load module on unconnected core");
    return;
  }

  if (!self->name) {
    g_warning ("Invalid name while loading warnings");
    return;
  }

  if (self->props)
    props = wp_properties_to_pw_properties (self->props);

  self->pw_impl_module =
    pw_context_load_module (context, self->name, self->args, props);

  if (self->pw_impl_module && self->props) {
    /* With the module loaded, properties are just passthrough now */
    wp_properties_unref (self->props);
    self->props = NULL;
  }

  G_OBJECT_CLASS (wp_impl_module_parent_class)->constructed (object);
}

static void
wp_impl_module_finalize (GObject * object)
{
  WpImplModule *self = WP_IMPL_MODULE (object);

  g_weak_ref_clear (&self->core);

  if (self->pw_impl_module)
    pw_impl_module_destroy (self->pw_impl_module);

  g_free (self->name);
  g_free (self->args);

  if (self->props)
    wp_properties_unref (self->props);
}

static void
wp_impl_module_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  WpImplModule *self = WP_IMPL_MODULE (object);

  switch (prop_id) {
    case PROP_CORE:
      g_value_set_pointer (value, g_weak_ref_get (&self->core));
      break;

    case PROP_NAME:
      g_value_set_string (value, self->name);
      break;

    case PROP_ARGUMENTS:
      g_value_set_string (value, self->args);
      break;

    case PROP_PROPERTIES:
      if (self->pw_impl_module) {
        const struct pw_properties *props =
          pw_impl_module_get_properties (self->pw_impl_module);

        /* Should we just wrap instead of copying? */
        if (props)
          g_value_set_boxed (value, wp_properties_new_copy (props));
        else
          g_value_set_boxed (value, NULL);
      } else {
        g_value_set_boxed (value, self->props);
      }
      break;

    case PROP_PW_IMPL_MODULE:
      g_value_set_pointer (value, self->pw_impl_module);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
wp_impl_module_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  WpImplModule *self = WP_IMPL_MODULE (object);
  WpProperties *props;

  switch (prop_id) {
    case PROP_CORE:
      g_weak_ref_set (&self->core, g_value_get_pointer (value));
      break;

    case PROP_NAME:
      g_free (self->name);
      self->name = g_value_dup_string (value);
      break;

    case PROP_ARGUMENTS:
      g_free (self->args);
      self->args = g_value_dup_string (value);
      break;

    case PROP_PROPERTIES:
      props = g_value_get_boxed (value);

      if (props && self->pw_impl_module) {
        pw_impl_module_update_properties (self->pw_impl_module,
            wp_properties_peek_dict (props));
      } else {
        if (props)
          self->props = wp_properties_ref (props);
        else
          self->props = NULL;
      }
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
wp_impl_module_class_init (WpImplModuleClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;

  gobject_class->constructed = wp_impl_module_constructed;
  gobject_class->finalize = wp_impl_module_finalize;
  gobject_class->get_property = wp_impl_module_get_property;
  gobject_class->set_property = wp_impl_module_set_property;

  g_object_class_install_property (gobject_class, PROP_CORE,
      g_param_spec_pointer ("core", "Core", "The WirePlumber core",
        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_NAME,
      g_param_spec_string ("name", "Name", "The name of the PipeWire module",
        NULL,
        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_ARGUMENTS,
      g_param_spec_string ("arguments", "Arguments",
        "The arguments to provide to the module while loading", NULL,
        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROPERTIES,
      g_param_spec_boxed ("properties", "Properties",
        "Properties of the module", WP_TYPE_PROPERTIES,
        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PW_IMPL_MODULE,
      g_param_spec_pointer ("pw-impl-module", "Underlying pw_impl_module",
        "Pointer to the underlying pw_impl_module structure for the module",
        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

/*!
 * \brief Loads a PipeWire module into the WirePlumber process
 *
 * \ingroup wpimplmodule
 * \since 0.4.2
 * \param core (transfer none): The WirePlumber core
 * \param name (transfer none): the name of the module to load
 * \param arguments (nullable) (transfer none): arguments to be passed to the module
 * \param properties (nullable) (transfer none): additional properties to be
 *    provided to the module
 * \returns (nullable) (transfer full): the WpImplModule for the module that
 *    was loaded on success, %NULL on failure.
 */
WpImplModule *
wp_impl_module_load (WpCore * core, const gchar * name,
    const gchar * arguments, WpProperties * properties)
{
  WpImplModule *module = WP_IMPL_MODULE (
      g_object_new (WP_TYPE_IMPL_MODULE,
        "core", core,
        "name", name,
        "arguments", arguments,
        "properties", properties,
        NULL)
      );

  if (!module->pw_impl_module) {
    /* Module loading failed, free and return */
    g_object_unref (module);
    return NULL;
  }

  return module;
}

/*!
 * \brief Loads a PipeWire module with arguments from file into the WirePlumber process
 *
 * \ingroup wpimplmodule
 * \since 0.4.15
 * \param core (transfer none): The WirePlumber core
 * \param name (transfer none): the name of the module to load
 * \param filename (transfer none): filename to be used as arguments
 * \param properties (nullable) (transfer none): additional properties to be
 *    provided to the module
 * \returns (nullable) (transfer full): the WpImplModule for the module that
 *    was loaded on success, %NULL on failure.
 */
WpImplModule *
wp_impl_module_load_file (WpCore * core, const gchar * name,
    const gchar * filename, WpProperties * properties)
{
  char *config = "";
  int fd = open(filename, O_RDONLY);
  if (fd < 0) {
    g_warning("Failed to open config file %s: %m", filename);
    return NULL;
  }

  struct stat stats;
  int err = fstat(fd, &stats);
  if (err < 0) {
    g_warning("Failed to stat config file %s: %m", filename);
    close(fd);
    return NULL;
  }

  config = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
  if (config == MAP_FAILED){
    g_warning("Failed to mmap config file %s: %m", filename);
    close(fd);
    return NULL;
  }
  close(fd);

  WpImplModule *module = WP_IMPL_MODULE (
      g_object_new (WP_TYPE_IMPL_MODULE,
        "core", core,
        "name", name,
        "arguments", config,
        "properties", properties,
        NULL)
      );

  munmap(config, stats.st_size);

  if (!module->pw_impl_module) {
    /* Module loading failed, free and return */
    g_object_unref (module);
    return NULL;
  }

  return module;
}
 07070100000075000081A4000000000000000000000001656CC35F0000035B000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/module.h    /* WirePlumber
 *
 * Copyright © 2021 Asymptotic
 *    @author Arun Raghavan <arun@asymptotic.io>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_MODULE_H__
#define __WIREPLUMBER_MODULE_H__

#include <glib-object.h>

#include "core.h"
#include "defs.h"
#include "properties.h"

G_BEGIN_DECLS

/*!
 * \brief The WpImplModule GType
 * \since 0.4.2
 * \ingroup wpimplmodule
 */
#define WP_TYPE_IMPL_MODULE (wp_impl_module_get_type())
WP_API
G_DECLARE_FINAL_TYPE (WpImplModule, wp_impl_module, WP, IMPL_MODULE, GObject);

WP_API
WpImplModule * wp_impl_module_load (WpCore * core, const gchar * name,
    const gchar * arguments, WpProperties * properties);
WP_API
WpImplModule * wp_impl_module_load_file (WpCore * core, const gchar * name,
    const gchar * filename, WpProperties * properties);

G_END_DECLS

#endif /* __WIREPLUMBER_MODULE_H__ */
 07070100000076000081A4000000000000000000000001656CC35F00006DA7000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/node.c  /* WirePlumber
 *
 * Copyright © 2019-2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-node"

#include "node.h"
#include "core.h"
#include "object-manager.h"
#include "log.h"
#include "wpenums.h"
#include "private/pipewire-object-mixin.h"

#include <pipewire/impl.h>

/*! \defgroup wpnode WpNode */
/*!
 * \struct WpNode
 *
 * The WpNode class allows accessing the properties and methods of a
 * PipeWire node object (`struct pw_node`).
 *
 * A WpNode is constructed internally when a new node appears on the
 * PipeWire registry and it is made available through the WpObjectManager API.
 * Alternatively, a WpNode can also be constructed using
 * wp_node_new_from_factory(), which creates a new node object
 * on the remote PipeWire server by calling into a factory.
 *
 * \gproperties
 *
 * \gproperty{state, WpNodeState, G_PARAM_READABLE, The current state of the node}
 *
 * \gproperty{n-input-ports, uint, G_PARAM_READABLE, The number of input ports}
 *
 * \gproperty{n-output-ports, uint, G_PARAM_READABLE, The number of output ports}
 *
 * \gproperty{max-input-ports, uint, G_PARAM_READABLE, The max number of input ports}
 *
 * \gproperty{max-output-ports, uint, G_PARAM_READABLE, The max number of output ports}
 *
 * \gsignals
 *
 * \par ports-changed
 * \parblock
 * \code
 * void
 * ports_changed_callback (WpNode * self,
 *                         gpointer user_data)
 * \endcode
 *
 * Emitted when the node's ports change. This is only emitted when
 * WP_NODE_FEATURE_PORTS is enabled.
 *
 * Flags: G_SIGNAL_RUN_LAST
 * \endparblock
 *
 * \par state-changed
 * \parblock
 * \code
 * void
 * state_changed_callback (WpNode * self,
 *                         WpNodeState * old_state,
 *                         WpNodeState * new_state,
 *                         gpointer user_data)
 * \endcode
 *
 * Emitted when the node changes state. This is only emitted when
 * WP_PIPEWIRE_OBJECT_FEATURE_INFO is enabled.
 *
 * Parameters:
 * - `old_state` - the old state
 * - `new_state` - the new state
 *
 * Flags: G_SIGNAL_RUN_LAST
 * \endparblock
 */


enum {
  PROP_STATE = WP_PW_OBJECT_MIXIN_PROP_CUSTOM_START,
  PROP_N_INPUT_PORTS,
  PROP_N_OUTPUT_PORTS,
  PROP_MAX_INPUT_PORTS,
  PROP_MAX_OUTPUT_PORTS,
};

enum {
  SIGNAL_STATE_CHANGED,
  SIGNAL_PORTS_CHANGED,
  N_SIGNALS,
};

static guint32 signals[N_SIGNALS] = {0};

struct _WpNode
{
  WpGlobalProxy parent;
  WpObjectManager *ports_om;
};

static void wp_node_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface);

G_DEFINE_TYPE_WITH_CODE (WpNode, wp_node, WP_TYPE_GLOBAL_PROXY,
    G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT,
        wp_pw_object_mixin_object_interface_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
        wp_node_pw_object_mixin_priv_interface_init))

static void
wp_node_init (WpNode * self)
{
}

static void
wp_node_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object);

  switch (property_id) {
  case PROP_STATE:
    g_value_set_enum (value, d->info ?
        ((struct pw_node_info *) d->info)->state : 0);
    break;
  case PROP_N_INPUT_PORTS:
    g_value_set_uint (value, d->info ?
        ((struct pw_node_info *) d->info)->n_input_ports : 0);
    break;
  case PROP_N_OUTPUT_PORTS:
    g_value_set_uint (value, d->info ?
        ((struct pw_node_info *) d->info)->n_output_ports : 0);
    break;
  case PROP_MAX_INPUT_PORTS:
    g_value_set_uint (value, d->info ?
        ((struct pw_node_info *) d->info)->max_input_ports : 0);
    break;
  case PROP_MAX_OUTPUT_PORTS:
    g_value_set_uint (value, d->info ?
        ((struct pw_node_info *) d->info)->max_output_ports : 0);
    break;
  default:
    wp_pw_object_mixin_get_property (object, property_id, value, pspec);
    break;
  }
}

static void
wp_node_on_ports_om_installed (WpObjectManager *ports_om, WpNode * self)
{
  wp_object_update_features (WP_OBJECT (self), WP_NODE_FEATURE_PORTS, 0);
}

static void
wp_node_emit_ports_changed (WpObjectManager *ports_om, WpNode * self)
{
  g_signal_emit (self, signals[SIGNAL_PORTS_CHANGED], 0);
}

static void
wp_node_enable_feature_ports (WpNode * self)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
  guint32 bound_id = wp_proxy_get_bound_id (WP_PROXY (self));

  wp_debug_object (self, "enabling WP_NODE_FEATURE_PORTS, bound_id:%u",
      bound_id);

  self->ports_om = wp_object_manager_new ();
  wp_object_manager_add_interest (self->ports_om,
      WP_TYPE_PORT,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, PW_KEY_NODE_ID, "=u", bound_id,
      NULL);
  wp_object_manager_request_object_features (self->ports_om,
      WP_TYPE_PORT, WP_OBJECT_FEATURES_ALL);

  g_signal_connect_object (self->ports_om, "installed",
      G_CALLBACK (wp_node_on_ports_om_installed), self, 0);
  g_signal_connect_object (self->ports_om, "objects-changed",
      G_CALLBACK (wp_node_emit_ports_changed), self, 0);

  wp_core_install_object_manager (core, self->ports_om);
}

static WpObjectFeatures
wp_node_get_supported_features (WpObject * object)
{
  return wp_pw_object_mixin_get_supported_features (object)
      | WP_NODE_FEATURE_PORTS;
}

enum {
  STEP_PORTS = WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START,
};

static void
wp_node_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case WP_PW_OBJECT_MIXIN_STEP_BIND:
  case WP_TRANSITION_STEP_ERROR:
    /* base class can handle BIND and ERROR */
    WP_OBJECT_CLASS (wp_node_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO:
    /* just wait, info will be emitted anyway after binding */
    break;
  case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS:
    wp_pw_object_mixin_cache_params (object, missing);
    break;
  case STEP_PORTS:
    wp_node_enable_feature_ports (WP_NODE (object));
    break;
  default:
    g_assert_not_reached ();
  }
}

static void
wp_node_deactivate (WpObject * object, WpObjectFeatures features)
{
  wp_pw_object_mixin_deactivate (object, features);

  if (features & WP_NODE_FEATURE_PORTS) {
    WpNode *self = WP_NODE (object);
    g_clear_object (&self->ports_om);
    wp_object_update_features (object, 0, WP_NODE_FEATURE_PORTS);
  }

  WP_OBJECT_CLASS (wp_node_parent_class)->deactivate (object, features);
}

static const struct pw_node_events node_events = {
  PW_VERSION_NODE_EVENTS,
  .info = (HandleEventInfoFunc(node)) wp_pw_object_mixin_handle_event_info,
  .param = wp_pw_object_mixin_handle_event_param,
};

static void
wp_node_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy,
      node, &node_events);
}

static void
wp_node_pw_proxy_destroyed (WpProxy * proxy)
{
  WpNode *self = WP_NODE (proxy);

  wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy);

  g_clear_object (&self->ports_om);
  wp_object_update_features (WP_OBJECT (self), 0, WP_NODE_FEATURE_PORTS);

  WP_PROXY_CLASS (wp_node_parent_class)->pw_proxy_destroyed (proxy);
}

static void
wp_node_class_init (WpNodeClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->get_property = wp_node_get_property;

  wpobject_class->get_supported_features = wp_node_get_supported_features;
  wpobject_class->activate_get_next_step =
      wp_pw_object_mixin_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_node_activate_execute_step;
  wpobject_class->deactivate = wp_node_deactivate;

  proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Node;
  proxy_class->pw_iface_version = PW_VERSION_NODE;
  proxy_class->pw_proxy_created = wp_node_pw_proxy_created;
  proxy_class->pw_proxy_destroyed = wp_node_pw_proxy_destroyed;

  wp_pw_object_mixin_class_override_properties (object_class);

  g_object_class_install_property (object_class, PROP_STATE,
      g_param_spec_enum ("state", "state", "state", WP_TYPE_NODE_STATE, 0,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_N_INPUT_PORTS,
      g_param_spec_uint ("n-input-ports", "n-input-ports", "n-input-ports",
          0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_N_OUTPUT_PORTS,
      g_param_spec_uint ("n-output-ports", "n-output-ports", "n-output-ports",
          0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_MAX_INPUT_PORTS,
      g_param_spec_uint ("max-input-ports", "max-input-ports", "max-input-ports",
          0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_MAX_OUTPUT_PORTS,
      g_param_spec_uint ("max-output-ports", "max-output-ports", "max-output-ports",
          0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  signals[SIGNAL_STATE_CHANGED] = g_signal_new (
      "state-changed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
      WP_TYPE_NODE_STATE, WP_TYPE_NODE_STATE);

  signals[SIGNAL_PORTS_CHANGED] = g_signal_new (
      "ports-changed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
}

static void
wp_node_process_info (gpointer instance, gpointer old_info, gpointer i)
{
  const struct pw_node_info *info = i;

  if (info->change_mask & PW_NODE_CHANGE_MASK_STATE) {
    enum pw_node_state old_state = old_info ?
        ((struct pw_node_info *) old_info)->state : PW_NODE_STATE_CREATING;
    g_signal_emit (instance, signals[SIGNAL_STATE_CHANGED], 0,
        old_state, info->state);
  }
  if (info->change_mask & PW_NODE_CHANGE_MASK_INPUT_PORTS) {
    g_object_notify (G_OBJECT (instance), "n-input-ports");
    g_object_notify (G_OBJECT (instance), "max-input-ports");
  }
  if (info->change_mask & PW_NODE_CHANGE_MASK_OUTPUT_PORTS) {
    g_object_notify (G_OBJECT (instance), "n-output-ports");
    g_object_notify (G_OBJECT (instance), "max-output-ports");
  }
}

static gint
wp_node_enum_params (gpointer instance, guint32 id,
    guint32 start, guint32 num, WpSpaPod *filter)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  return pw_node_enum_params (d->iface, 0, id, start, num,
      filter ? wp_spa_pod_get_spa_pod (filter) : NULL);
}

static gint
wp_node_set_param (gpointer instance, guint32 id, guint32 flags,
    WpSpaPod * param)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  g_autoptr (WpSpaPod) p = param;
  return pw_node_set_param (d->iface, id, flags,
      wp_spa_pod_get_spa_pod (p));
}

static void
wp_node_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface)
{
  wp_pw_object_mixin_priv_interface_info_init (iface, node, NODE);
  iface->process_info = wp_node_process_info;
  iface->enum_params = wp_node_enum_params;
  iface->set_param = wp_node_set_param;
}

/*!
 * \brief Constructs a node on the PipeWire server by asking the remote factory
 * \a factory_name to create it.
 *
 * Because of the nature of the PipeWire protocol, this operation completes
 * asynchronously at some point in the future. In order to find out when
 * this is done, you should call wp_object_activate(), requesting at least
 * WP_PROXY_FEATURE_BOUND. When this feature is ready, the node is ready for
 * use on the server. If the node cannot be created, this activation operation
 * will fail.
 *
 * \ingroup wpnode
 * \param core the wireplumber core
 * \param factory_name the pipewire factory name to construct the node
 * \param properties (nullable) (transfer full): the properties to pass to the
 *   factory
 * \returns (nullable) (transfer full): the new node or NULL if the core
 *   is not connected and therefore the node cannot be created
 */
WpNode *
wp_node_new_from_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties)
{
  g_autoptr (WpProperties) props = properties;
  return g_object_new (WP_TYPE_NODE,
      "core", core,
      "factory-name", factory_name,
      "global-properties", props,
      NULL);
}

/*!
 * \brief Gets the current state of the node
 * \ingroup wpnode
 * \param self the node
 * \param error (out) (optional) (transfer none): the error
 * \returns the current state of the node
 */
WpNodeState
wp_node_get_state (WpNode * self, const gchar ** error)
{
  g_return_val_if_fail (WP_IS_NODE (self), WP_NODE_STATE_ERROR);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_PIPEWIRE_OBJECT_FEATURE_INFO, WP_NODE_STATE_ERROR);

  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);
  const struct pw_node_info *info = d->info;

  if (error)
    *error = info->error;
  return (WpNodeState) info->state;
}

/*!
 * \brief Gets the number of input ports of this node
 *
 * \remarks Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 *
 * \ingroup wpnode
 * \param self the node
 * \param max (out) (optional): the maximum supported number of input ports
 * \returns the number of input ports of this node, as reported by the node info
 */
guint
wp_node_get_n_input_ports (WpNode * self, guint * max)
{
  g_return_val_if_fail (WP_IS_NODE (self), 0);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0);

  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);
  const struct pw_node_info *info = d->info;

  if (max)
    *max = info->max_input_ports;
  return info->n_input_ports;
}

/*!
 * \brief Gets the number of output ports of this node
 *
 * \remarks Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 *
 * \ingroup wpnode
 * \param self the node
 * \param max (out) (optional): the maximum supported number of output ports
 * \returns the number of output ports of this node, as reported by the node info
 */
guint
wp_node_get_n_output_ports (WpNode * self, guint * max)
{
  g_return_val_if_fail (WP_IS_NODE (self), 0);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0);

  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);
  const struct pw_node_info *info = d->info;

  if (max)
    *max = info->max_output_ports;
  return info->n_output_ports;
}

/*!
 * \brief Gets the number of ports of this node
 *
 * Note that this number may not add up to
 * wp_node_get_n_input_ports() + wp_node_get_n_output_ports()
 * because it is discovered by looking at the number of available ports
 * in the registry, however ports may appear there with a delay or may
 * not appear at all if this client does not have permission to read them
 *
 * \remarks Requires WP_NODE_FEATURE_PORTS
 *
 * \ingroup wpnode
 * \param self the node
 * \returns the number of ports of this node.
 */
guint
wp_node_get_n_ports (WpNode * self)
{
  g_return_val_if_fail (WP_IS_NODE (self), 0);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_NODE_FEATURE_PORTS, 0);

  return wp_object_manager_get_n_objects (self->ports_om);
}

/*!
 * \brief Gets a new iterator that iterates over all the ports that belong
 * to this node
 *
 * \remarks Requires WP_NODE_FEATURE_PORTS
 *
 * \ingroup wpnode
 * \param self the node
 * \returns (transfer full): a WpIterator that iterates over WpPort objects
 */
WpIterator *
wp_node_new_ports_iterator (WpNode * self)
{
  g_return_val_if_fail (WP_IS_NODE (self), NULL);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_NODE_FEATURE_PORTS, NULL);

  return wp_object_manager_new_iterator (self->ports_om);
}

/*!
 * \brief Gets a new iterator that iterates over all
 * the ports that belong to this node and match the constraints
 *
 * The constraints specified in the variable arguments must follow the rules
 * documented in wp_object_interest_new().
 *
 * \remarks Requires WP_NODE_FEATURE_PORTS
 *
 * \ingroup wpnode
 * \param self the node
 * \param ... a list of constraints, terminated by NULL
 * \returns (transfer full): a WpIterator that iterates over WpPort objects
 */
WpIterator *
wp_node_new_ports_filtered_iterator (WpNode * self, ...)
{
  WpObjectInterest *interest;
  va_list args;
  va_start (args, self);
  interest = wp_object_interest_new_valist (WP_TYPE_PORT, &args);
  va_end (args);
  return wp_node_new_ports_filtered_iterator_full (self, interest);
}

/*!
 * \brief Gets a new iterator that iterates over all
 * the ports that belong to this node and match the \a interest
 *
 * \remarks Requires WP_NODE_FEATURE_PORTS
 *
 * \ingroup wpnode
 * \param self the node
 * \param interest (transfer full): the interest
 * \returns (transfer full): a WpIterator that iterates over WpPort objects
 */
WpIterator *
wp_node_new_ports_filtered_iterator_full (WpNode * self,
    WpObjectInterest * interest)
{
  g_return_val_if_fail (WP_IS_NODE (self), NULL);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_NODE_FEATURE_PORTS, NULL);

  return wp_object_manager_new_filtered_iterator_full (self->ports_om,
      interest);
}

/*!
 * \brief Retrieves the first port that matches the constraints
 *
 * The constraints specified in the variable arguments must follow the rules
 * documented in wp_object_interest_new().
 *
 * \remarks Requires WP_NODE_FEATURE_PORTS
 *
 * \ingroup wpnode
 * \param self the node
 * \param ... a list of constraints, terminated by NULL
 * \returns (transfer full) (nullable): the first port that matches the
 *    constraints, or NULL if there is no such port
 */
WpPort *
wp_node_lookup_port (WpNode * self, ...)
{
  WpObjectInterest *interest;
  va_list args;
  va_start (args, self);
  interest = wp_object_interest_new_valist (WP_TYPE_PORT, &args);
  va_end (args);
  return wp_node_lookup_port_full (self, interest);
}

/*!
 * \brief Retrieves the first port that matches the \a interest
 *
 * \remarks Requires WP_NODE_FEATURE_PORTS
 *
 * \ingroup wpnode
 * \param self the node
 * \param interest (transfer full): the interest
 * \returns (transfer full) (nullable): the first port that matches the
 *    \a interest, or NULL if there is no such port
 */
WpPort *
wp_node_lookup_port_full (WpNode * self, WpObjectInterest * interest)
{
  g_return_val_if_fail (WP_IS_NODE (self), NULL);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_NODE_FEATURE_PORTS, NULL);

  return (WpPort *)
      wp_object_manager_lookup_full (self->ports_om, interest);
}

/*!
 * \brief Sends a command to a node
 *
 * Valid commands are the short string reprepsentations of
 * `enum spa_node_command`. For example, "Suspend" or "Flush" are valid commands
 *
 * \ingroup wpnode
 * \param self the node
 * \param command the command
 */
void
wp_node_send_command (WpNode * self, const gchar * command)
{
  WpSpaIdValue command_value = wp_spa_id_value_from_short_name (
      "Spa:Pod:Object:Command:Node", command);

  g_return_if_fail (WP_IS_NODE (self));
  g_return_if_fail (command_value != NULL);
  g_return_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
      WP_PROXY_FEATURE_BOUND);

  struct spa_command cmd =
      SPA_NODE_COMMAND_INIT(wp_spa_id_value_number (command_value));
  pw_node_send_command (wp_proxy_get_pw_proxy (WP_PROXY (self)), &cmd);
}

/*! \defgroup wpimplnode WpImplNode */

enum {
  PROP_PW_IMPL_NODE = WP_PW_OBJECT_MIXIN_PROP_CUSTOM_START,
};

struct _WpImplNode
{
  WpProxy parent;
  GWeakRef core;
  struct pw_impl_node *pw_impl_node;
};

static void wp_impl_node_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface);

G_DEFINE_TYPE_WITH_CODE (WpImplNode, wp_impl_node, WP_TYPE_PROXY,
    G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT,
        wp_pw_object_mixin_object_interface_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
        wp_impl_node_pw_object_mixin_priv_interface_init))

static void
wp_impl_node_init (WpImplNode * self)
{
}

static void
wp_impl_node_constructed (GObject * object)
{
  WpImplNode * self = WP_IMPL_NODE (object);
  WpPwObjectMixinData * data = wp_pw_object_mixin_get_data (self);

  data->info = (gpointer) pw_impl_node_get_info (self->pw_impl_node);
  data->iface = pw_impl_node_get_implementation (self->pw_impl_node);

  /* TODO handle the actual node properties */
  data->properties = wp_properties_new_empty();

  WpObjectFeatures ft =
      wp_pw_object_mixin_get_supported_features (WP_OBJECT (self))
      & ~WP_PROXY_FEATURE_BOUND;
  wp_object_update_features (WP_OBJECT (self), ft, 0);

  G_OBJECT_CLASS (wp_impl_node_parent_class)->constructed (object);
}

static void
wp_impl_node_dispose (GObject * object)
{
  WpImplNode *self = WP_IMPL_NODE (object);

  WpObjectFeatures ft =
      wp_pw_object_mixin_get_supported_features (WP_OBJECT (self))
      & ~WP_PROXY_FEATURE_BOUND;
  wp_object_update_features (WP_OBJECT (self), 0, ft);

  G_OBJECT_CLASS (wp_impl_node_parent_class)->dispose (object);
}

static void
wp_impl_node_finalize (GObject * object)
{
  WpImplNode *self = WP_IMPL_NODE (object);

  g_clear_pointer (&self->pw_impl_node, pw_impl_node_destroy);

  G_OBJECT_CLASS (wp_impl_node_parent_class)->finalize (object);
}

static void
wp_impl_node_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpImplNode *self = WP_IMPL_NODE (object);

  switch (property_id) {
  case PROP_PW_IMPL_NODE:
    self->pw_impl_node = g_value_get_pointer (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_impl_node_get_property (GObject * object, guint property_id, GValue * value,
    GParamSpec * pspec)
{
  WpImplNode *self = WP_IMPL_NODE (object);

  switch (property_id) {
  case PROP_PW_IMPL_NODE:
    g_value_set_pointer (value, self->pw_impl_node);
    break;
  default:
    wp_pw_object_mixin_get_property (object, property_id, value, pspec);
    break;
  }
}

enum {
  STEP_EXPORT = WP_TRANSITION_STEP_CUSTOM_START,
};

static guint
wp_impl_node_activate_get_next_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  /* BOUND is the only feature that can be in @em missing */
  g_return_val_if_fail (missing == WP_PROXY_FEATURE_BOUND,
      WP_TRANSITION_STEP_ERROR);

  return STEP_EXPORT;
}

static void
wp_impl_node_activate_execute_step (WpObject * object,
      WpFeatureActivationTransition * transition, guint step,
      WpObjectFeatures missing)
{
  WpImplNode *self = WP_IMPL_NODE (object);

  switch (step) {
  case STEP_EXPORT: {
    g_autoptr (WpCore) core = wp_object_get_core (object);
    struct pw_core *pw_core = wp_core_get_pw_core (core);
    g_return_if_fail (pw_core);

    wp_proxy_set_pw_proxy (WP_PROXY (self),
        pw_core_export (pw_core, PW_TYPE_INTERFACE_Node, NULL,
            self->pw_impl_node, 0));
    break;
  }
  default:
    g_assert_not_reached ();
  }
}

/*!
 * \struct WpImplNode
 *
 * A WpImplNode allows running a node implementation (`struct pw_impl_node`)
 * locally, loading the implementation from factory or wrapping a manually
 * constructed `pw_impl_node`. This object can then be exported to PipeWire
 * by requesting WP_PROXY_FEATURE_BOUND.
 *
 * \gproperties
 *
 * \gproperty{pw-impl-node, gpointer, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The actual node implementation\, `struct pw_impl_node *`}
 */
static void
wp_impl_node_class_init (WpImplNodeClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;

  object_class->constructed = wp_impl_node_constructed;
  object_class->dispose = wp_impl_node_dispose;
  object_class->finalize = wp_impl_node_finalize;
  object_class->set_property = wp_impl_node_set_property;
  object_class->get_property = wp_impl_node_get_property;

  wpobject_class->get_supported_features =
      wp_pw_object_mixin_get_supported_features;
  wpobject_class->activate_get_next_step = wp_impl_node_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_impl_node_activate_execute_step;

  wp_pw_object_mixin_class_override_properties (object_class);

  g_object_class_install_property (object_class, PROP_PW_IMPL_NODE,
      g_param_spec_pointer ("pw-impl-node", "pw-impl-node",
          "The actual node implementation, struct pw_impl_node *",
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}

static int
impl_node_collect_params (void *data, int seq,
    uint32_t id, uint32_t index, uint32_t next, struct spa_pod *param)
{
  GPtrArray *result = data;
  g_ptr_array_add (result, wp_spa_pod_new_wrap_const (param));
  return 0;
}

static GPtrArray *
wp_impl_node_enum_params_sync (gpointer instance, guint32 id,
      guint32 start, guint32 num, WpSpaPod *filter)
{
  WpImplNode *self = WP_IMPL_NODE (instance);
  GPtrArray *result =
      g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref);

  pw_impl_node_for_each_param (self->pw_impl_node, 1, id, start, num,
      filter ? wp_spa_pod_get_spa_pod (filter) : NULL,
      impl_node_collect_params, result);
  return result;
}

static gint
wp_impl_node_set_param (gpointer instance, guint32 id, guint32 flags,
    WpSpaPod * param)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  g_autoptr (WpSpaPod) p = param;
  return spa_node_set_param (d->iface, id, flags,
      wp_spa_pod_get_spa_pod (p));
}

static void
wp_impl_node_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface)
{
  wp_pw_object_mixin_priv_interface_info_init (iface, node, NODE);
  iface->flags = WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE;
  iface->enum_params_sync = wp_impl_node_enum_params_sync;
  iface->set_param = wp_impl_node_set_param;
}

/*!
 * \brief Constructs a node object from an existing `pw_impl_node`.
 * \ingroup wpimplnode
 * \param core the wireplumber core
 * \param node an existing `pw_impl_node` to wrap
 * \returns (transfer full): A new WpImplNode wrapping \a node
 */
WpImplNode *
wp_impl_node_new_wrap (WpCore * core, struct pw_impl_node * node)
{
  return g_object_new (WP_TYPE_IMPL_NODE,
      "core", core,
      "pw-impl-node", node,
      NULL);
}

/*!
 * \brief Constructs a new node, locally on this process, using the specified
 * \a factory_name.
 *
 * To export this node to the PipeWire server, you need to call
 * wp_object_activate() requesting WP_PROXY_FEATURE_BOUND and
 * wait for the operation to complete.
 *
 * \ingroup wpimplnode
 * \param core the wireplumber core
 * \param factory_name the name of the pipewire factory
 * \param properties (nullable) (transfer full): properties to be passed to node
 *    constructor
 * \returns (nullable) (transfer full): A new WpImplNode wrapping the
 *   node that was constructed by the factory, or %NULL if the factory
 *   does not exist or was unable to construct the node
 */
WpImplNode *
wp_impl_node_new_from_pw_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties)
{
  g_autoptr (WpProperties) props = properties;
  struct pw_context *pw_context = wp_core_get_pw_context (core);
  struct pw_impl_factory *factory = NULL;
  struct pw_impl_node *node = NULL;

  g_return_val_if_fail (pw_context != NULL, NULL);

  factory = pw_context_find_factory (pw_context, factory_name);
  if (!factory) {
    wp_warning ("pipewire factory '%s' not found", factory_name);
    return NULL;
  }

  node = pw_impl_factory_create_object (factory,
      NULL, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE,
      props ? wp_properties_to_pw_properties (props) : NULL, 0);
  if (!node) {
    wp_warning ("failed to create node from factory '%s'", factory_name);
    return NULL;
  }

  return wp_impl_node_new_wrap (core, node);
}
 07070100000077000081A4000000000000000000000001656CC35F00000A55000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/node.h  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_NODE_H__
#define __WIREPLUMBER_NODE_H__

#include "global-proxy.h"
#include "port.h"
#include "iterator.h"
#include "object-interest.h"

G_BEGIN_DECLS

struct pw_impl_node;

/*!
 * \brief The state of the node
 * \ingroup wpnode
 */
typedef enum {
  /*! error state */
  WP_NODE_STATE_ERROR = -1,
  /*! the node is being created */
  WP_NODE_STATE_CREATING = 0,
  /*! the node is suspended, the device might be closed */
  WP_NODE_STATE_SUSPENDED = 1,
  /*! the node is running but there is no active port */
  WP_NODE_STATE_IDLE = 2,
  /*! the node is running */
  WP_NODE_STATE_RUNNING = 3,
} WpNodeState;

/*!
 * \brief An extension of WpProxyFeatures
 * \ingroup wpnode
 */
typedef enum { /*< flags >*/
  /*! caches information about ports, enabling
   * the use of wp_node_get_n_ports(), wp_node_lookup_port(),
   * wp_node_new_ports_iterator() and related methods */
  WP_NODE_FEATURE_PORTS = (WP_PROXY_FEATURE_CUSTOM_START << 0),
} WpNodeFeatures;

/*!
 * \brief The WpNode GType
 * \ingroup wpnode
 */
#define WP_TYPE_NODE (wp_node_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpNode, wp_node, WP, NODE, WpGlobalProxy)

WP_API
WpNode * wp_node_new_from_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties);

WP_API
WpNodeState wp_node_get_state (WpNode * self, const gchar ** error);

WP_API
guint wp_node_get_n_input_ports (WpNode * self, guint * max);

WP_API
guint wp_node_get_n_output_ports (WpNode * self, guint * max);

WP_API
guint wp_node_get_n_ports (WpNode * self);

WP_API
WpIterator * wp_node_new_ports_iterator (WpNode * self);

WP_API
WpIterator * wp_node_new_ports_filtered_iterator (WpNode * self, ...)
    G_GNUC_NULL_TERMINATED;

WP_API
WpIterator * wp_node_new_ports_filtered_iterator_full (WpNode * self,
    WpObjectInterest * interest);

WP_API
WpPort * wp_node_lookup_port (WpNode * self, ...) G_GNUC_NULL_TERMINATED;

WP_API
WpPort * wp_node_lookup_port_full (WpNode * self, WpObjectInterest * interest);

WP_API
void wp_node_send_command (WpNode * self, const gchar *command);

/*!
 * \brief The WpImplNode GType
 * \ingroup wpimplnode
 */
#define WP_TYPE_IMPL_NODE (wp_impl_node_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpImplNode, wp_impl_node, WP, IMPL_NODE, WpProxy)

WP_API
WpImplNode * wp_impl_node_new_wrap (WpCore * core, struct pw_impl_node * node);

WP_API
WpImplNode * wp_impl_node_new_from_pw_factory (WpCore * core,
    const gchar * factory_name, WpProperties * properties);

G_END_DECLS

#endif
   07070100000078000081A4000000000000000000000001656CC35F00007B4D000000000000000000000000000000000000002C00000000wireplumber-0.4.17/lib/wp/object-interest.c   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-object-interest"

#include "object-interest.h"
#include "global-proxy.h"
#include "session-item.h"
#include "proxy-interfaces.h"
#include "log.h"
#include "error.h"

#include <pipewire/pipewire.h>

/*! \defgroup wpobjectinterest WpObjectInterest */
/*!
 * \struct WpObjectInterest
 *
 * An object interest is a helper that is used in WpObjectManager to
 * declare interest in certain kinds of objects.
 *
 * An interest is defined by a GType and a set of constraints on the object's
 * properties. An object "matches" the interest if it is of the specified
 * GType (either the same type or a descendant of it) and all the constraints
 * are satisfied.
 */

struct constraint
{
  WpConstraintType type;
  WpConstraintVerb verb;
  gchar subject_type; /* a basic GVariantType as a single char */
  gchar *subject;
  GVariant *value;
};

struct _WpObjectInterest
{
  grefcount ref;
  gboolean valid;
  GType gtype;
  struct pw_array constraints;
};

G_DEFINE_BOXED_TYPE (WpObjectInterest, wp_object_interest,
                     wp_object_interest_ref, wp_object_interest_unref)

/*!
 * \brief Creates a new interest that declares interest in objects of the specified
 * \a gtype, with the constraints specified in the variable arguments.
 *
 * The variable arguments should be a list of constraints terminated with NULL,
 * where each constraint consists of the following arguments:
 *  - a `WpConstraintType`: the constraint type
 *  - a `const gchar *`: the subject name
 *  - a `const gchar *`: the format string
 *  - 0 or more arguments according to the format string
 *
 * The format string is interpreted as follows:
 *  - the first character is the constraint verb:
 *     - `=`: WP_CONSTRAINT_VERB_EQUALS
 *     - `!`: WP_CONSTRAINT_VERB_NOT_EQUALS
 *     - `c`: WP_CONSTRAINT_VERB_IN_LIST
 *     - `~`: WP_CONSTRAINT_VERB_IN_RANGE
 *     - `#`: WP_CONSTRAINT_VERB_MATCHES
 *     - `+`: WP_CONSTRAINT_VERB_IS_PRESENT
 *     - `-`: WP_CONSTRAINT_VERB_IS_ABSENT
 *  - the rest of the characters are interpreted as a GVariant format string,
 *    as it would be used in g_variant_new()
 *
 * The rest of this function's arguments up to the start of the next constraint
 * depend on the GVariant format part of the format string and are used to
 * construct a GVariant for the constraint's value argument.
 *
 * For further reading on the constraint's arguments, see
 * wp_object_interest_add_constraint()
 *
 * For example, this interest matches objects that are descendands of WpProxy
 * with a "bound-id" between 0 and 100 (inclusive), with a pipewire property
 * called "format.dsp" that contains the string "audio" somewhere in the value
 * and with a pipewire property "port.name" being present (with any value):
 * \code
 * interest = wp_object_interest_new (WP_TYPE_PROXY,
 *     WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "~(uu)", 0, 100,
 *     WP_CONSTRAINT_TYPE_PW_PROPERTY, "format.dsp", "#s", "*audio*",
 *     WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.name", "+",
 *     NULL);
 * \endcode
 *
 * \ingroup wpobjectinterest
 * \param gtype the type of the object to declare interest in
 * \param ... a set of constraints, terminated with NULL
 * \returns (transfer full): the new object interest
 */
WpObjectInterest *
wp_object_interest_new (GType gtype, ...)
{
  WpObjectInterest *self;
  va_list args;
  va_start (args, gtype);
  self = wp_object_interest_new_valist (gtype, &args);
  va_end (args);
  return self;
}

/*!
 * \brief va_list version of wp_object_interest_new()
 *
 * \ingroup wpobjectinterest
 * \param gtype the type of the object to declare interest in
 * \param args pointer to va_list containing the constraints
 * \returns (transfer full): the new object interest
 */
WpObjectInterest *
wp_object_interest_new_valist (GType gtype, va_list *args)
{
  WpObjectInterest *self = wp_object_interest_new_type (gtype);
  WpConstraintType type;

  g_return_val_if_fail (self != NULL, NULL);

  for (type = va_arg (*args, WpConstraintType);
       type != WP_CONSTRAINT_TYPE_NONE;
       type = va_arg (*args, WpConstraintType))
  {
    const gchar *subject, *format;
    WpConstraintVerb verb = 0;
    GVariant *value = NULL;

    subject = va_arg (*args, const gchar *);
    g_return_val_if_fail (subject != NULL, NULL);

    format = va_arg (*args, const gchar *);
    g_return_val_if_fail (format != NULL, NULL);

    verb = format[0];
    if (verb != 0 && format[1] != '\0')
      value = g_variant_new_va (format + 1, NULL, args);

    wp_object_interest_add_constraint (self, type, subject, verb, value);
  }
  return self;
}

/*!
 * \brief Creates a new interest that declares interest in objects of the
 * specified \a gtype, without any property constraints.
 *
 * To add property constraints, you can call wp_object_interest_add_constraint()
 * afterwards.
 *
 * \ingroup wpobjectinterest
 * \param gtype the type of the object to declare interest in
 * \returns (transfer full): the new object interest
 */
WpObjectInterest *
wp_object_interest_new_type (GType gtype)
{
  WpObjectInterest *self = g_slice_new0 (WpObjectInterest);
  g_return_val_if_fail (self != NULL, NULL);
  g_ref_count_init (&self->ref);
  self->gtype = gtype;
  pw_array_init (&self->constraints, sizeof (struct constraint));
  return self;
}

/*!
 * \brief Adds a constraint to this interest. Constraints consist of a \a type,
 * a \a subject, a \a verb and, depending on the \a verb, a \a value.
 *
 * Constraints are almost like a spoken language sentence that declare a
 * condition that must be true in order to consider that an object can match
 * this interest. For instance, a constraint can be "pipewire property
 * 'object.id' equals 10". This would be translated to:
 * \code
 * wp_object_interest_add_constraint (i,
 *    WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id",
 *    WP_CONSTRAINT_VERB_EQUALS, g_variant_new_int (10));
 * \endcode
 *
 * Some verbs require a \a value and some others do not. For those that do,
 * the \a value must be of a specific type:
 *  - WP_CONSTRAINT_VERB_EQUALS: \a value can be a string, a (u)int32,
 *    a (u)int64, a double or a boolean. The \a subject value must equal this
 *    value for the constraint to be satisfied
 *  - WP_CONSTRAINT_VERB_IN_LIST: \a value must be a tuple that contains any
 *    number of items of the same type; the items can be string, (u)int32,
 *    (u)int64 or double. These items make a list that the \a subject's value
 *    will be checked against. If any of the items equals the \a subject value,
 *    the constraint is satisfied
 *  - WP_CONSTRAINT_VERB_IN_RANGE: \a value must be a tuple that contains exactly
 *    2 numbers of the same type ((u)int32, (u)int64 or double), meaning the
 *    minimum and maximum (inclusive) of the range. If the \a subject value is a
 *    number within this range, the constraint is satisfied
 *  - WP_CONSTRAINT_VERB_MATCHES: \a value must be a string that defines a
 *    pattern usable with GPatternSpec If the \a subject value matches this
 *    pattern, the constraint is satisfied
 *
 * In case the type of the \a subject value is not the same type as the one
 * requested by the type of the \a value, the \a subject value is converted.
 * For GObject properties, this conversion is done using g_value_transform(),
 * so limitations of this function apply. In the case of PipeWire properties,
 * which are *always* strings, conversion is done as follows:
 *  - to boolean: `"true"` or `"1"` means TRUE, `"false"` or `"0"` means FALSE
 *  - to int / uint / int64 / uint64: One of the `strtol()` family of functions
 *    is used to convert, using base 10
 *  - to double: `strtod()` is used
 *
 * This method does not fail if invalid arguments are given. However,
 * wp_object_interest_validate() should be called after adding all the
 * constraints on an interest in order to catch errors.
 *
 * \ingroup wpobjectinterest
 * \param self the object interest
 * \param type the constraint type
 * \param subject the subject that the constraint applies to
 * \param verb the operation that is performed to check the constraint
 * \param value (transfer floating)(nullable): the value to check for
 */
void
wp_object_interest_add_constraint (WpObjectInterest * self,
    WpConstraintType type, const gchar * subject,
    WpConstraintVerb verb, GVariant * value)
{
  struct constraint *c;

  g_return_if_fail (self != NULL);

  c = pw_array_add (&self->constraints, sizeof (struct constraint));
  g_return_if_fail (c != NULL);
  c->type = type;
  c->verb = verb;
  /* subject_type is filled in by _validate() */
  c->subject_type = '\0';
  c->subject = g_strdup (subject);
  c->value = value ? g_variant_ref_sink (value) : NULL;

  /* mark as invalid to force validation */
  self->valid = FALSE;
}

/*!
 * \brief Increases the reference count of an object interest
 * \ingroup wpobjectinterest
 * \param self the object interest to ref
 * \returns (transfer full): \a self with an additional reference count on it
 */
WpObjectInterest *
wp_object_interest_ref (WpObjectInterest *self)
{
  g_ref_count_inc (&self->ref);
  return self;
}

static void
wp_object_interest_free (WpObjectInterest * self)
{
  struct constraint *c;

  g_return_if_fail (self != NULL);

  pw_array_for_each (c, &self->constraints) {
    g_clear_pointer (&c->subject, g_free);
    g_clear_pointer (&c->value, g_variant_unref);
  }
  pw_array_clear (&self->constraints);
  g_slice_free (WpObjectInterest, self);
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 *
 * \ingroup wpobjectinterest
 * \param self (transfer full): the object interest to unref
 */
void
wp_object_interest_unref (WpObjectInterest * self)
{
  if (g_ref_count_dec (&self->ref))
    wp_object_interest_free (self);
}

/*!
 * \brief Validates the interest, ensuring that the interest GType
 * is a valid object and that all the constraints have been expressed properly.
 *
 * \remark This is called internally when \a self is first used to find a match,
 * so it is not necessary to call it explicitly
 *
 * \ingroup wpobjectinterest
 * \param self the object interest to validate
 * \param error (out) (optional): the error, in case validation failed
 * \returns TRUE if the interest is valid and can be used in a match,
 *   FALSE otherwise
 */
gboolean
wp_object_interest_validate (WpObjectInterest * self, GError ** error)
{
  struct constraint *c;
  gboolean is_props;

  g_return_val_if_fail (self != NULL, FALSE);

  /* if already validated, we are done */
  if (self->valid)
    return TRUE;

  if (!G_TYPE_IS_OBJECT (self->gtype) && !G_TYPE_IS_INTERFACE (self->gtype) &&
          !g_type_is_a (self->gtype, WP_TYPE_PROPERTIES)) {
    g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
        "type '%s' is not a valid interest type", g_type_name (self->gtype));
    return FALSE;
  }

  is_props = g_type_is_a (self->gtype, WP_TYPE_PROPERTIES);

  pw_array_for_each (c, &self->constraints) {
    const GVariantType *value_type = NULL;

    if (c->type <= WP_CONSTRAINT_TYPE_NONE ||
        c->type > WP_CONSTRAINT_TYPE_G_PROPERTY) {
      g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
          "invalid constraint type %d", c->type);
      return FALSE;
    }

    if (is_props && c->type == WP_CONSTRAINT_TYPE_G_PROPERTY) {
      g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
          "constraint type %d cannot apply to type '%s'",
          c->type, g_type_name (self->gtype));
      return FALSE;
    }

    if (!c->subject) {
      g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
          "constraint subject cannot be NULL");
      return FALSE;
    }

    switch (c->verb) {
      case WP_CONSTRAINT_VERB_EQUALS:
      case WP_CONSTRAINT_VERB_NOT_EQUALS:
      case WP_CONSTRAINT_VERB_IN_LIST:
      case WP_CONSTRAINT_VERB_IN_RANGE:
      case WP_CONSTRAINT_VERB_MATCHES:
        if (!c->value) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "verb %d (%c) requires a value", c->verb, (gchar) c->verb);
          return FALSE;
        }
        value_type = g_variant_get_type (c->value);
        break;

      case WP_CONSTRAINT_VERB_IS_PRESENT:
      case WP_CONSTRAINT_VERB_IS_ABSENT:
        if (c->value) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "verb %d (%c) should not have a value", c->verb, (gchar) c->verb);
          return FALSE;
        }
        break;

      default:
        g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
            "invalid constraint verb %d (%c)", c->verb, (gchar) c->verb);
        return FALSE;
    }

    switch (c->verb) {
      case WP_CONSTRAINT_VERB_EQUALS:
      case WP_CONSTRAINT_VERB_NOT_EQUALS:
        if (!g_variant_type_equal (value_type, G_VARIANT_TYPE_STRING) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_BOOLEAN) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT32) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT32) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT64) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT64) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_DOUBLE)) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "WP_CONSTRAINT_VERB_{NOT_,}EQUALS requires a basic GVariant type"
              " (actual type was '%s')", g_variant_get_type_string (c->value));
          return FALSE;
        }

        break;
      case WP_CONSTRAINT_VERB_IN_LIST: {
        const GVariantType *tuple_type;

        if (!g_variant_type_is_definite (value_type) ||
            !g_variant_type_is_tuple (value_type)) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "WP_CONSTRAINT_VERB_IN_LIST requires a tuple GVariant type"
              " (actual type was '%s')", g_variant_get_type_string (c->value));
          return FALSE;
        }

        for (tuple_type = value_type = g_variant_type_first (value_type);
            tuple_type != NULL;
            tuple_type = g_variant_type_next (tuple_type)) {
          if (!g_variant_type_equal (tuple_type, value_type)) {
            g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
                "tuple must contain children of the same type"
                " (mismatching type was '%s' at '%.*s')",
                g_variant_get_type_string (c->value),
                (int) g_variant_type_get_string_length (tuple_type),
                g_variant_type_peek_string (tuple_type));
            return FALSE;
          }
        }

        if (!g_variant_type_equal (value_type, G_VARIANT_TYPE_STRING) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT32) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT32) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT64) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT64) &&
            !g_variant_type_equal (value_type, G_VARIANT_TYPE_DOUBLE)) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "list tuple must contain string, (u)int32, (u)int64 or double"
              " (mismatching type was '%s' at '%.*s')",
              g_variant_get_type_string (c->value),
              (int) g_variant_type_get_string_length (value_type),
              g_variant_type_peek_string (value_type));
          return FALSE;
        }

        break;
      }
      case WP_CONSTRAINT_VERB_IN_RANGE: {
        const GVariantType *tuple_type;

        if (!g_variant_type_is_definite (value_type) ||
            !g_variant_type_is_tuple (value_type)) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "range requires a tuple GVariant type (actual type was '%s')",
              g_variant_get_type_string (c->value));
          return FALSE;
        }

        tuple_type = value_type = g_variant_type_first (value_type);
        if (!tuple_type) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "range requires a non-empty tuple (actual type was '%s')",
              g_variant_get_type_string (c->value));
          return FALSE;
        }

        if (!g_variant_type_equal (tuple_type, G_VARIANT_TYPE_INT32) &&
            !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_UINT32) &&
            !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_INT64) &&
            !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_UINT64) &&
            !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_DOUBLE)) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "range tuple must contain (u)int32, (u)int64 or double"
              " (mismatching type was '%s' at '%.*s')",
              g_variant_get_type_string (c->value),
              (int) g_variant_type_get_string_length (tuple_type),
              g_variant_type_peek_string (tuple_type));
          return FALSE;
        }

        tuple_type = g_variant_type_next (tuple_type);
        if (!tuple_type || !g_variant_type_equal (tuple_type, value_type)) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "range tuple must contain 2 children of the same type"
              " (mismatching type was '%s' at '%.*s')",
              g_variant_get_type_string (c->value),
              (int) g_variant_type_get_string_length (tuple_type),
              g_variant_type_peek_string (tuple_type));
          return FALSE;
        }

        tuple_type = g_variant_type_next (tuple_type);
        if (tuple_type) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "range tuple must contain exactly 2 children, not more"
              " (mismatching type was '%s')",
              g_variant_get_type_string (c->value));
          return FALSE;
        }

        break;
      }
      case WP_CONSTRAINT_VERB_MATCHES:
        if (!g_variant_type_equal (value_type, G_VARIANT_TYPE_STRING)) {
          g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
              "WP_CONSTRAINT_VERB_MATCHES requires a string GVariant"
              " (actual type was '%s')", g_variant_get_type_string (c->value));
          return FALSE;
        }

        break;
      case WP_CONSTRAINT_VERB_IS_PRESENT:
      case WP_CONSTRAINT_VERB_IS_ABSENT:
        break;
      default:
        g_return_val_if_reached (FALSE);
    }

    /* cache the type that the property must have */
    if (value_type)
      c->subject_type = *g_variant_type_peek_string (value_type);
  }

  return (self->valid = TRUE);
}

G_GNUC_CONST static GType
subject_type_to_gtype (gchar type)
{
  switch (type) {
    case 'b': return G_TYPE_BOOLEAN;
    case 'i': return G_TYPE_INT;
    case 'u': return G_TYPE_UINT;
    case 'x': return G_TYPE_INT64;
    case 't': return G_TYPE_UINT64;
    case 'd': return G_TYPE_DOUBLE;
    case 's': return G_TYPE_STRING;
    default: g_return_val_if_reached (G_TYPE_INVALID);
  }
}

static inline gboolean
property_string_to_gvalue (gchar subj_type, const gchar * str, GValue * val)
{
  g_value_init (val, subject_type_to_gtype (subj_type));

  switch (subj_type) {
    case 'b':
      if (!strcmp (str, "true") || !strcmp (str, "1"))
        g_value_set_boolean (val, TRUE);
      else if (!strcmp (str, "false") || !strcmp (str, "0"))
        g_value_set_boolean (val, FALSE);
      else {
        wp_trace ("failed to convert '%s' to boolean", str);
        return FALSE;
      }
      break;
    case 's':
      g_value_set_static_string (val, str);
      break;

#define CASE_NUMBER(l, T, convert) \
    case l: { \
      g##T number; \
      errno = 0; \
      number = convert; \
      if (errno != 0) { \
        wp_trace ("failed to convert '%s' to " #T, str); \
        return FALSE; \
      } \
      g_value_set_##T (val, number); \
      break; \
    }
    CASE_NUMBER ('i', int, strtol (str, NULL, 10))
    CASE_NUMBER ('u', uint, strtoul (str, NULL, 10))
    CASE_NUMBER ('x', int64, strtoll (str, NULL, 10))
    CASE_NUMBER ('t', uint64, strtoull (str, NULL, 10))
    CASE_NUMBER ('d', double, strtod (str, NULL))
#undef CASE_NUMBER
    default:
      g_return_val_if_reached (FALSE);
  }
  return TRUE;
}

static inline gboolean
constraint_verb_equals (gchar subj_type, const GValue * subj_val,
    GVariant * check_val)
{
  switch (subj_type) {
    case 'd': {
      gdouble a = g_value_get_double (subj_val);
      gdouble b = g_variant_get_double (check_val);
      return G_APPROX_VALUE (a, b, FLT_EPSILON);
    }
    case 's':
      return !g_strcmp0 (g_value_get_string (subj_val),
                         g_variant_get_string (check_val, NULL));
#define CASE_BASIC(l, T, R) \
    case l: \
      return (g_value_get_##T (subj_val) == g_variant_get_##R (check_val));
    CASE_BASIC ('b', boolean, boolean)
    CASE_BASIC ('i', int, int32)
    CASE_BASIC ('u', uint, uint32)
    CASE_BASIC ('x', int64, int64)
    CASE_BASIC ('t', uint64, uint64)
#undef CASE_BASIC
    default:
      g_return_val_if_reached (FALSE);
  }
}

static inline gboolean
constraint_verb_matches (gchar subj_type, const GValue * subj_val,
    GVariant * check_val)
{
  switch (subj_type) {
    case 's': {
      const gchar *check_str = g_variant_get_string (check_val, NULL);
      const gchar *subj_str = g_value_get_string (subj_val);
      if (!check_str || !subj_str)
        return FALSE;
      return g_pattern_match_simple (check_str, subj_str);
    }
    default:
      g_return_val_if_reached (FALSE);
  }
  return TRUE;
}

static inline gboolean
constraint_verb_in_list (gchar subj_type, const GValue * subj_val,
    GVariant * check_val)
{
  GVariantIter iter;
  g_autoptr (GVariant) child = NULL;

  g_variant_iter_init (&iter, check_val);
  while ((child = g_variant_iter_next_value (&iter))) {
    if (constraint_verb_equals (subj_type, subj_val, child))
      return TRUE;
    g_variant_unref (child);
  }
  return FALSE;
}

static inline gboolean
constraint_verb_in_range (gchar subj_type, const GValue * subj_val,
    GVariant * check_val)
{
  switch (subj_type) {
#define CASE_RANGE(l, t, T) \
    case l: { \
      g##T val, min, max; \
      g_variant_get (check_val, "("#t#t")", &min, &max); \
      val = g_value_get_##T (subj_val); \
      if (val < min || val > max) \
        return FALSE; \
      break; \
    }
    CASE_RANGE('i', i, int)
    CASE_RANGE('u', u, uint)
    CASE_RANGE('x', x, int64)
    CASE_RANGE('t', t, uint64)
    CASE_RANGE('d', d, double)
#undef CASE_RANGE
    default:
      g_return_val_if_reached (FALSE);
  }
  return TRUE;
}

/*!
 * \brief Checks if the specified \a object matches the type and all the
 * constraints that are described in \a self
 *
 * If \a self is configured to match GObject subclasses, this is equivalent to
 * `wp_object_interest_matches_full (self, G_OBJECT_TYPE (object), object,
 * NULL, NULL)` and if it is configured to match WpProperties, this is
 * equivalent to `wp_object_interest_matches_full (self, self->gtype, NULL,
 * (WpProperties *) object, NULL);`
 *
 * \ingroup wpobjectinterest
 * \param self the object interest
 * \param object the target object to check for a match
 * \returns TRUE if the object matches, FALSE otherwise
 */
gboolean
wp_object_interest_matches (WpObjectInterest * self, gpointer object)
{
  if (g_type_is_a (self->gtype, WP_TYPE_PROPERTIES)) {
    g_return_val_if_fail (object != NULL, FALSE);
    return wp_object_interest_matches_full (self, 0, self->gtype, NULL,
        (WpProperties *) object, NULL) == WP_INTEREST_MATCH_ALL;
  }
  else {
    g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
    return wp_object_interest_matches_full (self, 0, G_OBJECT_TYPE (object),
        object, NULL, NULL) == WP_INTEREST_MATCH_ALL;
  }
}

/*!
 * \brief A low-level version of wp_object_interest_matches().
 *
 * In this version, the object's type is directly given in \a object_type and
 * is not inferred from the \a object. \a object is only used to check for
 * constraints against GObject properties.
 *
 * \a pw_props and \a pw_global_props are used to check constraints against
 * PipeWire object properties and global properties, respectively.
 *
 * \a object, \a pw_props and \a pw_global_props may be NULL, but in case there
 * are any constraints that require them, the match will fail.
 * As a special case, if \a object is not NULL and is a subclass of WpProxy,
 * then \a pw_props and \a pw_global_props, if required, will be internally
 * retrieved from \a object by calling wp_pipewire_object_get_properties() and
 * wp_global_proxy_get_global_properties() respectively.
 *
 * When \a flags contains WP_INTEREST_MATCH_FLAGS_CHECK_ALL, all the constraints
 * are checked and the returned value contains accurate information about which
 * types of constraints have failed to match, if any. When this flag is not
 * present, this function returns after the first failure has been encountered.
 * This means that the returned flags set will contain all but one flag, which
 * will indicate the kind of constraint that failed (more could have failed,
 * but they are not checked...)
 *
 * \ingroup wpobjectinterest
 * \param self the object interest
 * \param flags flags to alter the behavior of this function
 * \param object_type the type to be checked against the interest's type
 * \param object (type GObject)(transfer none)(nullable): the object to be used for
 *   checking constraints of type WP_CONSTRAINT_TYPE_G_PROPERTY
 * \param pw_props (transfer none)(nullable): the properties to be used for
 *   checking constraints of type WP_CONSTRAINT_TYPE_PW_PROPERTY
 * \param pw_global_props (transfer none)(nullable): the properties to be used for
 *   checking constraints of type WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY
 * \returns flags that indicate which components of the interest match.
 *   WP_INTEREST_MATCH_ALL indicates a fully successful match; any other
 *   combination indicates a failure on the component(s) that do not appear on
 *   the flag set
 */
WpInterestMatch
wp_object_interest_matches_full (WpObjectInterest * self,
    WpInterestMatchFlags flags, GType object_type, gpointer object,
    WpProperties * pw_props, WpProperties * pw_global_props)
{
  WpInterestMatch result = WP_INTEREST_MATCH_ALL;
  g_autoptr (WpProperties) props = NULL;
  g_autoptr (WpProperties) global_props = NULL;
  g_autoptr (GError) error = NULL;
  struct constraint *c;

  g_return_val_if_fail (self != NULL, WP_INTEREST_MATCH_NONE);

  if (G_UNLIKELY (!wp_object_interest_validate (self, &error))) {
    wp_critical_boxed (WP_TYPE_OBJECT_INTEREST, self, "validation failed: %s",
        error->message);
    return WP_INTEREST_MATCH_NONE;
  }

  /* check if the GType matches */
  if (!g_type_is_a (object_type, self->gtype))
    result &= ~WP_INTEREST_MATCH_GTYPE;

  /* prepare for constraint lookups on proxy properties */
  if (object) {
    if (!pw_global_props && WP_IS_GLOBAL_PROXY (object)) {
      WpGlobalProxy *pwg = (WpGlobalProxy *) object;
      pw_global_props = global_props =
          wp_global_proxy_get_global_properties (pwg);
    }

    if (!pw_props && WP_IS_PIPEWIRE_OBJECT (object)) {
      WpObject *oo = (WpObject *) object;
      WpPipewireObject *pwo = (WpPipewireObject *) object;

      if (wp_object_get_active_features (oo) & WP_PIPEWIRE_OBJECT_FEATURE_INFO)
        pw_props = props = wp_pipewire_object_get_properties (pwo);
    }

    if (!pw_global_props && WP_IS_SESSION_ITEM (object)) {
      WpSessionItem *si = (WpSessionItem *) object;
      pw_global_props = props = wp_session_item_get_properties (si);
    }
  }

  /* check all constraints; if any of them fails at any point, fail the match */
  pw_array_for_each (c, &self->constraints) {
    WpProperties *lookup_props = pw_global_props;
    g_auto (GValue) value = G_VALUE_INIT;
    gboolean exists = FALSE;

    /* return early if the match failed and CHECK_ALL is not specified */
    if (!(flags & WP_INTEREST_MATCH_FLAGS_CHECK_ALL) &&
        result != WP_INTEREST_MATCH_ALL)
      return result;

    /* collect, check & convert the subject property */
    switch (c->type) {
      case WP_CONSTRAINT_TYPE_PW_PROPERTY:
        lookup_props = pw_props;
        SPA_FALLTHROUGH;

      case WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY: {
        const gchar *lookup_str = NULL;

        if (lookup_props)
          exists = !!(lookup_str = wp_properties_get (lookup_props, c->subject));

        if (exists && c->subject_type)
          property_string_to_gvalue (c->subject_type, lookup_str, &value);
        break;
      }
      case WP_CONSTRAINT_TYPE_G_PROPERTY: {
        GType value_type;
        GParamSpec *pspec = NULL;

        if (object)
          exists = !!(pspec = g_object_class_find_property (
              G_OBJECT_GET_CLASS (object), c->subject));

        if (exists && c->subject_type) {
          g_value_init (&value, pspec->value_type);
          g_object_get_property (object, c->subject, &value);
          value_type = G_VALUE_TYPE (&value);

          /* transform if not compatible */
          if (value_type != subject_type_to_gtype (c->subject_type)) {
            if (g_value_type_transformable (value_type,
                    subject_type_to_gtype (c->subject_type))) {
              g_auto (GValue) orig = G_VALUE_INIT;
              g_value_init (&orig, value_type);
              g_value_copy (&value, &orig);
              g_value_unset (&value);
              g_value_init (&value, subject_type_to_gtype (c->subject_type));
              g_value_transform (&orig, &value);
            }
            else {
              result &= ~(1 << c->type);
              continue;
            }
          }
        }

        break;
      }
      default:
        g_return_val_if_reached (WP_INTEREST_MATCH_NONE);
    }

    /* match the subject to the constraint's value,
       according to the operation defined by the verb */
    switch (c->verb) {
      case WP_CONSTRAINT_VERB_EQUALS:
        if (!exists ||
            !constraint_verb_equals (c->subject_type, &value, c->value))
          result &= ~(1 << c->type);
        break;
      case WP_CONSTRAINT_VERB_NOT_EQUALS:
        if (exists &&
            constraint_verb_equals (c->subject_type, &value, c->value))
          result &= ~(1 << c->type);
        break;
      case WP_CONSTRAINT_VERB_MATCHES:
        if (!exists ||
            !constraint_verb_matches (c->subject_type, &value, c->value))
          result &= ~(1 << c->type);
        break;
      case WP_CONSTRAINT_VERB_IN_LIST:
        if (!exists ||
            !constraint_verb_in_list (c->subject_type, &value, c->value))
          result &= ~(1 << c->type);
        break;
      case WP_CONSTRAINT_VERB_IN_RANGE:
        if (!exists ||
            !constraint_verb_in_range (c->subject_type, &value, c->value))
          result &= ~(1 << c->type);
        break;
      case WP_CONSTRAINT_VERB_IS_PRESENT:
        if (!exists)
          result &= ~(1 << c->type);
        break;
      case WP_CONSTRAINT_VERB_IS_ABSENT:
        if (exists)
          result &= ~(1 << c->type);
        break;
      default:
        g_return_val_if_reached (WP_INTEREST_MATCH_NONE);
    }
  }
  return result;
}
   07070100000079000081A4000000000000000000000001656CC35F000010B0000000000000000000000000000000000000002C00000000wireplumber-0.4.17/lib/wp/object-interest.h   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_OBJECT_INTEREST_H__
#define __WIREPLUMBER_OBJECT_INTEREST_H__

#include <glib-object.h>
#include "defs.h"
#include "properties.h"

G_BEGIN_DECLS

/*!
 * \brief Constraint types for wp_object_interest_add_constraint()
 * \ingroup wpobjectinterest
 */
typedef enum {
  /*! invalid constraint type */
  WP_CONSTRAINT_TYPE_NONE = 0,
  /*! constraint applies to a PipeWire global property of the object
   *  (the ones returned by wp_global_proxy_get_global_properties()) */
  WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY,
  /*! constraint applies to a PipeWire property of the object
   *  (the ones returned by wp_pipewire_object_get_properties()) */
  WP_CONSTRAINT_TYPE_PW_PROPERTY,
  /*! constraint applies to a GObject property of the object */
  WP_CONSTRAINT_TYPE_G_PROPERTY,
} WpConstraintType;

/*!
 * \brief Verbs to use with wp_object_interest_add_constraint()
 * \ingroup wpobjectinterest
 */
typedef enum {
  /*! the subject's value must equal the constraint's value */
  WP_CONSTRAINT_VERB_EQUALS = '=',
  /*! the subject's value must be different from the constraint's value */
  WP_CONSTRAINT_VERB_NOT_EQUALS = '!',
  /*! the subject's value must equal at least
   *  one of the values in the list given as the constraint's value */
  WP_CONSTRAINT_VERB_IN_LIST = 'c',
  /*! the subject's value must be a number in the range defined
   *  by the constraint's value */
  WP_CONSTRAINT_VERB_IN_RANGE = '~',
  /*! the subject's value must match the pattern specified in the
   *  constraint's value */
  WP_CONSTRAINT_VERB_MATCHES = '#',
  /*! the subject property must exist */
  WP_CONSTRAINT_VERB_IS_PRESENT = '+',
  /*! the subject property must not exist */
  WP_CONSTRAINT_VERB_IS_ABSENT = '-',
} WpConstraintVerb;

/*!
 * \brief Flags that indicate which constraints have been matched in
 *  wp_object_interest_matches_full()
 * \ingroup wpobjectinterest
 */
typedef enum { /*< flags >*/
  WP_INTEREST_MATCH_NONE = 0,
  WP_INTEREST_MATCH_GTYPE = (1 << 0),
  WP_INTEREST_MATCH_PW_GLOBAL_PROPERTIES = (1 << 1),
  WP_INTEREST_MATCH_PW_PROPERTIES = (1 << 2),
  WP_INTEREST_MATCH_G_PROPERTIES = (1 << 3),
} WpInterestMatch;

/*!
 * \brief Special WpInterestMatch value that indicates that all constraints
 * have been matched
 * \ingroup wpobjectinterest
 */
#define WP_INTEREST_MATCH_ALL \
    (WP_INTEREST_MATCH_GTYPE | \
     WP_INTEREST_MATCH_PW_GLOBAL_PROPERTIES | \
     WP_INTEREST_MATCH_PW_PROPERTIES | \
     WP_INTEREST_MATCH_G_PROPERTIES)

/*!
 * \brief Flags to alter the behaviour of wp_object_interest_matches_full()
 * \ingroup wpobjectinterest
 */
typedef enum { /*< flags >*/
  WP_INTEREST_MATCH_FLAGS_NONE = 0,
  /*! check all the constraints instead of returning after the first mis-match */
  WP_INTEREST_MATCH_FLAGS_CHECK_ALL = (1 << 0),
} WpInterestMatchFlags;

/*!
 * \brief The WpObjectInterest GType
 * \ingroup wpobjectinterest
 */
#define WP_TYPE_OBJECT_INTEREST (wp_object_interest_get_type ())
WP_API
GType wp_object_interest_get_type (void) G_GNUC_CONST;

typedef struct _WpObjectInterest WpObjectInterest;

WP_API
WpObjectInterest * wp_object_interest_new (GType gtype, ...) G_GNUC_NULL_TERMINATED;

WP_API
WpObjectInterest * wp_object_interest_new_valist (GType gtype, va_list * args);

WP_API
WpObjectInterest * wp_object_interest_new_type (GType gtype);

WP_API
void wp_object_interest_add_constraint (WpObjectInterest * self,
    WpConstraintType type, const gchar * subject,
    WpConstraintVerb verb, GVariant * value);

WP_API
WpObjectInterest * wp_object_interest_ref (WpObjectInterest *self);

WP_API
void wp_object_interest_unref (WpObjectInterest * self);

WP_API
gboolean wp_object_interest_validate (WpObjectInterest * self, GError ** error);

WP_API
gboolean wp_object_interest_matches (WpObjectInterest * self, gpointer object);

WP_API
WpInterestMatch wp_object_interest_matches_full (WpObjectInterest * self,
    WpInterestMatchFlags flags, GType object_type, gpointer object,
    WpProperties * pw_props, WpProperties * pw_global_props);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpObjectInterest, wp_object_interest_unref)

G_END_DECLS

#endif
0707010000007A000081A4000000000000000000000001656CC35F0000AE16000000000000000000000000000000000000002B00000000wireplumber-0.4.17/lib/wp/object-manager.c    /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-object-manager"

#include "object-manager.h"
#include "log.h"
#include "proxy-interfaces.h"
#include "private/registry.h"

#include <pipewire/pipewire.h>

/*! \defgroup wpobjectmanager WpObjectManager */
/*!
 * \struct WpObjectManager
 *
 * The WpObjectManager class provides a way to collect a set of objects and
 * be notified when objects that fulfill a certain set of criteria are created
 * or destroyed.
 *
 * There are 4 kinds of objects that can be managed by a
 * WpObjectManager:
 *   * remote PipeWire global objects that are advertised on the registry;
 *     these are bound locally to subclasses of WpGlobalProxy
 *   * remote PipeWire global objects that are created by calling a remote
 *     factory through the WirePlumber API; these are very similar to other
 *     global objects but it should be noted that the same WpGlobalProxy
 *     instance that created them appears in the WpObjectManager (as soon as
 *     its WP_PROXY_FEATURE_BOUND is enabled)
 *   * local PipeWire objects that are being exported to PipeWire
 *     (WpImplMetadata, WpImplEndpoint, etc); these appear in the
 *     WpObjectManager as soon as they are exported (so, when their
 *     WP_PROXY_FEATURE_BOUND is enabled)
 *   * WirePlumber-specific objects, such as plugins, factories and session items
 *
 * To start an object manager, you first need to declare interest in a certain
 * kind of object by calling wp_object_manager_add_interest() and then install
 * it on the WpCore with wp_core_install_object_manager().
 *
 * Upon installing a WpObjectManager on a WpCore, any pre-existing objects
 * that match the interests of this WpObjectManager will immediately become
 * available to get through wp_object_manager_new_iterator() and the
 * WpObjectManager \c object-added signal will be emitted for all of them.
 * However, note that if these objects need to be prepared (to activate some
 * features on them), the emission of \c object-added will be delayed. To know
 * when it is safe to access the initial set of objects, wait until the
 * \c installed signal has been emitted. That signal is emitted asynchronously
 * after all the initial objects have been prepared.
 *
 * \gproperties
 *
 * \gproperty{core, WpCore *, G_PARAM_READABLE, The core}
 *
 * \gsignals
 *
 * \par installed
 * \parblock
 * \code
 * void
 * installed_callback (WpObjectManager * self,
 *                     gpointer user_data)
 * \endcode
 *
 * This is emitted once after the object manager is installed with
 * wp_core_install_object_manager(). If there are objects that need to be
 * prepared asynchronously internally, emission of this signal is delayed
 * until all objects are ready.
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 *
 * \par object-added
 * \parblock
 * \code
 * void
 * object_added_callback (WpObjectManager * self,
 *                        gpointer object,
 *                        gpointer user_data)
 * \endcode
 *
 * Emitted when an object that matches the interests of this object manager
 * is made available.
 *
 * Parameters:
 * - `object` - the managed object that was just added
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 *
 * \par object-removed
 * \parblock
 * \code
 * void
 * object_removed_callback (WpObjectManager * self,
 *                          gpointer object,
 *                          gpointer user_data)
 * \endcode
 *
 * Emitted when an object that was previously added on this object manager is
 * now being removed (and most likely destroyed). At the time that this signal
 * is emitted, the object is still alive.
 *
 * Parameters:
 * - `object` - the managed object that is being removed
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 *
 * \par objects-changed
 * \parblock
 * \code
 * void
 * objects_changed_callback (WpObjectManager * self,
 *                          gpointer user_data)
 * \endcode
 *
 * Emitted when one or more objects have been recently added or removed from
 * this object manager. This signal is useful to get notified only once when
 * multiple changes happen in a short timespan. The receiving callback may
 * retrieve the updated list of objects by calling wp_object_manager_new_iterator()
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 */

struct _WpObjectManager
{
  GObject parent;
  GWeakRef core;

  /* element-type: WpObjectInterest* */
  GPtrArray *interests;
  /* element-type: <GType, WpProxyFeatures> */
  GHashTable *features;
  /* objects that we are interested in, without a ref */
  GPtrArray *objects;

  gboolean installed;
  gboolean changed;
  guint pending_objects;
  GSource *idle_source;
};

enum {
  PROP_0,
  PROP_CORE,
};

enum {
  SIGNAL_OBJECT_ADDED,
  SIGNAL_OBJECT_REMOVED,
  SIGNAL_OBJECTS_CHANGED,
  SIGNAL_INSTALLED,
  LAST_SIGNAL,
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE (WpObjectManager, wp_object_manager, G_TYPE_OBJECT)

static void
wp_object_manager_init (WpObjectManager * self)
{
  g_weak_ref_init (&self->core, NULL);
  self->interests = g_ptr_array_new_with_free_func (
      (GDestroyNotify) wp_object_interest_unref);
  self->features = g_hash_table_new (g_direct_hash, g_direct_equal);
  self->objects = g_ptr_array_new ();
  self->installed = FALSE;
  self->changed = FALSE;
  self->pending_objects = 0;
}

static void
wp_object_manager_finalize (GObject * object)
{
  WpObjectManager *self = WP_OBJECT_MANAGER (object);

  if (self->idle_source) {
    g_source_destroy (self->idle_source);
    g_clear_pointer (&self->idle_source, g_source_unref);
  }
  g_clear_pointer (&self->objects, g_ptr_array_unref);
  g_clear_pointer (&self->features, g_hash_table_unref);
  g_clear_pointer (&self->interests, g_ptr_array_unref);
  g_weak_ref_clear (&self->core);

  G_OBJECT_CLASS (wp_object_manager_parent_class)->finalize (object);
}

static void
wp_object_manager_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpObjectManager *self = WP_OBJECT_MANAGER (object);

  switch (property_id) {
  case PROP_CORE:
    g_value_take_object (value, g_weak_ref_get (&self->core));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_object_manager_class_init (WpObjectManagerClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;

  object_class->finalize = wp_object_manager_finalize;
  object_class->get_property = wp_object_manager_get_property;

  /* Install the properties */

  g_object_class_install_property (object_class, PROP_CORE,
      g_param_spec_object ("core", "core", "The WpCore", WP_TYPE_CORE,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  signals[SIGNAL_OBJECT_ADDED] = g_signal_new (
      "object-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT);

  signals[SIGNAL_OBJECT_REMOVED] = g_signal_new (
      "object-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT);

  signals[SIGNAL_OBJECTS_CHANGED] = g_signal_new (
      "objects-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      0, NULL, NULL, NULL, G_TYPE_NONE, 0);

  signals[SIGNAL_INSTALLED] = g_signal_new (
      "installed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      0, NULL, NULL, NULL, G_TYPE_NONE, 0);
}

/*!
 * \brief Constructs a new object manager.
 * \ingroup wpobjectmanager
 * \returns (transfer full): the newly constructed object manager
 */
WpObjectManager *
wp_object_manager_new (void)
{
  return g_object_new (WP_TYPE_OBJECT_MANAGER, NULL);
}

/*!
 * \brief Checks if an object manager is installed.
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \returns TRUE if the object manager is installed (i.e. the
 *   WpObjectManager \c installed signal has been emitted), FALSE otherwise
 */
gboolean
wp_object_manager_is_installed (WpObjectManager * self)
{
  g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), FALSE);
  return self->installed;
}

/*!
 * \brief Equivalent to:
 * \code
 * WpObjectInterest *i = wp_object_interest_new (gtype, ...);
 * wp_object_manager_add_interest_full (self, i);
 * \endcode
 *
 * The constraints specified in the variable arguments must follow the rules
 * documented in wp_object_interest_new().
 *
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \param gtype the GType of the objects that we are declaring interest in
 * \param ... a list of constraints, terminated by NULL
 */
void
wp_object_manager_add_interest (WpObjectManager * self, GType gtype, ...)
{
  WpObjectInterest *interest;
  va_list args;

  g_return_if_fail (WP_IS_OBJECT_MANAGER (self));

  va_start (args, gtype);
  interest = wp_object_interest_new_valist (gtype, &args);
  wp_object_manager_add_interest_full (self, interest);
  va_end (args);
}

/*!
 * \brief Declares interest in a certain kind of object.
 *
 * Interest consists of a GType that the object must be an ancestor of
 * (g_type_is_a() must match) and optionally, a set of additional constraints
 * on certain properties of the object. Refer to WpObjectInterest for more details.
 *
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \param interest (transfer full): the interest
 */
void
wp_object_manager_add_interest_full (WpObjectManager *self,
    WpObjectInterest * interest)
{
  g_autoptr (GError) error = NULL;

  g_return_if_fail (WP_IS_OBJECT_MANAGER (self));

  if (G_UNLIKELY (!wp_object_interest_validate (interest, &error))) {
    wp_critical_object (self, "interest validation failed: %s",
        error->message);
    wp_object_interest_unref (interest);
    return;
  }
  g_ptr_array_add (self->interests, interest);
}

static void
store_children_object_features (GHashTable *store, GType object_type,
    WpObjectFeatures wanted_features)
{
  g_autofree GType *children = NULL;
  GType *child;

  child = children = g_type_children (object_type, NULL);
  while (*child) {
    WpObjectFeatures existing_ft = (WpObjectFeatures) GPOINTER_TO_UINT (
        g_hash_table_lookup (store, GSIZE_TO_POINTER (*child)));
    g_hash_table_insert (store, GSIZE_TO_POINTER (*child),
        GUINT_TO_POINTER (existing_ft | wanted_features));
    store_children_object_features (store, *child, wanted_features);
    child++;
  }
}

/*!
 * \brief Requests the object manager to automatically prepare the
 * \a wanted_features on any managed object that is of the specified
 * \a object_type.
 *
 * These features will always be prepared before the object appears on the
 * object manager.
 *
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \param object_type the WpProxy descendant type
 * \param wanted_features the features to enable on this kind of object
 */
void
wp_object_manager_request_object_features (WpObjectManager *self,
    GType object_type, WpObjectFeatures wanted_features)
{
  g_return_if_fail (WP_IS_OBJECT_MANAGER (self));
  g_return_if_fail (g_type_is_a (object_type, WP_TYPE_OBJECT));

  g_hash_table_insert (self->features, GSIZE_TO_POINTER (object_type),
      GUINT_TO_POINTER (wanted_features));
  store_children_object_features (self->features, object_type, wanted_features);
}

/*!
 * \brief Gets the number of objects managed by the object manager.
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \returns the number of objects managed by this WpObjectManager
 */
guint
wp_object_manager_get_n_objects (WpObjectManager * self)
{
  g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), 0);
  return self->objects->len;
}

struct om_iterator_data
{
  WpObjectManager *om;
  GPtrArray *objects;
  WpObjectInterest *interest;
  guint index;
};

static void
om_iterator_reset (WpIterator *it)
{
  struct om_iterator_data *it_data = wp_iterator_get_user_data (it);
  it_data->index = 0;
}

static gboolean
om_iterator_next (WpIterator *it, GValue *item)
{
  struct om_iterator_data *it_data = wp_iterator_get_user_data (it);

  while (it_data->index < it_data->objects->len) {
    gpointer obj = g_ptr_array_index (it_data->objects, it_data->index++);

    /* take the next object that matches the interest, if any */
    if (!it_data->interest ||
        wp_object_interest_matches (it_data->interest, obj)) {
      g_value_init_from_instance (item, obj);
      return TRUE;
    }
  }
  return FALSE;
}

static gboolean
om_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret,
    gpointer data)
{
  struct om_iterator_data *it_data = wp_iterator_get_user_data (it);
  gpointer *obj, *base;
  guint len;

  obj = base = it_data->objects->pdata;
  len = it_data->objects->len;

  while ((obj - base) < len) {
    /* only pass matching objects to the fold func if we have an interest */
    if (!it_data->interest ||
        wp_object_interest_matches (it_data->interest, *obj)) {
      g_auto (GValue) item = G_VALUE_INIT;
      g_value_init_from_instance (&item, *obj);
      if (!func (&item, ret, data))
        return FALSE;
    }
    obj++;
  }
  return TRUE;
}

static void
om_iterator_finalize (WpIterator *it)
{
  struct om_iterator_data *it_data = wp_iterator_get_user_data (it);
  g_clear_pointer (&it_data->objects, g_ptr_array_unref);
  g_clear_pointer (&it_data->interest, wp_object_interest_unref);
  g_object_unref (it_data->om);
}

static const WpIteratorMethods om_iterator_methods = {
  .version = WP_ITERATOR_METHODS_VERSION,
  .reset = om_iterator_reset,
  .next = om_iterator_next,
  .fold = om_iterator_fold,
  .finalize = om_iterator_finalize,
};

/*!
 * \brief Iterates through all the objects managed by this object manager.
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \returns (transfer full): a WpIterator that iterates over all the managed
 *   objects of this object manager
 */
WpIterator *
wp_object_manager_new_iterator (WpObjectManager * self)
{
  WpIterator *it;
  struct om_iterator_data *it_data;

  g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), NULL);

  it = wp_iterator_new (&om_iterator_methods, sizeof (struct om_iterator_data));
  it_data = wp_iterator_get_user_data (it);
  it_data->om = g_object_ref (self);
  it_data->objects = g_ptr_array_copy (self->objects, NULL, NULL);
  it_data->index = 0;
  return it;
}

/*!
 * \brief Equivalent to:
 * \code
 * WpObjectInterest *i = wp_object_interest_new (gtype, ...);
 * return wp_object_manager_new_filtered_iterator_full (self, i);
 * \endcode
 *
 * The constraints specified in the variable arguments must follow the rules
 * documented in wp_object_interest_new().
 *
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \param gtype the GType of the objects to iterate through
 * \param ... a list of constraints, terminated by NULL
 * \returns (transfer full): a WpIterator that iterates over all the matching
 *   objects of this object manager
 */
WpIterator *
wp_object_manager_new_filtered_iterator (WpObjectManager * self, GType gtype,
    ...)
{
  WpObjectInterest *interest;
  va_list args;

  g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), NULL);

  va_start (args, gtype);
  interest = wp_object_interest_new_valist (gtype, &args);
  va_end (args);

  return wp_object_manager_new_filtered_iterator_full (self, interest);
}

/*!
 * \brief Iterates through all the objects managed by this object manager that
 * match the specified \a interest.
 *
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \param interest (transfer full): the interest
 * \returns (transfer full): a WpIterator that iterates over all the matching
 *   objects of this object manager
 */
WpIterator *
wp_object_manager_new_filtered_iterator_full (WpObjectManager * self,
    WpObjectInterest * interest)
{
  WpIterator *it;
  struct om_iterator_data *it_data;
  g_autoptr (GError) error = NULL;

  g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), NULL);

  if (G_UNLIKELY (!wp_object_interest_validate (interest, &error))) {
    wp_critical_object (self, "interest validation failed: %s",
        error->message);
    wp_object_interest_unref (interest);
    return NULL;
  }

  it = wp_iterator_new (&om_iterator_methods, sizeof (struct om_iterator_data));
  it_data = wp_iterator_get_user_data (it);
  it_data->om = g_object_ref (self);
  it_data->objects = g_ptr_array_copy (self->objects, NULL, NULL);
  it_data->interest = interest;
  it_data->index = 0;
  return it;
}

/*!
 * \brief Equivalent to:
 * \code
 * WpObjectInterest *i = wp_object_interest_new (gtype, ...);
 * return wp_object_manager_lookup_full (self, i);
 * \endcode
 *
 * The constraints specified in the variable arguments must follow the rules
 * documented in wp_object_interest_new().
 *
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \param gtype the GType of the object to lookup
 * \param ... a list of constraints, terminated by NULL
 * \returns (type GObject)(transfer full)(nullable): the first managed object
 *    that matches the lookup interest, or NULL if no object matches
 */
gpointer
wp_object_manager_lookup (WpObjectManager * self, GType gtype, ...)
{
  WpObjectInterest *interest;
  va_list args;

  g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), NULL);

  va_start (args, gtype);
  interest = wp_object_interest_new_valist (gtype, &args);
  va_end (args);

  return wp_object_manager_lookup_full (self, interest);
}

/*!
 * \brief Searches for an object that matches the specified \a interest and
 * returns it, if found.
 *
 * If more than one objects match, only the first one is returned.
 * To find multiple objects that match certain criteria,
 * wp_object_manager_new_filtered_iterator() is more suitable.
 *
 * \ingroup wpobjectmanager
 * \param self the object manager
 * \param interest (transfer full): the interst
 * \returns (type GObject)(transfer full)(nullable): the first managed object
 *    that matches the lookup interest, or NULL if no object matches
 */
gpointer
wp_object_manager_lookup_full (WpObjectManager * self,
    WpObjectInterest * interest)
{
  g_auto (GValue) ret = G_VALUE_INIT;
  g_autoptr (WpIterator) it =
      wp_object_manager_new_filtered_iterator_full (self, interest);

  if (wp_iterator_next (it, &ret))
    return g_value_dup_object (&ret);

  return NULL;
}

static gboolean
wp_object_manager_is_interested_in_object (WpObjectManager * self,
    GObject * object)
{
  guint i;
  WpObjectInterest *interest = NULL;

  for (i = 0; i < self->interests->len; i++) {
    interest = g_ptr_array_index (self->interests, i);
    if (wp_object_interest_matches (interest, object))
      return TRUE;
  }
  return FALSE;
}

static gboolean
wp_object_manager_is_interested_in_global (WpObjectManager * self,
    WpGlobal * global, WpObjectFeatures * wanted_features)
{
  guint i;
  WpObjectInterest *interest = NULL;

  for (i = 0; i < self->interests->len; i++) {
    interest = g_ptr_array_index (self->interests, i);

    /* check all constraints */
    WpInterestMatch match = wp_object_interest_matches_full (interest,
        WP_INTEREST_MATCH_FLAGS_CHECK_ALL, global->type, global->proxy,
        NULL, global->properties);

    /* and consider the manager interested if the type and the globals match...
       if pw_properties / g_properties fail, that's ok because they are not
       known yet (the proxy is likely NULL and properties not yet retrieved) */
    if (SPA_FLAG_IS_SET (match, (WP_INTEREST_MATCH_GTYPE |
                                 WP_INTEREST_MATCH_PW_GLOBAL_PROPERTIES))) {
      gpointer ft = g_hash_table_lookup (self->features,
          GSIZE_TO_POINTER (global->type));
      *wanted_features = (WpObjectFeatures) GPOINTER_TO_UINT (ft);

      /* force INFO to be present so that we can check PW_PROPERTIES constraints */
      if (!(match & WP_INTEREST_MATCH_PW_PROPERTIES) &&
            !(*wanted_features & WP_PIPEWIRE_OBJECT_FEATURE_INFO) &&
            g_type_is_a (global->type, WP_TYPE_PIPEWIRE_OBJECT))
        *wanted_features |= WP_PIPEWIRE_OBJECT_FEATURE_INFO;

      return TRUE;
    }
  }
  return FALSE;
}

static gboolean
idle_emit_objects_changed (WpObjectManager * self)
{
  g_clear_pointer (&self->idle_source, g_source_unref);

  if (G_UNLIKELY (!self->installed)) {
    wp_trace_object (self, "installed");
    g_signal_emit (self, signals[SIGNAL_INSTALLED], 0);
    self->installed = TRUE;
  }
  wp_trace_object (self, "emit objects-changed");
  g_signal_emit (self, signals[SIGNAL_OBJECTS_CHANGED], 0);

  return G_SOURCE_REMOVE;
}

static void
wp_object_manager_maybe_objects_changed (WpObjectManager * self)
{
  wp_trace_object (self, "pending:%u changed:%d idle_source:%p installed:%d",
      self->pending_objects, self->changed, self->idle_source, self->installed);

  /* always wait until there are no pending objects */
  if (self->pending_objects > 0)
    return;

  /* Emit 'objects-changed' when:
   * - there are no pending objects
   * - object-added or object-removed has been emitted at least once
   */
  if (self->changed) {
    self->changed = FALSE;

    /* schedule emission in idle; if it is already scheduled from earlier,
       there is nothing to do; we will emit objects-changed once for all
       changes... win-win */
    if (!self->idle_source) {
      g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
      if (core) {
        wp_core_idle_add_closure (core, &self->idle_source,
            g_cclosure_new_object (
                G_CALLBACK (idle_emit_objects_changed), G_OBJECT (self)));
      }
    }
  }
  /* Emit 'installed' when:
   * - there are no pending objects
   * - !changed: there was no object added
   * - !installed: not already installed
   * - the registry does not have pending globals; these may be interesting
   * to our object manager, so let's wait a bit until they are released
   * and re-evaluate again later
   * - the registry has globals; if we are on early startup where we don't
   * have any globals yet, wait...
   */
  else if (!self->installed) {
    g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
    if (core) {
      WpRegistry *reg = wp_core_get_registry (core);
      if (reg->tmp_globals->len == 0 && reg->globals->len != 0) {
        wp_trace_object (self, "installed");
        g_signal_emit (self, signals[SIGNAL_INSTALLED], 0);
        self->installed = TRUE;
      }
    }
  }
}

/* caller must also call wp_object_manager_maybe_objects_changed() after */
static void
wp_object_manager_add_object (WpObjectManager * self, gpointer object)
{
  if (wp_object_manager_is_interested_in_object (self, object)) {
    wp_trace_object (self, "added: " WP_OBJECT_FORMAT, WP_OBJECT_ARGS (object));
    g_ptr_array_add (self->objects, object);
    g_signal_emit (self, signals[SIGNAL_OBJECT_ADDED], 0, object);
    self->changed = TRUE;
  }
}

/* caller must also call wp_object_manager_maybe_objects_changed() after */
static void
wp_object_manager_rm_object (WpObjectManager * self, gpointer object)
{
  guint index;
  if (g_ptr_array_find (self->objects, object, &index)) {
    g_ptr_array_remove_index_fast (self->objects, index);
    g_signal_emit (self, signals[SIGNAL_OBJECT_REMOVED], 0, object);
    self->changed = TRUE;
  }
}

static void
on_proxy_ready (GObject * proxy, GAsyncResult * res, gpointer data)
{
  g_autoptr (WpObjectManager) self = WP_OBJECT_MANAGER (data);
  g_autoptr (GError) error = NULL;

  self->pending_objects--;

  if (!wp_object_activate_finish (WP_OBJECT (proxy), res, &error)) {
    wp_debug_object (self, "proxy activation failed: %s", error->message);
  } else {
    wp_object_manager_add_object (self, proxy);
  }

  wp_object_manager_maybe_objects_changed (self);
}

/* caller must also call wp_object_manager_maybe_objects_changed() after */
static void
wp_object_manager_add_global (WpObjectManager * self, WpGlobal * global)
{
  WpProxyFeatures features = 0;

  /* do not allow proxies that don't have a defined subclass;
     bind will fail because proxy_class->pw_iface_type is NULL */
  if (global->type == WP_TYPE_GLOBAL_PROXY)
    return;

  if (wp_object_manager_is_interested_in_global (self, global, &features)) {
    g_autoptr (WpCore) core = g_weak_ref_get (&self->core);

    self->pending_objects++;

    if (!global->proxy)
      global->proxy = g_object_new (global->type,
          "core", core,
          "global", global,
          NULL);

    wp_trace_object (self, "adding global:%u -> " WP_OBJECT_FORMAT,
        global->id, WP_OBJECT_ARGS (global->proxy));

    wp_object_activate (WP_OBJECT (global->proxy), features, NULL,
        on_proxy_ready, g_object_ref (self));
  }
}

/*
 * WpRegistry:
 *
 * The registry keeps track of registered objects on the wireplumber core.
 * There are 3 kinds of registered objects:
 *
 * 1) PipeWire global objects, which live in another process.
 *
 *    These objects are represented by a WpGlobal with the
 *    WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY flag set. They appear when
 *    the registry_global() event is fired and are removed by
 *    registry_global_remove(). These objects do not have an associated
 *    WpProxy, unless there is at least one WpObjectManager that is interested
 *    in them. In this case, a WpProxy is constructed and it is owned by the
 *    WpGlobal until the global is removed by the registry_global_remove() event.
 *
 * 2) PipeWire global objects, which were constructed by this process, either
 *    by calling into a remove factory (see wp_node_new_from_factory()) or
 *    by exporting a local object (WpImplSession etc...).
 *
 *    These objects are also represented by a WpGlobal, which may however be
 *    constructed before they appear on the registry. The associated WpProxy
 *    calls into wp_registry_prepare_new_global() at the time it receives
 *    the 'bound' event and creates a global that has the
 *    WP_GLOBAL_FLAG_OWNED_BY_PROXY flag enabled. As the flag name suggests,
 *    these globals are "owned" by the WpProxy and the WpGlobal has no ref
 *    on the WpProxy itself. This allows destroying the proxy in client code
 *    by dropping its last reference.
 *
 *    Normally, these global objects also appear on the pipewire registry. When
 *    this happens, the WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY flag is also added
 *    and that keeps an additional reference on the global (both flags must
 *    be dropped before the WpGlobal is destroyed).
 *
 *    In some cases, such an object might appear first on the registry and
 *    then receive the 'bound' event. In order to handle this situation, globals
 *    are not advertised immediately when they appear on the registry, but
 *    they are added on a tmp_globals list instead, which is emptied on the
 *    next core sync. In all cases, the proxy 'bound' and the registry 'global'
 *    events will be fired in the same sync cycle, so we can catch a late
 *    'bound' event and still associate the proxy with the WpGlobal before
 *    object managers are notified about the existence of this global.
 *
 * 3) WirePlumber global objects (WpModule, WpPlugin, WpSiFactory).
 *
 *    These are local objects that have nothing to do with PipeWire. They do not
 *    have a global id and they are also not subclasses of WpProxy. The registry
 *    always owns a reference on them, so that they are kept alive for as long
 *    as the WpCore is alive.
 */

#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "wp-registry"

static void
wp_registry_notify_add_object (WpRegistry *self, gpointer object)
{
  for (guint i = 0; i < self->object_managers->len; i++) {
    WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
    wp_object_manager_add_object (om, object);
    wp_object_manager_maybe_objects_changed (om);
  }
}

static void
wp_registry_notify_rm_object (WpRegistry *self, gpointer object)
{
  for (guint i = 0; i < self->object_managers->len; i++) {
    WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
    wp_object_manager_rm_object (om, object);
    wp_object_manager_maybe_objects_changed (om);
  }
}

static void
object_manager_destroyed (gpointer data, GObject * om)
{
  WpRegistry *self = data;
  g_ptr_array_remove_fast (self->object_managers, om);
}

/* find the subclass of WpPipewireGloabl that can handle
   the given pipewire interface type of the given version */
static inline GType
find_proxy_instance_type (const char * type, guint32 version)
{
  g_autofree GType *children;
  guint n_children;

  children = g_type_children (WP_TYPE_GLOBAL_PROXY, &n_children);

  for (guint i = 0; i < n_children; i++) {
    WpProxyClass *klass = (WpProxyClass *) g_type_class_ref (children[i]);
    if (g_strcmp0 (klass->pw_iface_type, type) == 0 &&
        klass->pw_iface_version == version) {
      g_type_class_unref (klass);
      return children[i];
    }

    g_type_class_unref (klass);
  }

  return WP_TYPE_GLOBAL_PROXY;
}

/* called by the registry when a global appears */
static void
registry_global (void *data, uint32_t id, uint32_t permissions,
    const char *type, uint32_t version, const struct spa_dict *props)
{
  WpRegistry *self = data;
  GType gtype = find_proxy_instance_type (type, version);

  wp_debug_object (wp_registry_get_core (self),
      "global:%u perm:0x%x type:%s/%u -> %s",
      id, permissions, type, version, g_type_name (gtype));

  wp_registry_prepare_new_global (self, id, permissions,
      WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY, gtype, NULL, props, NULL);
}

/* called by the registry when a global is removed */
static void
registry_global_remove (void *data, uint32_t id)
{
  WpRegistry *self = data;
  WpGlobal *global = NULL;

  if (id < self->globals->len)
    global = g_ptr_array_index (self->globals, id);

  /* if not found, look in the tmp_globals, as it may still not be exposed */
  if (!global) {
    for (guint i = 0; i < self->tmp_globals->len; i++) {
      WpGlobal *g = g_ptr_array_index (self->tmp_globals, i);
      if (g->id == id) {
        global = g;
        break;
      }
    }
  }

  g_return_if_fail (global &&
      global->flags & WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY);

  wp_debug_object (wp_registry_get_core (self),
      "global removed:%u type:%s", id, g_type_name (global->type));

  wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY);
}

static const struct pw_registry_events registry_events = {
  PW_VERSION_REGISTRY_EVENTS,
  .global = registry_global,
  .global_remove = registry_global_remove,
};

void
wp_registry_init (WpRegistry *self)
{
  self->globals =
      g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref);
  self->tmp_globals =
      g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref);
  self->objects = g_ptr_array_new_with_free_func (g_object_unref);
  self->object_managers = g_ptr_array_new ();
}

void
wp_registry_clear (WpRegistry *self)
{
  wp_registry_detach (self);
  g_clear_pointer (&self->globals, g_ptr_array_unref);
  g_clear_pointer (&self->tmp_globals, g_ptr_array_unref);

  /* remove all the registered objects
     this will normally also destroy the object managers, eventually, since
     they are normally ref'ed by modules, which are registered objects */
  {
    g_autoptr (GPtrArray) objlist = g_steal_pointer (&self->objects);

    while (objlist->len > 0) {
      g_autoptr (GObject) object = g_ptr_array_steal_index_fast (objlist,
          objlist->len - 1);
      wp_registry_notify_rm_object (self, object);
    }
  }

  /* in case there are any object managers left,
     remove the weak ref on them and let them be... */
  {
    g_autoptr (GPtrArray) object_mgrs;
    GObject *om;

    object_mgrs = g_steal_pointer (&self->object_managers);

    while (object_mgrs->len > 0) {
      om = g_ptr_array_steal_index_fast (object_mgrs, object_mgrs->len - 1);
      g_object_weak_unref (om, object_manager_destroyed, self);
    }
  }
}

void
wp_registry_attach (WpRegistry *self, struct pw_core *pw_core)
{
  self->pw_registry = pw_core_get_registry (pw_core,
      PW_VERSION_REGISTRY, 0);
  pw_registry_add_listener (self->pw_registry, &self->listener,
      &registry_events, self);
}

void
wp_registry_detach (WpRegistry *self)
{
  if (self->pw_registry) {
    spa_hook_remove (&self->listener);
    pw_proxy_destroy ((struct pw_proxy *) self->pw_registry);
    self->pw_registry = NULL;
  }

  /* remove pipewire globals */
  GPtrArray *objlist = self->globals;
  while (objlist && objlist->len > 0) {
    g_autoptr (WpGlobal) global = g_ptr_array_steal_index_fast (objlist,
        objlist->len - 1);

    if (!global)
      continue;

    if (global->proxy)
      wp_registry_notify_rm_object (self, global->proxy);

    /* remove the APPEARS_ON_REGISTRY flag to unref the proxy if it is owned
      by the registry; set registry to NULL to avoid further interference */
    global->registry = NULL;
    wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY);

    /* the registry's ref on global is dropped here; it may still live if
      there is a proxy that owns a ref on it, but global->registry is set
      to NULL, so there is no further interference */
  }

  /* drop tmp globals as well */
  objlist = self->tmp_globals;
  while (objlist && objlist->len > 0) {
    g_autoptr (WpGlobal) global = g_ptr_array_steal_index_fast (objlist,
        objlist->len - 1);
    wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY);
  }
}

static gboolean
expose_tmp_globals (WpCore *core)
{
  WpRegistry *self = wp_core_get_registry (core);
  g_autoptr (GPtrArray) tmp_globals = NULL;
  g_autoptr (GPtrArray) object_managers = NULL;

  /* in case the registry was cleared in the meantime... */
  if (G_UNLIKELY (!self->tmp_globals))
    return G_SOURCE_REMOVE;

  /* steal the tmp_globals list and replace it with an empty one */
  tmp_globals = self->tmp_globals;
  self->tmp_globals =
      g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref);

  wp_debug_object (core, "exposing %u new globals", tmp_globals->len);

  /* traverse in the order that the globals appeared on the registry */
  for (guint i = 0; i < tmp_globals->len; i++) {
    WpGlobal *g = g_ptr_array_index (tmp_globals, i);

    /* if global was already removed, drop it */
    if (g->flags == 0 || g->id == SPA_ID_INVALID)
      continue;

    /* if old global is owned by proxy, remove it */
    if (self->globals->len > g->id) {
      WpGlobal *old_g = g_ptr_array_index (self->globals, g->id);
      if (old_g && (old_g->flags & WP_GLOBAL_FLAG_OWNED_BY_PROXY))
        wp_global_rm_flag (old_g, WP_GLOBAL_FLAG_OWNED_BY_PROXY);
    }

    g_return_val_if_fail (self->globals->len <= g->id ||
        g_ptr_array_index (self->globals, g->id) == NULL, G_SOURCE_REMOVE);

    /* set the registry, so that wp_global_rm_flag() can work full-scale */
    g->registry = self;

    /* store it in the globals list */
    if (self->globals->len <= g->id)
      g_ptr_array_set_size (self->globals, g->id + 1);
    g_ptr_array_index (self->globals, g->id) = wp_global_ref (g);
  }

  object_managers = g_ptr_array_copy (self->object_managers,
      (GCopyFunc) g_object_ref, NULL);
  g_ptr_array_set_free_func (object_managers, g_object_unref);

  /* notify object managers */
  for (guint i = 0; i < object_managers->len; i++) {
    WpObjectManager *om = g_ptr_array_index (object_managers, i);

    for (guint i = 0; i < tmp_globals->len; i++) {
      WpGlobal *g = g_ptr_array_index (tmp_globals, i);

      /* if global was already removed, drop it */
      if (g->flags == 0 || g->id == SPA_ID_INVALID)
        continue;

      wp_object_manager_add_global (om, g);
    }
    wp_object_manager_maybe_objects_changed (om);
  }

  return G_SOURCE_REMOVE;
}

/*
 * \param new_global (out) (transfer full) (optional): the new global
 *
 * This is normally called up to 2 times in the same sync cycle:
 * one from registry_global(), another from the proxy bound event
 * Unfortunately the order in which those 2 events happen is specific
 * to the implementation of the object, which is why this is implemented
 * with a temporary globals list that get exposed later to the object managers
 */
void
wp_registry_prepare_new_global (WpRegistry * self, guint32 id,
    guint32 permissions, guint32 flag, GType type,
    WpGlobalProxy *proxy, const struct spa_dict *props,
    WpGlobal ** new_global)
{
  g_autoptr (WpGlobal) global = NULL;
  WpCore *core = wp_registry_get_core (self);

  g_return_if_fail (flag != 0);

  for (guint i = 0; i < self->tmp_globals->len; i++) {
    WpGlobal *g = g_ptr_array_index (self->tmp_globals, i);
    if (g->id == id) {
      global = wp_global_ref (g);
      break;
    }
  }

  wp_debug_object (core, "%s WpGlobal:%u type:%s proxy:%p",
      global ? "reuse" : "new", id, g_type_name (type),
      (global && global->proxy) ? global->proxy : proxy);

  if (!global) {
    global = g_rc_box_new0 (WpGlobal);
    global->flags = flag;
    global->id = id;
    global->type = type;
    global->permissions = permissions;
    global->properties = props ?
        wp_properties_new_copy_dict (props) : wp_properties_new_empty ();
    global->proxy = proxy;
    g_ptr_array_add (self->tmp_globals, wp_global_ref (global));

    /* ensure we have 'object.id' so that we can filter by id on object managers */
    wp_properties_setf (global->properties, PW_KEY_OBJECT_ID, "%u", global->id);

    /* schedule exposing when adding the first global */
    if (self->tmp_globals->len == 1) {
      wp_core_idle_add_closure (core, NULL,
          g_cclosure_new_object (G_CALLBACK (expose_tmp_globals), G_OBJECT (core)));
    }
  } else {
    /* store the most permissive permissions */
    if (permissions > global->permissions)
      global->permissions = permissions;

    global->flags |= flag;

    /* store the most deep type (i.e. WpImplNode instead of WpNode),
       so that object-manager interests can work more accurately
       if the interest is on a specific subclass */
    if (g_type_depth (type) > g_type_depth (global->type))
      global->type = type;

    if (proxy) {
      g_return_if_fail (global->proxy == NULL);
      global->proxy = proxy;
    }

    if (props)
      wp_properties_update_from_dict (global->properties, props);
  }

  if (new_global)
    *new_global = g_steal_pointer (&global);
}

/*
 * \brief Finds a registered object
 *
 * \param reg the registry
 * \param func (scope call): a function that takes the object being searched
 *   as the first argument and \a data as the second. it should return TRUE if
 *   the object is found or FALSE otherwise
 * \param data the second argument to \a func
 * \returns (transfer full) (type GObject *) (nullable): the registered object
 *   or NULL if not found
 */
gpointer
wp_registry_find_object (WpRegistry *reg, GEqualFunc func, gconstpointer data)
{
  GObject *object;
  guint i;

  /* prevent bad things when called from within wp_registry_clear() */
  if (G_UNLIKELY (!reg->objects))
    return NULL;

  for (i = 0; i < reg->objects->len; i++) {
    object = g_ptr_array_index (reg->objects, i);
    if (func (object, data))
      return g_object_ref (object);
  }

  return NULL;
}

/*
 * \brief Registers \a obj with the core, making it appear on WpObjectManager
 * instances as well.
 *
 * The core will also maintain a ref to that object until it
 * is removed.
 *
 * \param reg the registry
 * \param obj (transfer full) (type GObject*): the object to register
 */
void
wp_registry_register_object (WpRegistry *reg, gpointer obj)
{
  g_return_if_fail (G_IS_OBJECT (obj));

  /* prevent bad things when called from within wp_registry_clear() */
  if (G_UNLIKELY (!reg->objects)) {
    g_object_unref (obj);
    return;
  }

  g_ptr_array_add (reg->objects, obj);

  /* notify object managers */
  wp_registry_notify_add_object (reg, obj);
}

/*
 * \brief Detaches and unrefs the specified object from this core.
 *
 * \param reg the registry
 * \param obj (transfer none) (type GObject*): a pointer to the object to remove
 */
void
wp_registry_remove_object (WpRegistry *reg, gpointer obj)
{
  g_return_if_fail (G_IS_OBJECT (obj));

  /* prevent bad things when called from within wp_registry_clear() */
  if (G_UNLIKELY (!reg->objects))
    return;

  /* notify object managers */
  wp_registry_notify_rm_object (reg, obj);

  g_ptr_array_remove_fast (reg->objects, obj);
}

/*!
 * \brief Installs the object manager on this core, activating its internal
 * management engine.
 *
 * This will immediately emit signals about objects added on \a om
 * if objects that the \a om is interested in were in existence already.
 *
 * \ingroup wpobjectmanager
 * \param self the core
 * \param om (transfer none): a WpObjectManager
 */
void
wp_core_install_object_manager (WpCore * self, WpObjectManager * om)
{
  WpRegistry *reg;
  guint i;

  g_return_if_fail (WP_IS_CORE (self));
  g_return_if_fail (WP_IS_OBJECT_MANAGER (om));

  reg = wp_core_get_registry (self);

  g_object_weak_ref (G_OBJECT (om), object_manager_destroyed, reg);
  g_ptr_array_add (reg->object_managers, om);
  g_weak_ref_set (&om->core, self);

  /* add pre-existing objects to the object manager,
     in case it's interested in them */
  for (i = 0; i < reg->globals->len; i++) {
    WpGlobal *g = g_ptr_array_index (reg->globals, i);
    /* check if null because the globals array can have gaps */
    if (g)
      wp_object_manager_add_global (om, g);
  }
  for (i = 0; i < reg->objects->len; i++) {
    GObject *o = g_ptr_array_index (reg->objects, i);
    wp_object_manager_add_object (om, o);
  }

  wp_object_manager_maybe_objects_changed (om);
}

/* WpGlobal */

G_DEFINE_BOXED_TYPE (WpGlobal, wp_global, wp_global_ref, wp_global_unref)

void
wp_global_rm_flag (WpGlobal *global, guint rm_flag)
{
  WpRegistry *reg = global->registry;
  guint32 id = global->id;

  /* no flag to remove */
  if (!(global->flags & rm_flag))
    return;

  wp_trace_boxed (WP_TYPE_GLOBAL, global,
      "remove global %u flag 0x%x [flags:0x%x, reg:%p]",
      id, rm_flag, global->flags, reg);

  /* global was owned by the proxy; by removing the flag, we clear out
     also the proxy pointer, which is presumably no longer valid and we
     notify all listeners that the proxy is gone */
  if (rm_flag == WP_GLOBAL_FLAG_OWNED_BY_PROXY) {
    global->flags &= ~WP_GLOBAL_FLAG_OWNED_BY_PROXY;
    if (reg && global->proxy) {
      wp_registry_notify_rm_object (reg, global->proxy);
    }
    global->proxy = NULL;
  }
  /* registry removed the global */
  else if (rm_flag == WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY) {
    global->flags &= ~WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY;

    /* destroy the proxy if it exists */
    if (global->proxy) {
      /* steal the proxy to avoid calling wp_registry_notify_rm_object()
         again while removing OWNED_BY_PROXY;
         keep a temporary ref so that _deactivate() doesn't crash in case the
         pw-proxy-destroyed signal causes external references to be dropped */
      g_autoptr (WpGlobalProxy) proxy =
          g_object_ref (g_steal_pointer (&global->proxy));

      /* notify all listeners that the proxy is gone */
      if (reg)
        wp_registry_notify_rm_object (reg, proxy);

      /* remove FEATURE_BOUND to destroy the underlying pw_proxy */
      wp_object_deactivate (WP_OBJECT (proxy), WP_PROXY_FEATURE_BOUND);

      /* stop all in-progress activations */
      wp_object_abort_activation (WP_OBJECT (proxy), "PipeWire proxy removed");

      /* if the proxy is not owning the global, unref it */
      if (global->flags == 0)
        g_object_unref (proxy);
    }

    /* It's possible to receive consecutive {add, remove, add} events for the
     * same id. Since the WpGlobal might not be destroyed immediately below,
     * (e.g. it's in tmp_globals list), we must invalidate the id now, so that
     * this WpGlobal is not used in reference to objects added later.
     */
    global->id = SPA_ID_INVALID;
    wp_properties_setf (global->properties, PW_KEY_OBJECT_ID, NULL);
  }

  /* drop the registry's ref on global when it does not appear on the registry anymore */
  if (!(global->flags & WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY) && reg) {
    g_clear_pointer (&g_ptr_array_index (reg->globals, id), wp_global_unref);
  }
}

struct pw_proxy *
wp_global_bind (WpGlobal * global)
{
  g_return_val_if_fail (global->proxy, NULL);
  g_return_val_if_fail (global->registry, NULL);

  WpProxyClass *klass = WP_PROXY_GET_CLASS (global->proxy);
  return pw_registry_bind (global->registry->pw_registry, global->id,
      klass->pw_iface_type, klass->pw_iface_version, 0);
}
  0707010000007B000081A4000000000000000000000001656CC35F000006F9000000000000000000000000000000000000002B00000000wireplumber-0.4.17/lib/wp/object-manager.h    /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_OBJECT_MANAGER_H__
#define __WIREPLUMBER_OBJECT_MANAGER_H__

#include <glib-object.h>
#include "object.h"
#include "iterator.h"
#include "object-interest.h"

G_BEGIN_DECLS

/*!
 * \brief The WpObjectManager GType
 * \ingroup wpobjectmanager
 */
#define WP_TYPE_OBJECT_MANAGER (wp_object_manager_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpObjectManager, wp_object_manager, WP, OBJECT_MANAGER, GObject)

WP_API
WpObjectManager * wp_object_manager_new (void);

/* installation */

WP_API
gboolean wp_object_manager_is_installed (WpObjectManager * self);

/* interest */

WP_API
void wp_object_manager_add_interest (WpObjectManager * self,
    GType gtype, ...) G_GNUC_NULL_TERMINATED;

WP_API
void wp_object_manager_add_interest_full (WpObjectManager * self,
    WpObjectInterest * interest);

/* object features */

WP_API
void wp_object_manager_request_object_features (WpObjectManager *self,
    GType object_type, WpObjectFeatures wanted_features);

/* object inspection */

WP_API
guint wp_object_manager_get_n_objects (WpObjectManager * self);

WP_API
WpIterator * wp_object_manager_new_iterator (WpObjectManager * self);

WP_API
WpIterator * wp_object_manager_new_filtered_iterator (WpObjectManager * self,
    GType gtype, ...);

WP_API
WpIterator * wp_object_manager_new_filtered_iterator_full (
    WpObjectManager * self, WpObjectInterest * interest);

WP_API
gpointer wp_object_manager_lookup (WpObjectManager * self,
    GType gtype, ...) G_GNUC_NULL_TERMINATED;

WP_API
gpointer wp_object_manager_lookup_full (WpObjectManager * self,
    WpObjectInterest * interest);

G_END_DECLS

#endif
   0707010000007C000081A4000000000000000000000001656CC35F00004443000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/object.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-object"

#include "object.h"
#include "log.h"
#include "core.h"
#include "error.h"

/*! \defgroup wpfeatureactivationtransition WpFeatureActivationTransition */
/*!
 * \struct WpFeatureActivationTransition
 * A WpTransition that is used by WpObject to implement feature activation.
 */
struct _WpFeatureActivationTransition
{
  WpTransition parent;
  WpObjectFeatures missing;
};

G_DEFINE_TYPE (WpFeatureActivationTransition,
               wp_feature_activation_transition,
               WP_TYPE_TRANSITION)

static void
wp_feature_activation_transition_init (
    WpFeatureActivationTransition * transition)
{
}

static WpObjectFeatures
wp_feature_activation_transition_calc_missing_features (
    WpFeatureActivationTransition * self, WpObject * object)
{
  /* missing features = features that have been requested,
     they are supported and they are not active yet;
     note that supported features may change while the transition is ongoing,
     which is why we store the requested features as they were originally
     and keep trying to activate everything that is supported at the time */
  WpObjectFeatures requested =
      wp_feature_activation_transition_get_requested_features (self);
  WpObjectFeatures supported = wp_object_get_supported_features (object);
  WpObjectFeatures active = wp_object_get_active_features (object);
  return (requested & supported & ~active);
}

static guint
wp_feature_activation_transition_get_next_step (
    WpTransition * transition, guint step)
{
  WpFeatureActivationTransition *self =
      WP_FEATURE_ACTIVATION_TRANSITION (transition);
  WpObject *object = wp_transition_get_source_object (transition);

  self->missing =
      wp_feature_activation_transition_calc_missing_features (self, object);
  wp_trace_object (object, "missing features to activate: 0x%x",
      self->missing);

  /* nothing to activate, we are done */
  if (self->missing == 0)
    return WP_TRANSITION_STEP_NONE;

  g_return_val_if_fail (WP_OBJECT_GET_CLASS (object)->activate_get_next_step,
      WP_TRANSITION_STEP_ERROR);

  step = WP_OBJECT_GET_CLASS (object)->activate_get_next_step (object, self,
      step, self->missing);

  g_return_val_if_fail (step == WP_TRANSITION_STEP_NONE ||
          WP_OBJECT_GET_CLASS (object)->activate_execute_step,
      WP_TRANSITION_STEP_ERROR);
  return step;
}

static void
wp_feature_activation_transition_execute_step (
    WpTransition * transition, guint step)
{
  WpFeatureActivationTransition *self =
      WP_FEATURE_ACTIVATION_TRANSITION (transition);
  WpObject *object = wp_transition_get_source_object (transition);

  WP_OBJECT_GET_CLASS (object)->activate_execute_step (object, self, step,
      self->missing);
}

static void
wp_feature_activation_transition_class_init (
    WpFeatureActivationTransitionClass * klass)
{
  WpTransitionClass *transition_class = (WpTransitionClass *) klass;

  transition_class->get_next_step =
      wp_feature_activation_transition_get_next_step;
  transition_class->execute_step =
      wp_feature_activation_transition_execute_step;
}

/*!
 * \brief Gets the features requested to be activated in this transition.
 * \ingroup wpfeatureactivationtransition
 * \param self the transition
 * \returns the features that were requested to be activated in this transition;
 *   this contains the features as they were passed in wp_object_activate() and
 *   therefore it may contain unsupported or already active features
 */
WpObjectFeatures
wp_feature_activation_transition_get_requested_features (
    WpFeatureActivationTransition * self)
{
  return GPOINTER_TO_UINT (wp_transition_get_data (WP_TRANSITION (self)));
}

/*! \defgroup wpobject WpObject */
/*!
 * \struct WpObject
 *
 * Base class for objects that have activatable features.
 *
 * \gproperties
 *
 * \gproperty{core, WpCore *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The WpCore associated with this object}
 *
 * \gproperty{active-features, guint (WpObjectFeatures), G_PARAM_READABLE,
 *   The active WpObjectFeatures on this proxy}
 *
 * \gproperty{supported-features, guint (WpObjectFeatures), G_PARAM_READABLE,
 *   The supported WpObjectFeatures on this proxy}
 */
typedef struct _WpObjectPrivate WpObjectPrivate;
struct _WpObjectPrivate
{
  /* properties */
  GWeakRef core;

  /* features state */
  WpObjectFeatures ft_active;
  GQueue *transitions; // element-type: WpFeatureActivationTransition*
  GSource *idle_advnc_source;
  GWeakRef ongoing_transition;
};

enum {
  PROP_0,
  PROP_CORE,
  PROP_ACTIVE_FEATURES,
  PROP_SUPPORTED_FEATURES,
};

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpObject, wp_object, G_TYPE_OBJECT)

static void
wp_object_init (WpObject * self)
{
  WpObjectPrivate *priv = wp_object_get_instance_private (self);

  g_weak_ref_init (&priv->core, NULL);
  priv->transitions = g_queue_new ();
  g_weak_ref_init (&priv->ongoing_transition, NULL);
}

static void
wp_object_dispose (GObject * object)
{
  WpObject *self = WP_OBJECT (object);
  WpObjectPrivate *priv = wp_object_get_instance_private (self);

  wp_trace_object (self, "dispose");

  wp_object_deactivate (self, WP_OBJECT_FEATURES_ALL);

  if (priv->idle_advnc_source)
    g_source_destroy (priv->idle_advnc_source);

  G_OBJECT_CLASS (wp_object_parent_class)->dispose (object);
}

static void
wp_object_finalize (GObject * object)
{
  WpObject *self = WP_OBJECT (object);
  WpObjectPrivate *priv = wp_object_get_instance_private (self);

  /* there should be no transitions, since transitions hold a ref on WpObject */
  g_warn_if_fail (g_queue_is_empty (priv->transitions));
  g_clear_pointer (&priv->transitions, g_queue_free);
  g_clear_pointer (&priv->idle_advnc_source, g_source_unref);
  g_weak_ref_clear (&priv->ongoing_transition);
  g_weak_ref_clear (&priv->core);

  /* everything must have been deactivated in dispose() */
  g_warn_if_fail (priv->ft_active == 0);

  G_OBJECT_CLASS (wp_object_parent_class)->finalize (object);
}

static void
wp_object_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpObject *self = WP_OBJECT (object);
  WpObjectPrivate *priv = wp_object_get_instance_private (self);

  switch (property_id) {
  case PROP_CORE:
    g_weak_ref_set (&priv->core, g_value_get_object (value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_object_get_property (GObject * object, guint property_id, GValue * value,
    GParamSpec * pspec)
{
  WpObject *self = WP_OBJECT (object);
  WpObjectPrivate *priv = wp_object_get_instance_private (self);

  switch (property_id) {
  case PROP_CORE:
    g_value_take_object (value, g_weak_ref_get (&priv->core));
    break;
  case PROP_ACTIVE_FEATURES:
    g_value_set_uint (value, priv->ft_active);
    break;
  case PROP_SUPPORTED_FEATURES:
    g_value_set_uint (value, wp_object_get_supported_features (self));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_object_class_init (WpObjectClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;

  object_class->dispose = wp_object_dispose;
  object_class->finalize = wp_object_finalize;
  object_class->get_property = wp_object_get_property;
  object_class->set_property = wp_object_set_property;

  g_object_class_install_property (object_class, PROP_CORE,
      g_param_spec_object ("core", "core", "The WpCore", WP_TYPE_CORE,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_ACTIVE_FEATURES,
      g_param_spec_uint ("active-features", "active-features",
          "The active WpObjectFeatures on this proxy", 0, G_MAXUINT, 0,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_SUPPORTED_FEATURES,
      g_param_spec_uint ("supported-features", "supported-features",
          "The supported WpObjectFeatures on this proxy", 0, G_MAXUINT, 0,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

/*!
 * \brief Gets the core associated with this object.
 *
 * \ingroup wpobject
 * \param self the object
 * \returns (transfer full): the core associated with this object
 */
WpCore *
wp_object_get_core (WpObject * self)
{
  g_return_val_if_fail (WP_IS_OBJECT (self), NULL);

  WpObjectPrivate *priv = wp_object_get_instance_private (self);
  return g_weak_ref_get (&priv->core);
}

/*!
 * \brief Gets the active features of this object.
 * \ingroup wpobject
 * \param self the object
 * \returns A bitset containing the active features of this object
 */
WpObjectFeatures
wp_object_get_active_features (WpObject * self)
{
  g_return_val_if_fail (WP_IS_OBJECT (self), 0);

  WpObjectPrivate *priv = wp_object_get_instance_private (self);
  return priv->ft_active;
}

/*!
 * \brief Gets the supported features of this object.
 * \ingroup wpobject
 * \param self the object
 * \returns A bitset containing the supported features of this object;
 *   note that supported features may change at runtime
 */
WpObjectFeatures
wp_object_get_supported_features (WpObject * self)
{
  g_return_val_if_fail (WP_IS_OBJECT (self), 0);
  g_return_val_if_fail (WP_OBJECT_GET_CLASS (self)->get_supported_features, 0);

  return WP_OBJECT_GET_CLASS (self)->get_supported_features (self);
}

static gboolean
wp_object_advance_transitions (WpObject * self)
{
  WpObjectPrivate *priv = wp_object_get_instance_private (self);
  g_autoptr (WpTransition) t = NULL;

  /* clear before advancing; a transition may need to schedule
     a new call to wp_object_advance_transitions() */
  g_clear_pointer (&priv->idle_advnc_source, g_source_unref);

  /* advance ongoing transition if any */
  t = g_weak_ref_get (&priv->ongoing_transition);
  if (t) {
    wp_transition_advance (t);
    if (!wp_transition_get_completed (t))
      return G_SOURCE_REMOVE;
  }

  /* set next transition and advance */
  if (!g_queue_is_empty (priv->transitions)) {
    WpTransition *next = g_queue_pop_head (priv->transitions);
    g_weak_ref_set (&priv->ongoing_transition, next);
    wp_transition_advance (next);
  }

  return G_SOURCE_REMOVE;
}

static void
on_transition_completed (WpTransition * transition, GParamSpec * param,
    WpObject * self)
{
  WpObjectPrivate *priv = wp_object_get_instance_private (self);

  /* abort activation if a transition failed */
  if (wp_transition_had_error (transition)) {
    wp_object_abort_activation (self, "a transition failed");
    return;
  }

  /* advance pending transitions */
  if (!g_queue_is_empty (priv->transitions) && !priv->idle_advnc_source) {
    g_autoptr (WpCore) core = g_weak_ref_get (&priv->core);
    g_return_if_fail (core != NULL);

    wp_core_idle_add (core, &priv->idle_advnc_source,
        G_SOURCE_FUNC (wp_object_advance_transitions), g_object_ref (self),
        g_object_unref);
  }
}

/*!
 * \brief Callback version of wp_object_activate_closure()
 *
 * \ingroup wpobject
 * \param self the object
 * \param features the features to enable
 * \param cancellable (nullable): a cancellable for the async operation
 * \param callback (scope async): a function to call when activation is complete
 * \param user_data (closure): data for \a callback
 */
void
wp_object_activate (WpObject * self,
    WpObjectFeatures features, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer user_data)
{
  g_return_if_fail (WP_IS_OBJECT (self));

  GClosure *closure = g_cclosure_new (G_CALLBACK (callback), user_data, NULL);

  wp_object_activate_closure (self, features, cancellable, closure);
}

/*!
 * \brief Activates the requested \a features and invokes \a closure when this
 * is done. \a features may contain unsupported or already active features.
 * The operation will filter them and activate only ones that are supported and
 * inactive.
 *
 * If multiple calls to this method is done, the operations will be executed
 * one after the other to ensure features only get activated once.
 *
 * \note \a closure may be invoked in sync while this method is being called,
 * if there are no features to activate.
 *
 * \ingroup wpobject
 * \param self the object
 * \param features the features to enable
 * \param cancellable (nullable): a cancellable for the async operation
 * \param closure (transfer full): the closure to use when activation is completed
 */
void
wp_object_activate_closure (WpObject * self,
    WpObjectFeatures features, GCancellable * cancellable,
    GClosure *closure)
{
  g_return_if_fail (WP_IS_OBJECT (self));

  WpObjectPrivate *priv = wp_object_get_instance_private (self);
  g_autoptr (WpCore) core = g_weak_ref_get (&priv->core);

  g_return_if_fail (core != NULL);

  WpTransition *transition = wp_transition_new_closure (
      WP_TYPE_FEATURE_ACTIVATION_TRANSITION, self, cancellable, closure);
  wp_transition_set_source_tag (transition, wp_object_activate);
  wp_transition_set_data (transition, GUINT_TO_POINTER (features), NULL);
  g_signal_connect_object (transition, "notify::completed",
      G_CALLBACK (on_transition_completed), self, 0);

  g_queue_push_tail (priv->transitions, transition);

  if (!priv->idle_advnc_source) {
    wp_core_idle_add (core, &priv->idle_advnc_source,
        G_SOURCE_FUNC (wp_object_advance_transitions), g_object_ref (self),
        g_object_unref);
  }
}

/*!
 * \brief Finishes the async operation that was started with wp_object_activate()
 *
 * \ingroup wpobject
 * \param self the object
 * \param res the async operation result
 * \param error (out) (optional): the error of the operation, if any
 * \returns TRUE if the requested features were activated,
 *   FALSE if there was an error
 */
gboolean
wp_object_activate_finish (WpObject * self, GAsyncResult * res, GError ** error)
{
  g_return_val_if_fail (WP_IS_OBJECT (self), FALSE);
  g_return_val_if_fail (
      g_async_result_is_tagged (res, wp_object_activate), FALSE);
  return wp_transition_finish (res, error);
}

/*!
 * \brief Deactivates the given \a features, leaving the object in the state
 * it was before they were enabled.
 *
 * This is seldom needed to call manually, but it can be used to save
 * resources if some features are no longer needed.
 *
 * \ingroup wpobject
 * \param self the object
 * \param features the features to deactivate
 */
void
wp_object_deactivate (WpObject * self, WpObjectFeatures features)
{
  g_return_if_fail (WP_IS_OBJECT (self));
  g_return_if_fail (WP_OBJECT_GET_CLASS (self)->deactivate);

  WpObjectPrivate *priv = wp_object_get_instance_private (self);
  WP_OBJECT_GET_CLASS (self)->deactivate (self, features & priv->ft_active);
}

/*!
 * \brief Aborts the current object activation by returning a transition error
 * if any transitions are pending.
 *
 * This is usually used to stop any pending activation if an error happened.
 *
 * \ingroup wpobject
 * \param self the object
 * \param msg the message used in the transition error
 * \since 0.4.6
 */
void
wp_object_abort_activation (WpObject * self, const gchar *msg)
{
  WpObjectPrivate *priv;
  g_autoptr (WpTransition) t = NULL;

  g_return_if_fail (WP_IS_OBJECT (self));

  priv =  wp_object_get_instance_private (self);

  g_clear_pointer (&priv->idle_advnc_source, g_source_unref);

  /* abort ongoing transition if any */
  t = g_weak_ref_get (&priv->ongoing_transition);
  if (t && !wp_transition_get_completed (t)) {
    wp_transition_return_error (t, g_error_new (WP_DOMAIN_LIBRARY,
            WP_LIBRARY_ERROR_OPERATION_FAILED,
            "Object activation aborted: %s", msg));
    return;
  }

  /* recursively abort the queued transitions if any */
  if (!g_queue_is_empty (priv->transitions)) {
    WpTransition *next = g_queue_pop_head (priv->transitions);
    g_weak_ref_set (&priv->ongoing_transition, next);
    wp_object_abort_activation (self, msg);
  }
}

/*!
 * \brief Allows subclasses to update the currently active features.
 *
 * \a activated should contain new features and \a deactivated
 * should contain features that were just deactivated.
 * Calling this method also advances the activation transitions.
 *
 * \remark Private method to be called by subclasses only.
 *
 * \ingroup wpobject
 * \param self the object
 * \param activated the features that were activated, or 0
 * \param deactivated the features that were deactivated, or 0
 */
void
wp_object_update_features (WpObject * self, WpObjectFeatures activated,
    WpObjectFeatures deactivated)
{
  g_return_if_fail (WP_IS_OBJECT (self));

  WpObjectPrivate *priv = wp_object_get_instance_private (self);
  guint old_ft = priv->ft_active;
  g_autoptr (WpTransition) t = NULL;

  priv->ft_active |= activated;
  priv->ft_active &= ~deactivated;

  if (priv->ft_active != old_ft) {
    wp_debug_object (self, "features changed 0x%x -> 0x%x", old_ft,
        priv->ft_active);
    g_object_notify (G_OBJECT (self), "active-features");
  }

  t = g_weak_ref_get (&priv->ongoing_transition);
  if ((t || !g_queue_is_empty (priv->transitions)) && !priv->idle_advnc_source) {
    g_autoptr (WpCore) core = g_weak_ref_get (&priv->core);
    g_return_if_fail (core != NULL);

    wp_core_idle_add (core, &priv->idle_advnc_source,
        G_SOURCE_FUNC (wp_object_advance_transitions), g_object_ref (self),
        g_object_unref);
  }
}
 0707010000007D000081A4000000000000000000000001656CC35F00000CC8000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/object.h    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_OBJECT_H__
#define __WIREPLUMBER_OBJECT_H__

#include "core.h"
#include "transition.h"

G_BEGIN_DECLS

/*!
 * \brief Flags that specify functionality that is available on this class.
 *
 * Use wp_object_activate() to enable more features,
 * wp_object_get_supported_features() to see which features are supported and
 * wp_object_get_active_features() to find out which features are already
 * enabled. Features can also be deactivated later using wp_object_deactivate().
 *
 * Actual feature flags are to be specified by subclasses and their interfaces.
 * WP_OBJECT_FEATURES_ALL is a special value that can be used to activate
 * all the supported features in any given object.
 *
 * \ingroup wpobject
 */
typedef guint WpObjectFeatures;

/*!
 * \brief Special value that can be used to activate
 * all the supported features in any given object.
 * \ingroup wpobject
 */
static const WpObjectFeatures WP_OBJECT_FEATURES_ALL = 0xffffffff;

/*!
 * \brief The WpFeatureActivationTransition GType
 * \ingroup wpfeatureactivationtransition
 */
#define WP_TYPE_FEATURE_ACTIVATION_TRANSITION \
    (wp_feature_activation_transition_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpFeatureActivationTransition,
                      wp_feature_activation_transition,
                      WP, FEATURE_ACTIVATION_TRANSITION, WpTransition)

WP_API
WpObjectFeatures wp_feature_activation_transition_get_requested_features (
    WpFeatureActivationTransition * self);

/*!
 * \brief The WpObject GType
 * \ingroup wpobject
 */
#define WP_TYPE_OBJECT (wp_object_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpObject, wp_object, WP, OBJECT, GObject)

struct _WpObjectClass
{
  GObjectClass parent_class;

  WpObjectFeatures (*get_supported_features) (WpObject * self);

  guint (*activate_get_next_step) (WpObject * self,
      WpFeatureActivationTransition * transition, guint step,
      WpObjectFeatures missing);
  void (*activate_execute_step) (WpObject * self,
      WpFeatureActivationTransition * transition, guint step,
      WpObjectFeatures missing);

  void (*deactivate) (WpObject * self, WpObjectFeatures features);

  /*< private >*/
  WP_PADDING(8)
};

WP_API
WpCore * wp_object_get_core (WpObject * self);

WP_API
WpObjectFeatures wp_object_get_active_features (WpObject * self);

WP_API
WpObjectFeatures wp_object_get_supported_features (WpObject * self);

WP_API
void wp_object_activate (WpObject * self,
    WpObjectFeatures features, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer user_data);

WP_API
void wp_object_activate_closure (WpObject * self,
    WpObjectFeatures features, GCancellable * cancellable, GClosure *closure);

WP_API
gboolean wp_object_activate_finish (WpObject * self, GAsyncResult * res,
    GError ** error);

WP_API
void wp_object_deactivate (WpObject * self, WpObjectFeatures features);

/* for subclasses only */

WP_API
void wp_object_abort_activation (WpObject * self, const gchar *msg);

WP_API
void wp_object_update_features (WpObject * self, WpObjectFeatures activated,
    WpObjectFeatures deactivated);

G_END_DECLS

#endif
0707010000007E000081A4000000000000000000000001656CC35F00001B43000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/plugin.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-plugin"

#include "plugin.h"
#include "log.h"
#include "private/registry.h"

/*! \defgroup wpplugin WpPlugin */
/*!
 * \struct WpPlugin
 *
 * WpPlugin is a base class for objects that provide functionality to the
 * WirePlumber daemon.
 *
 * Typically, a plugin is created within a module and then registered to
 * make it available for use by the daemon. The daemon is responsible for
 * calling wp_object_activate() on it after all modules have been loaded,
 * the core is connected and the initial discovery of global objects is
 * done.
 *
 * Being a WpObject subclass, the plugin inherits WpObject's activation system.
 * For most implementations, there is only need for activating one
 * feature, WP_PLUGIN_FEATURE_ENABLED, and this can be done by implementing
 * only WpPluginClass::enable() and WpPluginClass::disable().
 * For more advanced plugins that need to have more features, you may
 * implement directly the functions of WpObjectClass and ignore the ones of
 * WpPluginClass.
 *
 * \gproperties
 *
 * \gproperty{name, gchar *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The name of this plugin}
 */

enum {
  PROP_0,
  PROP_NAME,
};

typedef struct _WpPluginPrivate WpPluginPrivate;
struct _WpPluginPrivate
{
  GQuark name_quark;
};

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpPlugin, wp_plugin, WP_TYPE_OBJECT)

static void
wp_plugin_init (WpPlugin * self)
{
}

static void
wp_plugin_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpPlugin *self = WP_PLUGIN (object);
  WpPluginPrivate *priv = wp_plugin_get_instance_private (self);

  switch (property_id) {
  case PROP_NAME:
    priv->name_quark = g_quark_from_string (g_value_get_string (value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_plugin_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpPlugin *self = WP_PLUGIN (object);
  WpPluginPrivate *priv = wp_plugin_get_instance_private (self);

  switch (property_id) {
  case PROP_NAME:
    g_value_set_string (value, g_quark_to_string (priv->name_quark));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static WpObjectFeatures
wp_plugin_get_supported_features (WpObject * self)
{
  return WP_PLUGIN_FEATURE_ENABLED;
}

enum {
  STEP_ENABLE = WP_TRANSITION_STEP_CUSTOM_START,
};

static guint
wp_plugin_activate_get_next_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  /* we only support ENABLED, so this is the only
     feature that can be in @em missing */
  g_return_val_if_fail (missing == WP_PLUGIN_FEATURE_ENABLED,
      WP_TRANSITION_STEP_ERROR);

  return STEP_ENABLE;
}

static void
wp_plugin_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case STEP_ENABLE: {
    WpPlugin *self = WP_PLUGIN (object);
    wp_info_object (self, "enabling plugin '%s'", wp_plugin_get_name (self));
    g_return_if_fail (WP_PLUGIN_GET_CLASS (self)->enable);
    WP_PLUGIN_GET_CLASS (self)->enable (self, WP_TRANSITION (transition));
    break;
  }
  case WP_TRANSITION_STEP_ERROR:
    break;
  default:
    g_assert_not_reached ();
  }
}

static void
wp_plugin_deactivate (WpObject * object, WpObjectFeatures features)
{
  if (features & WP_PLUGIN_FEATURE_ENABLED) {
    WpPlugin *self = WP_PLUGIN (object);
    wp_info_object (self, "disabling plugin '%s'", wp_plugin_get_name (self));
    if (WP_PLUGIN_GET_CLASS (self)->disable)
      WP_PLUGIN_GET_CLASS (self)->disable (self);
    wp_object_update_features (WP_OBJECT (self), 0, WP_PLUGIN_FEATURE_ENABLED);
  }
}

static void
wp_plugin_class_init (WpPluginClass * klass)
{
  GObjectClass * object_class = (GObjectClass *) klass;
  WpObjectClass * wpobject_class = (WpObjectClass *) klass;

  object_class->set_property = wp_plugin_set_property;
  object_class->get_property = wp_plugin_get_property;

  wpobject_class->get_supported_features = wp_plugin_get_supported_features;
  wpobject_class->activate_get_next_step = wp_plugin_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_plugin_activate_execute_step;
  wpobject_class->deactivate = wp_plugin_deactivate;

  g_object_class_install_property (object_class, PROP_NAME,
      g_param_spec_string ("name", "name",
          "The name of this plugin", NULL,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}

/*!
 * \brief Registers the plugin to its associated core, making it available for use
 *
 * \ingroup wpplugin
 * \param plugin (transfer full): the plugin
 */
void
wp_plugin_register (WpPlugin * plugin)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
  g_return_if_fail (WP_IS_CORE (core));

  wp_registry_register_object (wp_core_get_registry (core), plugin);
}

static gboolean
find_plugin_func (gpointer plugin, gpointer name_quark)
{
  if (!WP_IS_PLUGIN (plugin))
    return FALSE;

  WpPluginPrivate *priv = wp_plugin_get_instance_private (plugin);
  return priv->name_quark == GPOINTER_TO_UINT (name_quark);
}

/*!
 * \brief Looks up a plugin.
 *
 * \ingroup wpplugin
 * \param core the core
 * \param plugin_name the lookup name
 * \returns (transfer full) (nullable): the plugin matching the lookup name
 */
WpPlugin *
wp_plugin_find (WpCore * core, const gchar * plugin_name)
{
  g_return_val_if_fail (WP_IS_CORE (core), NULL);

  GQuark q = g_quark_try_string (plugin_name);
  if (q == 0)
    return NULL;
  GObject *p = wp_registry_find_object (wp_core_get_registry (core),
      (GEqualFunc) find_plugin_func, GUINT_TO_POINTER (q));
  return p ? WP_PLUGIN (p) : NULL;
}

/*!
 * \brief Retreives the name of a plugin.
 *
 * \ingroup wpplugin
 * \param self the plugin
 * \returns the name of this plugin
 */
const gchar *
wp_plugin_get_name (WpPlugin * self)
{
  g_return_val_if_fail (WP_IS_PLUGIN (self), NULL);

  WpPluginPrivate *priv = wp_plugin_get_instance_private (self);
  return g_quark_to_string (priv->name_quark);
}

/**
 * \var _WpPluginClass::enable
 *
 * \brief Enables the plugin. The plugin is required to start any operations
 * only when this method is called and not before.
 *
 * When enabling the plugin is done, you must call wp_object_update_features()
 * with WP_PLUGIN_FEATURE_ENABLED marked as activated, or return an error
 * on \a transition.
 *
 * \param self the plugin
 * \param transition the activation transition
 */

/**
 * \var _WpPluginClass::disable
 *
 * \brief Disables the plugin. The plugin is required to stop all operations
 * and release all resources associated with it.
 *
 * \param self the plugin
 */
 0707010000007F000081A4000000000000000000000001656CC35F0000042E000000000000000000000000000000000000002300000000wireplumber-0.4.17/lib/wp/plugin.h    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_PLUGIN_H__
#define __WIREPLUMBER_PLUGIN_H__

#include "object.h"

G_BEGIN_DECLS

/*!
 * \brief Flags to be used as WpObjectFeatures on WpPlugin subclasses.
 * \ingroup wpplugin
 */
typedef enum { /*< flags >*/
  /*! enables the plugin */
  WP_PLUGIN_FEATURE_ENABLED = (1 << 0),
} WpPluginFeatures;

/*!
 * \brief The WpPlugin GType
 * \ingroup wpplugin
 */
#define WP_TYPE_PLUGIN (wp_plugin_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpPlugin, wp_plugin, WP, PLUGIN, WpObject)

struct _WpPluginClass
{
  WpObjectClass parent_class;

  void (*enable) (WpPlugin * self, WpTransition * transition);
  void (*disable) (WpPlugin * self);

  /*< private >*/
  WP_PADDING(6)
};

WP_API
void wp_plugin_register (WpPlugin * plugin);

WP_API
WpPlugin * wp_plugin_find (WpCore * core, const gchar * plugin_name);

WP_API
const gchar * wp_plugin_get_name (WpPlugin * self);

G_END_DECLS

#endif
  07070100000080000081A4000000000000000000000001656CC35F00001195000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/port.c  /* WirePlumber
 *
 * Copyright © 2019-2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-port"

#include "port.h"
#include "private/pipewire-object-mixin.h"

/*! \defgroup wpport WpPort */
/*!
 * \struct WpPort
 *
 * The WpPort class allows accessing the properties
 * and methods of a PipeWire port object (`struct pw_port`).
 *
 * A WpPort is constructed internally when a new port appears
 * on the PipeWire registry and it is made available through the
 * WpObjectManager API.
 */

struct _WpPort
{
  WpGlobalProxy parent;
};

static void wp_port_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface);

G_DEFINE_TYPE_WITH_CODE (WpPort, wp_port, WP_TYPE_GLOBAL_PROXY,
    G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT,
        wp_pw_object_mixin_object_interface_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
        wp_port_pw_object_mixin_priv_interface_init))

static void
wp_port_init (WpPort * self)
{
}

static void
wp_port_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case WP_PW_OBJECT_MIXIN_STEP_BIND:
  case WP_TRANSITION_STEP_ERROR:
    /* base class can handle BIND and ERROR */
    WP_OBJECT_CLASS (wp_port_parent_class)->
        activate_execute_step (object, transition, step, missing);
    break;
  case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO:
    /* just wait, info will be emitted anyway after binding */
    break;
  case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS:
    wp_pw_object_mixin_cache_params (object, missing);
    break;
  default:
    g_assert_not_reached ();
  }
}

static void
wp_port_deactivate (WpObject * object, WpObjectFeatures features)
{
  wp_pw_object_mixin_deactivate (object, features);
  WP_OBJECT_CLASS (wp_port_parent_class)->deactivate (object, features);
}

static const struct pw_port_events port_events = {
  PW_VERSION_PORT_EVENTS,
  .info = (HandleEventInfoFunc(port)) wp_pw_object_mixin_handle_event_info,
  .param = wp_pw_object_mixin_handle_event_param,
};

static void
wp_port_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy,
      port, &port_events);
}

static void
wp_port_pw_proxy_destroyed (WpProxy * proxy)
{
  wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy);

  WP_PROXY_CLASS (wp_port_parent_class)->pw_proxy_destroyed (proxy);
}

static void
wp_port_class_init (WpPortClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;
  WpProxyClass *proxy_class = (WpProxyClass *) klass;

  object_class->get_property = wp_pw_object_mixin_get_property;

  wpobject_class->get_supported_features =
      wp_pw_object_mixin_get_supported_features;
  wpobject_class->activate_get_next_step =
      wp_pw_object_mixin_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_port_activate_execute_step;
  wpobject_class->deactivate = wp_port_deactivate;

  proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Port;
  proxy_class->pw_iface_version = PW_VERSION_PORT;
  proxy_class->pw_proxy_created = wp_port_pw_proxy_created;
  proxy_class->pw_proxy_destroyed = wp_port_pw_proxy_destroyed;

  wp_pw_object_mixin_class_override_properties (object_class);
}

static gint
wp_port_enum_params (gpointer instance, guint32 id,
    guint32 start, guint32 num, WpSpaPod *filter)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  return pw_port_enum_params (d->iface, 0, id, start, num,
      filter ? wp_spa_pod_get_spa_pod (filter) : NULL);
}

static void
wp_port_pw_object_mixin_priv_interface_init (
    WpPwObjectMixinPrivInterface * iface)
{
  wp_pw_object_mixin_priv_interface_info_init (iface, port, PORT);
  iface->enum_params = wp_port_enum_params;
}

/*!
 * \brief Gets the current direction of the port
 * \remarks Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 * \ingroup wpport
 * \param self the port
 * \returns the current direction of the port
 */
WpDirection
wp_port_get_direction (WpPort * self)
{
  g_return_val_if_fail (WP_IS_PORT (self), 0);
  g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
          WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0);

  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self);
  const struct pw_port_info *info = d->info;

  return (WpDirection) info->direction;
}
   07070100000081000081A4000000000000000000000001656CC35F000002DB000000000000000000000000000000000000002100000000wireplumber-0.4.17/lib/wp/port.h  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_PORT_H__
#define __WIREPLUMBER_PORT_H__

#include "global-proxy.h"

G_BEGIN_DECLS

/*!
 * \brief The different directions that a port can have
 * \ingroup wpport
 */
typedef enum {
  WP_DIRECTION_INPUT, /*!< a sink, consuming input */
  WP_DIRECTION_OUTPUT, /*!< a source, producing output */
} WpDirection;

/*!
 * \brief The WpPort GType
 * \ingroup wpport
 */
#define WP_TYPE_PORT (wp_port_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpPort, wp_port, WP, PORT, WpGlobalProxy)

WP_API
WpDirection wp_port_get_direction (WpPort * self);

G_END_DECLS

#endif
 07070100000082000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002200000000wireplumber-0.4.17/lib/wp/private 07070100000083000081A4000000000000000000000001656CC35F00008022000000000000000000000000000000000000003A00000000wireplumber-0.4.17/lib/wp/private/pipewire-object-mixin.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-pw-obj-mixin"

#include "private/pipewire-object-mixin.h"
#include "core.h"
#include "spa-type.h"
#include "spa-pod.h"
#include "log.h"
#include "error.h"

#include <spa/utils/result.h>

G_DEFINE_INTERFACE (WpPwObjectMixinPriv, wp_pw_object_mixin_priv, WP_TYPE_PROXY)

static void
wp_pw_object_mixin_priv_default_init (WpPwObjectMixinPrivInterface * iface)
{
}

static struct spa_param_info *
find_param_info (gpointer instance, guint32 id)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance);

  /* offsets are 0 on objects that don't support params */
  if (d->info && iface->n_params_offset && iface->param_info_offset) {
    struct spa_param_info * param_info =
        G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset);
    guint32 n_params =
        G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset);

    for (guint i = 0; i < n_params; i++) {
      if (param_info[i].id == id)
        return &param_info[i];
    }
  }
  return NULL;
}

/*************/
/* INTERFACE */

static gconstpointer
wp_pw_object_mixin_get_native_info (WpPipewireObject * obj)
{
  return wp_pw_object_mixin_get_data (obj)->info;
}

static WpProperties *
wp_pw_object_mixin_get_properties (WpPipewireObject * obj)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj);
  return d->properties ? wp_properties_ref (d->properties) : NULL;
}

static GVariant *
wp_pw_object_mixin_get_param_info (WpPipewireObject * obj)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj);
  WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj);
  struct spa_param_info *info;
  guint32 n_params;
  g_auto (GVariantBuilder) b =
      G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_DICTIONARY);

  if (!d->info ||
      iface->param_info_offset == 0 ||
      iface->n_params_offset == 0)
    return NULL;

  info = G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset);
  n_params = G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a{ss}"));

  for (guint i = 0; i < n_params; i++) {
    WpSpaIdValue idval;
    const gchar *nick = NULL;
    gchar flags[3];
    guint flags_idx = 0;

    idval = wp_spa_id_value_from_number ("Spa:Enum:ParamId", info[i].id);
    nick = wp_spa_id_value_short_name (idval);
    g_return_val_if_fail (nick != NULL, NULL);

    if (info[i].flags & SPA_PARAM_INFO_READ)
      flags[flags_idx++] = 'r';
    if (info[i].flags & SPA_PARAM_INFO_WRITE)
      flags[flags_idx++] = 'w';
    flags[flags_idx] = '\0';

    g_variant_builder_add (&b, "{ss}", nick, flags);
  }

  return g_variant_builder_end (&b);
}

static void
enum_params_done (WpCore * core, GAsyncResult * res, gpointer data)
{
  g_autoptr (GTask) task = G_TASK (data);
  GList *taskl = NULL;
  g_autoptr (GError) error = NULL;
  gpointer instance = g_task_get_source_object (G_TASK (data));
  GPtrArray *params = g_task_get_task_data (task);
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);

  /* finish the sync task */
  wp_core_sync_finish (core, res, &error);

  /* return if task was previously removed from the list */
  taskl = g_list_find (d->enum_params_tasks, task);
  if (!taskl)
    return;

  /* remove the task from the stored list; ref is held by the g_autoptr */
  d->enum_params_tasks = g_list_delete_link (d->enum_params_tasks, taskl);

  wp_debug_object (instance, "got %u params, %s, task " WP_OBJECT_FORMAT,
      params->len, error ? "with error" : "ok", WP_OBJECT_ARGS (task));

  if (error)
    g_task_return_error (task, g_steal_pointer (&error));
  else {
    g_task_return_pointer (task, g_ptr_array_ref (params),
        (GDestroyNotify) g_ptr_array_unref);
  }
}

static void
enum_params_error (WpProxy * proxy, int seq, int res, const gchar *msg,
    GTask * task)
{
  gint t_seq = GPOINTER_TO_INT (g_task_get_source_tag (task));

  if (SPA_RESULT_ASYNC_SEQ (t_seq) == SPA_RESULT_ASYNC_SEQ (seq)) {
    gpointer instance = g_task_get_source_object (task);
    WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
    GList *taskl = NULL;

    taskl = g_list_find (d->enum_params_tasks, task);
    if (taskl) {
      d->enum_params_tasks = g_list_delete_link (d->enum_params_tasks, taskl);
      g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
          WP_LIBRARY_ERROR_OPERATION_FAILED, "%s", msg);
    }
  }
}

static void
wp_pw_object_mixin_enum_params_unchecked (gpointer obj,
    guint32 id, WpSpaPod *filter, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer user_data)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj);
  WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj);
  g_autoptr (GTask) task = NULL;
  gint seq = 0;
  GPtrArray *params = NULL;

  g_return_if_fail (iface->enum_params_sync || iface->enum_params);

  if (iface->enum_params_sync) {
    params = iface->enum_params_sync (obj, id, 0, -1, filter);
  } else {
    seq = iface->enum_params (obj, id, 0, -1, filter);

    /* return early if seq contains an error */
    if (G_UNLIKELY (SPA_RESULT_IS_ERROR (seq))) {
      wp_message_object (obj, "enum_params failed: %s", spa_strerror (seq));
      g_task_report_new_error (obj, callback, user_data, NULL,
          WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
          "enum_params failed: %s", spa_strerror (seq));
      return;
    }
  }

  if (!params)
    params = g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref);

  /* create task */
  task = g_task_new (obj, cancellable, callback, user_data);

  /* debug */
  if (wp_log_level_is_enabled (G_LOG_LEVEL_DEBUG)) {
    const gchar *name = NULL;
    name = wp_spa_id_value_short_name (
        wp_spa_id_value_from_number ("Spa:Enum:ParamId", id));
    wp_debug_object (obj, "enum id %u (%s), seq 0x%x (%u), task "
        WP_OBJECT_FORMAT "%s", id, name, seq, seq, WP_OBJECT_ARGS (task),
        iface->enum_params_sync ? ", sync" : "");
  }

  if (iface->enum_params_sync) {
    g_task_return_pointer (task, params, (GDestroyNotify) g_ptr_array_unref);
  } else {
    g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (obj));

    /* watch for errors */
    g_signal_connect_object (obj, "error", G_CALLBACK (enum_params_error),
        task, 0);

    /* store */
    g_task_set_task_data (task, params, (GDestroyNotify) g_ptr_array_unref);
    g_task_set_source_tag (task, GINT_TO_POINTER (seq));
    d->enum_params_tasks = g_list_append (d->enum_params_tasks, task);

    /* call sync */
    wp_core_sync (core, cancellable, (GAsyncReadyCallback) enum_params_done,
        g_object_ref (task));
  }
}

static void
wp_pw_object_mixin_enum_params (WpPipewireObject * obj, const gchar * id,
    WpSpaPod *filter, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer user_data)
{
  WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj);
  WpSpaIdValue param_id;

  if (!(iface->enum_params || iface->enum_params_sync)) {
    g_task_report_new_error (obj, callback, user_data, NULL,
        WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
        "enum_params is not supported on this object");
    return;
  }

  /* translate the id */
  param_id = wp_spa_id_value_from_short_name ("Spa:Enum:ParamId", id);
  if (!param_id) {
    wp_critical_object (obj, "invalid param id: %s", id);
    return;
  }

  wp_pw_object_mixin_enum_params_unchecked (obj,
      wp_spa_id_value_number (param_id), filter,
      cancellable, callback, user_data);
}

static WpIterator *
wp_pw_object_mixin_enum_params_finish (WpPipewireObject * obj,
    GAsyncResult * res, GError ** error)
{
  g_return_val_if_fail (g_task_is_valid (res, obj), NULL);

  GPtrArray *array = g_task_propagate_pointer (G_TASK (res), error);
  if (!array)
    return NULL;

  return wp_iterator_new_ptr_array (array, WP_TYPE_SPA_POD);
}

static WpIterator *
wp_pw_object_mixin_enum_params_sync (WpPipewireObject * obj, const gchar * id,
    WpSpaPod * filter)
{
  WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj);
  GPtrArray *params = NULL;
  WpSpaIdValue param_id;

  /* translate the id */
  param_id = wp_spa_id_value_from_short_name ("Spa:Enum:ParamId", id);
  if (!param_id) {
    wp_critical_object (obj, "invalid param id: %s", id);
    return NULL;
  }

  if (iface->enum_params_sync) {
    /* use enum_params_sync if supported */
    params = iface->enum_params_sync (obj, wp_spa_id_value_number (param_id),
        0, -1, filter);
  } else {
    /* otherwise, find and return the cached params */
    WpPwObjectMixinData *data = wp_pw_object_mixin_get_data (obj);
    params = wp_pw_object_mixin_get_stored_params (data,
        wp_spa_id_value_number (param_id));
    /* TODO filter */
  }

  return params ? wp_iterator_new_ptr_array (params, WP_TYPE_SPA_POD) : NULL;
}

static gboolean
wp_pw_object_mixin_set_param (WpPipewireObject * obj, const gchar * id,
    guint32 flags, WpSpaPod * param)
{
  WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj);
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj);
  WpSpaIdValue param_id;
  gint ret;

  if (!d->iface) {
    wp_message_object (obj, "ignoring set_param on already destroyed objects");
    return FALSE;
  }

  if (!iface->set_param) {
    wp_warning_object (obj, "set_param is not supported on this object");
    return FALSE;
  }

  param_id = wp_spa_id_value_from_short_name ("Spa:Enum:ParamId", id);
  if (!param_id) {
    wp_critical_object (obj, "invalid param id: %s", id);
    wp_spa_pod_unref (param);
    return FALSE;
  }

  ret = iface->set_param (obj, wp_spa_id_value_number (param_id), flags, param);

  if (G_UNLIKELY (SPA_RESULT_IS_ERROR (ret))) {
    wp_message_object (obj, "set_param failed: %s", spa_strerror (ret));
    return FALSE;
  }
  return TRUE;
}

void
wp_pw_object_mixin_object_interface_init (WpPipewireObjectInterface * iface)
{
  iface->get_native_info = wp_pw_object_mixin_get_native_info;
  iface->get_properties = wp_pw_object_mixin_get_properties;
  iface->get_param_info = wp_pw_object_mixin_get_param_info;
  iface->enum_params = wp_pw_object_mixin_enum_params;
  iface->enum_params_finish = wp_pw_object_mixin_enum_params_finish;
  iface->enum_params_sync = wp_pw_object_mixin_enum_params_sync;
  iface->set_param = wp_pw_object_mixin_set_param;
}

/********/
/* DATA */

G_DEFINE_QUARK (WpPwObjectMixinData, wp_pw_object_mixin_data)

static void wp_pw_object_mixin_param_store_free (gpointer data);

static WpPwObjectMixinData *
wp_pw_object_mixin_data_new (void)
{
  WpPwObjectMixinData *d = g_slice_new0 (WpPwObjectMixinData);
  spa_hook_list_init (&d->hooks);
  return d;
}

static void
wp_pw_object_mixin_data_free (gpointer data)
{
  WpPwObjectMixinData *d = data;
  spa_hook_list_clean (&d->hooks);
  g_clear_pointer (&d->properties, wp_properties_unref);
  g_list_free_full (d->params, wp_pw_object_mixin_param_store_free);
  g_clear_pointer (&d->subscribed_ids, g_array_unref);
  g_warn_if_fail (d->enum_params_tasks == NULL);
  g_slice_free (WpPwObjectMixinData, d);
}

WpPwObjectMixinData *
wp_pw_object_mixin_get_data (gpointer instance)
{
  WpPwObjectMixinData *d = g_object_get_qdata (G_OBJECT (instance),
      wp_pw_object_mixin_data_quark ());
  if (G_UNLIKELY (!d)) {
    d = wp_pw_object_mixin_data_new ();
    g_object_set_qdata_full (G_OBJECT (instance),
        wp_pw_object_mixin_data_quark (), d, wp_pw_object_mixin_data_free);
  }
  return d;
}

/****************/
/* PARAMS STORE */

typedef struct _WpPwObjectMixinParamStore WpPwObjectMixinParamStore;
struct _WpPwObjectMixinParamStore
{
  guint32 param_id;
  GPtrArray *params;
};

static WpPwObjectMixinParamStore *
wp_pw_object_mixin_param_store_new (void)
{
  WpPwObjectMixinParamStore *d = g_slice_new0 (WpPwObjectMixinParamStore);
  return d;
}

static void
wp_pw_object_mixin_param_store_free (gpointer data)
{
  WpPwObjectMixinParamStore * p = data;
  g_clear_pointer (&p->params, g_ptr_array_unref);
  g_slice_free (WpPwObjectMixinParamStore, p);
}

static gint
param_store_has_id (gconstpointer param, gconstpointer id)
{
  guint32 param_id = ((const WpPwObjectMixinParamStore *) param)->param_id;
  return (param_id == GPOINTER_TO_UINT (id)) ? 0 : 1;
}

GPtrArray *
wp_pw_object_mixin_get_stored_params (WpPwObjectMixinData * data, guint32 id)
{
  GList *link = g_list_find_custom (data->params, GUINT_TO_POINTER (id),
      param_store_has_id);
  WpPwObjectMixinParamStore *s = link ? link->data : NULL;
  return (s && s->params) ? g_ptr_array_ref (s->params) : NULL;
}

void
wp_pw_object_mixin_store_param (WpPwObjectMixinData * data, guint32 id,
    guint32 flags, gpointer param)
{
  GList *link = g_list_find_custom (data->params, GUINT_TO_POINTER (id),
      param_store_has_id);
  WpPwObjectMixinParamStore *s = link ? link->data : NULL;
  gint16 index = (gint16) (flags & 0xffff);

  /* if the link exists, data must also exist */
  g_warn_if_fail (!link || link->data);

  if (!s) {
    if (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE)
      return;
    s = wp_pw_object_mixin_param_store_new ();
    s->param_id = id;
    data->params = g_list_append (data->params, s);
  }
  else if (s && (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE)) {
    wp_pw_object_mixin_param_store_free (s);
    data->params = g_list_delete_link (data->params, link);
    return;
  }

  if (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_CLEAR)
    g_clear_pointer (&s->params, g_ptr_array_unref);

  if (!param)
    return;

  if (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_ARRAY) {
    if (!s->params)
      s->params = (GPtrArray *) param;
    else
      g_ptr_array_extend_and_steal (s->params, (GPtrArray *) param);
  }
  else {
    WpSpaPod *param_pod = param;

    if (!s->params)
      s->params =
          g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref);

    /* copy if necessary to make sure we don't reference
       `const struct spa_pod *` data allocated on the stack */
    param_pod = wp_spa_pod_ensure_unique_owner (param_pod);
    g_ptr_array_insert (s->params, index, param_pod);
  }
}

/******************/
/* PROPERTIES API */

void
wp_pw_object_mixin_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  switch (property_id) {
  case WP_PW_OBJECT_MIXIN_PROP_NATIVE_INFO:
    g_value_set_pointer (value, (gpointer)
        wp_pipewire_object_get_native_info (WP_PIPEWIRE_OBJECT (object)));
    break;
  case WP_PW_OBJECT_MIXIN_PROP_PROPERTIES:
    g_value_take_boxed (value,
        wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (object)));
    break;
  case WP_PW_OBJECT_MIXIN_PROP_PARAM_INFO:
    g_value_set_variant (value,
        wp_pipewire_object_get_param_info (WP_PIPEWIRE_OBJECT (object)));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

void
wp_pw_object_mixin_class_override_properties (GObjectClass * klass)
{
  g_object_class_override_property (klass,
      WP_PW_OBJECT_MIXIN_PROP_NATIVE_INFO, "native-info");
  g_object_class_override_property (klass,
      WP_PW_OBJECT_MIXIN_PROP_PROPERTIES, "properties");
  g_object_class_override_property (klass,
      WP_PW_OBJECT_MIXIN_PROP_PARAM_INFO, "param-info");
}

/****************/
/* FEATURES API */

static const struct {
  WpObjectFeatures feature;
  guint32 param_ids[2];
} params_features[] = {
  { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, { SPA_PARAM_PropInfo, SPA_PARAM_Props } },
  { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_FORMAT, { SPA_PARAM_EnumFormat, SPA_PARAM_Format } },
  { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROFILE, { SPA_PARAM_EnumProfile, SPA_PARAM_Profile } },
  { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PORT_CONFIG, { SPA_PARAM_EnumPortConfig, SPA_PARAM_PortConfig } },
  { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_ROUTE, { SPA_PARAM_EnumRoute, SPA_PARAM_Route } },
};

static WpObjectFeatures
get_feature_for_param_id (guint32 param_id)
{
  for (guint i = 0; i < G_N_ELEMENTS (params_features); i++) {
    if (params_features[i].param_ids[0] == param_id ||
        params_features[i].param_ids[1] == param_id)
      return params_features[i].feature;
  }
  return 0;
}

WpObjectFeatures
wp_pw_object_mixin_get_supported_features (WpObject * object)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (object);
  WpObjectFeatures ft =
      WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO;

  if (d->info && iface->n_params_offset && iface->param_info_offset) {
    struct spa_param_info * param_info =
        G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset);
    guint32 n_params =
        G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset);

    for (guint i = 0; i < n_params; i++)
      ft |= get_feature_for_param_id (param_info[i].id);
  }
  return ft;
}

guint
wp_pw_object_mixin_activate_get_next_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object);

  /* bind if not already bound */
  if (missing & WP_PROXY_FEATURE_BOUND || !d->iface)
    return WP_PW_OBJECT_MIXIN_STEP_BIND;
  /* wait for info before proceeding, if necessary */
  else if ((missing & WP_PIPEWIRE_OBJECT_FEATURES_ALL) && !d->info)
    return WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO;
  /* then cache params */
  else if (missing & WP_PIPEWIRE_OBJECT_FEATURES_ALL)
    return WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS;
  else
    return WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START;

  /* returning to STEP_NONE is handled by WpFeatureActivationTransition */
}

static void
enum_params_for_cache_done (GObject * object, GAsyncResult * res, gpointer data)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object);
  guint32 param_id = GPOINTER_TO_UINT (data);
  g_autoptr (GError) error = NULL;
  g_autoptr (GPtrArray) params = NULL;
  const gchar *name = NULL;

  params = g_task_propagate_pointer (G_TASK (res), &error);
  if (error) {
    wp_debug_object (object, "enum params failed: %s", error->message);
    return;
  }

  name = wp_spa_id_value_short_name (wp_spa_id_value_from_number (
        "Spa:Enum:ParamId", param_id));

  wp_debug_object (object, "cached params id:%u (%s), n_params:%u", param_id,
      name, params->len);

  wp_pw_object_mixin_store_param (d, param_id,
      WP_PW_OBJECT_MIXIN_STORE_PARAM_ARRAY |
      WP_PW_OBJECT_MIXIN_STORE_PARAM_CLEAR |
      WP_PW_OBJECT_MIXIN_STORE_PARAM_APPEND,
      g_steal_pointer (&params));

  g_signal_emit_by_name (object, "params-changed", name);
}

G_DEFINE_QUARK (WpPwObjectMixinParamCacheActivatedFeatures, activated_features)

static void
param_cache_features_enabled (WpCore * core, GAsyncResult * res, gpointer data)
{
  WpObject *object = WP_OBJECT (data);
  WpObjectFeatures activated = GPOINTER_TO_UINT (
      g_object_get_qdata (G_OBJECT (object), activated_features_quark ()));
  wp_object_update_features (object, activated, 0);
}

void
wp_pw_object_mixin_cache_params (WpObject * object, WpObjectFeatures missing)
{
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (object);
  g_autoptr (WpCore) core = wp_object_get_core (object);
  struct spa_param_info * param_info;
  WpObjectFeatures activated = 0;

  g_return_if_fail (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE));

  for (guint i = 0; i < G_N_ELEMENTS (params_features); i++) {
    if (missing & params_features[i].feature) {
      param_info = find_param_info (object, params_features[i].param_ids[0]);
      if (param_info && param_info->flags & SPA_PARAM_INFO_READ) {
        wp_pw_object_mixin_enum_params_unchecked (object,
            param_info->id, NULL, NULL, enum_params_for_cache_done,
            GUINT_TO_POINTER (param_info->id));
      }

      param_info = find_param_info (object, params_features[i].param_ids[1]);
      if (param_info && param_info->flags & SPA_PARAM_INFO_READ) {
        wp_pw_object_mixin_enum_params_unchecked (object,
            param_info->id, NULL, NULL, enum_params_for_cache_done,
            GUINT_TO_POINTER (param_info->id));
      }

      activated |= params_features[i].feature;
    }
  }

  g_object_set_qdata (G_OBJECT (object),
      activated_features_quark (), GUINT_TO_POINTER (activated));
  wp_core_sync_closure (core, NULL, g_cclosure_new_object (
          G_CALLBACK (param_cache_features_enabled),
          G_OBJECT (object)));
}

void
wp_pw_object_mixin_deactivate (WpObject * object, WpObjectFeatures features)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (object);

  /* deactivate param caching */
  if (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE)) {
    for (guint i = 0; i < G_N_ELEMENTS (params_features); i++) {
      if (features & params_features[i].feature) {
        wp_pw_object_mixin_store_param (d, params_features[i].param_ids[0],
            WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL);
        wp_pw_object_mixin_store_param (d, params_features[i].param_ids[1],
            WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL);
        wp_object_update_features (object, 0, params_features[i].feature);
      }
    }
  }
}

/************************/
/* PROXY EVENT HANDLERS */

void
wp_pw_object_mixin_handle_pw_proxy_destroyed (WpProxy * proxy)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (proxy);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (proxy);

  spa_hook_remove (&d->listener);
  g_clear_pointer (&d->properties, wp_properties_unref);
  g_clear_pointer (&d->info, iface->free_info);
  d->iface = NULL;

  /* deactivate param caching */
  if (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE)) {
    for (guint i = 0; i < G_N_ELEMENTS (params_features); i++) {
      wp_pw_object_mixin_store_param (d, params_features[i].param_ids[0],
          WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL);
      wp_pw_object_mixin_store_param (d, params_features[i].param_ids[1],
          WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL);
    }
  }

  /* cancel enum_params tasks */
  {
    GList *link;
    for (link = g_list_first (d->enum_params_tasks);
         link; link = g_list_first (d->enum_params_tasks)) {
      GTask *task = G_TASK (link->data);
      d->enum_params_tasks = g_list_delete_link (d->enum_params_tasks, link);
      g_task_return_new_error (task,
          WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
          "pipewire proxy destroyed before finishing");
    }
  }

  wp_object_update_features (WP_OBJECT (proxy), 0,
      WP_PIPEWIRE_OBJECT_FEATURES_ALL);
}

/***************************/
/* PIPEWIRE EVENT HANDLERS */

void
wp_pw_object_mixin_handle_event_info (gpointer instance, gconstpointer update)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance);
  guint64 change_mask =
      G_STRUCT_MEMBER (guint64, update, iface->change_mask_offset);
  guint64 process_info_change_mask =
      change_mask & ~(iface->CHANGE_MASK_PROPS | iface->CHANGE_MASK_PARAMS);
  gpointer old_info = NULL;

  wp_debug_object (instance, "info, change_mask:0x%"G_GINT64_MODIFIER"x [%s%s]",
      change_mask,
      (change_mask & iface->CHANGE_MASK_PROPS) ? "props," : "",
      (change_mask & iface->CHANGE_MASK_PARAMS) ? "params," : "");

  /* make a copy of d->info for process_info() */
  if (iface->process_info && d->info && process_info_change_mask) {
    /* copy everything that changed except props and params, for efficiency;
       process_info() is only interested in variables that are not PROPS & PARAMS */
    G_STRUCT_MEMBER (guint64, d->info, iface->change_mask_offset) =
        process_info_change_mask;
    old_info = iface->update_info (NULL, d->info);
  }

  /* update params */
  if (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE) &&
       (change_mask & iface->CHANGE_MASK_PARAMS) && d->info) {
    struct spa_param_info * old_param_info =
        G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset);
    struct spa_param_info * param_info =
        G_STRUCT_MEMBER (struct spa_param_info *, update, iface->param_info_offset);
    guint32 old_n_params =
        G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset);
    guint32 n_params =
        G_STRUCT_MEMBER (guint32, update, iface->n_params_offset);
    WpObjectFeatures active_ft =
        wp_object_get_active_features (WP_OBJECT (instance));

    for (guint i = 0; i < n_params; i++) {
      /* param changes when flags change */
      if (i >= old_n_params || old_param_info[i].flags != param_info[i].flags) {
        /* update cached params if the relevant feature is active */
        if (active_ft & get_feature_for_param_id (param_info[i].id) &&
            param_info[i].flags & SPA_PARAM_INFO_READ)
        {
          wp_pw_object_mixin_enum_params_unchecked (instance,
              param_info[i].id, NULL, NULL, enum_params_for_cache_done,
              GUINT_TO_POINTER (param_info[i].id));
        }
      }
    }
  }

  /* update our info struct */
  d->info = iface->update_info (d->info, update);

  /* update properties */
  if (change_mask & iface->CHANGE_MASK_PROPS) {
    const struct spa_dict * props =
        G_STRUCT_MEMBER (const struct spa_dict *, d->info, iface->props_offset);

    g_clear_pointer (&d->properties, wp_properties_unref);
    d->properties = wp_properties_new_wrap_dict (props);

    g_object_notify (G_OBJECT (instance), "properties");
  }

  if (change_mask & iface->CHANGE_MASK_PARAMS)
    g_object_notify (G_OBJECT (instance), "param-info");

  /* custom handling, if required */
  if (iface->process_info && process_info_change_mask) {
    iface->process_info (instance, old_info, d->info);
    g_clear_pointer (&old_info, iface->free_info);
  }

  wp_object_update_features (WP_OBJECT (instance),
      WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0);
}

static gint
task_has_seq (gconstpointer task, gconstpointer seq)
{
  gpointer t_seq = g_task_get_source_tag (G_TASK (task));
  return (GPOINTER_TO_INT (t_seq) == GPOINTER_TO_INT (seq)) ? 0 : 1;
}

void
wp_pw_object_mixin_handle_event_param (gpointer instance, int seq,
    uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  g_autoptr (WpSpaPod) w_param = wp_spa_pod_new_wrap_const (param);
  GList *list;
  GTask *task;

  list = g_list_find_custom (d->enum_params_tasks, GINT_TO_POINTER (seq),
      task_has_seq);
  task = list ? G_TASK (list->data) : NULL;

  wp_trace_boxed (WP_TYPE_SPA_POD, w_param,
      WP_OBJECT_FORMAT " param id:%u, index:%u",
      WP_OBJECT_ARGS (instance), id, index);

  if (task) {
    GPtrArray *array = g_task_get_task_data (task);
    g_ptr_array_add (array, wp_spa_pod_copy (w_param));
  } else {
    /* this should never happen */
    wp_warning_object (instance,
        "param event was received without calling enum_params");
  }
}

/***********************************/
/* PIPEWIRE METHOD IMPLEMENTATIONS */

int
wp_pw_object_mixin_impl_add_listener (gpointer instance,
    struct spa_hook *listener, gconstpointer events, gpointer data)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance);
  struct spa_hook_list save;

  spa_hook_list_isolate (&d->hooks, &save, listener, events, data);

  G_STRUCT_MEMBER (guint64, d->info, iface->change_mask_offset) = iface->CHANGE_MASK_ALL;
  iface->emit_info (&d->hooks, d->info);
  G_STRUCT_MEMBER (guint64, d->info, iface->change_mask_offset) = 0;

  spa_hook_list_join (&d->hooks, &save);
  return 0;
}

int
wp_pw_object_mixin_impl_enum_params (gpointer instance, int seq,
    guint32 id, guint32 start, guint32 num, const struct spa_pod *filter)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance);
  g_autoptr (GPtrArray) params = NULL;
  g_autoptr (WpSpaPod) filter_pod = NULL;

  if (!iface->enum_params_sync)
    return -ENOTSUP;

  struct spa_param_info * info = find_param_info (instance, id);
  if (!info || !(info->flags & SPA_PARAM_INFO_READ))
    return -EINVAL;

  filter_pod = filter ? wp_spa_pod_new_wrap_const (filter) : NULL;
  params = iface->enum_params_sync (instance, id, start, num, filter_pod);

  if (params) {
    for (guint i = 0; i < params->len; i++) {
      WpSpaPod *pod = g_ptr_array_index (params, i);

      wp_trace_boxed (WP_TYPE_SPA_POD, pod,
          WP_OBJECT_FORMAT " emit param id:%u, index:%u",
          WP_OBJECT_ARGS (instance), id, start+i);

      iface->emit_param (&d->hooks, seq, id, start+i, start+i+1,
          wp_spa_pod_get_spa_pod (pod));
    }
  }
  return 0;
}

int
wp_pw_object_mixin_impl_subscribe_params (gpointer instance,
    guint32 *ids, guint32 n_ids)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance);

  if (!iface->enum_params_sync)
    return -ENOTSUP;

  for (guint i = 0; i < n_ids; i++)
    wp_pw_object_mixin_impl_enum_params (instance, 1, ids[i], 0, -1, NULL);

  if (!d->subscribed_ids)
    d->subscribed_ids = g_array_new (FALSE, FALSE, sizeof (guint32));

  /* FIXME: deduplicate stored ids */
  g_array_append_vals (d->subscribed_ids, ids, n_ids);
  return 0;
}

int
wp_pw_object_mixin_impl_set_param (gpointer instance, guint32 id,
    guint32 flags, const struct spa_pod *param)
{
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance);

  if (!iface->set_param)
    return -ENOTSUP;

  struct spa_param_info * info = find_param_info (instance, id);
  if (!info || !(info->flags & SPA_PARAM_INFO_WRITE))
    return -EINVAL;

  WpSpaPod *param_pod = wp_spa_pod_new_wrap_const (param);

  wp_trace_boxed (WP_TYPE_SPA_POD, param_pod,
          WP_OBJECT_FORMAT " set_param id:%u flags:0x%x",
          WP_OBJECT_ARGS (instance), id, flags);

  return iface->set_param (instance, id, flags, param_pod);
}

/**********************/
/*      NOTIFIERS     */

void
wp_pw_object_mixin_notify_info (gpointer instance, guint32 change_mask)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance);

  wp_debug_object (instance, "notify info, change_mask:0x%x [%s%s]",
      change_mask,
      (change_mask & iface->CHANGE_MASK_PROPS) ? "props," : "",
      (change_mask & iface->CHANGE_MASK_PARAMS) ? "params," : "");

  G_STRUCT_MEMBER (guint64, d->info, iface->change_mask_offset) =
      (change_mask & iface->CHANGE_MASK_ALL);
  iface->emit_info (&d->hooks, d->info);
  G_STRUCT_MEMBER (guint64, d->info, iface->change_mask_offset) = 0;

  if (change_mask & iface->CHANGE_MASK_PROPS)
    g_object_notify (G_OBJECT (instance), "properties");

  if (change_mask & iface->CHANGE_MASK_PARAMS)
    g_object_notify (G_OBJECT (instance), "param-info");
}

void
wp_pw_object_mixin_notify_params_changed (gpointer instance, guint32 id)
{
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
  WpPwObjectMixinPrivInterface *iface =
      WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance);
  gboolean subscribed = FALSE;
  const gchar *name = NULL;

  struct spa_param_info * info = find_param_info (instance, id);
  g_return_if_fail (info);

  if (d->subscribed_ids) {
    for (guint i = 0; i < d->subscribed_ids->len; i++) {
      if (g_array_index (d->subscribed_ids, guint32, i) == id) {
        subscribed = TRUE;
        break;
      }
    }
  }

  name = wp_spa_id_value_short_name (wp_spa_id_value_from_number (
        "Spa:Enum:ParamId", id));

  wp_debug_object (instance, "notify param id:%u (%s)", id, name);

  /* toggle the serial flag; this notifies that there is a data change */
  info->flags ^= SPA_PARAM_INFO_SERIAL;

  G_STRUCT_MEMBER (guint64, d->info, iface->change_mask_offset) =
      iface->CHANGE_MASK_PARAMS;
  iface->emit_info (&d->hooks, d->info);
  G_STRUCT_MEMBER (guint64, d->info, iface->change_mask_offset) = 0;

  if (subscribed)
    wp_pw_object_mixin_impl_enum_params (instance, 1, id, 0, -1, NULL);

  g_signal_emit_by_name (instance, "params-changed", name);
}
  07070100000084000081A4000000000000000000000001656CC35F00002263000000000000000000000000000000000000003A00000000wireplumber-0.4.17/lib/wp/private/pipewire-object-mixin.h /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_PIPEWIRE_OBJECT_MIXIN_H__
#define __WIREPLUMBER_PIPEWIRE_OBJECT_MIXIN_H__

#include "proxy-interfaces.h"

#include <pipewire/pipewire.h>

G_BEGIN_DECLS

enum {
  /* this is the same STEP_BIND as in WpGlobalProxy */
  WP_PW_OBJECT_MIXIN_STEP_BIND = WP_TRANSITION_STEP_CUSTOM_START,
  WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO,
  WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS,

  WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START,
};

enum {
  WP_PW_OBJECT_MIXIN_PROP_0,

  // WpPipewireObject
  WP_PW_OBJECT_MIXIN_PROP_NATIVE_INFO,
  WP_PW_OBJECT_MIXIN_PROP_PROPERTIES,
  WP_PW_OBJECT_MIXIN_PROP_PARAM_INFO,

  WP_PW_OBJECT_MIXIN_PROP_CUSTOM_START,
};

#define WP_TYPE_PW_OBJECT_MIXIN_PRIV (wp_pw_object_mixin_priv_get_type ())
G_DECLARE_INTERFACE (WpPwObjectMixinPriv, wp_pw_object_mixin_priv,
                     WP, PW_OBJECT_MIXIN_PRIV, WpProxy)

struct _WpPwObjectMixinPrivInterface
{
  GTypeInterface parent;

  /* WpPwObjectMixinPriv-specific flags */
#define WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE (1 << 0)
  guint32 flags;

  /* pipewire info struct abstraction layer */
  gsize info_size;
  gsize change_mask_offset;
  gsize props_offset;
  gsize param_info_offset;
  gsize n_params_offset;

  guint64 CHANGE_MASK_ALL;
  guint64 CHANGE_MASK_PROPS;
  guint64 CHANGE_MASK_PARAMS;

  gpointer (*update_info) (gpointer info, gconstpointer update);
  void (*free_info) (gpointer info);

  /* to further process info struct updates - for proxy objects only */
  void (*process_info) (gpointer instance, gpointer old_info, gpointer info);

  /* pipewire interface methods - proxy & impl */
  gint (*enum_params) (gpointer instance, guint32 id,
      guint32 start, guint32 num, WpSpaPod *filter);
  GPtrArray * (*enum_params_sync) (gpointer instance, guint32 id,
      guint32 start, guint32 num, WpSpaPod *filter);
  gint (*set_param) (gpointer instance, guint32 id, guint32 flags,
      WpSpaPod *param /* transfer full */);

  /* pipewire interface events - for impl objects only */
  void (*emit_info) (struct spa_hook_list * hooks, gconstpointer info);
  void (*emit_param) (struct spa_hook_list * hooks, int seq,
      guint32 id, guint32 index, guint32 next, const struct spa_pod *param);
};

/* fills in info struct abstraction layer in WpPwObjectMixinPrivInterface */
#define wp_pw_object_mixin_priv_interface_info_init(iface, type, TYPE) \
({ \
  iface->info_size = sizeof (struct pw_ ## type ## _info); \
  iface->change_mask_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, change_mask); \
  iface->props_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, props); \
  iface->param_info_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, params); \
  iface->n_params_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, n_params); \
  iface->CHANGE_MASK_ALL = PW_ ## TYPE ## _CHANGE_MASK_ALL; \
  iface->CHANGE_MASK_PROPS = PW_ ## TYPE ## _CHANGE_MASK_PROPS; \
  iface->CHANGE_MASK_PARAMS = PW_ ## TYPE ## _CHANGE_MASK_PARAMS; \
  iface->update_info = (gpointer (*)(gpointer, gconstpointer)) pw_ ## type ## _info_update; \
  iface->free_info = (void (*)(gpointer)) pw_ ## type ## _info_free; \
})

/* same as above, for types that don't have params */
#define wp_pw_object_mixin_priv_interface_info_init_no_params(iface, type, TYPE) \
({ \
  iface->flags = WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE; \
  iface->info_size = sizeof (struct pw_ ## type ## _info); \
  iface->change_mask_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, change_mask); \
  iface->props_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, props); \
  iface->param_info_offset = 0; \
  iface->n_params_offset = 0; \
  iface->CHANGE_MASK_ALL = PW_ ## TYPE ## _CHANGE_MASK_ALL; \
  iface->CHANGE_MASK_PROPS = PW_ ## TYPE ## _CHANGE_MASK_PROPS; \
  iface->CHANGE_MASK_PARAMS = 0; \
  iface->update_info = (gpointer (*)(gpointer, gconstpointer)) pw_ ## type ## _info_update; \
  iface->free_info = (void (*)(gpointer)) pw_ ## type ## _info_free; \
})

/*************/
/* INTERFACE */

/* implements WpPipewireObject for an object that implements WpPwObjectMixinPriv */
void wp_pw_object_mixin_object_interface_init (WpPipewireObjectInterface * iface);

/********/
/* DATA */

typedef struct _WpPwObjectMixinData WpPwObjectMixinData;
struct _WpPwObjectMixinData
{
  gpointer info;            /* pointer to the info struct */
  gpointer iface;           /* pointer to the interface (ex. pw_endpoint) */
  struct spa_hook listener;
  struct spa_hook_list hooks;
  WpProperties *properties;
  GList *enum_params_tasks;  /* element-type: GTask* */
  GList *params;             /* element-type: WpPwObjectMixinParamStore* */
  GArray *subscribed_ids;    /* element-type: guint32 */
};

/* get mixin data (stored as qdata on the @em instance) */
WpPwObjectMixinData * wp_pw_object_mixin_get_data (gpointer instance);

/****************/
/* PARAMS STORE */

/* param store access; (transfer container) */
GPtrArray * wp_pw_object_mixin_get_stored_params (WpPwObjectMixinData * data,
    guint32 id);

/* param store manipulation
 * @em flags: see below
 * @em param: (transfer full): WpSpaPod* or GPtrArray* */
void wp_pw_object_mixin_store_param (WpPwObjectMixinData * data, guint32 id,
    guint32 flags, gpointer param);

/* set the index at which to store the new param */
#define WP_PW_OBJECT_MIXIN_STORE_PARAM_SET(x)  ((x) & 0x7fff)
#define WP_PW_OBJECT_MIXIN_STORE_PARAM_APPEND  (0xffff)
#define WP_PW_OBJECT_MIXIN_STORE_PARAM_PREPEND (0)
/* @em param is a GPtrArray* */
#define WP_PW_OBJECT_MIXIN_STORE_PARAM_ARRAY   (1 << 16)
/* clear the existing array of params before storing */
#define WP_PW_OBJECT_MIXIN_STORE_PARAM_CLEAR   (1 << 17)
/* completely remove stored params for @id */
#define WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE  (1 << 18)

/******************/
/* PROPERTIES API */

/* assign to get_property or chain it from there */
void wp_pw_object_mixin_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec);

/* call from class_init */
void wp_pw_object_mixin_class_override_properties (GObjectClass * klass);

/****************/
/* FEATURES API */

/* call from get_supported_features */
WpObjectFeatures wp_pw_object_mixin_get_supported_features (WpObject * object);

/* assign directly to activate_get_next_step */
guint wp_pw_object_mixin_activate_get_next_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing);

/* call from activate_execute_step when
   step == WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS */
void wp_pw_object_mixin_cache_params (WpObject * object,
    WpObjectFeatures missing);

/* handle deactivation of PARAM_* caching features */
void wp_pw_object_mixin_deactivate (WpObject * object,
    WpObjectFeatures features);

/************************/
/* PROXY EVENT HANDLERS */
/*  (for proxy objects) */

#define wp_pw_object_mixin_handle_pw_proxy_created(instance, pw_proxy, type, events) \
({ \
  WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); \
  d->iface = pw_proxy; \
  pw_ ## type ## _add_listener ((struct pw_ ## type *) pw_proxy, \
      &d->listener, events, instance); \
})

void wp_pw_object_mixin_handle_pw_proxy_destroyed (WpProxy * proxy);

/***************************/
/* PIPEWIRE EVENT HANDLERS */
/*   (for proxy objects)   */

#define HandleEventInfoFunc(type) \
  void (*)(void *, const struct pw_ ## type ## _info *)

void wp_pw_object_mixin_handle_event_info (gpointer instance, gconstpointer info);

/* assign as the param event callback */
void wp_pw_object_mixin_handle_event_param (gpointer instance, int seq,
    guint32 id, guint32 index, guint32 next, const struct spa_pod *param);

/***********************************/
/* PIPEWIRE METHOD IMPLEMENTATIONS */
/*       (for impl objects)        */

#define ImplAddListenerFunc(type) \
  int (*)(void *, struct spa_hook *, const struct pw_ ## type ## _events *, void *)

int wp_pw_object_mixin_impl_add_listener (gpointer instance,
    struct spa_hook *listener, gconstpointer events, gpointer data);

int wp_pw_object_mixin_impl_enum_params (gpointer instance, int seq,
    guint32 id, guint32 start, guint32 num, const struct spa_pod *filter);

int wp_pw_object_mixin_impl_subscribe_params (gpointer instance,
    guint32 *ids, guint32 n_ids);

int wp_pw_object_mixin_impl_set_param (gpointer instance, guint32 id,
    guint32 flags, const struct spa_pod *param);

/**********************/
/*      NOTIFIERS     */
/* (for impl objects) */

void wp_pw_object_mixin_notify_info (gpointer instance, guint32 change_mask);

void wp_pw_object_mixin_notify_params_changed (gpointer instance, guint32 id);

G_END_DECLS

#endif
 07070100000085000081A4000000000000000000000001656CC35F0000095F000000000000000000000000000000000000002D00000000wireplumber-0.4.17/lib/wp/private/registry.h  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_REGISTRY_H__
#define __WIREPLUMBER_REGISTRY_H__

#include "core.h"
#include "global-proxy.h"

#include <pipewire/pipewire.h>

G_BEGIN_DECLS

typedef struct _WpRegistry WpRegistry;
typedef struct _WpGlobal WpGlobal;

/* registry */

struct _WpRegistry
{
  struct pw_registry *pw_registry;
  struct spa_hook listener;

  GPtrArray *globals; // elementy-type: WpGlobal*
  GPtrArray *tmp_globals; // elementy-type: WpGlobal*
  GPtrArray *objects; // element-type: GObject*
  GPtrArray *object_managers; // element-type: WpObjectManager*
};

void wp_registry_init (WpRegistry *self);
void wp_registry_clear (WpRegistry *self);
void wp_registry_attach (WpRegistry *self, struct pw_core *pw_core);
void wp_registry_detach (WpRegistry *self);

void wp_registry_prepare_new_global (WpRegistry * self, guint32 id,
    guint32 permissions, guint32 flag, GType type,
    WpGlobalProxy *proxy, const struct spa_dict *props,
    WpGlobal ** new_global);

gpointer wp_registry_find_object (WpRegistry *reg, GEqualFunc func,
    gconstpointer data);
void wp_registry_register_object (WpRegistry *reg, gpointer obj);
void wp_registry_remove_object (WpRegistry *reg, gpointer obj);

WpCore * wp_registry_get_core (WpRegistry * self) G_GNUC_CONST;

/* core */

WpRegistry * wp_core_get_registry (WpCore * self) G_GNUC_CONST;

/* global */

typedef enum {
  WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY = 0x1,
  WP_GLOBAL_FLAG_OWNED_BY_PROXY = 0x2,
} WpGlobalFlags;

struct _WpGlobal
{
  guint32 flags;
  guint32 id;
  GType type;
  guint32 permissions;
  WpProperties *properties;
  WpGlobalProxy *proxy;
  WpRegistry *registry;
};

#define WP_TYPE_GLOBAL (wp_global_get_type ())
GType wp_global_get_type (void);

static inline void
wp_global_clear (WpGlobal * self)
{
  g_clear_pointer (&self->properties, wp_properties_unref);
}

static inline WpGlobal *
wp_global_ref (WpGlobal * self)
{
  return g_rc_box_acquire (self);
}

static inline void
wp_global_unref (WpGlobal * self)
{
  g_rc_box_release_full (self, (GDestroyNotify) wp_global_clear);
}

void wp_global_rm_flag (WpGlobal *global, guint rm_flag);
struct pw_proxy * wp_global_bind (WpGlobal * global);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpGlobal, wp_global_unref)

G_END_DECLS

#endif
 07070100000086000081A4000000000000000000000001656CC35F00007E32000000000000000000000000000000000000002700000000wireplumber-0.4.17/lib/wp/properties.c    /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-properties"

#include "properties.h"

#include <errno.h>
#include <pipewire/properties.h>

/*! \defgroup wpproperties WpProperties */
/*!
 * \struct WpProperties
 *
 * WpProperties is a data structure that contains string key-value pairs,
 * which are used to send/receive/attach arbitrary properties to PipeWire objects.
 *
 * This could be thought of as a hash table with strings as both keys and
 * values. However, the reason that this class exists instead of using
 * GHashTable directly is that in reality it wraps the PipeWire native
 * `struct spa_dict` and `struct pw_properties` and therefore it can be
 * easily passed to PipeWire function calls that require a `struct spa_dict *`
 * or a `struct pw_properties *` as arguments. Or alternatively, it can easily
 * wrap a `struct spa_dict *` or a `struct pw_properties *` that was given
 * from the PipeWire API without necessarily doing an expensive copy operation.
 *
 * WpProperties normally wraps a `struct pw_properties`, unless it was created
 * with wp_properties_new_wrap_dict(), in which case it wraps a
 * `struct spa_dict` and it is immutable (you cannot add/remove/modify any
 * key-value pair).
 *
 * In most cases, it actually owns the `struct pw_properties`
 * internally and manages its lifetime. The exception to that rule is when
 * WpProperties is constructed with wp_properties_new_wrap(), in which case
 * the ownership of the `struct pw_properties` remains outside. This must
 * be used with care, as the `struct pw_properties` may be free'ed externally.
 *
 * WpProperties is reference-counted with wp_properties_ref() and
 * wp_properties_unref().
 */

enum {
  FLAG_IS_DICT = (1<<1),
  FLAG_NO_OWNERSHIP = (1<<2),
};

struct _WpProperties
{
  grefcount ref;
  guint32 flags;
  union {
    struct pw_properties *props;
    const struct spa_dict *dict;
  };
};

G_DEFINE_BOXED_TYPE(WpProperties, wp_properties, wp_properties_ref, wp_properties_unref)

/*!
 * \brief Creates a new empty properties set
 * \ingroup wpproperties
 * \returns (transfer full): the newly constructed properties set
 */
WpProperties *
wp_properties_new_empty (void)
{
  WpProperties * self = g_slice_new0 (WpProperties);
  g_ref_count_init (&self->ref);
  self->flags = 0;
  self->props = pw_properties_new (NULL, NULL);
  return self;
}

/*!
 * \brief Constructs a new properties set that contains the given properties
 *
 * \ingroup wpproperties
 * \param key a property name
 * \param ... a property value, followed by any number of further property
 *   key-value pairs, followed by NULL
 * \returns (transfer full): the newly constructed properties set
 */
WpProperties *
wp_properties_new (const gchar * key, ...)
{
  WpProperties * self;
  va_list varargs;

  va_start(varargs, key);
  self = wp_properties_new_valist (key, varargs);
  va_end(varargs);

  return self;
}

/*!
 * \brief This is the `va_list` version of wp_properties_new()
 *
 * \ingroup wpproperties
 * \param key a property name
 * \param args the variable arguments passed to wp_properties_new()
 * \returns (transfer full): the newly constructed properties set
 */
WpProperties *
wp_properties_new_valist (const gchar * key, va_list args)
{
  WpProperties * self = wp_properties_new_empty ();
  const gchar *value;

  while (key != NULL) {
    value = va_arg(args, gchar *);
    if (value && key[0])
      wp_properties_set (self, key, value);
    key = va_arg(args, gchar *);
  }

  return self;
}

/*!
 * \brief Constructs a new properties set that contains the properties that can
 * be parsed from the given string
 *
 * \ingroup wpproperties
 * \param str a string containing either a whitespace separated list of key=value
 *    pairs (ex. "key1=value1 key2=value2") or a JSON object (ex. '{"key1":"value1"}')
 * \returns (transfer full): the newly constructed properties set
 */
WpProperties *
wp_properties_new_string (const gchar * str)
{
  WpProperties * self;

  g_return_val_if_fail (str != NULL, NULL);

  self = g_slice_new0 (WpProperties);
  g_ref_count_init (&self->ref);
  self->flags = 0;
  self->props = pw_properties_new_string (str);
  return self;
}

/*!
 * \brief Constructs a new WpProperties that wraps the given \a props structure,
 * allowing reading properties on that \a props structure through
 * the WpProperties API.
 *
 * Care must be taken when using this function, since the returned WpProperties
 * object does not own the \a props structure. Therefore, if the owner decides
 * to free \a props, the returned WpProperties will crash when used. In addition,
 * the returned WpProperties object will not try to free \a props when destroyed.
 *
 * Furthermore, note that the returned WpProperties object is immutable. That
 * means that you cannot add or modify any properties on it, unless you make
 * a copy first.
 *
 * \ingroup wpproperties
 * \param props a native `pw_properties` structure to wrap
 * \returns (transfer full): the newly constructed properties set
 */
WpProperties *
wp_properties_new_wrap (const struct pw_properties * props)
{
  WpProperties * self;

  g_return_val_if_fail (props != NULL, NULL);

  self = g_slice_new0 (WpProperties);
  g_ref_count_init (&self->ref);
  self->flags = FLAG_NO_OWNERSHIP;
  self->props = (struct pw_properties *) props;
  return self;
}

/*!
 * \brief Constructs a new WpProperties that wraps the given \a props structure,
 * allowing reading & writing properties on that \a props structure through
 * the WpProperties API.
 *
 * In constrast with wp_properties_new_wrap(), this function assumes ownership
 * of the \a props structure, so it will try to free \a props when it is destroyed.
 *
 * \ingroup wpproperties
 * \param props a native `pw_properties` structure to wrap
 * \returns (transfer full): the newly constructed properties set
 */
WpProperties *
wp_properties_new_take (struct pw_properties * props)
{
  WpProperties * self;

  g_return_val_if_fail (props != NULL, NULL);

  self = g_slice_new0 (WpProperties);
  g_ref_count_init (&self->ref);
  self->flags = 0;
  self->props = props;
  return self;
}

/*!
 * \brief Constructs a new WpProperties that contains a copy of all the properties
 * contained in the given \a props structure.
 *
 * \ingroup wpproperties
 * \param props a native `pw_properties` structure to copy
 * \returns (transfer full): the newly constructed properties set
 */
WpProperties *
wp_properties_new_copy (const struct pw_properties * props)
{
  WpProperties * self;

  g_return_val_if_fail (props != NULL, NULL);

  self = g_slice_new0 (WpProperties);
  g_ref_count_init (&self->ref);
  self->flags = 0;
  self->props = pw_properties_copy (props);
  return self;
}

/*!
 * \brief Constructs a new WpProperties that wraps the given \a dict structure,
 * allowing reading properties from that \a dict through the WpProperties API.
 *
 * Note that the returned object does not own the \a dict, so care must be taken
 * not to free it externally while this WpProperties object is alive.
 *
 * In addition, note that the returned WpProperties object is immutable. That
 * means that you cannot add or modify any properties on it, since there is
 * no defined method for modifying a `struct spa_dict`. If you need to change
 * this properties set later, you should make a copy with wp_properties_copy().
 *
 * \ingroup wpproperties
 * \param dict a native `spa_dict` structure to wrap
 * \returns (transfer full): the newly constructed properties set
 */
WpProperties *
wp_properties_new_wrap_dict (const struct spa_dict * dict)
{
  WpProperties * self;

  g_return_val_if_fail (dict != NULL, NULL);

  self = g_slice_new0 (WpProperties);
  g_ref_count_init (&self->ref);
  self->flags = FLAG_NO_OWNERSHIP | FLAG_IS_DICT;
  self->dict = dict;
  return self;
}

/*!
 * \brief Constructs a new WpProperties that contains a copy of all the
 * properties contained in the given \a dict structure.
 *
 * \ingroup wpproperties
 * \param dict a native `spa_dict` structure to copy
 * \returns (transfer full): the newly constructed properties set
 */
WpProperties *
wp_properties_new_copy_dict (const struct spa_dict * dict)
{
  WpProperties * self;

  g_return_val_if_fail (dict != NULL, NULL);

  self = g_slice_new0 (WpProperties);
  g_ref_count_init (&self->ref);
  self->flags = 0;
  self->props = pw_properties_new_dict (dict);
  return self;
}

/*!
 * \brief Constructs and returns a new WpProperties object that contains a copy
 * of all the properties contained in \a other.
 *
 * \ingroup wpproperties
 * \param other a properties object
 * \returns (transfer full): the newly constructed properties set
 */

WpProperties *
wp_properties_copy (WpProperties * other)
{
  return wp_properties_new_copy_dict (wp_properties_peek_dict (other));
}

static void
wp_properties_free (WpProperties * self)
{
  if (!(self->flags & FLAG_NO_OWNERSHIP))
    pw_properties_free (self->props);
  g_slice_free (WpProperties, self);
}

/*!
 * \ingroup wpproperties
 * \param self a properties object
 * \returns (transfer full): \a self with an additional reference count on it
 */
WpProperties *
wp_properties_ref (WpProperties * self)
{
  g_ref_count_inc (&self->ref);
  return self;
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 *
 * \ingroup wpproperties
 * \param self (transfer full): a properties object
 */
void
wp_properties_unref (WpProperties * self)
{
  if (g_ref_count_dec (&self->ref))
    wp_properties_free (self);
}

/*!
 * \brief Ensures that the given properties set is uniquely owned.
 *
 * "Uniquely owned" means that:
 *  - its reference count is 1
 *  - it is not wrapping a native `spa_dict` or `pw_properties` object
 *
 * If \a self is not uniquely owned already, then it is unrefed and a copy of
 * it is returned instead. You should always consider \a self as unsafe to use
 * after this call and you should use the returned object instead.
 *
 * \ingroup wpproperties
 * \param self (transfer full): a properties object
 * \returns (transfer full): the uniquely owned properties object
 */
WpProperties *
wp_properties_ensure_unique_owner (WpProperties * self)
{
  if (!g_ref_count_compare (&self->ref, 1) ||
      self->flags & (FLAG_IS_DICT | FLAG_NO_OWNERSHIP))
  {
    WpProperties *copy = wp_properties_copy (self);
    wp_properties_unref (self);
    return copy;
  }
  return self;
}

/*!
 * \brief Updates (adds new or modifies existing) properties in \a self,
 * using the given \a props as a source.
 *
 * Any properties that are not contained in \a props are left untouched.
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \param props a properties set that contains properties to update
 * \returns the number of properties that were changed
 */
gint
wp_properties_update (WpProperties * self, WpProperties * props)
{
  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  return pw_properties_update (self->props, wp_properties_peek_dict (props));
}

/*!
 * \brief Updates (adds new or modifies existing) properties in \a self,
 * using the given \a dict as a source.
 *
 * Any properties that are not contained in \a dict are left untouched.
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \param dict a `spa_dict` that contains properties to update
 * \returns the number of properties that were changed
 */
gint
wp_properties_update_from_dict (WpProperties * self,
    const struct spa_dict * dict)
{
  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  return pw_properties_update (self->props, dict);
}

/*!
 * \brief Adds new properties in \a self, using the given \a props as a source.
 *
 * Properties (keys) from \a props that are already contained in \a self
 * are not modified, unlike what happens with wp_properties_update().
 * Properties in \a self that are not contained in \a props are left untouched.
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \param props a properties set that contains properties to add
 * \returns the number of properties that were changed
 */
gint
wp_properties_add (WpProperties * self, WpProperties * props)
{
  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  return pw_properties_add (self->props, wp_properties_peek_dict (props));
}

/*!
 * \brief Adds new properties in \a self, using the given \a dict as a source.
 *
 * Properties (keys) from \a dict that are already contained in \a self
 * are not modified, unlike what happens with wp_properties_update_from_dict().
 * Properties in \a self that are not contained in \a dict are left untouched.
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \param dict a `spa_dict` that contains properties to add
 * \returns the number of properties that were changed
 */
gint
wp_properties_add_from_dict (WpProperties * self,
    const struct spa_dict * dict)
{
  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  return pw_properties_add (self->props, dict);
}

/*!
 * \brief Updates (adds new or modifies existing) properties in \a self,
 * using the given \a props as a source.
 *
 * Unlike wp_properties_update(), this function only updates properties that
 * have one of the specified keys; the rest is left untouched.
 *
 * \ingroup wpproperties
 * \param self a properties set
 * \param props a properties set that contains properties to update
 * \param key1 a property to update
 * \param ... a list of additional properties to update, followed by NULL
 * \returns the number of properties that were changed
 */
gint
wp_properties_update_keys (WpProperties * self, WpProperties * props,
    const gchar * key1, ...)
{
  gint changed = 0;
  const gchar *value;

  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  va_list args;
  va_start (args, key1);
  for (; key1; key1 = va_arg (args, const gchar *)) {
    if ((value = wp_properties_get (props, key1)) != NULL)
      changed += wp_properties_set (self, key1, value);
  }
  va_end (args);
  return changed;
}

/*!
 * \brief Updates (adds new or modifies existing) properties in \a self,
 * using the given \a dict as a source.
 *
 * Unlike wp_properties_update_from_dict(), this function only updates
 * properties that have one of the specified keys; the rest is left untouched.
 *
 * \ingroup wpproperties
 * \param self a properties set
 * \param dict a `spa_dict` that contains properties to update
 * \param key1 a property to update
 * \param ... a list of additional properties to update, followed by NULL
 * \returns the number of properties that were changed
 */
gint
wp_properties_update_keys_from_dict (WpProperties * self,
    const struct spa_dict * dict, const gchar * key1, ...)
{
  gint changed = 0;
  const gchar *value;

  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  va_list args;
  va_start (args, key1);
  for (; key1; key1 = va_arg (args, const gchar *)) {
    if ((value = spa_dict_lookup (dict, key1)) != NULL)
      changed += wp_properties_set (self, key1, value);
  }
  va_end (args);
  return changed;
}

/*!
 * \brief The same as wp_properties_update_keys(), using a NULL-terminated array
 * for specifying the keys to update
 *
 * \ingroup wpproperties
 * \param self a properties set
 * \param props a properties set that contains properties to update
 * \param keys (array zero-terminated=1): the properties to update
 * \returns the number of properties that were changed
 */
gint
wp_properties_update_keys_array (WpProperties * self, WpProperties * props,
    const gchar * keys[])
{
  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  return pw_properties_update_keys (self->props,
      wp_properties_peek_dict (props), keys);
}

/*!
 * \brief Adds new properties in \a self, using the given \a props as a source.
 *
 * Unlike wp_properties_add(), this function only adds properties that
 * have one of the specified keys; the rest is left untouched.
 *
 * \ingroup wpproperties
 * \param self a properties set
 * \param props a properties set that contains properties to add
 * \param key1 a property to add
 * \param ... a list of additional properties to add, followed by NULL
 * \returns the number of properties that were changed
 */
gint
wp_properties_add_keys (WpProperties * self, WpProperties * props,
    const gchar * key1, ...)
{
  gint changed = 0;
  const gchar *value;

  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  va_list args;
  va_start (args, key1);
  for (; key1; key1 = va_arg (args, const gchar *)) {
    if ((value = wp_properties_get (props, key1)) == NULL)
      continue;
    if (wp_properties_get (self, key1) == NULL)
      changed += wp_properties_set (self, key1, value);
  }
  va_end (args);
  return changed;
}

/*!
 * \brief Adds new properties in \a self, using the given \a dict as a source.
 *
 * Unlike wp_properties_add_from_dict(), this function only adds
 * properties that have one of the specified keys; the rest is left untouched.
 *
 * \ingroup wpproperties
 * \param self a properties set
 * \param dict a `spa_dict` that contains properties to add
 * \param key1 a property to add
 * \param ... a list of additional properties to add, followed by NULL
 * \returns the number of properties that were changed
 */
gint
wp_properties_add_keys_from_dict (WpProperties * self,
    const struct spa_dict * dict, const gchar * key1, ...)
{
  gint changed = 0;
  const gchar *value;

  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  va_list args;
  va_start (args, key1);
  for (; key1; key1 = va_arg (args, const gchar *)) {
    if ((value = spa_dict_lookup (dict, key1)) == NULL)
      continue;
    if (wp_properties_get (self, key1) == NULL)
      changed += wp_properties_set (self, key1, value);
  }
  va_end (args);
  return changed;
}

/*!
 * \brief The same as wp_properties_add_keys(), using a NULL-terminated array
 * for specifying the keys to add
 *
 * \ingroup wpproperties
 * \param self a properties set
 * \param props a properties set that contains properties to add
 * \param keys (array zero-terminated=1): the properties to add
 * \returns the number of properties that were changed
 */
gint
wp_properties_add_keys_array (WpProperties * self, WpProperties * props,
    const gchar * keys[])
{
  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  return pw_properties_add_keys (self->props,
      wp_properties_peek_dict (props), keys);
}

/*!
 * \brief Looks up a given property value from a key
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \param key a property key
 * \returns (transfer none) (nullable): the value of the property identified
 *   with \a key, or NULL if this property is not contained in \a self
 */
const gchar *
wp_properties_get (WpProperties * self, const gchar * key)
{
  g_return_val_if_fail (self != NULL, NULL);
  g_return_val_if_fail (key != NULL, NULL);

  return spa_dict_lookup (wp_properties_peek_dict (self), key);
}

/*!
 * \brief Sets the given property \a key - \a value pair on \a self.
 *
 * If the property already existed, the value is overwritten with the new one.
 *
 * If the \a value is NULL, then the specified property is removed from \a self
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \param key a property key
 * \param value (nullable): a property value
 * \returns 1 if the property was changed. 0 if nothing was changed because
 *   the property already existed with the same value or because the key to
 *   remove did not exist.
 */
gint
wp_properties_set (WpProperties * self, const gchar * key,
    const gchar * value)
{
  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  return pw_properties_set (self->props, key, value);
}

/*!
 * \brief Formats the given \a format string with the specified arguments
 * and sets the result as a value of the property specified with \a key
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \param key a property key
 * \param format a printf-style format to be formatted and set as a value for
 *   this property \a key
 * \param ... arguments for \a format
 * \returns 1 if the property was changed. 0 if nothing was changed because
 *   the property already existed with the same value
 */
gint
wp_properties_setf (WpProperties * self, const gchar * key,
    const gchar * format, ...)
{
  gint res;
  va_list varargs;

  va_start (varargs, format);
  res = wp_properties_setf_valist (self, key, format, varargs);
  va_end (varargs);

  return res;
}

/*!
 * \brief This is the `va_list` version of wp_properties_setf()
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \param key a property key
 * \param format a printf-style format to be formatted and set as a value for
 *   this property \a key
 * \param args the variable arguments passed to wp_properties_setf()
 * \returns 1 if the property was changed. 0 if nothing was changed because
 *   the property already existed with the same value
 */
gint
wp_properties_setf_valist (WpProperties * self, const gchar * key,
    const gchar * format, va_list args)
{
  g_return_val_if_fail (self != NULL, -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL);
  g_return_val_if_fail (!(self->flags & FLAG_NO_OWNERSHIP), -EINVAL);

  return pw_properties_setva (self->props, key, format, args);
}

struct _WpPropertiesItem
{
  WpProperties *props;
  const struct spa_dict_item *item;
};

G_DEFINE_BOXED_TYPE (WpPropertiesItem, wp_properties_item,
    wp_properties_item_ref, wp_properties_item_unref)

static WpPropertiesItem *
wp_properties_item_new (WpProperties *props, const struct spa_dict_item *item)
{
  WpPropertiesItem *self = g_rc_box_new0 (WpPropertiesItem);
  self->props = wp_properties_ref (props);
  self->item = item;
  return self;
}

static void
wp_properties_item_free (gpointer p)
{
  WpPropertiesItem *self = p;
  wp_properties_unref (self->props);
}

/*!
 * \brief Increases the reference count of a properties item object
 * \ingroup wpproperties
 * \param self a properties item object
 * \returns (transfer full): \a self with an additional reference count on it
 * \since 0.4.2
 */
WpPropertiesItem *
wp_properties_item_ref (WpPropertiesItem *self)
{
  return g_rc_box_acquire (self);
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 * \ingroup wpproperties
 * \param self (transfer full): a properties item object
 * \since 0.4.2
 */
void
wp_properties_item_unref (WpPropertiesItem *self)
{
  g_rc_box_release_full (self, wp_properties_item_free);
}

/*!
 * \brief Gets the key from a properties item
 *
 * \ingroup wpproperties
 * \param self the item held by the GValue that was returned from the WpIterator
 *   of wp_properties_new_iterator()
 * \returns (transfer none): the property key of the \a item
 * \since 0.4.2
 */
const gchar *
wp_properties_item_get_key (WpPropertiesItem * self)
{
  return self->item->key;
}

/*!
 * \brief Gets the value from a properties item
 *
 * \ingroup wpproperties
 * \param self the item held by the GValue that was returned from the WpIterator
 *   of wp_properties_new_iterator()
 * \returns (transfer none): the property value of the \a item
 * \since 0.4.2
 */
const gchar *
wp_properties_item_get_value (WpPropertiesItem * self)
{
  return self->item->value;
}

struct dict_iterator_data
{
  WpProperties *properties;
  const struct spa_dict_item *item;
};

static void
dict_iterator_reset (WpIterator *it)
{
  struct dict_iterator_data *it_data = wp_iterator_get_user_data (it);
  it_data->item = wp_properties_peek_dict (it_data->properties)->items;
}

static gboolean
dict_iterator_next (WpIterator *it, GValue *item)
{
  struct dict_iterator_data *it_data = wp_iterator_get_user_data (it);
  const struct spa_dict *dict = wp_properties_peek_dict (it_data->properties);

  if ((it_data->item - dict->items) < dict->n_items) {
    g_value_init (item, WP_TYPE_PROPERTIES_ITEM);
    g_autoptr (WpPropertiesItem) pi = wp_properties_item_new (
        it_data->properties, it_data->item);
    g_value_take_boxed (item, g_steal_pointer (&pi));
    it_data->item++;
    return TRUE;
  }
  return FALSE;
}

static gboolean
dict_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret,
    gpointer data)
{
  struct dict_iterator_data *it_data = wp_iterator_get_user_data (it);
  const struct spa_dict *dict = wp_properties_peek_dict (it_data->properties);
  const struct spa_dict_item *i;

  spa_dict_for_each (i, dict) {
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpPropertiesItem) pi = wp_properties_item_new (
        it_data->properties, i);
    g_value_init (&item, WP_TYPE_PROPERTIES_ITEM);
    g_value_take_boxed (&item, g_steal_pointer (&pi));
    if (!func (&item, ret, data))
      return FALSE;
  }
  return TRUE;
}

static void
dict_iterator_finalize (WpIterator *it)
{
  struct dict_iterator_data *it_data = wp_iterator_get_user_data (it);
  wp_properties_unref (it_data->properties);
}

static const WpIteratorMethods dict_iterator_methods = {
  .version = WP_ITERATOR_METHODS_VERSION,
  .reset = dict_iterator_reset,
  .next = dict_iterator_next,
  .fold = dict_iterator_fold,
  .finalize = dict_iterator_finalize,
};

/*!
 * \brief Iterates through all the properties in the properties object
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \returns (transfer full): an iterator that iterates over the properties.
 *   The items in the iterator are of type WpPropertiesItem.
 *   Use wp_properties_item_get_key() and
 *   wp_properties_item_get_value() to retrieve their contents.
 */
WpIterator *
wp_properties_new_iterator (WpProperties * self)
{
  g_autoptr (WpIterator) it = NULL;
  struct dict_iterator_data *it_data;

  g_return_val_if_fail (self != NULL, NULL);

  it = wp_iterator_new (&dict_iterator_methods,
      sizeof (struct dict_iterator_data));
  it_data = wp_iterator_get_user_data (it);
  it_data->properties = wp_properties_ref (self);
  it_data->item = wp_properties_peek_dict (it_data->properties)->items;
  return g_steal_pointer (&it);
}

/*!
 * \brief Gets the key from a properties iterator item
 *
 * \ingroup wpproperties
 * \param item a GValue that was returned from the WpIterator of
 *   wp_properties_new_iterator()
 * \returns (transfer none): the property key of the \a item
 * \deprecated Use wp_properties_item_get_key() instead
 */
const gchar *
wp_properties_iterator_item_get_key (const GValue * item)
{
  WpPropertiesItem *pi = g_value_get_boxed (item);
  g_return_val_if_fail (pi != NULL, NULL);
  return wp_properties_item_get_key (pi);
}

/*!
 * \brief Gets the value from a properties iterator item
 *
 * \ingroup wpproperties
 * \param item a GValue that was returned from the WpIterator of
 *   wp_properties_new_iterator()
 * \returns (transfer none): the property value of the \a item
 * \deprecated Use wp_properties_item_get_value() instead
 */
const gchar *
wp_properties_iterator_item_get_value (const GValue * item)
{
  WpPropertiesItem *pi = g_value_get_boxed (item);
  g_return_val_if_fail (pi != NULL, NULL);
  return wp_properties_item_get_value (pi);
}

/*!
 * \brief Gets the number of properties contained in this object
 * \ingroup wpproperties
 * \param self a properties object
 * \returns the number of properties contained in this object
 * \since 0.4.10
 */
guint
wp_properties_get_count (WpProperties * self)
{
  const struct spa_dict *dict = wp_properties_peek_dict(self);
  g_return_val_if_fail (dict != NULL, 0);

  return dict->n_items;
}

/*!
 * \brief Sorts the keys in alphabetical order
 * \ingroup wpproperties
 * \param self a properties object
 */
void
wp_properties_sort (WpProperties * self)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (!(self->flags & FLAG_IS_DICT));
  g_return_if_fail (!(self->flags & FLAG_NO_OWNERSHIP));

  return spa_dict_qsort (&self->props->dict);
}

/*!
 * \brief Gets the dictionary wrapped by a properties object
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \returns (transfer none): the internal properties set as a `struct spa_dict *`
 */
const struct spa_dict *
wp_properties_peek_dict (WpProperties * self)
{
  g_return_val_if_fail (self != NULL, NULL);

  return (self->flags & FLAG_IS_DICT) ? self->dict : &self->props->dict;
}

/*!
 * \brief Gets a copy of the properties object as a `struct pw_properties`
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \returns (transfer full): a copy of the properties in \a self
 *   as a `struct pw_properties`
 */
struct pw_properties *
wp_properties_to_pw_properties (WpProperties * self)
{
  g_return_val_if_fail (self != NULL, NULL);

  return pw_properties_new_dict (wp_properties_peek_dict (self));
}

/*!
 * \brief Similar to wp_properties_to_pw_properties(), but this method avoids making
 * a copy of the properties by returning the `struct pw_properties` that is
 * stored internally and then freeing the WpProperties wrapper.
 *
 * If \a self is not uniquely owned (see wp_properties_ensure_unique_owner()),
 * then this method does make a copy and is the same as
 * wp_properties_to_pw_properties(), performance-wise.
 *
 * \ingroup wpproperties
 * \param self (transfer full): a properties object
 * \returns (transfer full): the properties in \a self as a `struct pw_properties`
 */
struct pw_properties *
wp_properties_unref_and_take_pw_properties (WpProperties * self)
{
  g_return_val_if_fail (self != NULL, NULL);

  g_autoptr (WpProperties) unique = wp_properties_ensure_unique_owner (self);
  /* set the flag so that unref-ing \a unique will not destroy unique->props */
  unique->flags = FLAG_NO_OWNERSHIP;
  return unique->props;
}

/*!
 * \brief Checks if all property values contained in \a other are matching with
 * the values in \a self.
 *
 * If a property is contained in \a other and not in \a self, the result is not
 * matched. If a property is contained in both sets, then the value of the
 * property in \a other is interpreted as a glob-style pattern
 * (using g_pattern_match_simple()) and the value in \a self is checked to
 * see if it matches with this pattern.
 *
 * \ingroup wpproperties
 * \param self a properties object
 * \param other a set of properties to match
 * \returns TRUE if all matches were successfull, FALSE if at least one
 *   property value did not match
 */
gboolean
wp_properties_matches (WpProperties * self, WpProperties *other)
{
  const struct spa_dict * dict;
  const struct spa_dict_item *item;
  const gchar *value;

  g_return_val_if_fail (self != NULL, FALSE);

  /* Check if the property values match the ones from 'self' */
  dict = wp_properties_peek_dict (other);
  spa_dict_for_each(item, dict) {
    value = wp_properties_get (self, item->key);
    if (!value || !g_pattern_match_simple (value, item->value))
      return FALSE;
  }

  return TRUE;
}
  07070100000087000081A4000000000000000000000001656CC35F0000127A000000000000000000000000000000000000002700000000wireplumber-0.4.17/lib/wp/properties.h    /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_PROPERTIES_H__
#define __WIREPLUMBER_PROPERTIES_H__

#include <glib-object.h>
#include "defs.h"
#include "iterator.h"

G_BEGIN_DECLS

struct pw_properties;
struct spa_dict;

/*!
 * \brief The WpProperties GType
 * \ingroup wpproperties
 */
#define WP_TYPE_PROPERTIES (wp_properties_get_type ())
WP_API
GType wp_properties_get_type (void);

typedef struct _WpProperties WpProperties;

WP_API
WpProperties * wp_properties_new_empty (void);

WP_API
WpProperties * wp_properties_new (const gchar * key, ...) G_GNUC_NULL_TERMINATED;

WP_API
WpProperties * wp_properties_new_valist (const gchar * key, va_list args);

WP_API
WpProperties * wp_properties_new_string (const gchar * str);

WP_API
WpProperties * wp_properties_new_wrap (const struct pw_properties * props);

WP_API
WpProperties * wp_properties_new_take (struct pw_properties * props);

WP_API
WpProperties * wp_properties_new_copy (const struct pw_properties * props);

WP_API
WpProperties * wp_properties_new_wrap_dict (const struct spa_dict * dict);

WP_API
WpProperties * wp_properties_new_copy_dict (const struct spa_dict * dict);

WP_API
WpProperties * wp_properties_copy (WpProperties * other);

/* ref counting */

WP_API
WpProperties * wp_properties_ref (WpProperties * self);

WP_API
void wp_properties_unref (WpProperties * self);

WP_API
WpProperties * wp_properties_ensure_unique_owner (WpProperties * self);

/* update */

WP_API
gint wp_properties_update (WpProperties * self, WpProperties * props);

WP_API
gint wp_properties_update_from_dict (WpProperties * self,
    const struct spa_dict * dict);

/* add */

WP_API
gint wp_properties_add (WpProperties * self, WpProperties * props);

WP_API
gint wp_properties_add_from_dict (WpProperties * self,
    const struct spa_dict * dict);

/* update keys */

WP_API
gint wp_properties_update_keys (WpProperties * self, WpProperties * props,
    const gchar * key1, ...) G_GNUC_NULL_TERMINATED;

WP_API
gint wp_properties_update_keys_from_dict (WpProperties * self,
    const struct spa_dict * dict, const gchar * key1, ...) G_GNUC_NULL_TERMINATED;

WP_API
gint wp_properties_update_keys_array (WpProperties * self, WpProperties * props,
    const gchar * keys[]);

/* add keys */

WP_API
gint wp_properties_add_keys (WpProperties * self, WpProperties * props,
    const gchar * key1, ...) G_GNUC_NULL_TERMINATED;

WP_API
gint wp_properties_add_keys_from_dict (WpProperties * self,
    const struct spa_dict * dict, const gchar * key1, ...) G_GNUC_NULL_TERMINATED;

WP_API
gint wp_properties_add_keys_array (WpProperties * self, WpProperties * props,
    const gchar * keys[]);

/* get/set */

WP_API
const gchar * wp_properties_get (WpProperties * self, const gchar * key);

WP_API
gint wp_properties_set (WpProperties * self, const gchar * key,
    const gchar * value);

WP_API
gint wp_properties_setf (WpProperties * self, const gchar * key,
    const gchar * format, ...) G_GNUC_PRINTF(3, 4);

WP_API
gint wp_properties_setf_valist (WpProperties * self, const gchar * key,
    const gchar * format, va_list args) G_GNUC_PRINTF(3, 0);

/* iterate */

#define WP_TYPE_PROPERTIES_ITEM (wp_properties_item_get_type ())
WP_API
GType wp_properties_item_get_type (void);

typedef struct _WpPropertiesItem WpPropertiesItem;

WP_API
WpPropertiesItem *wp_properties_item_ref (WpPropertiesItem *self);

WP_API
void wp_properties_item_unref (WpPropertiesItem *self);

WP_API
const gchar * wp_properties_item_get_key (WpPropertiesItem * self);

WP_API
const gchar * wp_properties_item_get_value (WpPropertiesItem * self);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpPropertiesItem, wp_properties_item_unref)

WP_API
WpIterator * wp_properties_new_iterator (WpProperties * self);

WP_API
G_DEPRECATED_FOR(wp_properties_item_get_key)
const gchar * wp_properties_iterator_item_get_key (const GValue * item);

WP_API
G_DEPRECATED_FOR(wp_properties_item_get_value)
const gchar * wp_properties_iterator_item_get_value (const GValue * item);

/* count */

WP_API
guint wp_properties_get_count (WpProperties * self);


/* sort */

WP_API
void wp_properties_sort (WpProperties * self);

/* convert */

WP_API
const struct spa_dict * wp_properties_peek_dict (WpProperties * self);

WP_API
struct pw_properties * wp_properties_to_pw_properties (WpProperties * self);

WP_API
struct pw_properties * wp_properties_unref_and_take_pw_properties (
    WpProperties * self);

/* comparison */

WP_API
gboolean wp_properties_matches (WpProperties * self, WpProperties *other);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpProperties, wp_properties_unref)

G_END_DECLS

#endif
  07070100000088000081A4000000000000000000000001656CC35F00002A01000000000000000000000000000000000000002D00000000wireplumber-0.4.17/lib/wp/proxy-interfaces.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-proxy-ifaces"

#include "proxy-interfaces.h"
#include "properties.h"

/*! \defgroup wppipewireobject WpPipewireObject */
/*!
 * \struct WpPipewireObject
 *
 * An interface for standard PipeWire objects.
 *
 * The common characteristic of all objects that implement this interface is
 * the presence of an "info" structure that contains additional properties
 * for this object (in the form of a spa_dict / pw_properties) and optionally
 * also some parameters that can be enumerated and set on the object.
 *
 * \gproperties
 *
 * \gproperty{properties, WpProperties *, G_PARAM_READABLE,
 *   The properties of the pipewire object}
 *
 * \gproperty{param-info, GVariant * (a{ss}), G_PARAM_READABLE,
 *   The param info of the object}
 *
 * \gproperty{native-info, gpointer, G_PARAM_READABLE,
 *   The native info structure}
 *
 * \gsignals
 *
 * \par params-changed
 * \parblock
 * \code
 * params_changed_callback (WpPipewireObject * self,
 *                          const gchar *id,
 *                          gpointer user_data)
 * \endcode
 *
 * Emitted when the params for id have changed. On proxies that cache params
 * from a remote object, this is emitted after the cached values have changed.
 *
 * You can expect this to be emitted only when the relevant
 * WP_PIPEWIRE_OBJECT_FEATURE_PARAM_* has been activated.
 *
 * Parameters:
 * - `id` - the parameter id as a string (ex "Props", "EnumRoute")
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 */

G_DEFINE_INTERFACE (WpPipewireObject, wp_pipewire_object, WP_TYPE_PROXY)

static void
wp_pipewire_object_default_init (WpPipewireObjectInterface * iface)
{
  g_object_interface_install_property (iface,
      g_param_spec_pointer ("native-info", "native-info",
          "The native info structure",
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_interface_install_property (iface,
      g_param_spec_boxed ("properties", "properties",
          "The properties of the pipewire object", WP_TYPE_PROPERTIES,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_interface_install_property (iface,
      g_param_spec_variant ("param-info", "param-info",
          "The param info of the object", G_VARIANT_TYPE ("a{ss}"), NULL,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_signal_new ("params-changed", G_TYPE_FROM_INTERFACE (iface),
      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
}

/*!
 * \brief Retrieves the native infor structure of this object
 * (pw_node_info, pw_port_info, etc...)
 *
 * \remark Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 *
 * \ingroup wppipewireobject
 * \param self the pipewire object
 * \returns (nullable): the native pipewire info structure of this object
 */
gconstpointer
wp_pipewire_object_get_native_info (WpPipewireObject * self)
{
  g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECT (self), NULL);
  g_return_val_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->get_native_info,
      NULL);

  return WP_PIPEWIRE_OBJECT_GET_IFACE (self)->get_native_info (self);
}

/*!
 * \brief Retrieves the PipeWire properties of this object
 *
 * \remark Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 *
 * \ingroup wppipewireobject
 * \param self the pipewire object
 * \returns (transfer full): the pipewire properties of this object;
 *   normally these are the properties that are part of the info structure
 */
WpProperties *
wp_pipewire_object_get_properties (WpPipewireObject * self)
{
  g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECT (self), NULL);
  g_return_val_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->get_properties,
      NULL);

  return WP_PIPEWIRE_OBJECT_GET_IFACE (self)->get_properties (self);
}

/*!
 * \brief Iterates over the object's PipeWire properties
 *
 * \remark Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 *
 * \ingroup wppipewireobject
 * \param self the pipewire object
 * \returns (transfer full): an iterator that iterates over the pipewire
 *   properties of this object. Use wp_properties_iterator_item_get_key() and
 *   wp_properties_iterator_item_get_value() to parse the items returned by
 *   this iterator.
 */
WpIterator *
wp_pipewire_object_new_properties_iterator (WpPipewireObject * self)
{
  g_autoptr (WpProperties) properties =
      wp_pipewire_object_get_properties (self);
  return properties ? wp_properties_new_iterator (properties) : NULL;
}

/*!
 * \brief Returns the value of a single pipewire property.
 *
 * This is the same as getting the whole properties structure with
 * wp_pipewire_object_get_properties() and accessing a single property with
 * wp_properties_get(), but saves one call and having to clean up the
 * WpProperties reference count afterwards.
 *
 * The value is owned by the proxy, but it is guaranteed to stay alive
 * until execution returns back to the event loop.
 *
 * \remark Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 *
 * \ingroup wppipewireobject
 * \param self the pipewire object
 * \param key the property name
 * \returns (transfer none) (nullable): the value of the pipewire property
 *   \a key or NULL if the property doesn't exist
 */
const gchar *
wp_pipewire_object_get_property (WpPipewireObject * self, const gchar * key)
{
  g_autoptr (WpProperties) properties =
      wp_pipewire_object_get_properties (self);
  return properties ? wp_properties_get (properties, key) : NULL;
}

/*!
 * \brief Returns the available parameters of this pipewire object.
 *
 * The return value is a GVariant of type `a{ss}`, where the key of each map
 * entry is a spa param type id (the same ids that you can pass in
 * wp_pipewire_object_enum_params()) and the value is a string that can
 * contain the following letters, each of them representing a flag:
 *   - `r`: the param is readable (`SPA_PARAM_INFO_READ`)
 *   - `w`: the param is writable (`SPA_PARAM_INFO_WRITE`)
 *
 * For params that are readable, you can query them with
 * wp_pipewire_object_enum_params()
 *
 * Params that are writable can be set with wp_pipewire_object_set_param()
 *
 * \remark Requires WP_PIPEWIRE_OBJECT_FEATURE_INFO
 *
 * \ingroup wppipewireobject
 * \param self the pipewire object
 * \returns (transfer full) (nullable): a variant of type `a{ss}` or NULL
 *   if the object does not support params at all
 */
GVariant *
wp_pipewire_object_get_param_info (WpPipewireObject * self)
{
  g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECT (self), NULL);
  g_return_val_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->get_param_info,
      NULL);

  return WP_PIPEWIRE_OBJECT_GET_IFACE (self)->get_param_info (self);
}

/*!
 * \brief Enumerate object parameters.
 *
 * This will asynchronously return the result, or an error, by calling the
 * given \a callback. The result is going to be a WpIterator containing
 * WpSpaPod objects, which can be retrieved
 * with wp_pipewire_object_enum_params_finish().
 *
 * \ingroup wppipewireobject
 * \param self the pipewire object
 * \param id (nullable): the parameter id to enumerate or NULL for all parameters
 * \param filter (nullable): a param filter or NULL
 * \param cancellable (nullable): a cancellable for the async operation
 * \param callback (scope async): a callback to call with the result
 * \param user_data (closure): data to pass to \a callback
 */
void
wp_pipewire_object_enum_params (WpPipewireObject * self, const gchar * id,
    WpSpaPod *filter, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer user_data)
{
  g_return_if_fail (WP_IS_PIPEWIRE_OBJECT (self));
  g_return_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_params);

  WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_params (self, id, filter,
      cancellable, callback, user_data);
}

/*!
 * \brief Finishes an asynchronous parameter enumeration operation.
 *
 * \ingroup wppipewireobject
 * \param self the pipewire object
 * \param res the async result
 * \param error (out) (optional): the reported error of the operation, if any
 * \returns (transfer full) (nullable): an iterator to iterate over the
 *   collected params, or NULL if the operation resulted in error;
 *   the items in the iterator are WpSpaPod
 */
WpIterator *
wp_pipewire_object_enum_params_finish (WpPipewireObject * self,
    GAsyncResult * res, GError ** error)
{
  g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECT (self), NULL);
  g_return_val_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_params_finish,
      NULL);

  return WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_params_finish (self, res,
      error);
}

/*!
 * \brief This method can be used to retrieve object parameters in a synchronous
 * way (in contrast with wp_pipewire_object_enum_params(), which is async).
 *
 * The `WP_PIPEWIRE_OBJECT_FEATURE_PARAM_<something>` feature
 * that corresponds to the specified \a id must have been activated earlier.
 * These features enable monitoring and caching of params underneath, so that
 * they are always available for retrieval with this method.
 *
 * Note, however, that cached params may be out-of-date if they have changed
 * very recently on the remote object and the caching mechanism hasn't been
 * able to update them yet, so if you really need up-to-date information you
 * should only rely on wp_pipewire_object_enum_params() instead.
 *
 * \ingroup wppipewireobject
 * \param self the pipewire object
 * \param id the parameter id to enumerate
 * \param filter (nullable): a param filter or NULL
 * \returns (transfer full) (nullable): an iterator to iterate over cached
 *    parameters, or NULL if parameters for this \a id are not cached;
 *    the items in the iterator are WpSpaPod
 */
WpIterator *
wp_pipewire_object_enum_params_sync (WpPipewireObject * self,
    const gchar * id, WpSpaPod * filter)
{
  g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECT (self), NULL);
  g_return_val_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_params_sync,
      NULL);

  return WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_params_sync (self, id,
      filter);
}

/*!
 * \brief Sets a parameter on the object.
 *
 * \ingroup wppipewireobject
 * \param self the pipewire object
 * \param id the parameter id to set
 * \param flags optional flags or 0
 * \param param (transfer full): the parameter to set
 * \returns TRUE on success, FALSE if setting the param failed
 */
gboolean
wp_pipewire_object_set_param (WpPipewireObject * self, const gchar * id,
    guint32 flags, WpSpaPod * param)
{
  g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECT (self), FALSE);
  g_return_val_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->set_param, FALSE);

  return WP_PIPEWIRE_OBJECT_GET_IFACE (self)->set_param (self, id, flags,
      param);
}
   07070100000089000081A4000000000000000000000001656CC35F00000973000000000000000000000000000000000000002D00000000wireplumber-0.4.17/lib/wp/proxy-interfaces.h  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_PROXY_INTERFACES_H__
#define __WIREPLUMBER_PROXY_INTERFACES_H__

#include "proxy.h"
#include "properties.h"
#include "spa-pod.h"

G_BEGIN_DECLS

/*!
 * \brief The WpPipewireObject GType
 * \ingroup wppipewireobject
 */
#define WP_TYPE_PIPEWIRE_OBJECT (wp_pipewire_object_get_type ())
WP_API
G_DECLARE_INTERFACE (WpPipewireObject, wp_pipewire_object,
                     WP, PIPEWIRE_OBJECT, WpProxy)

struct _WpPipewireObjectInterface
{
  GTypeInterface parent_iface;

  gconstpointer (*get_native_info) (WpPipewireObject * self);

  WpProperties * (*get_properties) (WpPipewireObject * self);

  GVariant * (*get_param_info) (WpPipewireObject * self);

  void (*enum_params) (WpPipewireObject * self, const gchar * id,
      WpSpaPod * filter, GCancellable * cancellable,
      GAsyncReadyCallback callback, gpointer user_data);

  WpIterator * (*enum_params_finish) (WpPipewireObject * self,
      GAsyncResult * res, GError ** error);

  WpIterator * (*enum_params_sync) (WpPipewireObject * self,
      const gchar * id, WpSpaPod * filter);

  gboolean (*set_param) (WpPipewireObject * self, const gchar * id,
      guint32 flags, WpSpaPod * param);

  /*< private >*/
  WP_PADDING(5)
};

WP_API
gconstpointer wp_pipewire_object_get_native_info (WpPipewireObject * self);

WP_API
WpProperties * wp_pipewire_object_get_properties (WpPipewireObject * self);

WP_API
WpIterator * wp_pipewire_object_new_properties_iterator (
    WpPipewireObject * self);

WP_API
const gchar * wp_pipewire_object_get_property (WpPipewireObject * self,
    const gchar * key);

WP_API
GVariant * wp_pipewire_object_get_param_info (WpPipewireObject * self);

WP_API
void wp_pipewire_object_enum_params (WpPipewireObject * self, const gchar * id,
    WpSpaPod *filter, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer user_data);

WP_API
WpIterator * wp_pipewire_object_enum_params_finish (WpPipewireObject * self,
    GAsyncResult * res, GError ** error);

WP_API
WpIterator * wp_pipewire_object_enum_params_sync (WpPipewireObject * self,
    const gchar * id, WpSpaPod * filter);

WP_API
gboolean wp_pipewire_object_set_param (WpPipewireObject * self,
    const gchar * id, guint32 flags, WpSpaPod * param);


G_END_DECLS

#endif
 0707010000008A000081A4000000000000000000000001656CC35F000025F4000000000000000000000000000000000000002200000000wireplumber-0.4.17/lib/wp/proxy.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-proxy"

#include "proxy.h"
#include "log.h"
#include "error.h"

#include <pipewire/pipewire.h>
#include <spa/utils/hook.h>
#include <spa/utils/result.h>

/*! \defgroup wpproxy WpProxy */
/*!
 * \struct WpProxy
 *
 * Base class for all objects that expose PipeWire objects using `pw_proxy`
 * underneath.
 *
 * This base class cannot be instantiated. It provides handling of
 * pw_proxy's events and exposes common functionality.
 *
 * \gproperties
 *
 * \gproperty{bound-id, guint, G_PARAM_READABLE,
 *   The id that this object has on the registry}
 *
 * \gproperty{pw-proxy, gpointer, G_PARAM_READABLE,
 *   The `struct pw_proxy *`}
 *
 * \gsignals
 *
 * \par bound
 * \parblock
 * \code
 * void
 * bound_callback (WpProxy * self,
 *                 guint id,
 *                 gpointer user_data)
 * \endcode
 * Parameters:
 * - `id` - the bound id of the proxy
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 *
 * \par pw-proxy-created
 * \parblock
 * \code
 * void
 * pw_proxy_created_callback (WpProxy * self,
 *                            gpointer object,
 *                            gpointer user_data)
 * \endcode
 * Parameters:
 * - `object` - pointer to the pw_proxy that was just created
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 *
 * \par pw-proxy-destroyed
 * \parblock
 * \code
 * void
 * pw_proxy_destroyed_callback (WpProxy * self,
 *                              gpointer user_data)
 * \endcode
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 *
 * \par error
 * \parblock
 * \code
 * void
 * error_callback (WpProxy * self,
 *                 gint seq, gint res, const gchar *message,
 *                 gpointer user_data)
 * \endcode
 * Emitted when an error occurs on the remote object.
 * The parameters are exactly the same as on the underlying pw_proxy error
 * callback.
 *
 * Parameters:
 * - `seq` - the sequence number of the operation that caused the error
 * - `res` - the error code
 * - `message` - a description of the error
 *
 * Flags: G_SIGNAL_RUN_FIRST
 * \endparblock
 */

typedef struct _WpProxyPrivate WpProxyPrivate;
struct _WpProxyPrivate
{
  struct pw_proxy *pw_proxy;
  struct spa_hook listener;
};

enum {
  PROP_0,
  PROP_BOUND_ID,
  PROP_PW_PROXY,
};

enum
{
  SIGNAL_PW_PROXY_CREATED,
  SIGNAL_PW_PROXY_DESTROYED,
  SIGNAL_BOUND,
  SIGNAL_ERROR,
  LAST_SIGNAL,
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpProxy, wp_proxy, WP_TYPE_OBJECT)

static void
proxy_event_destroy (void *data)
{
  WpProxy *self = WP_PROXY (data);
  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);

  wp_trace_object (self, "destroyed pw_proxy %p (%u)", priv->pw_proxy,
      pw_proxy_get_bound_id (priv->pw_proxy));

  spa_hook_remove (&priv->listener);
  priv->pw_proxy = NULL;
  wp_object_update_features (WP_OBJECT (self), 0, WP_PROXY_FEATURE_BOUND);

  wp_object_abort_activation (WP_OBJECT (self), "PipeWire proxy destroyed");

  g_signal_emit (self, signals[SIGNAL_PW_PROXY_DESTROYED], 0);
}

static void
proxy_event_bound (void *data, uint32_t global_id)
{
  WpProxy *self = WP_PROXY (data);

  wp_trace_object (self, "bound to %u", global_id);

  wp_object_update_features (WP_OBJECT (self), WP_PROXY_FEATURE_BOUND, 0);
  g_signal_emit (self, signals[SIGNAL_BOUND], 0, global_id);
}

static void
proxy_event_removed (void *data)
{
  wp_trace_object (data, "removed");
}

static void
proxy_event_error (void *data, int seq, int res, const char *message)
{
  WpProxy *self = WP_PROXY (data);
  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);

  wp_trace_object (self, "error seq:%d res:%d (%s) %s",
      seq, res, spa_strerror(res), message);

  /* we destroy the proxy on error if feature bound is still not enabled */
  if (priv->pw_proxy &&
      !(wp_object_get_active_features (WP_OBJECT (self)) & WP_PROXY_FEATURE_BOUND))
    pw_proxy_destroy (priv->pw_proxy);

  wp_object_abort_activation (WP_OBJECT (self), message);

  g_signal_emit (self, signals[SIGNAL_ERROR], 0, seq, res, message);
}

static const struct pw_proxy_events proxy_events = {
  PW_VERSION_PROXY_EVENTS,
  .destroy = proxy_event_destroy,
  .bound = proxy_event_bound,
  .removed = proxy_event_removed,
  .error = proxy_event_error,
};

static void
wp_proxy_init (WpProxy * self)
{
}

static void
wp_proxy_dispose (GObject * object)
{
  WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY (object));

  if (priv->pw_proxy)
    pw_proxy_destroy (priv->pw_proxy);

  G_OBJECT_CLASS (wp_proxy_parent_class)->dispose (object);
}

static void
wp_proxy_get_property (GObject * object, guint property_id, GValue * value,
    GParamSpec * pspec)
{
  WpProxy *self = WP_PROXY (object);

  switch (property_id) {
  case PROP_BOUND_ID:
    g_value_set_uint (value, wp_proxy_get_bound_id (self));
    break;
  case PROP_PW_PROXY:
    g_value_set_pointer (value, wp_proxy_get_pw_proxy (self));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_proxy_deactivate (WpObject * object, WpObjectFeatures features)
{
  if (features & WP_PROXY_FEATURE_BOUND) {
    WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY (object));
    if (priv->pw_proxy)
      pw_proxy_destroy (priv->pw_proxy);
    wp_object_update_features (object, 0, WP_PROXY_FEATURE_BOUND);
  }
}

static void
wp_proxy_class_init (WpProxyClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpObjectClass *wpobject_class = (WpObjectClass *) klass;

  object_class->get_property = wp_proxy_get_property;
  object_class->dispose = wp_proxy_dispose;

  wpobject_class->deactivate = wp_proxy_deactivate;

  /* Install the properties */

  g_object_class_install_property (object_class, PROP_BOUND_ID,
      g_param_spec_uint ("bound-id", "bound-id",
          "The id that this object has on the registry", 0, G_MAXUINT, 0,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PW_PROXY,
      g_param_spec_pointer ("pw-proxy", "pw-proxy", "The struct pw_proxy *",
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  /* Signals */
  signals[SIGNAL_PW_PROXY_CREATED] = g_signal_new (
      "pw-proxy-created", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      G_STRUCT_OFFSET (WpProxyClass, pw_proxy_created), NULL, NULL, NULL,
      G_TYPE_NONE, 1, G_TYPE_POINTER);

  signals[SIGNAL_PW_PROXY_DESTROYED] = g_signal_new (
      "pw-proxy-destroyed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      G_STRUCT_OFFSET (WpProxyClass, pw_proxy_destroyed), NULL, NULL, NULL,
      G_TYPE_NONE, 0);

  signals[SIGNAL_BOUND] = g_signal_new (
      "bound", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      G_STRUCT_OFFSET (WpProxyClass, bound), NULL, NULL, NULL,
      G_TYPE_NONE, 1, G_TYPE_UINT);

  signals[SIGNAL_ERROR] = g_signal_new (
      "error", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      G_STRUCT_OFFSET (WpProxyClass, error), NULL, NULL, NULL,
      G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
}

/*!
 * \brief Returns the proxy bound id.
 *
 * The bound id is the id that this object has on the
 * PipeWire registry (a.k.a. the global id). The object must have the
 * WP_PROXY_FEATURE_BOUND feature before this method can be called.
 *
 * \remarks Requires WP_PROXY_FEATURE_BOUND
 *
 * \ingroup wpproxy
 * \param self the proxy
 * \returns the bound id of this object
 */
guint32
wp_proxy_get_bound_id (WpProxy * self)
{
  g_return_val_if_fail (WP_IS_PROXY (self), 0);
  g_warn_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
      WP_PROXY_FEATURE_BOUND);

  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
  return priv->pw_proxy ? pw_proxy_get_bound_id (priv->pw_proxy) : SPA_ID_INVALID;
}

/*!
 * \brief Gets the interface type of the proxied object
 * \ingroup wpproxy
 * \param self the proxy
 * \param version (out) (optional): the version of the interface
 * \returns the PipeWire type of the interface that is being proxied
 */
const gchar *
wp_proxy_get_interface_type (WpProxy * self, guint32 * version)
{
  g_return_val_if_fail (WP_IS_PROXY (self), NULL);

  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
  if (priv->pw_proxy)
    return pw_proxy_get_type (priv->pw_proxy, version);
  else {
    WpProxyClass *klass = WP_PROXY_GET_CLASS (self);
    if (version)
      *version = klass->pw_iface_version;
    return klass->pw_iface_type;
  }
}

/*!
 * \brief Gets the `pw_proxy` wrapped by this proxy object
 * \ingroup wpproxy
 * \param self the proxy
 * \returns a pointer to the underlying `pw_proxy` object
 */
struct pw_proxy *
wp_proxy_get_pw_proxy (WpProxy * self)
{
  g_return_val_if_fail (WP_IS_PROXY (self), NULL);

  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
  return priv->pw_proxy;
}

/*!
 * \brief Private method to be used by subclasses to set the `pw_proxy` pointer
 * when it is available.
 *
 * This can be called only if there is no `pw_proxy` already set.
 * Takes ownership of \a proxy.
 *
 * \ingroup wpproxy
 */
void
wp_proxy_set_pw_proxy (WpProxy * self, struct pw_proxy * proxy)
{
  g_return_if_fail (WP_IS_PROXY (self));

  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);

  g_return_if_fail (proxy);

  g_return_if_fail (priv->pw_proxy == NULL);
  priv->pw_proxy = proxy;

  pw_proxy_add_listener (priv->pw_proxy, &priv->listener, &proxy_events,
      self);

  /* inform subclasses and listeners */
  g_signal_emit (self, signals[SIGNAL_PW_PROXY_CREATED], 0, priv->pw_proxy);
}
0707010000008B000081A4000000000000000000000001656CC35F00000B4E000000000000000000000000000000000000002200000000wireplumber-0.4.17/lib/wp/proxy.h /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_PROXY_H__
#define __WIREPLUMBER_PROXY_H__

#include "object.h"

G_BEGIN_DECLS

struct pw_proxy;

/*!
 * \brief Flags to be used as WpObjectFeatures for WpProxy subclasses.
 * \ingroup wpproxy
 */
typedef enum { /*< flags >*/
  /* standard features */
  WP_PROXY_FEATURE_BOUND                       = (1 << 0),

  /* WpPipewireObjectInterface */
  WP_PIPEWIRE_OBJECT_FEATURE_INFO              = (1 << 4),
  WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS       = (1 << 5),
  WP_PIPEWIRE_OBJECT_FEATURE_PARAM_FORMAT      = (1 << 6),
  WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROFILE     = (1 << 7),
  WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PORT_CONFIG = (1 << 8),
  WP_PIPEWIRE_OBJECT_FEATURE_PARAM_ROUTE       = (1 << 9),

  WP_PROXY_FEATURE_CUSTOM_START                = (1 << 16), /*< skip >*/
} WpProxyFeatures;

/*!
 * \brief The minimal feature set for proxies implementing WpPipewireObject.
 * This is a subset of \em WP_PIPEWIRE_OBJECT_FEATURES_ALL
 * \ingroup wpproxy
 */
#define WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL \
    (WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO)

/*!
 * \brief The complete common feature set for proxies implementing
 * WpPipewireObject. This is a subset of \em WP_OBJECT_FEATURES_ALL
 * \ingroup wpproxy
 */
#define WP_PIPEWIRE_OBJECT_FEATURES_ALL \
    (WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | \
     WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS | \
     WP_PIPEWIRE_OBJECT_FEATURE_PARAM_FORMAT | \
     WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROFILE | \
     WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PORT_CONFIG | \
     WP_PIPEWIRE_OBJECT_FEATURE_PARAM_ROUTE)

/*!
 * \brief The WpProxy GType
 * \ingroup wpproxy
 */
#define WP_TYPE_PROXY (wp_proxy_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpProxy, wp_proxy, WP, PROXY, WpObject)

struct _WpProxyClass
{
  WpObjectClass parent_class;

  /*! \brief the PipeWire type of the interface that is being proxied by
   *  this class (ex. `PW_TYPE_INTERFACE_Node` for WpNode */
  const gchar * pw_iface_type;

  /*! \brief the PipeWire version of the interface that is being
   *  proxied by this class */
  guint32 pw_iface_version;

  /* signals */

  void (*pw_proxy_created) (WpProxy * self, struct pw_proxy * proxy);
  void (*pw_proxy_destroyed) (WpProxy * self);
  void (*bound) (WpProxy * self, guint32 id);
  void (*error) (WpProxy * self, int seq, int res, const char *message);

  /*< private >*/
  WP_PADDING(6)
};

WP_API
guint32 wp_proxy_get_bound_id (WpProxy * self);

WP_API
const gchar * wp_proxy_get_interface_type (WpProxy * self, guint32 * version);

WP_API
struct pw_proxy * wp_proxy_get_pw_proxy (WpProxy * self);

/* for subclasses only */

WP_API
void wp_proxy_set_pw_proxy (WpProxy * self, struct pw_proxy * proxy);

G_END_DECLS

#endif
  0707010000008C000081A4000000000000000000000001656CC35F000031D5000000000000000000000000000000000000002900000000wireplumber-0.4.17/lib/wp/session-item.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-si"

#include "session-item.h"
#include "log.h"
#include "error.h"
#include "private/registry.h"

/*! \defgroup wpsessionitem WpSessionItem */
/*!
 * \struct WpSessionItem
 *
 * \gproperties
 *
 * \gproperty{id, guint, G_PARAM_READABLE,
 *   The session item unique id}
 *
 * \gproperty{properties, WpProperties *, G_PARAM_READABLE,
 *   The session item properties}
 */

enum {
  STEP_ACTIVATE = WP_TRANSITION_STEP_CUSTOM_START,
  STEP_EXPORT,
};

typedef struct _WpSessionItemPrivate WpSessionItemPrivate;
struct _WpSessionItemPrivate
{
  guint id;
  WpProperties *properties;
};

enum {
  PROP_0,
  PROP_ID,
  PROP_PROPERTIES,
};

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpSessionItem, wp_session_item,
    WP_TYPE_OBJECT)

static guint
get_next_id ()
{
  static guint next_id = 0;
  g_atomic_int_inc (&next_id);
  return next_id;
}

static void
wp_session_item_init (WpSessionItem * self)
{
  WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);

  priv->id = get_next_id ();
  priv->properties = NULL;
}

static void
wp_session_item_default_reset (WpSessionItem * self)
{
  WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);

  g_clear_pointer (&priv->properties, wp_properties_unref);
}

static void
wp_session_item_dispose (GObject * object)
{
  WpSessionItem * self = WP_SESSION_ITEM (object);

  wp_trace_object (self, "dispose");

  wp_session_item_reset (self);

  G_OBJECT_CLASS (wp_session_item_parent_class)->dispose (object);
}

static void
wp_session_item_get_gobject_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpSessionItem *self = WP_SESSION_ITEM (object);
  WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);

  switch (property_id) {
  case PROP_ID:
    g_value_set_uint (value, priv->id);
    break;
  case PROP_PROPERTIES:
    g_value_take_boxed (value, wp_session_item_get_properties (self));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static WpObjectFeatures
session_item_default_get_supported_features (WpObject * self)
{
  return WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED;
}

static guint
session_item_default_activate_get_next_step (WpObject * object,
     WpFeatureActivationTransition * t, guint step, WpObjectFeatures missing)
{
  switch (step) {
    case WP_TRANSITION_STEP_NONE:
      if (missing & WP_SESSION_ITEM_FEATURE_ACTIVE)
        return STEP_ACTIVATE;
      else
        if (missing & WP_SESSION_ITEM_FEATURE_EXPORTED)
          return STEP_EXPORT;
        else
          return WP_TRANSITION_STEP_NONE;

    case STEP_ACTIVATE:
      if (missing & WP_SESSION_ITEM_FEATURE_EXPORTED)
        return STEP_EXPORT;
      else
        return WP_TRANSITION_STEP_NONE;

    case STEP_EXPORT:
      return WP_TRANSITION_STEP_NONE;

    default:
      return WP_TRANSITION_STEP_ERROR;
  }
}

static void
session_item_default_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * t, guint step, WpObjectFeatures missing)
{
  WpSessionItem *self = WP_SESSION_ITEM (object);
  WpTransition *transition = WP_TRANSITION (t);

  switch (step) {
    case STEP_ACTIVATE:
      if (!WP_SESSION_ITEM_GET_CLASS (self)->enable_active) {
        wp_transition_return_error (transition,
            g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
            "session-item: virtual enable_active method is not defined"));
        return;
      }
      WP_SESSION_ITEM_GET_CLASS (self)->enable_active (self, transition);
      break;

    case STEP_EXPORT:
      if (!WP_SESSION_ITEM_GET_CLASS (self)->enable_exported) {
        wp_transition_return_error (transition,
            g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
            "session-item: virtual enable_exported method is not defined"));
        return;
      }
      WP_SESSION_ITEM_GET_CLASS (self)->enable_exported (self, transition);
      break;

    case WP_TRANSITION_STEP_ERROR:
      break;

    default:
      g_return_if_reached ();
  }
}

static void
session_item_default_deactivate (WpObject * object, WpObjectFeatures features)
{
  WpSessionItem *self = WP_SESSION_ITEM (object);
  guint current = wp_object_get_active_features (object);

  if (features & current & WP_SESSION_ITEM_FEATURE_ACTIVE) {
    g_return_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->disable_active);
    WP_SESSION_ITEM_GET_CLASS (self)->disable_active (self);
  }

  if (features & current & WP_SESSION_ITEM_FEATURE_EXPORTED) {
    g_return_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->disable_exported);
    WP_SESSION_ITEM_GET_CLASS (self)->disable_exported (self);
  }
}

static void
wp_session_item_class_init (WpSessionItemClass * klass)
{
  WpObjectClass * wpobject_class = (WpObjectClass *) klass;
  GObjectClass * object_class = (GObjectClass *) klass;

  object_class->dispose = wp_session_item_dispose;
  object_class->get_property = wp_session_item_get_gobject_property;

  wpobject_class->get_supported_features =
      session_item_default_get_supported_features;
  wpobject_class->activate_get_next_step =
      session_item_default_activate_get_next_step;
  wpobject_class->activate_execute_step =
      session_item_default_activate_execute_step;
  wpobject_class->deactivate = session_item_default_deactivate;

  klass->reset = wp_session_item_default_reset;

  g_object_class_install_property (object_class, PROP_ID,
      g_param_spec_uint ("id", "id",
          "The session item unique id", 0, G_MAXUINT, 0,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PROPERTIES,
      g_param_spec_boxed ("properties", "properties",
          "The session item properties", WP_TYPE_PROPERTIES,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

/*!
 * \brief Gets the unique Id of the session item
 * \ingroup wpsessionitem
 * \param self the session item
 */
guint
wp_session_item_get_id (WpSessionItem * self)
{
  WpSessionItemPrivate *priv = NULL;

  g_return_val_if_fail (WP_IS_SESSION_ITEM (self), SPA_ID_INVALID);

  priv = wp_session_item_get_instance_private (self);
  return priv->id;
}

/*!
 * \brief Resets the session item.
 *
 * This essentially removes the configuration and deactivates all active features.
 *
 * \ingroup wpsessionitem
 * \param self the session item
 */
void
wp_session_item_reset (WpSessionItem * self)
{
  g_return_if_fail (WP_IS_SESSION_ITEM (self));
  g_return_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->reset);

  return WP_SESSION_ITEM_GET_CLASS (self)->reset (self);
}

/*!
 * \brief Configures the session item with a set of properties
 *
 * \ingroup wpsessionitem
 * \param self the session item
 * \param props (transfer full): the properties used to configure the item
 * \returns TRUE on success, FALSE if the item could not be configured
 */
gboolean
wp_session_item_configure (WpSessionItem * self, WpProperties * props)
{
  g_return_val_if_fail (WP_IS_SESSION_ITEM (self), FALSE);
  g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->configure,
      FALSE);

  return WP_SESSION_ITEM_GET_CLASS (self)->configure (self, props);
}

/*!
 * \brief Checks if the session item is configured
 *
 * \ingroup wpsessionitem
 * \param self the session item
 * \returns TRUE if the item is configured, FALSE otherwise
 */
gboolean
wp_session_item_is_configured (WpSessionItem * self)
{
  WpSessionItemPrivate *priv = NULL;

  g_return_val_if_fail (WP_IS_SESSION_ITEM (self), FALSE);

  priv = wp_session_item_get_instance_private (self);
  return priv->properties != NULL;
}

/*!
 * \brief An associated proxy is a WpProxy subclass instance that
 * is somehow related to this item. For example:
 *  - An exported WpSiEndpoint should have at least:
 *      - an associated WpSiEndpoint
 *      - an associated WpSession
 *  - In cases where the item wraps a single PipeWire node, it should also
 *    have an associated WpNode
 *
 * \ingroup wpsessionitem
 * \param self the session item
 * \param proxy_type a WpProxy subclass GType
 * \returns (nullable) (transfer full) (type WpProxy): the associated proxy
 *   of the specified @em proxy_type, or NULL if there is no association to
 *   such a proxy
 */
gpointer
wp_session_item_get_associated_proxy (WpSessionItem * self, GType proxy_type)
{
  g_return_val_if_fail (WP_IS_SESSION_ITEM (self), NULL);
  g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->get_associated_proxy,
      NULL);
  g_return_val_if_fail (g_type_is_a (proxy_type, WP_TYPE_PROXY), NULL);

  return WP_SESSION_ITEM_GET_CLASS (self)->get_associated_proxy (self, proxy_type);
}

/*!
 * \brief Gets the bound id of a proxy associated with the session item
 *
 * \ingroup wpsessionitem
 * \param self the session item
 * \param proxy_type a WpProxy subclass GType
 * \returns the bound id of the associated proxy of the specified @em proxy_type,
 *   or `SPA_ID_INVALID` if there is no association to such a proxy
 */
guint32
wp_session_item_get_associated_proxy_id (WpSessionItem * self, GType proxy_type)
{
  g_autoptr (WpProxy) proxy = wp_session_item_get_associated_proxy (self,
      proxy_type);
  if (!proxy)
    return SPA_ID_INVALID;

  return wp_proxy_get_bound_id (proxy);
}

/*!
 * \brief Registers the session item to its associated core
 *
 * \ingroup wpsessionitem
 * \param self (transfer full): the session item
 */
void
wp_session_item_register (WpSessionItem * self)
{
  g_autoptr (WpCore) core = NULL;

  g_return_if_fail (WP_IS_SESSION_ITEM (self));

  core = wp_object_get_core (WP_OBJECT (self));
  wp_registry_register_object (wp_core_get_registry (core), self);
}

/*!
 * \brief Removes the session item from the registry
 *
 * \ingroup wpsessionitem
 * \param self (transfer none): the session item
 */
void
wp_session_item_remove (WpSessionItem * self)
{
  g_autoptr (WpCore) core = NULL;

  g_return_if_fail (WP_IS_SESSION_ITEM (self));

  core = wp_object_get_core (WP_OBJECT (self));
  wp_registry_remove_object (wp_core_get_registry (core), self);
}

/*!
 * \brief Gets the properties of a session item.
 *
 * \ingroup wpsessionitem
 * \param self the session item
 * \returns (transfer full): the item's properties.
 */
WpProperties *
wp_session_item_get_properties (WpSessionItem * self)
{
  WpSessionItemPrivate *priv = NULL;

  g_return_val_if_fail (WP_IS_SESSION_ITEM (self), NULL);

  priv = wp_session_item_get_instance_private (self);
  return priv->properties ? wp_properties_ref (priv->properties) : NULL;
}

/*!
 * \brief Looks up a named session item property value for a given key.
 *
 * \ingroup wpsessionitem
 * \param self the session item
 * \param key the property key
 * \returns the item property value for the given key.
 */
const gchar *
wp_session_item_get_property (WpSessionItem * self, const gchar *key)
{
  WpSessionItemPrivate *priv = NULL;

  g_return_val_if_fail (WP_IS_SESSION_ITEM (self), NULL);

  priv = wp_session_item_get_instance_private (self);
  return priv->properties ? wp_properties_get (priv->properties, key) : NULL;
}

/*!
 * \brief Sets the item's properties.
 *
 * This should only be done by sub-classes after the configuration has been done.
 *
 * \ingroup wpsessionitem
 * \param self the session item
 * \param props (transfer full): the new properties to set
 */
void
wp_session_item_set_properties (WpSessionItem * self,
    WpProperties *props)
{
  WpSessionItemPrivate *priv = NULL;

  g_return_if_fail (WP_IS_SESSION_ITEM (self));

  priv = wp_session_item_get_instance_private (self);
  g_clear_pointer (&priv->properties, wp_properties_unref);
  priv->properties = wp_properties_ensure_unique_owner (props);
}

static gboolean
on_session_item_proxy_destroyed_deferred (WpSessionItem * item)
{
  wp_info_object (item, "destroying session item upon request by the server");
  wp_object_deactivate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_EXPORTED);
  return G_SOURCE_REMOVE;
}

/*!
 * \brief Helper callback for sub-classes that deffers and unexports
 * the session item.
 *
 * Only meant to be used when the pipewire proxy destroyed signal is triggered.
 *
 * \ingroup wpsessionitem
 * \param proxy the proxy that was destroyed by the server
 * \param item the associated session item
 */
void
wp_session_item_handle_proxy_destroyed (WpProxy * proxy, WpSessionItem * item)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (proxy));
  if (core)
    wp_core_idle_add_closure (core, NULL, g_cclosure_new_object (
        G_CALLBACK (on_session_item_proxy_destroyed_deferred),
        G_OBJECT (item)));
}
   0707010000008D000081A4000000000000000000000001656CC35F00000B55000000000000000000000000000000000000002900000000wireplumber-0.4.17/lib/wp/session-item.h  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_SESSION_ITEM_H__
#define __WIREPLUMBER_SESSION_ITEM_H__

#include "object.h"
#include "proxy.h"

G_BEGIN_DECLS

/*!
 * \brief Flags to be used as WpObjectFeatures for WpSessionItem subclasses.
 * \ingroup wpsessionitem
 */
typedef enum { /*< flags >*/
  /* main features */
  WP_SESSION_ITEM_FEATURE_ACTIVE       = (1 << 0),
  WP_SESSION_ITEM_FEATURE_EXPORTED     = (1 << 1),

  WP_SESSION_ITEM_FEATURE_CUSTOM_START = (1 << 16), /*< skip >*/
} WpSessionItemFeatures;

/*!
 * \brief The WpSessionItem GType
 * \ingroup wpsessionitem
 */
#define WP_TYPE_SESSION_ITEM (wp_session_item_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpSessionItem, wp_session_item,
                          WP, SESSION_ITEM, WpObject)

struct _WpSessionItemClass
{
  WpObjectClass parent_class;

  /*! See wp_session_item_reset() */
  void (*reset) (WpSessionItem * self);
  /*! See wp_session_item_configure() */
  gboolean (*configure) (WpSessionItem * self, WpProperties * props);
  /*! See wp_session_item_get_associated_proxy() */
  gpointer (*get_associated_proxy) (WpSessionItem * self, GType proxy_type);

  /*! disables the active feature of the session item */
  void (*disable_active) (WpSessionItem * self);
  /*! disables the exported feature of the session item */
  void (*disable_exported) (WpSessionItem * self);
  /*! enables the active feature of the session item */
  void (*enable_active) (WpSessionItem * self, WpTransition * transition);
  /*! enables the exported feature of the session item */
  void (*enable_exported) (WpSessionItem * self, WpTransition * transition);

  /*< private >*/
  WP_PADDING(5)
};

/* Id */

WP_API
guint wp_session_item_get_id (WpSessionItem * self);

/* configuration */

WP_API
void wp_session_item_reset (WpSessionItem * self);

WP_API
gboolean wp_session_item_configure (WpSessionItem * self, WpProperties * props);

WP_API
gboolean wp_session_item_is_configured (WpSessionItem * self);

/* associated proxies */

WP_API
gpointer wp_session_item_get_associated_proxy (WpSessionItem * self,
    GType proxy_type);

WP_API
guint32 wp_session_item_get_associated_proxy_id (WpSessionItem * self,
    GType proxy_type);

/* registry */

WP_API
void wp_session_item_register (WpSessionItem * self);

WP_API
void wp_session_item_remove (WpSessionItem * self);

/* properties */

WP_API
WpProperties * wp_session_item_get_properties (WpSessionItem * self);

WP_API
const gchar * wp_session_item_get_property (WpSessionItem * self,
    const gchar *key);

/* for subclasses only */

WP_API
void wp_session_item_set_properties (WpSessionItem * self, WpProperties *props);

WP_API
void wp_session_item_handle_proxy_destroyed (WpProxy * proxy,
    WpSessionItem * item);

G_END_DECLS

#endif
   0707010000008E000081A4000000000000000000000001656CC35F00001AC4000000000000000000000000000000000000002700000000wireplumber-0.4.17/lib/wp/si-factory.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-si-factory"

#include "si-factory.h"
#include "private/registry.h"

/*! \defgroup wpsifactory WpSiFactory */
/*!
 * \struct WpSiFactory
 *
 * A factory for session items.
 *
 * The most simple way to register a new item implementation would be:
 * \code
 * wp_si_factory_register (core,
 *    wp_si_factory_new_simple ("foobar", FOO_TYPE_BAR));
 * \endcode
 *
 * And the most simple way to construct an item from a registered factory:
 * \code
 * item = wp_session_item_make (core, "foobar");
 * \endcode
 *
 * \gproperties
 *
 * \gproperty{name, gchar *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The factory's name}
 */
enum {
  PROP_0,
  PROP_NAME,
};

typedef struct _WpSiFactoryPrivate WpSiFactoryPrivate;
struct _WpSiFactoryPrivate
{
  GQuark name_quark;
};

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpSiFactory, wp_si_factory, G_TYPE_OBJECT)

static void
wp_si_factory_init (WpSiFactory * self)
{
}

static void
wp_si_factory_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpSiFactory *self = WP_SI_FACTORY (object);
  WpSiFactoryPrivate *priv = wp_si_factory_get_instance_private (self);

  switch (property_id) {
  case PROP_NAME:
    priv->name_quark = g_quark_from_string (g_value_get_string (value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_si_factory_get_property (GObject * object, guint property_id, GValue * value,
    GParamSpec * pspec)
{
  WpSiFactory *self = WP_SI_FACTORY (object);
  WpSiFactoryPrivate *priv = wp_si_factory_get_instance_private (self);

  switch (property_id) {
  case PROP_NAME:
    g_value_set_string (value, g_quark_to_string (priv->name_quark));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_si_factory_class_init (WpSiFactoryClass * klass)
{
  GObjectClass * object_class = (GObjectClass *) klass;

  object_class->get_property = wp_si_factory_get_property;
  object_class->set_property = wp_si_factory_set_property;

  g_object_class_install_property (object_class, PROP_NAME,
      g_param_spec_string ("name", "name", "The factory's name", "",
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}

/*!
 * \brief Gets the name of the factory
 *
 * \ingroup wpsifactory
 * \param self the factory
 * \returns the factory name
 */
const gchar *
wp_si_factory_get_name (WpSiFactory * self)
{
  g_return_val_if_fail (WP_IS_SI_FACTORY (self), NULL);

  WpSiFactoryPrivate *priv = wp_si_factory_get_instance_private (self);
  return g_quark_to_string (priv->name_quark);
}

/*!
 * \brief Creates a new instance of the session item that is constructed
 * by this factory
 *
 * \ingroup wpsifactory
 * \param self the factory
 * \param core the core
 * \returns (transfer full): a new session item instance
 */
WpSessionItem *
wp_si_factory_construct (WpSiFactory * self, WpCore * core)
{
  g_return_val_if_fail (WP_IS_SI_FACTORY (self), NULL);
  g_return_val_if_fail (WP_SI_FACTORY_GET_CLASS (self)->construct, NULL);

  return WP_SI_FACTORY_GET_CLASS (self)->construct (self, core);
}

/*!
 * \brief Registers the \a factory on the \a core.
 *
 * \ingroup wpsifactory
 * \param core the core
 * \param factory (transfer full): the factory to register
 */
void
wp_si_factory_register (WpCore * core, WpSiFactory * factory)
{
  g_return_if_fail (WP_IS_CORE (core));
  g_return_if_fail (WP_IS_SI_FACTORY (factory));

  wp_registry_register_object (wp_core_get_registry (core), factory);
}

static gboolean
find_factory_func (gpointer factory, gpointer name_quark)
{
  if (!WP_IS_SI_FACTORY (factory))
    return FALSE;

  WpSiFactoryPrivate *priv =
      wp_si_factory_get_instance_private ((WpSiFactory *) factory);
  return priv->name_quark == GPOINTER_TO_UINT (name_quark);
}

/*!
 * \brief Looks up a factory matching a name
 *
 * \ingroup wpsifactory
 * \param core the core
 * \param factory_name the lookup name
 * \returns (transfer full) (nullable): the factory matching the lookup name
 */
WpSiFactory *
wp_si_factory_find (WpCore * core, const gchar * factory_name)
{
  g_return_val_if_fail (WP_IS_CORE (core), NULL);

  GQuark q = g_quark_try_string (factory_name);
  if (q == 0)
    return NULL;
  GObject *f = wp_registry_find_object (wp_core_get_registry (core),
      (GEqualFunc) find_factory_func, GUINT_TO_POINTER (q));
  return f ? WP_SI_FACTORY (f) : NULL;
}

/*!
 * \brief Finds the factory associated with the given \a name from the \a core
 * and uses it to construct a new WpSessionItem.
 *
 * \ingroup wpsifactory
 * \param core the WpCore
 * \param factory_name the name of the factory to be used for constructing
 *   the object
 * \returns (transfer full) (nullable): the new session item
 */
WpSessionItem *
wp_session_item_make (WpCore * core, const gchar * factory_name)
{
  g_autoptr (WpSiFactory) f = wp_si_factory_find (core, factory_name);
  return f ? wp_si_factory_construct (f, core) : NULL;
}

struct _WpSimpleSiFactory
{
  WpSiFactory parent;
  GType si_type;
};

G_DECLARE_FINAL_TYPE (WpSimpleSiFactory, wp_simple_si_factory,
                      WP, SIMPLE_SI_FACTORY, WpSiFactory)
G_DEFINE_TYPE (WpSimpleSiFactory, wp_simple_si_factory, WP_TYPE_SI_FACTORY)

static void
wp_simple_si_factory_init (WpSimpleSiFactory * self)
{
}

static WpSessionItem *
wp_simple_si_factory_construct (WpSiFactory * self, WpCore *core)
{
  return g_object_new (WP_SIMPLE_SI_FACTORY (self)->si_type,
      "core", core,
      NULL);
}

static void
wp_simple_si_factory_class_init (WpSimpleSiFactoryClass * klass)
{
  WpSiFactoryClass * factory_class = (WpSiFactoryClass *) klass;

  factory_class->construct = wp_simple_si_factory_construct;
}

/*!
 * \brief Creates a simple factory that constructs objects of a given GType.
 *
 * \ingroup wpsifactory
 * \param factory_name the factory name; must be a static string!
 * \param si_type the WpSessionItem subclass type to instantiate for
 *    constructing items
 * \returns (transfer full): the new factory
 */
WpSiFactory *
wp_si_factory_new_simple (const gchar * factory_name, GType si_type)
{
  g_return_val_if_fail (factory_name != NULL, NULL);
  g_return_val_if_fail (g_type_is_a (si_type, WP_TYPE_SESSION_ITEM), NULL);

  WpSimpleSiFactory *self = g_object_new (
      wp_simple_si_factory_get_type (), NULL);

  /* assign the quark directly to use g_quark_from_static_string */
  WpSiFactoryPrivate *priv =
      wp_si_factory_get_instance_private (WP_SI_FACTORY (self));
  priv->name_quark = g_quark_from_static_string (factory_name);

  self->si_type = si_type;

  return WP_SI_FACTORY (self);
}
0707010000008F000081A4000000000000000000000001656CC35F000004A3000000000000000000000000000000000000002700000000wireplumber-0.4.17/lib/wp/si-factory.h    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_SI_FACTORY_H__
#define __WIREPLUMBER_SI_FACTORY_H__

#include "core.h"
#include "session-item.h"

G_BEGIN_DECLS

/*!
 * \brief The WpSiFactory GType
 * \ingroup wpsifactory
 */
#define WP_TYPE_SI_FACTORY (wp_si_factory_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpSiFactory, wp_si_factory, WP, SI_FACTORY, GObject)

struct _WpSiFactoryClass
{
  GObjectClass parent_class;

  WpSessionItem * (*construct) (WpSiFactory * self, WpCore * core);

  /*< private >*/
  WP_PADDING(7)
};

WP_API
WpSiFactory * wp_si_factory_new_simple (const gchar * factory_name,
    GType si_type);

WP_API
const gchar * wp_si_factory_get_name (WpSiFactory * self);

WP_API
WpSessionItem * wp_si_factory_construct (WpSiFactory * self, WpCore * core);

WP_API
void wp_si_factory_register (WpCore * core, WpSiFactory * factory);

WP_API
WpSiFactory * wp_si_factory_find (WpCore * core, const gchar * factory_name);

WP_API
WpSessionItem * wp_session_item_make (WpCore * core, const gchar * factory_name);

G_END_DECLS

#endif
 07070100000090000081A4000000000000000000000001656CC35F00003CF2000000000000000000000000000000000000002A00000000wireplumber-0.4.17/lib/wp/si-interfaces.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-si-interfaces"

#include "si-interfaces.h"
#include "wpenums.h"

/*! \defgroup wpsiinterfaces Session Item Interfaces */

/*!
 * \struct WpSiEndpoint
 *
 * An interface for session items that implement a PipeWire endpoint.
 *
 * \gsignals
 *
 * \par endpoint-properties-changed
 * \parblock
 * \code
 * void
 * endpoint_properties_changed_callback (WpSiEndpoint * self,
 *                                       gpointer user_data)
 * \endcode
 * Emitted when the endpoint properties change
 *
 * Flags: G_SIGNAL_RUN_LAST
 * \endparblock
 */
G_DEFINE_INTERFACE (WpSiEndpoint, wp_si_endpoint, WP_TYPE_SESSION_ITEM)

static WpProperties *
wp_si_endpoint_default_get_properties (WpSiEndpoint * self)
{
  return NULL;
}

static void
wp_si_endpoint_default_init (WpSiEndpointInterface * iface)
{
  iface->get_properties = wp_si_endpoint_default_get_properties;

  g_signal_new ("endpoint-properties-changed", G_TYPE_FROM_INTERFACE (iface),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
}

/*!
 * \brief This should return information that is used for registering the
 * endpoint.
 *
 * The return value should be a GVariant tuple of type (ssya{ss}) that contains,
 * in order:
 *  - s: the endpoint's name
 *  - s: the media class
 *  - y: the direction
 *  - a{ss}: additional properties to be added to the list of global properties
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \returns (transfer full): registration info for the endpoint
 */
GVariant *
wp_si_endpoint_get_registration_info (WpSiEndpoint * self)
{
  g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), NULL);
  g_return_val_if_fail (WP_SI_ENDPOINT_GET_IFACE (self)->get_registration_info, NULL);

  return WP_SI_ENDPOINT_GET_IFACE (self)->get_registration_info (self);
}

/*!
 * \brief Gets the properties of the endpoint
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \returns (transfer full) (nullable): the properties of the endpoint
 */
WpProperties *
wp_si_endpoint_get_properties (WpSiEndpoint * self)
{
  g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), NULL);
  g_return_val_if_fail (WP_SI_ENDPOINT_GET_IFACE (self)->get_properties, NULL);

  return WP_SI_ENDPOINT_GET_IFACE (self)->get_properties (self);
}

/*!
 * \struct WpSiAdapter
 * An interface for port adapters
 *
 * \gsignals
 *
 * \par adapter-ports-state-changed
 * \parblock
 * \code
 * void
 * adapter_ports_state_changed_callback (WpSiAdapter * self,
 *                                       gpointer user_data)
 * \endcode
 * Emitted when the ports state changes
 *
 * Flags: G_SIGNAL_RUN_LAST
 * \endparblock
 */

G_DEFINE_INTERFACE (WpSiAdapter, wp_si_adapter, WP_TYPE_SESSION_ITEM)

static void
wp_si_adapter_default_init (WpSiAdapterInterface * iface)
{
  g_signal_new ("adapter-ports-state-changed", G_TYPE_FROM_INTERFACE (iface),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE,
      2, WP_TYPE_SI_ADAPTER_PORTS_STATE, WP_TYPE_SI_ADAPTER_PORTS_STATE);
}

/**
 * \brief Gets the ports state
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \returns The state of the ports
 * \since 0.4.10
 */
WpSiAdapterPortsState
wp_si_adapter_get_ports_state (WpSiAdapter * self)
{
  g_return_val_if_fail (WP_IS_SI_ADAPTER (self), WP_SI_ADAPTER_PORTS_STATE_NONE);
  g_return_val_if_fail (WP_SI_ADAPTER_GET_IFACE (self)->get_ports_state,
      WP_SI_ADAPTER_PORTS_STATE_NONE);

  return WP_SI_ADAPTER_GET_IFACE (self)->get_ports_state (self);
}

/**
 * \brief Gets the format used to configure the adapter session item's ports
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \param mode (out) (nullable): the mode
 * \returns (transfer full): The format used to configure the ports of the
 *   adapter session item. Some items automatically choose a format when being
 *   activated, others never set a format on activation and the user needs to
 *   manually set it externally with wp_si_adapter_set_ports_format().
 */
WpSpaPod *
wp_si_adapter_get_ports_format (WpSiAdapter * self, const gchar **mode)
{
  g_return_val_if_fail (WP_IS_SI_ADAPTER (self), NULL);
  g_return_val_if_fail (WP_SI_ADAPTER_GET_IFACE (self)->get_ports_format, NULL);

  return WP_SI_ADAPTER_GET_IFACE (self)->get_ports_format (self, mode);
}

/*!
 * \brief Sets the format and configures the adapter session item ports using
 * the given format.
 *
 * The result of the operation can be checked using the
 * wp_si_adapter_set_ports_format_finish() API. If format is NULL, the adapter
 * will be configured with the default format. If mode is NULL, the adapter
 * will use "dsp" mode.
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \param format (transfer full) (nullable): the format to be set
 * \param mode (nullable): the mode
 * \param callback (scope async): the callback to call when the operation is done
 * \param data (closure): user data for \a callback
 */
void
wp_si_adapter_set_ports_format (WpSiAdapter * self, WpSpaPod *format,
    const gchar *mode, GAsyncReadyCallback callback, gpointer data)
{
  g_return_if_fail (WP_IS_SI_ADAPTER (self));
  g_return_if_fail (WP_SI_ADAPTER_GET_IFACE (self)->set_ports_format);

  WP_SI_ADAPTER_GET_IFACE (self)->set_ports_format (self, format, mode,
      callback, data);
}

/*!
 * \brief Finishes the operation started by wp_si_adapter_set_format().
 * This is meant to be called in the callback that was passed to that method.
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \param res the async result
 * \param error (out) (optional): the operation's error, if it occurred
 * \returns TRUE on success, FALSE if there was an error
 */
gboolean
wp_si_adapter_set_ports_format_finish (WpSiAdapter * self, GAsyncResult * res,
      GError ** error)
{
  g_return_val_if_fail (WP_IS_SI_ADAPTER (self), FALSE);
  g_return_val_if_fail (WP_SI_ADAPTER_GET_IFACE (self)->set_ports_format_finish,
     FALSE);

  return WP_SI_ADAPTER_GET_IFACE (self)->set_ports_format_finish (self, res,
     error);
}

/*!
 * \struct WpSiLinkable
 *
 * An interface for retrieving PipeWire port information from a session item.
 * This information is used to create links in the nodes graph.
 *
 * This is normally implemented by the same session items that implement
 * WpSiEndpoint. The standard link implementation expects to be able to cast
 * a WpSiEndpoint into a WpSiLinkable.
 */

G_DEFINE_INTERFACE (WpSiLinkable, wp_si_linkable, WP_TYPE_SESSION_ITEM)

static WpSiAcquisition *
wp_si_linkable_default_get_acquisition (WpSiLinkable * self)
{
  return NULL;
}

static void
wp_si_linkable_default_init (WpSiLinkableInterface * iface)
{
  iface->get_acquisition = wp_si_linkable_default_get_acquisition;
}

/*!
 * \brief This method returns a variant of type "a(uuu)", where each tuple in
 * the array contains the following information:
 *   - u: (guint32) node id
 *   - u: (guint32) port id (the port must belong on the node specified above)
 *   - u: (guint32) the audio channel (enum spa_audio_channel) that this port
 *        makes available, or 0 for non-audio content
 *
 * The order in which ports appear in this array is important when no channel
 * information is available. The link implementation should link the ports
 * in the order they appear. This is normally a good enough substitute for
 * channel matching.
 *
 * The \a context argument can be used to get different sets of ports from
 * the item. The following well-known contexts are defined:
 *   - NULL: get the standard ports to be linked
 *   - "monitor": get the monitor ports
 *   - "control": get the control port
 *   - "reverse": get the reverse direction ports, if this item controls a
 *                filter node, which would have ports on both directions
 *
 * Contexts other than NULL may only be used internally to ease the
 * implementation of more complex item relationships. For example, a
 * WpSessionItem that is in control of an input (sink) adapter node may
 * implement WpSiLinkable where the NULL context will return the standard
 * input ports and the "monitor" context will return the adapter's monitor
 * ports. When linking this item to another item, the NULL context
 * will always be used, but the item may internally spawn a secondary
 * WpSessionItem that implements the "monitor" item. That secondary item
 * may implement WpSiLinkable, chaining calls to the WpSiLinkable
 * of the original item using the "monitor" context. This way, the monitor
 * WpSessionItem does not need to share control of the
 * underlying node; it only proxies calls to satisfy the API.
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \param context (nullable): an optional context for the ports
 * \returns (transfer full): a GVariant containing information about the
 *   ports of this item
 */
GVariant *
wp_si_linkable_get_ports (WpSiLinkable * self, const gchar * context)
{
  g_return_val_if_fail (WP_IS_SI_LINKABLE (self), NULL);
  g_return_val_if_fail (WP_SI_LINKABLE_GET_IFACE (self)->get_ports, NULL);

  return WP_SI_LINKABLE_GET_IFACE (self)->get_ports (self, context);
}

/*!
 * \brief Gets the acquisition interface associated with the item
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 *
 * \returns (transfer none) (nullable): the acquisition interface associated
 *   with this item, or NULL if this item does not require acquiring items
 *   before linking them
 */
WpSiAcquisition *
wp_si_linkable_get_acquisition (WpSiLinkable * self)
{
  g_return_val_if_fail (WP_IS_SI_LINKABLE (self), NULL);
  g_return_val_if_fail (
      WP_SI_LINKABLE_GET_IFACE (self)->get_acquisition, NULL);

  return WP_SI_LINKABLE_GET_IFACE (self)->get_acquisition (self);
}


/*!
 * \struct WpSiLink
 *
 * An interface for session items that provide a PipeWire endpoint link.
 *
 * \gsignals
 *
 * \par link-properties-changed
 * \parblock
 * \code
 * void
 * link_properties_changed_callback (WpSiLink * self,
 *                                   gpointer user_data)
 * \endcode
 * Emitted when the properties of the link change
 *
 * Flags: G_SIGNAL_RUN_LAST
 * \endparblock
 */

G_DEFINE_INTERFACE (WpSiLink, wp_si_link, WP_TYPE_SESSION_ITEM)

static WpProperties *
wp_si_link_default_get_properties (WpSiLink * self)
{
  return NULL;
}

static void
wp_si_link_default_init (WpSiLinkInterface * iface)
{
  iface->get_properties = wp_si_link_default_get_properties;

  g_signal_new ("link-properties-changed", G_TYPE_FROM_INTERFACE (iface),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
}

/*!
 * \brief This should return information that is used for registering the link,
 * as a GVariant of type a{ss} that contains additional properties to be
 * added to the list of global properties
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \returns (transfer full): registration info for the link
 */
GVariant *
wp_si_link_get_registration_info (WpSiLink * self)
{
  g_return_val_if_fail (WP_IS_SI_LINK (self), NULL);
  g_return_val_if_fail (WP_SI_LINK_GET_IFACE (self)->get_registration_info, NULL);

  return WP_SI_LINK_GET_IFACE (self)->get_registration_info (self);
}

/*!
 * \brief Gets the properties of the link
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \returns (transfer full) (nullable): the properties of the link
 */
WpProperties *
wp_si_link_get_properties (WpSiLink * self)
{
  g_return_val_if_fail (WP_IS_SI_LINK (self), NULL);
  g_return_val_if_fail (WP_SI_LINK_GET_IFACE (self)->get_properties, NULL);

  return WP_SI_LINK_GET_IFACE (self)->get_properties (self);
}

/*!
 * \brief Gets the output item linked by the link
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \returns (transfer none): the output item that is linked by this link
 */
WpSiLinkable *
wp_si_link_get_out_item (WpSiLink * self)
{
  g_return_val_if_fail (WP_IS_SI_LINK (self), NULL);
  g_return_val_if_fail (WP_SI_LINK_GET_IFACE (self)->get_out_item, NULL);

  return WP_SI_LINK_GET_IFACE (self)->get_out_item (self);
}

/*!
 * \brief Gets the input item linked by the link
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \returns (transfer none): the input item that is linked by this link
 */
WpSiLinkable *
wp_si_link_get_in_item (WpSiLink * self)
{
  g_return_val_if_fail (WP_IS_SI_LINK (self), NULL);
  g_return_val_if_fail (WP_SI_LINK_GET_IFACE (self)->get_in_item, NULL);

  return WP_SI_LINK_GET_IFACE (self)->get_in_item (self);
}

/*!
 * \struct WpSiAcquisition
 *
 * This interface provides a way to request an item for linking before doing
 * so. This allows item implementations to apply internal policy rules.
 *
 * A WpSiAcquisition is associated directly with a WpSiLinkable via
 * wp_si_linkable_get_acquisition(). In order to allow switching
 * policies, it is recommended that port info implementations use a separate
 * session item to implement this interface and allow replacing it.
 */

G_DEFINE_INTERFACE (WpSiAcquisition, wp_si_acquisition, WP_TYPE_SESSION_ITEM)

static void
wp_si_acquisition_default_init (WpSiAcquisitionInterface * iface)
{
}

/*!
 * \brief Acquires the \a item for linking by \a acquisitor.
 *
 * When a link is not allowed by policy, this operation should return
 * an error.
 *
 * When a link needs to be delayed for a short amount of time (ex. to apply
 * a fade out effect on another item), this operation should finish with a
 * delay. It is safe to assume that after this operation completes,
 * the item will be linked immediately.
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \param acquisitor the link that is trying to acquire a port info item
 * \param item the item that is being acquired
 * \param callback (scope async): the callback to call when the operation is done
 * \param data (closure): user data for \a callback
 */
void
wp_si_acquisition_acquire (WpSiAcquisition * self, WpSiLink * acquisitor,
    WpSiLinkable * item, GAsyncReadyCallback callback, gpointer data)
{
  g_return_if_fail (WP_IS_SI_ACQUISITION (self));
  g_return_if_fail (WP_SI_ACQUISITION_GET_IFACE (self)->acquire);

  WP_SI_ACQUISITION_GET_IFACE (self)->acquire (self, acquisitor, item, callback,
      data);
}

/*!
 * \brief Finishes the operation started by wp_si_acquisition_acquire().
 * This is meant to be called in the callback that was passed to that method.
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \param res the async result
 * \param error (out) (optional): the operation's error, if it occurred
 * \returns TRUE on success, FALSE if there was an error
 */
gboolean
wp_si_acquisition_acquire_finish (WpSiAcquisition * self, GAsyncResult * res,
    GError ** error)
{
  g_return_val_if_fail (WP_IS_SI_ACQUISITION (self), FALSE);
  g_return_val_if_fail (WP_SI_ACQUISITION_GET_IFACE (self)->acquire_finish,
      FALSE);

  return WP_SI_ACQUISITION_GET_IFACE (self)->acquire_finish (self, res, error);
}

/*!
 * \brief Releases the \a item, which means that it is being unlinked.
 *
 * \ingroup wpsiinterfaces
 * \param self the session item
 * \param acquisitor the link that had previously acquired the item
 * \param item the port info that is being released
 */
void
wp_si_acquisition_release (WpSiAcquisition * self, WpSiLink * acquisitor,
    WpSiLinkable * item)
{
  g_return_if_fail (WP_IS_SI_ACQUISITION (self));
  g_return_if_fail (WP_SI_ACQUISITION_GET_IFACE (self)->release);

  WP_SI_ACQUISITION_GET_IFACE (self)->release (self, acquisitor, item);
}
  07070100000091000081A4000000000000000000000001656CC35F000013CE000000000000000000000000000000000000002A00000000wireplumber-0.4.17/lib/wp/si-interfaces.h /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_SI_INTERFACES_H__
#define __WIREPLUMBER_SI_INTERFACES_H__

#include "session-item.h"
#include "properties.h"
#include "spa-pod.h"

G_BEGIN_DECLS

typedef struct _WpSiAcquisition WpSiAcquisition;

/*!
 * \brief The WpSiEndpoint GType
 * \ingroup wpsiinterfaces
 */
#define WP_TYPE_SI_ENDPOINT (wp_si_endpoint_get_type ())
WP_API
G_DECLARE_INTERFACE (WpSiEndpoint, wp_si_endpoint,
                     WP, SI_ENDPOINT, WpSessionItem)

struct _WpSiEndpointInterface
{
  GTypeInterface interface;

  GVariant * (*get_registration_info) (WpSiEndpoint * self);
  WpProperties * (*get_properties) (WpSiEndpoint * self);

  /*< private >*/
  WP_PADDING(6)
};

WP_API
GVariant * wp_si_endpoint_get_registration_info (WpSiEndpoint * self);

WP_API
WpProperties * wp_si_endpoint_get_properties (WpSiEndpoint * self);

/*!
 * \brief The WpSiAdapter GType
 * \ingroup wpsiinterfaces
 */
#define WP_TYPE_SI_ADAPTER (wp_si_adapter_get_type ())
WP_API
G_DECLARE_INTERFACE (WpSiAdapter, wp_si_adapter,
                     WP, SI_ADAPTER, WpSessionItem)

/*!
 * \brief The ports configuration state of the adapter
 * \ingroup wpsiinterfaces
 * \since 0.4.10
 */
typedef enum {
  WP_SI_ADAPTER_PORTS_STATE_NONE = 0, /*!< the ports have never been configured */
  WP_SI_ADAPTER_PORTS_STATE_CONFIGURING, /*!< the ports are being configured */
  WP_SI_ADAPTER_PORTS_STATE_CONFIGURED, /*!< the ports are configured */
} WpSiAdapterPortsState;

struct _WpSiAdapterInterface
{
  GTypeInterface interface;

  WpSpaPod * (*get_ports_format) (WpSiAdapter * self, const gchar **mode);
  void (*set_ports_format) (WpSiAdapter * self, WpSpaPod *format,
      const gchar *mode, GAsyncReadyCallback callback, gpointer data);
  gboolean (*set_ports_format_finish) (WpSiAdapter * self, GAsyncResult * res,
      GError ** error);
  WpSiAdapterPortsState (*get_ports_state) (WpSiAdapter * self);

  /*< private >*/
  WP_PADDING(4)
};

WP_API
WpSiAdapterPortsState wp_si_adapter_get_ports_state (WpSiAdapter * self);

WP_API
WpSpaPod *wp_si_adapter_get_ports_format (WpSiAdapter * self,
    const gchar **mode);

WP_API
void wp_si_adapter_set_ports_format (WpSiAdapter * self, WpSpaPod *format,
    const gchar *mode, GAsyncReadyCallback callback, gpointer data);

WP_API
gboolean wp_si_adapter_set_ports_format_finish (WpSiAdapter * self,
    GAsyncResult * res, GError ** error);

/*!
 * \brief The WpSiLinkable GType
 * \ingroup wpsiinterfaces
 */
#define WP_TYPE_SI_LINKABLE (wp_si_linkable_get_type ())
WP_API
G_DECLARE_INTERFACE (WpSiLinkable, wp_si_linkable,
                     WP, SI_LINKABLE, WpSessionItem)

struct _WpSiLinkableInterface
{
  GTypeInterface interface;

  GVariant * (*get_ports) (WpSiLinkable * self, const gchar * context);
  WpSiAcquisition * (*get_acquisition) (WpSiLinkable * self);

  /*< private >*/
  WP_PADDING(6)
};

WP_API
GVariant * wp_si_linkable_get_ports (WpSiLinkable * self,
    const gchar * context);

WP_API
WpSiAcquisition * wp_si_linkable_get_acquisition (WpSiLinkable * self);

/*!
 * \brief The WpSiLink GType
 * \ingroup wpsiinterfaces
 */
#define WP_TYPE_SI_LINK (wp_si_link_get_type ())
WP_API
G_DECLARE_INTERFACE (WpSiLink, wp_si_link,
                     WP, SI_LINK, WpSessionItem)

struct _WpSiLinkInterface
{
  GTypeInterface interface;

  GVariant * (*get_registration_info) (WpSiLink * self);
  WpProperties * (*get_properties) (WpSiLink * self);

  WpSiLinkable * (*get_out_item) (WpSiLink * self);
  WpSiLinkable * (*get_in_item) (WpSiLink * self);

  /*< private >*/
  WP_PADDING(4)
};

WP_API
GVariant * wp_si_link_get_registration_info (WpSiLink * self);

WP_API
WpProperties * wp_si_link_get_properties (WpSiLink * self);

WP_API
WpSiLinkable * wp_si_link_get_out_item (WpSiLink * self);

WP_API
WpSiLinkable * wp_si_link_get_in_item (WpSiLink * self);

/*!
 * \brief The WpSiAcquisition GType
 * \ingroup wpsiinterfaces
 */
#define WP_TYPE_SI_ACQUISITION (wp_si_acquisition_get_type ())
WP_API
G_DECLARE_INTERFACE (WpSiAcquisition, wp_si_acquisition,
                     WP, SI_ACQUISITION, WpSessionItem)

struct _WpSiAcquisitionInterface
{
  GTypeInterface interface;

  void (*acquire) (WpSiAcquisition * self, WpSiLink * acquisitor,
      WpSiLinkable * item, GAsyncReadyCallback callback, gpointer data);
  gboolean (*acquire_finish) (WpSiAcquisition * self, GAsyncResult * res,
      GError ** error);

  void (*release) (WpSiAcquisition * self, WpSiLink * acquisitor,
      WpSiLinkable * item);

  /*< private >*/
  WP_PADDING(5)
};

WP_API
void wp_si_acquisition_acquire (WpSiAcquisition * self, WpSiLink * acquisitor,
    WpSiLinkable * item, GAsyncReadyCallback callback, gpointer data);

WP_API
gboolean wp_si_acquisition_acquire_finish (
    WpSiAcquisition * self, GAsyncResult * res, GError ** error);

WP_API
void wp_si_acquisition_release (WpSiAcquisition * self, WpSiLink * acquisitor,
    WpSiLinkable * item);

G_END_DECLS

#endif
  07070100000092000081A4000000000000000000000001656CC35F0000A963000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/spa-json.c  /* WirePlumber
 *
 * Copyright © 2022 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-spa-json"

#include <spa/utils/defs.h>
#include <spa/utils/json.h>

#include "spa-json.h"

#define WP_SPA_JSON_STRING_INIT_SIZE 64
#define WP_SPA_JSON_BUILDER_INIT_SIZE 64

/*! \defgroup wpspajson WpSpaJson */
/*!
 * \struct WpSpaJson
 *
 * \since 0.4.8
 */
/*!
 * \struct WpSpaJsonBuilder
 *
 * \since 0.4.8
 */
/*!
 * \struct WpSpaJsonParser
 *
 * \since 0.4.8
 */

static void
builder_add_formatted (WpSpaJsonBuilder *self, const gchar *fmt, ...)
    G_GNUC_PRINTF (2, 3);

static WpSpaJsonBuilder *
wp_spa_json_builder_new_formatted (const gchar *fmt, ...)
    G_GNUC_PRINTF (1, 2);

enum {
  FLAG_NO_OWNERSHIP = (1 << 0),
};

struct _WpSpaJson
{
  grefcount ref;
  guint32 flags;

  /* only used if FLAG_NO_OWNERSHIP is not set */
  WpSpaJsonBuilder *builder;

  /* not used if constructed with _new_wrap() */
  struct spa_json json_data;

  /* json data */
  gchar *data;
  size_t size;
  struct spa_json *json;
};

G_DEFINE_BOXED_TYPE (WpSpaJson, wp_spa_json, wp_spa_json_ref, wp_spa_json_unref)

struct _WpSpaJsonBuilder
{
  gboolean add_separator;
  gchar *data;
  size_t size;
  size_t max_size;
};

G_DEFINE_BOXED_TYPE (WpSpaJsonBuilder, wp_spa_json_builder,
    wp_spa_json_builder_ref, wp_spa_json_builder_unref)

struct _WpSpaJsonParser
{
  WpSpaJson *json;
  struct spa_json data[2];
  struct spa_json *pos;
  struct spa_json curr;
};

G_DEFINE_BOXED_TYPE (WpSpaJsonParser, wp_spa_json_parser,
    wp_spa_json_parser_ref, wp_spa_json_parser_unref)

/*!
 * \brief Increases the reference count of a spa json object
 * \ingroup wpspajson
 * \param self a spa json object
 * \returns (transfer full): \a self with an additional reference count on it
 */
WpSpaJson *
wp_spa_json_ref (WpSpaJson *self)
{
  g_ref_count_inc (&self->ref);
  return self;
}

static void
wp_spa_json_free (WpSpaJson *self)
{
  g_clear_pointer (&self->builder, wp_spa_json_builder_unref);
  g_slice_free (WpSpaJson, self);
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 * \ingroup wpspajson
 * \param self (transfer full): a spa json object
 */
void
wp_spa_json_unref (WpSpaJson *self)
{
  if (g_ref_count_dec (&self->ref))
    wp_spa_json_free (self);
}

static WpSpaJsonBuilder *
wp_spa_json_builder_new (const gchar *data, size_t size)
{
  WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder);
  self->add_separator = FALSE;
  self->data = g_new0 (gchar, size + 1);
  self->max_size = size;
  memcpy (self->data, data, size),
  self->data[size] = '\0';
  self->size = size;
  return self;
}

static WpSpaJsonBuilder *
wp_spa_json_builder_new_empty (size_t size)
{
  WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder);
  self->add_separator = FALSE;
  self->data = g_new0 (gchar, size + 1);
  self->max_size = size;
  self->data[0] = '\0';
  self->size = 0;
  return self;
}

static WpSpaJsonBuilder *
wp_spa_json_builder_new_formatted (const gchar *fmt, ...)
{
  va_list args;
  WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder);
  self->add_separator = FALSE;
  va_start (args, fmt);
  self->data = g_strdup_vprintf (fmt, args);
  va_end (args);
  self->size = strlen (self->data);
  self->max_size = self->size;
  return self;
}

static WpSpaJson *
wp_spa_json_new_from_builder (WpSpaJsonBuilder *builder)
{
  WpSpaJson *self = g_slice_new0 (WpSpaJson);
  g_ref_count_init (&self->ref);
  self->flags = 0;
  self->builder = builder;
  self->data = builder->data;
  self->size = builder->size;
  spa_json_init (&self->json_data, self->data, self->size);
  self->json = &self->json_data;
  return self;
}

static WpSpaJson *
wp_spa_json_new (const gchar *data, size_t size)
{
  return wp_spa_json_new_from_builder (wp_spa_json_builder_new (data, size));
}

/*!
 * \brief Constructs a new WpSpaJson from a JSON string.
 *
 * \ingroup wpspajson
 * \param json_str a JSON string
 * \returns a new WpSpaJson that references the data in \a json_str. \a json_str
 *   is not copied, so it needs to stay alive.
 */
WpSpaJson *
wp_spa_json_new_from_string (const gchar *json_str)
{
  return wp_spa_json_new_from_stringn(json_str, strlen (json_str));
}

/*!
 * \brief Constructs a new WpSpaJson from a JSON string with specific length.
 *
 * \ingroup wpspajson
 * \param json_str a JSON string
 * \param len the specific length of the string
 * \returns a new WpSpaJson that references the data in \a json_str. \a json_str
 *   is not copied, so it needs to stay alive.
 *
 * \since 0.4.10
 */
WpSpaJson *
wp_spa_json_new_from_stringn (const gchar *json_str, size_t len)
{
  WpSpaJson *self = g_slice_new0 (WpSpaJson);
  g_ref_count_init (&self->ref);
  self->flags = FLAG_NO_OWNERSHIP;
  spa_json_init (&self->json_data, json_str, len);
  self->builder = NULL;
  self->data = (gchar *)self->json_data.cur;
  self->size = self->json_data.end - self->json_data.cur;
  self->json = &self->json_data;
  return self;
}

/*!
 * \brief Constructs a new WpSpaJson that wraps the given `spa_json`.
 *
 * \ingroup wpspajson
 * \param json a spa_json
 * \returns a new WpSpaJson that references the data in \a json. \a json is not
 *   copied, so it needs to stay alive.
 */
WpSpaJson *
wp_spa_json_new_wrap (struct spa_json *json)
{
  WpSpaJson *self = g_slice_new0 (WpSpaJson);
  g_ref_count_init (&self->ref);
  self->flags = FLAG_NO_OWNERSHIP;
  self->builder = NULL;
  self->data = (gchar *)json->cur;
  self->size = json->end - json->cur;
  self->json = json;
  return self;
}

/*!
 * \brief Converts a WpSpaJson pointer to a `struct spa_json` one, for use with
 * native pipewire & spa functions. The returned pointer is owned by WpSpaJson
 * and may not be modified or freed.
 *
 * \ingroup wpspajson
 * \param self a spa json object
 * \returns a const pointer to the underlying spa_json structure
 */
const struct spa_json *
wp_spa_json_get_spa_json (const WpSpaJson *self)
{
  return self->json;
}

/*!
 * \brief Returns the json data
 *
 * \ingroup wpspajson
 * \param self a spa json object
 * \returns a const pointer to the json data
 */
const gchar *
wp_spa_json_get_data (const WpSpaJson *self)
{
  return self->data;
}

/*!
 * \brief Returns the json data size
 *
 * \ingroup wpspajson
 * \param self a spa json object
 * \returns the json data size
 */
size_t
wp_spa_json_get_size (const WpSpaJson *self)
{
  return self->size;
}

/*!
 * \brief Returns a newly allocated json string with length matching the size
 *
 * \ingroup wpspajson
 * \param self a spa json object
 * \returns (transfer full): the json string with length matching the size
 * \since 0.4.11
 */
gchar *
wp_spa_json_to_string (const WpSpaJson *self)
{
  return g_strndup (self->data, self->size);
}

/*!
 * \brief Copies a spa json object
 *
 * \ingroup wpspajson
 * \param other a spa json object
 * \returns (transfer full): The newly copied spa json
 */
WpSpaJson *
wp_spa_json_copy (WpSpaJson *other)
{
  g_return_val_if_fail (other, NULL);
  g_return_val_if_fail (other->json, NULL);
  return wp_spa_json_new (other->data, other->size);
}

/*!
 * \brief Checks if the json is the unique owner of its data or not
 *
 * \ingroup wpspajson
 * \param self a spa json object
 * \returns TRUE if the json owns the data, FALSE otherwise.
 */
gboolean
wp_spa_json_is_unique_owner (WpSpaJson *self)
{
  return g_ref_count_compare (&self->ref, 1) &&
      !(self->flags & FLAG_NO_OWNERSHIP);
}

/*!
 * \brief If \a self is not uniquely owned already, then it is unrefed and a
 * copy of it is returned instead. You should always consider \a self as unsafe
 * to use after this call and you should use the returned object instead.
 *
 * \ingroup wpspajson
 * \param self (transfer full): a spa json object
 * \returns (transfer full): the uniquely owned spa json object which may or may
 * not be the same as \a self.
 */
WpSpaJson *
wp_spa_json_ensure_unique_owner (WpSpaJson *self)
{
  WpSpaJson *copy = NULL;

  if (wp_spa_json_is_unique_owner (self))
    return self;

  copy = wp_spa_json_copy (self);
  wp_spa_json_unref (self);
  return copy;
}

/*!
 * \brief Creates a spa json of type NULL
 * \ingroup wpspajson
 * \returns (transfer full): The new spa json
 */
WpSpaJson *
wp_spa_json_new_null (void)
{
  return wp_spa_json_new_from_builder (
      wp_spa_json_builder_new_formatted ("%s", "null"));
}

/*!
 * \brief Creates a spa json of type boolean
 *
 * \ingroup wpspajson
 * \param value the boolean value
 * \returns (transfer full): The new spa json
 */
WpSpaJson *
wp_spa_json_new_boolean (gboolean value)
{
  return wp_spa_json_new_from_builder (
      wp_spa_json_builder_new_formatted ("%s", value ? "true" : "false"));
}

/*!
 * \brief Creates a spa json of type int
 *
 * \ingroup wpspajson
 * \param value the int value
 * \returns (transfer full): The new spa json
 */
WpSpaJson *
wp_spa_json_new_int (gint value)
{
  return wp_spa_json_new_from_builder (
      wp_spa_json_builder_new_formatted ("%d", value));
}

/*!
 * \brief Creates a spa json of type float
 *
 * \ingroup wpspajson
 * \param value the float value
 * \returns (transfer full): The new spa json
 */
WpSpaJson *
wp_spa_json_new_float (float value)
{
  return wp_spa_json_new_from_builder (
      wp_spa_json_builder_new_formatted ("%.6f", value));
}

static void
ensure_allocated_max_size (WpSpaJsonBuilder *self, size_t size)
{
  size_t new_size = self->size + size + 1;  /* '\0' because of vsnprintf */
  if (new_size > self->max_size) {
    size_t next_size = new_size * 2;
    self->data = g_realloc (self->data, next_size);
    self->max_size = next_size;
  }
}

/*!
 * \brief Creates a spa json of type string
 *
 * \ingroup wpspajson
 * \param value the string value
 * \returns (transfer full): The new spa json
 */
WpSpaJson *
wp_spa_json_new_string (const gchar *value)
{
  WpSpaJsonBuilder *b = wp_spa_json_builder_new_empty (strlen (value));
  size_t enc_size = spa_json_encode_string (b->data + b->size,
      b->max_size - b->size, value);
  if (enc_size + 1 > b->max_size - b->size) {
    ensure_allocated_max_size (b, enc_size);
    enc_size = spa_json_encode_string (b->data + b->size,
        b->max_size - b->size, value);
    g_assert (enc_size < b->max_size - b->size);
  }
  b->size += enc_size;
  return wp_spa_json_new_from_builder (b);
}

/* Args is not a pointer in some architectures, so this needs to be a macro to
 * avoid args being copied */
#define wp_spa_json_builder_add_value(self,fmt,args)                           \
do {                                                                           \
  switch (*fmt) {                                                              \
    case 'n':                                                                  \
      wp_spa_json_builder_add_null (self);                                     \
      break;                                                                   \
    case 'b':                                                                  \
      wp_spa_json_builder_add_boolean (self, va_arg(args, gboolean));          \
      break;                                                                   \
    case 'i':                                                                  \
      wp_spa_json_builder_add_int (self, va_arg(args, gint));                  \
      break;                                                                   \
    case 'f':                                                                  \
      wp_spa_json_builder_add_float (self, (float)va_arg(args, double));       \
      break;                                                                   \
    case 's':                                                                  \
      wp_spa_json_builder_add_string (self, va_arg(args, const gchar *));      \
      break;                                                                   \
    case 'J':                                                                  \
      wp_spa_json_builder_add_json (self, va_arg(args, WpSpaJson *));          \
      break;                                                                   \
    default:                                                                   \
      break;                                                                   \
  }								               \
} while(false)

/*!
 * \brief Creates a spa json of type array
 *
 * \ingroup wpspajson
 * \param format (nullable): the first value format ("n", "b", "i", "f", "s" or "J")
 * \param ... a list of array types and values, followed by NULL
 * \returns (transfer full): The new spa json
 */
WpSpaJson *
wp_spa_json_new_array (const gchar *format, ...)
{
  va_list args;
  WpSpaJson *res;

  va_start (args, format);
  res = wp_spa_json_new_array_valist (format, args);
  va_end (args);
  return res;
}

/*!
 * \brief This is the `va_list` version of wp_spa_json_new_array()
 *
 * \ingroup wpspajson
 * \param format (nullable): the first value format ("n", "b", "i", "f", "s" or "J")
 * \param args the variable arguments passed to wp_spa_json_new_array()
 * \returns (transfer full): The new spa json
 */
WpSpaJson *
wp_spa_json_new_array_valist (const gchar *format, va_list args)
{
  g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_array ();

  if (!format)
    return wp_spa_json_builder_end (b);

  wp_spa_json_builder_add_value (b, format, args);
  wp_spa_json_builder_add_valist (b, args);
  return wp_spa_json_builder_end (b);
}

/*!
 * \brief Creates a spa json of type object
 *
 * \ingroup wpspajson
 * \param key (nullable): the first object property key
 * \param format (nullable): the first property format ("n", "b", "i", "f", "s" or "J")
 * \param ... a list of object properties and values, followed by NULL
 * \returns (transfer full): The new spa json
 */
WpSpaJson *
wp_spa_json_new_object (const gchar *key, const gchar *format, ...)
{
  va_list args;
  WpSpaJson *res;

  va_start (args, format);
  res = wp_spa_json_new_object_valist (key, format, args);
  va_end (args);
  return res;
}

/*!
 * \brief This is the `va_list` version of wp_spa_json_new_object()
 *
 * \ingroup wpspajson
 * \param key (nullable): the first object property key
 * \param format (nullable): the first property format ("n", "b", "i", "f", "s" or "J")
 * \param args the variable arguments passed to wp_spa_json_new_object()
 * \returns (transfer full): The new spa json
 */
WpSpaJson *
wp_spa_json_new_object_valist (const gchar *key, const gchar *format,
    va_list args)
{
  g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_object ();

  if (!key || !format)
    return wp_spa_json_builder_end (b);

  wp_spa_json_builder_add_property (b, key);
  wp_spa_json_builder_add_value (b, format, args);
  wp_spa_json_builder_add_valist (b, args);
  return wp_spa_json_builder_end (b);
}

/*!
 * \brief Checks wether the spa json is of type null or not
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \returns TRUE if it is of type null, FALSE otherwise
 */
gboolean
wp_spa_json_is_null (WpSpaJson *self)
{
  return spa_json_is_null (self->data, self->size);
}

/*!
 * \brief Checks wether the spa json is of type boolean or not
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \returns TRUE if it is of type boolean, FALSE otherwise
 */
gboolean
wp_spa_json_is_boolean (WpSpaJson *self)
{
  return spa_json_is_bool (self->data, self->size);
}

/*!
 * \brief Checks wether the spa json is of type int or not
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \returns TRUE if it is of type int, FALSE otherwise
 */
gboolean
wp_spa_json_is_int (WpSpaJson *self)
{
  return spa_json_is_int (self->data, self->size);
}

/*!
 * \brief Checks wether the spa json is of type float or not
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \returns TRUE if it is of type float, FALSE otherwise
 */
gboolean
wp_spa_json_is_float (WpSpaJson *self)
{
  return spa_json_is_float (self->data, self->size);
}

/*!
 * \brief Checks wether the spa json is of type string or not
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \returns TRUE if it is of type string, FALSE otherwise
 */
gboolean
wp_spa_json_is_string (WpSpaJson *self)
{
  return spa_json_is_string (self->data, self->size);
}

/*!
 * \brief Checks wether the spa json is of type array or not
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \returns TRUE if it is of type array, FALSE otherwise
 */
gboolean
wp_spa_json_is_array (WpSpaJson *self)
{
  return spa_json_is_array (self->data, self->size);
}

/*!
 * \brief Checks wether the spa json is of type object or not
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \returns TRUE if it is of type object, FALSE otherwise
 */
gboolean
wp_spa_json_is_object (WpSpaJson *self)
{
  return spa_json_is_object (self->data, self->size);
}

gboolean
wp_spa_json_parse_boolean_internal (const gchar *data, int len, gboolean *value)
{
  bool v = false;
  if (spa_json_parse_bool (data, len, &v) < 0)
    return FALSE;
  *value = v ? TRUE : FALSE;
  return TRUE;
}

/*!
 * \brief Parses the boolean value of a spa json object
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \param value (out): the boolean value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parse_boolean (WpSpaJson *self, gboolean *value)
{
  return wp_spa_json_parse_boolean_internal (self->data, self->size, value);
}

/*!
 * \brief Parses the int value of a spa json object
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \param value (out): the int value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parse_int (WpSpaJson *self, gint *value)
{
  return spa_json_parse_int (self->data, self->size, value) >= 0;
}

/*!
 * \brief Parses the float value of a spa json object
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \param value (out): the float value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parse_float (WpSpaJson *self, float *value)
{
  return spa_json_parse_float (self->data, self->size, value) >= 0;
}

static gchar *
wp_spa_json_parse_string_internal (const gchar *data, int len)
{
  gchar *res = g_new0 (gchar, len+1);
  if (res)
    spa_json_parse_string (data, len, res);
  return res;
}

/*!
 * \brief Parses the string value of a spa json object
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \returns (transfer full): The newly allocated parsed string
 */
gchar *
wp_spa_json_parse_string (WpSpaJson *self)
{
  return wp_spa_json_parse_string_internal (self->data, self->size);
}

/*!
 * \brief Parses the array types and values of a spa json object
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \param ... (out): the list of array types and values, followed by NULL
 * \returns TRUE if the types and values were obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parse_array (WpSpaJson *self, ...)
{
  va_list args;
  gboolean res;
  va_start (args, self);
  res = wp_spa_json_parse_array_valist (self, args);
  va_end (args);
  return res;
}

/*!
 * \brief This is the `va_list` version of wp_spa_json_parse_array()
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \param args (out): the variable arguments passed to wp_spa_json_parse_array()
 * \returns TRUE if the types and values were obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parse_array_valist (WpSpaJson *self, va_list args)
{
  g_autoptr (WpSpaJsonParser) p = wp_spa_json_parser_new_array (self);
  gboolean res = wp_spa_json_parser_get_valist (p, args);
  if (res)
    wp_spa_json_parser_end (p);
  return res;
}

/*!
 * \brief Parses the object properties and values of a spa json object
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \param ... (out): the list of object properties and values, followed by NULL
 * \returns TRUE if the properties and values were obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parse_object (WpSpaJson *self, ...)
{
  va_list args;
  gboolean res;
  va_start (args, self);
  res = wp_spa_json_parse_object_valist (self, args);
  va_end (args);
  return res;
}

/*!
 * \brief This is the `va_list` version of wp_spa_json_parse_object()
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \param args (out): the variable arguments passed to wp_spa_json_parse_object()
 * \returns TRUE if the properties and values were obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parse_object_valist (WpSpaJson *self, va_list args)
{
  g_autoptr (WpSpaJsonParser) p = wp_spa_json_parser_new_object (self);
  gboolean res = wp_spa_json_parser_get_valist (p, args);
  if (res)
    wp_spa_json_parser_end (p);
  return res;
}

/* Args is not a pointer in some architectures, so this needs to be a macro to
 * avoid args being copied */
#define wp_spa_json_parse_value(data,len,fmt,args)                             \
do {                                                                           \
  switch (*fmt) {                                                              \
    case 'n':                                                                  \
      if (!spa_json_is_null (data, len))                                       \
        return FALSE;                                                          \
      break;                                                                   \
    case 'b':                                                                  \
      if (!wp_spa_json_parse_boolean_internal (data, len,                      \
          va_arg(args, gboolean *)))                                           \
        return FALSE;                                                          \
      break;                                                                   \
    case 'i':                                                                  \
      if (spa_json_parse_int (data, len, va_arg(args, gint *)) < 0)            \
        return FALSE;                                                          \
      break;                                                                   \
    case 'f':                                                                  \
      if (spa_json_parse_float (data, len, va_arg(args, float *)) < 0)         \
        return FALSE;                                                          \
      break;                                                                   \
    case 's': {                                                                \
      gchar *str = wp_spa_json_parse_string_internal (data, len);              \
      if (!str)                                                                \
        return FALSE;                                                          \
      *va_arg(args, gchar **) = str;                                           \
      break;                                                                   \
    }                                                                          \
    case 'J': {                                                                \
      WpSpaJson *j = wp_spa_json_new (data, len);                              \
      if (!j)                                                                  \
        return FALSE;                                                          \
      *va_arg(args, WpSpaJson **) = j;                                         \
      break;                                                                   \
    }                                                                          \
    default:                                                                   \
      return FALSE;                                                            \
  }                                                                            \
} while(false)

/*!
 * \brief Parses the object property values of a spa json object
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \param ... the list of property names, formats and values, followed by NULL
 * \returns TRUE if the properties and values were obtained, FALSE otherwise
 */
gboolean
wp_spa_json_object_get (WpSpaJson *self, ...)
{
  va_list args;
  gboolean res;
  va_start (args, self);
  res = wp_spa_json_object_get_valist (self, args);
  va_end (args);
  return res;
}

/*!
 * \brief This is the `va_list` version of wp_spa_json_object_get()
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \param args the variable arguments passed to wp_spa_json_object_get()
 * \returns TRUE if the properties and values were obtained, FALSE otherwise
 */
gboolean
wp_spa_json_object_get_valist (WpSpaJson *self, va_list args)
{
  g_auto (GValue) item = G_VALUE_INIT;
  g_autoptr (WpIterator) it = NULL;
  const gchar *lookup_key = NULL;
  const gchar *lookup_fmt = NULL;

  g_return_val_if_fail (wp_spa_json_is_object (self), FALSE);

  lookup_key = va_arg(args, const gchar *);
  if (!lookup_key)
    return TRUE;
  lookup_fmt = va_arg(args, const gchar *);
  if (!lookup_fmt)
    return FALSE;

  it = wp_spa_json_new_iterator (self);
  for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
    WpSpaJson *key = g_value_get_boxed (&item);
    g_autofree gchar *key_str = NULL;
    WpSpaJson *value = NULL;

    key_str = wp_spa_json_parse_string (key);
    g_return_val_if_fail (key_str, FALSE);

    g_value_unset (&item);
    if (!wp_iterator_next (it, &item))
      return FALSE;
    value = g_value_get_boxed (&item);

    if (g_strcmp0 (key_str, lookup_key) == 0) {
      wp_spa_json_parse_value (value->data, value->size, lookup_fmt, args);
      lookup_key = va_arg(args, const gchar *);
      if (!lookup_key)
        return TRUE;
      lookup_fmt = va_arg(args, const gchar *);
      if (!lookup_fmt)
        return FALSE;
      wp_iterator_reset (it);
    }
  }

  return FALSE;
}

/*!
 * \brief Increases the reference count of a spa json builder
 *
 * \ingroup wpspajson
 * \param self a spa json builder object
 * \returns (transfer full): \a self with an additional reference count on it
 */
WpSpaJsonBuilder *
wp_spa_json_builder_ref (WpSpaJsonBuilder *self)
{
  return (WpSpaJsonBuilder *) g_rc_box_acquire ((gpointer) self);
}

static void
wp_spa_json_builder_free (WpSpaJsonBuilder *self)
{
  g_clear_pointer (&self->data, g_free);
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 *
 * \ingroup wpspajson
 * \param self (transfer full): a spa json builder object
 */
void
wp_spa_json_builder_unref (WpSpaJsonBuilder *self)
{
  g_rc_box_release_full (self, (GDestroyNotify) wp_spa_json_builder_free);
}

/*!
 * \brief Creates a spa json builder of type array
 *
 * \ingroup wpspajson
 * \returns (transfer full): the new spa json builder
 */
WpSpaJsonBuilder *
wp_spa_json_builder_new_array (void)
{
  WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder);
  self->add_separator = FALSE;
  self->data = g_new0 (gchar, WP_SPA_JSON_BUILDER_INIT_SIZE);
  self->max_size = WP_SPA_JSON_BUILDER_INIT_SIZE;
  self->data[0] = '[';
  self->size = 1;
  return self;
}

/*!
 * \brief Creates a spa json builder of type object
 *
 * \ingroup wpspajson
 * \returns (transfer full): the new spa json builder
 */
WpSpaJsonBuilder *
wp_spa_json_builder_new_object (void)
{
  WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder);
  self->add_separator = FALSE;
  self->data = g_new0 (gchar, WP_SPA_JSON_BUILDER_INIT_SIZE);
  self->max_size = WP_SPA_JSON_BUILDER_INIT_SIZE;
  self->data[0] = '{';
  self->size = 1;
  return self;
}

static void
ensure_separator (WpSpaJsonBuilder *self, gboolean for_property)
{
  gboolean insert = (self->data[0] == '{' && for_property) ||
      (self->data[0] == '[' && !for_property);
  if (insert) {
    if (self->add_separator) {
      ensure_allocated_max_size (self, 2);
      self->data[self->size++] = ',';
      self->data[self->size++] = ' ';
    } else {
      self->add_separator = TRUE;
    }
  }
}

static void
builder_add_formatted (WpSpaJsonBuilder *self, const gchar *fmt, ...)
{
  int s;
  va_list args;
  va_start (args, fmt);
  s = vsnprintf (self->data + self->size, self->max_size - self->size, fmt, args);
  va_end (args);
  g_return_if_fail (s > 0);
  self->size += s;
}

static void
builder_add_json (WpSpaJsonBuilder *self, WpSpaJson *json)
{
  g_return_if_fail (self->max_size - self->size >= json->size + 1);
  snprintf (self->data + self->size, json->size + 1, "%s", json->data);
  self->size += json->size;
}

/*!
 * \brief Adds a property into the builder
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 * \param key the name of the property
 */
void
wp_spa_json_builder_add_property (WpSpaJsonBuilder *self, const gchar *key)
{
  size_t enc_size;
  ensure_separator (self, TRUE);
  enc_size = spa_json_encode_string (self->data + self->size,
      self->max_size - self->size, key);
  if (enc_size + 2 > self->max_size - self->size) {
    ensure_allocated_max_size (self, enc_size + 1);
    enc_size = spa_json_encode_string (self->data + self->size,
        self->max_size - self->size, key);
    g_assert (enc_size + 1 < self->max_size - self->size);
  }
  self->data[self->size + enc_size] = ':';
  self->size += enc_size + 1;
}

/*!
 * \brief Adds a null value into the builder
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 */
void
wp_spa_json_builder_add_null (WpSpaJsonBuilder *self)
{
  ensure_separator (self, FALSE);
  ensure_allocated_max_size (self, 4);
  builder_add_formatted (self, "%s", "null");
}

/*!
 * \brief Adds a boolean value into the builder
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 * \param value the boolean value
 */
void
wp_spa_json_builder_add_boolean (WpSpaJsonBuilder *self, gboolean value)
{
  ensure_separator (self, FALSE);
  ensure_allocated_max_size (self, value ? 4 : 5);
  builder_add_formatted (self, "%s", value ? "true" : "false");
}

/*!
 * \brief Adds a int value into the builder
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 * \param value the int value
 */
void
wp_spa_json_builder_add_int (WpSpaJsonBuilder *self, gint value)
{
  ensure_separator (self, FALSE);
  ensure_allocated_max_size (self, 16);
  builder_add_formatted (self, "%d", value);
}

/*!
 * \brief Adds a float value into the builder
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 * \param value the float value
 */
void
wp_spa_json_builder_add_float (WpSpaJsonBuilder *self, float value)
{
  ensure_separator (self, FALSE);
  ensure_allocated_max_size (self, 32);
  builder_add_formatted (self, "%.6f", value);
}

/*!
 * \brief Adds a string value into the builder
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 * \param value the string value
 */
void
wp_spa_json_builder_add_string (WpSpaJsonBuilder *self, const gchar *value)
{
  size_t enc_size;
  ensure_separator (self, FALSE);
  enc_size = spa_json_encode_string (self->data + self->size,
      self->max_size - self->size, value);
  if (enc_size + 1 > self->max_size - self->size) {
    ensure_allocated_max_size (self, enc_size);
    enc_size = spa_json_encode_string (self->data + self->size,
        self->max_size - self->size, value);
    g_assert (enc_size < self->max_size - self->size);
  }
  self->size += enc_size;
}

/*!
 * \brief Adds a json value into the builder
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 * \param json (transfer none): the json value
 */
void
wp_spa_json_builder_add_json (WpSpaJsonBuilder *self, WpSpaJson *json)
{
  ensure_separator (self, FALSE);
  ensure_allocated_max_size (self, json->size);
  builder_add_json (self, json);
}

/*!
 * \brief Adds values into the builder
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 * \param ... the json values
 */
void
wp_spa_json_builder_add (WpSpaJsonBuilder *self, ...)
{
  va_list args;
  va_start (args, self);
  wp_spa_json_builder_add_valist (self, args);
  va_end (args);
}

/*!
 * \brief This is the `va_list` version of wp_spa_json_builder_add()
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 * \param args the variable arguments passed to wp_spa_json_builder_add()
 */
void
wp_spa_json_builder_add_valist (WpSpaJsonBuilder *self, va_list args)
{
  do {
    const char *format = NULL;

    /* add property key if object */
    if (self->data[0] == '{') {
      const gchar *key = va_arg(args, const gchar *);
      if (!key)
        return;
      wp_spa_json_builder_add_property (self, key);
    }

    /* get value format */
    format = va_arg(args, const gchar *);
    if (!format)
      return;

    /* add value */
    wp_spa_json_builder_add_value (self, format, args);
  } while (TRUE);
}

/*!
 * \brief Ends the builder process and returns the constructed spa json object
 *
 * \ingroup wpspajson
 * \param self the spa json builder object
 * \returns (transfer full): the constructed spa json object
 */
WpSpaJson *
wp_spa_json_builder_end (WpSpaJsonBuilder *self)
{
  /* close */
  switch (self->data[0]) {
    case '[':  /* array */
      ensure_allocated_max_size (self, 2);
      self->data[self->size++] = ']';
      self->data[self->size] = '\0';
      break;
    case '{':  /* object */
      ensure_allocated_max_size (self, 2);
      self->data[self->size++] = '}';
      self->data[self->size] = '\0';
      break;
    default:
      break;
  }

  return wp_spa_json_new_from_builder (wp_spa_json_builder_ref (self));
}

/*!
 * \brief Increases the reference count of a spa json parser
 *
 * \ingroup wpspajson
 * \param self a spa json parser object
 * \returns (transfer full): \a self with an additional reference count on it
 */
WpSpaJsonParser *
wp_spa_json_parser_ref (WpSpaJsonParser *self)
{
  return (WpSpaJsonParser *) g_rc_box_acquire ((gpointer) self);
}

static void
wp_spa_json_parser_free (WpSpaJsonParser *self)
{
  self->json = NULL;
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 *
 * \ingroup wpspajson
 * \param self (transfer full): a spa json parser object
 */
void
wp_spa_json_parser_unref (WpSpaJsonParser *self)
{
  g_rc_box_release_full (self, (GDestroyNotify) wp_spa_json_parser_free);
}

/*!
 * \brief Creates a spa json array parser. The \a json object must be valid for
 * the entire life-cycle of the returned parser.
 *
 * \ingroup wpspajson
 * \param json the spa json array to parse
 * \returns (transfer full): The new spa json parser
 */
WpSpaJsonParser *
wp_spa_json_parser_new_array (WpSpaJson *json)
{
  WpSpaJsonParser *self;

  g_return_val_if_fail (wp_spa_json_is_array (json), NULL);

  self = g_rc_box_new0 (WpSpaJsonParser);
  self->json = json;
  self->data[0] = *json->json;
  spa_json_enter_array (&self->data[0], &self->data[1]);
  self->pos = &self->data[1];
  return self;
}

/*!
 * \brief Creates a spa json object parser. The \a json object must be valid for
 * the entire life-cycle of the returned parser.
 *
 * \ingroup wpspajson
 * \param json the spa json object to parse
 * \returns (transfer full): The new spa json parser
 */
WpSpaJsonParser *
wp_spa_json_parser_new_object (WpSpaJson *json)
{
  WpSpaJsonParser *self;

  g_return_val_if_fail (wp_spa_json_is_object (json), NULL);

  self = g_rc_box_new0 (WpSpaJsonParser);
  self->json = json;
  self->data[0] = *json->json;
  spa_json_enter_object (&self->data[0], &self->data[1]);
  self->pos = &self->data[1];
  return self;
}

static int
check_nested_size (struct spa_json *parent, const gchar *data, int size)
{
  const gchar *nested_data;
  int nested_size;
  struct spa_json nested[2];

  /* only arrays and objects are considered nested data */
  if (!spa_json_is_array (data, size) && !spa_json_is_object (data, size))
    return 0;

  /* enter */
  nested[0] = *parent;
  spa_json_enter (&nested[0], &nested[1]);

  /* recursively advance */
  while ((nested_size = spa_json_next (&nested[1], &nested_data)) > 0) {
    if (check_nested_size (&nested[1], nested_data, nested_size) < 0)
      return -1;
  }
  if (nested_size < 0)
    return -1;

  /* advance one more time to reach end of nested data */
  if (spa_json_next (&nested[1], &nested_data) < 0)
    return -1;

  return nested_data - data;
}

static gboolean
wp_spa_json_parser_advance (WpSpaJsonParser *self)
{
  const gchar *data = NULL;
  int size, nested_size;

  if (!self->pos)
    return FALSE;

  /* advance */
  size = spa_json_next (self->pos, &data);
  if (size <= 0)
    return FALSE;
  g_return_val_if_fail (data != NULL, FALSE);

  /* if array or object, add the nested size */
  nested_size = check_nested_size (self->pos, data, size);
  if (nested_size < 0)
    return FALSE;
  size += nested_size;

  /* update current */
  spa_json_init (&self->curr, data, size);
  return TRUE;
}

/*!
 * \brief Gets the null value from a spa json parser
 *
 * \ingroup wpspajson
 * \param self the spa json parser object
 * \returns TRUE if the null value is present, FALSE otherwise
 */
gboolean
wp_spa_json_parser_get_null (WpSpaJsonParser *self)
{
  return wp_spa_json_parser_advance (self) &&
      spa_json_is_null (self->curr.cur, self->curr.end - self->curr.cur);
}

/*!
 * \brief Gets the boolean value from a spa json parser
 *
 * \ingroup wpspajson
 * \param self the spa json parser object
 * \param value (out): the boolean value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parser_get_boolean (WpSpaJsonParser *self, gboolean *value)
{
  return wp_spa_json_parser_advance (self) &&
      wp_spa_json_parse_boolean_internal (self->curr.cur,
          self->curr.end - self->curr.cur, value);
}

/*!
 * \brief Gets the int value from a spa json parser object
 *
 * \ingroup wpspajson
 * \param self the spa json parser object
 * \param value (out): the int value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parser_get_int (WpSpaJsonParser *self, gint *value)
{
  return wp_spa_json_parser_advance (self) &&
      spa_json_parse_int (self->curr.cur,
          self->curr.end - self->curr.cur, value) >= 0;
}

/*!
 * \brief Gets the float value from a spa json parser object
 *
 * \ingroup wpspajson
 * \param self the spa json parser object
 * \param value (out): the float value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parser_get_float (WpSpaJsonParser *self, float *value)
{
  return wp_spa_json_parser_advance (self) &&
      spa_json_parse_float (self->curr.cur,
          self->curr.end - self->curr.cur, value) >= 0;
}

/*!
 * \brief Gets the string value from a spa json parser object
 *
 * \ingroup wpspajson
 * \param self the spa json parser object
 * \returns (transfer full): The newly allocated parsed string
 */
gchar *
wp_spa_json_parser_get_string (WpSpaJsonParser *self)
{
  return wp_spa_json_parser_advance (self) ?
      wp_spa_json_parse_string_internal (self->curr.cur,
          self->curr.end - self->curr.cur) : NULL;
}

/*!
 * \brief Gets the spa json value from a spa json parser object
 *
 * \ingroup wpspajson
 * \param self the spa json parser object
 * \returns (transfer full): The spa json value or NULL if it could not be
 * obtained
 */
WpSpaJson *
wp_spa_json_parser_get_json (WpSpaJsonParser *self)
{
  return wp_spa_json_parser_advance (self) ?
      wp_spa_json_new_wrap (&self->curr) : NULL;
}

gboolean
wp_spa_json_parser_get_value (WpSpaJsonParser *self, const gchar *fmt,
    va_list args)
{
  if (wp_spa_json_parser_advance (self)) {
    wp_spa_json_parse_value (self->curr.cur, self->curr.end - self->curr.cur,
        fmt, args);
    return TRUE;
  }
  return FALSE;
}

/*!
 * \brief Gets the values from a spa json parser object
 *
 * \ingroup wpspajson
 * \param self the spa json parser object
 * \param ... (out): a list of values to get, followed by NULL
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parser_get (WpSpaJsonParser *self, ...)
{
  gboolean res;
  va_list args;
  va_start (args, self);
  res = wp_spa_json_parser_get_valist (self, args);
  va_end (args);
  return res;
}

/*!
 * \brief This is the `va_list` version of wp_spa_json_parser_get()
 *
 * \ingroup wpspajson
 * \param self the spa json parser object
 * \param args the variable arguments passed to wp_spa_json_parser_get()
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_json_parser_get_valist (WpSpaJsonParser *self, va_list args)
{
  do {
    const char *format = NULL;

    /* parse property key if object */
    if (self->json->data[0] == '{') {
      gchar **key = va_arg(args, gchar **);
      if (!key)
        return TRUE;
      *key = wp_spa_json_parser_get_string (self);
      if (!(*key))
        return FALSE;
    }

    /* parse format */
    format = va_arg(args, const gchar *);
    if (!format)
      return TRUE;

    /* advance */
    if (!wp_spa_json_parser_advance (self))
      return FALSE;

    /* parse value */
    wp_spa_json_parse_value (self->curr.cur, self->curr.end - self->curr.cur,
        format, args);
  } while (TRUE);

  return FALSE;
}

void
wp_spa_json_parser_end (WpSpaJsonParser *self)
{
  self->pos = NULL;
}

struct _WpSpaJsonIterator
{
  WpSpaJson *json;
  WpSpaJsonParser *parser;
};
typedef struct _WpSpaJsonIterator WpSpaJsonIterator;

static void
wp_spa_json_iterator_reset (WpIterator *iterator)
{
  WpSpaJsonIterator *self = wp_iterator_get_user_data (iterator);
  g_clear_pointer (&self->parser, wp_spa_json_parser_unref);
}

static gboolean
wp_spa_json_iterator_next (WpIterator *iterator, GValue *item)
{
  WpSpaJsonIterator *self = wp_iterator_get_user_data (iterator);

  /* init iterator if first time */
  if (!self->parser) {
    switch (self->json->json->cur[0]) {
      case '[':  /* array */
        self->parser = wp_spa_json_parser_new_array (self->json);
        break;
      case '{':  /* object */
        self->parser = wp_spa_json_parser_new_object (self->json);
        break;
      default:
        return FALSE;
    }
  }

  /* advance */
  if (!wp_spa_json_parser_advance (self->parser))
    return FALSE;

  if (item) {
    g_value_init (item, WP_TYPE_SPA_JSON);
    g_value_take_boxed (item, wp_spa_json_new_wrap (&self->parser->curr));
  }

  return TRUE;
}

static void
wp_spa_json_iterator_finalize (WpIterator *iterator)
{
  WpSpaJsonIterator *self = wp_iterator_get_user_data (iterator);
  g_clear_pointer (&self->parser, wp_spa_json_parser_unref);
  g_clear_pointer (&self->json, wp_spa_json_unref);
}

/*!
 * \brief Creates a new iterator for a spa json object.
 *
 * \ingroup wpspajson
 * \param self the spa json object
 * \returns (transfer full): the new spa json iterator
 */
WpIterator *
wp_spa_json_new_iterator (WpSpaJson *self)
{
  static const WpIteratorMethods methods = {
    .version = WP_ITERATOR_METHODS_VERSION,
    .reset = wp_spa_json_iterator_reset,
    .next = wp_spa_json_iterator_next,
    .fold = NULL,
    .foreach = NULL,
    .finalize = wp_spa_json_iterator_finalize
  };
  WpIterator *it = wp_iterator_new (&methods, sizeof (WpSpaJsonIterator));
  WpSpaJsonIterator *jit = wp_iterator_get_user_data (it);

  jit->json = wp_spa_json_ref (self);
  jit->parser = NULL;

  return it;
}
 07070100000093000081A4000000000000000000000001656CC35F0000179D000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/spa-json.h  /* WirePlumber
 *
 * Copyright © 2022 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_SPA_JSON_H__
#define __WIREPLUMBER_SPA_JSON_H__

#include <gio/gio.h>

#include "defs.h"
#include "iterator.h"

G_BEGIN_DECLS

struct spa_json;

/*!
 * \brief The WpSpaJson GType
 * \ingroup wpspajson
 */
#define WP_TYPE_SPA_JSON (wp_spa_json_get_type ())
WP_API
GType wp_spa_json_get_type (void);

typedef struct _WpSpaJson WpSpaJson;

WP_API
WpSpaJson *wp_spa_json_ref (WpSpaJson *self);

WP_API
void wp_spa_json_unref (WpSpaJson *self);

WP_API
WpSpaJson * wp_spa_json_new_from_string (const gchar *json_str);

WP_API
WpSpaJson * wp_spa_json_new_from_stringn (const gchar *json_str, size_t len);

WP_API
WpSpaJson * wp_spa_json_new_wrap (struct spa_json *json);

WP_API
const struct spa_json * wp_spa_json_get_spa_json (const WpSpaJson *self);

WP_API
const gchar * wp_spa_json_get_data (const WpSpaJson *self);

WP_API
size_t wp_spa_json_get_size (const WpSpaJson *self);

WP_API
gchar * wp_spa_json_to_string (const WpSpaJson *self);

WP_API
WpSpaJson *wp_spa_json_copy (WpSpaJson *other);

WP_API
gboolean wp_spa_json_is_unique_owner (WpSpaJson *self);

WP_API
WpSpaJson *wp_spa_json_ensure_unique_owner (WpSpaJson *self);

WP_API
WpSpaJson *wp_spa_json_new_null (void);

WP_API
WpSpaJson *wp_spa_json_new_boolean (gboolean value);

WP_API
WpSpaJson *wp_spa_json_new_int (gint value);

WP_API
WpSpaJson *wp_spa_json_new_float (float value);

WP_API
WpSpaJson *wp_spa_json_new_string (const gchar *value);

WP_API
WpSpaJson *wp_spa_json_new_array (const gchar *format, ...)
    G_GNUC_NULL_TERMINATED;

WP_API
WpSpaJson *wp_spa_json_new_array_valist (const gchar *format, va_list args);

WP_API
WpSpaJson *wp_spa_json_new_object (const gchar *key, const gchar *format, ...)
    G_GNUC_NULL_TERMINATED;

WP_API
WpSpaJson *wp_spa_json_new_object_valist (const gchar *key, const gchar *format,
    va_list args);

WP_API
gboolean wp_spa_json_is_null (WpSpaJson *self);

WP_API
gboolean wp_spa_json_is_boolean (WpSpaJson *self);

WP_API
gboolean wp_spa_json_is_int (WpSpaJson *self);

WP_API
gboolean wp_spa_json_is_float (WpSpaJson *self);

WP_API
gboolean wp_spa_json_is_string (WpSpaJson *self);

WP_API
gboolean wp_spa_json_is_array (WpSpaJson *self);

WP_API
gboolean wp_spa_json_is_object (WpSpaJson *self);

WP_API
gboolean wp_spa_json_parse_boolean (WpSpaJson *self, gboolean *value);

WP_API
gboolean wp_spa_json_parse_int (WpSpaJson *self, gint *value);

WP_API
gboolean wp_spa_json_parse_float (WpSpaJson *self, float *value);

WP_API
gchar *wp_spa_json_parse_string (WpSpaJson *self);

WP_API
gboolean wp_spa_json_parse_array (WpSpaJson *self, ...) G_GNUC_NULL_TERMINATED;

WP_API
gboolean wp_spa_json_parse_array_valist (WpSpaJson *self, va_list args);

WP_API
gboolean wp_spa_json_parse_object (WpSpaJson *self, ...) G_GNUC_NULL_TERMINATED;

WP_API
gboolean wp_spa_json_parse_object_valist (WpSpaJson *self, va_list args);

WP_API
gboolean wp_spa_json_object_get (WpSpaJson *self, ...) G_GNUC_NULL_TERMINATED;

WP_API
gboolean wp_spa_json_object_get_valist (WpSpaJson *self, va_list args);

WP_API
WpIterator *wp_spa_json_new_iterator (WpSpaJson *self);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpSpaJson, wp_spa_json_unref)

/*!
 * \brief The WpSpaJsonBuilder GType
 * \ingroup wpspajson
 */
#define WP_TYPE_SPA_JSON_BUILDER (wp_spa_json_builder_get_type ())
WP_API
GType wp_spa_json_builder_get_type (void);

typedef struct _WpSpaJsonBuilder WpSpaJsonBuilder;

WP_API
WpSpaJsonBuilder *wp_spa_json_builder_ref (WpSpaJsonBuilder *self);

WP_API
void wp_spa_json_builder_unref (WpSpaJsonBuilder *self);

WP_API
WpSpaJsonBuilder *wp_spa_json_builder_new_array (void);

WP_API
WpSpaJsonBuilder *wp_spa_json_builder_new_object (void);

WP_API
void wp_spa_json_builder_add_property (WpSpaJsonBuilder *self, const gchar *key);

WP_API
void wp_spa_json_builder_add_null (WpSpaJsonBuilder *self);

WP_API
void wp_spa_json_builder_add_boolean (WpSpaJsonBuilder *self, gboolean value);

WP_API
void wp_spa_json_builder_add_int (WpSpaJsonBuilder *self, gint value);

WP_API
void wp_spa_json_builder_add_float (WpSpaJsonBuilder *self, float value);

WP_API
void wp_spa_json_builder_add_string (WpSpaJsonBuilder *self, const gchar *value);

WP_API
void wp_spa_json_builder_add_json (WpSpaJsonBuilder *self, WpSpaJson *json);

WP_API
void wp_spa_json_builder_add (WpSpaJsonBuilder *self, ...)
    G_GNUC_NULL_TERMINATED;

WP_API
void wp_spa_json_builder_add_valist (WpSpaJsonBuilder *self, va_list args);

WP_API
WpSpaJson *wp_spa_json_builder_end (WpSpaJsonBuilder *self);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpSpaJsonBuilder, wp_spa_json_builder_unref)

/*!
 * \brief The WpSpaJsonParser GType
 * \ingroup wpspajson
 */
#define WP_TYPE_SPA_JSON_PARSER (wp_spa_json_parser_get_type ())
WP_API
GType wp_spa_json_parser_get_type (void);

typedef struct _WpSpaJsonParser WpSpaJsonParser;

WP_API
WpSpaJsonParser *wp_spa_json_parser_ref (WpSpaJsonParser *self);

WP_API
void wp_spa_json_parser_unref (WpSpaJsonParser *self);

WP_API
WpSpaJsonParser *wp_spa_json_parser_new_array (WpSpaJson *json);

WP_API
WpSpaJsonParser *wp_spa_json_parser_new_object (WpSpaJson *json);

WP_API
gboolean wp_spa_json_parser_get_null (WpSpaJsonParser *self);

WP_API
gboolean wp_spa_json_parser_get_boolean (WpSpaJsonParser *self,
    gboolean *value);

WP_API
gboolean wp_spa_json_parser_get_int (WpSpaJsonParser *self, gint *value);

WP_API
gboolean wp_spa_json_parser_get_float (WpSpaJsonParser *self, float *value);

WP_API
gchar *wp_spa_json_parser_get_string (WpSpaJsonParser *self);

WP_API
WpSpaJson *wp_spa_json_parser_get_json (WpSpaJsonParser *self);

WP_API
gboolean wp_spa_json_parser_get (WpSpaJsonParser *self, ...)
    G_GNUC_NULL_TERMINATED;

WP_API
gboolean wp_spa_json_parser_get_valist (WpSpaJsonParser *self, va_list args);

WP_API
void wp_spa_json_parser_end (WpSpaJsonParser *self);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpSpaJsonParser, wp_spa_json_parser_unref)

G_END_DECLS

#endif
   07070100000094000081A4000000000000000000000001656CC35F00014F02000000000000000000000000000000000000002400000000wireplumber-0.4.17/lib/wp/spa-pod.c   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-spa-pod"

#include "spa-pod.h"
#include "spa-type.h"

#include <spa/utils/type-info.h>
#include <spa/pod/builder.h>
#include <spa/pod/parser.h>
#include <spa/pod/filter.h>

#define WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE 64
#define WP_SPA_POD_ID_PROPERTY_NAME_MAX 16

/*! \defgroup wpspapod WpSpaPod */
/*!
 * \struct WpSpaPod
 */
/*!
 * \struct WpSpaPodBuilder
 */
/*!
 * \struct WpSpaPodParser
 */

enum {
  FLAG_NO_OWNERSHIP = (1 << 0),
  FLAG_CONSTANT = (1 << 1)
};

typedef enum {
  WP_SPA_POD_REGULAR = 0,
  WP_SPA_POD_PROPERTY,
  WP_SPA_POD_CONTROL,
} WpSpaPodType;

struct _WpSpaPod
{
  grefcount ref;
  guint32 flags;

  /* The pipewire spa pod API does not have a type for Property and Control,
   * so we create our own and separate them with their data from the regular
   * spa pod types */
  WpSpaPodType type;

  /* Pod */
  union {
    struct spa_pod pod_none;
    struct spa_pod_bool pod_bool;
    struct spa_pod_id pod_id;
    struct spa_pod_int pod_int;
    struct spa_pod_long pod_long;
    struct spa_pod_float pod_float;
    struct spa_pod_double pod_double;
    struct spa_pod_pointer pod_pointer;
    struct spa_pod_fd pod_fd;
    struct spa_pod_rectangle pod_rectangle;
    struct spa_pod_fraction pod_fraction;
    struct wp_property_data {
      WpSpaIdTable table;
      guint32 key;
      guint32 flags;
      gchar id_key_name[WP_SPA_POD_ID_PROPERTY_NAME_MAX];
    } data_property;         /* Only used for property pods */
    struct wp_control_data {
      guint32 offset;
      enum spa_control_type type;
    } data_control;          /* Only used for control pods */
  } static_pod;              /* Only used for statically allocated pods */
  WpSpaPodBuilder *builder;  /* Only used for dynamically allocated pods */
  struct spa_pod *pod;
};

G_DEFINE_BOXED_TYPE (WpSpaPod, wp_spa_pod, wp_spa_pod_ref, wp_spa_pod_unref)

struct _WpSpaPodBuilder
{
  struct spa_pod_builder builder;
  struct spa_pod_frame frame;
  WpSpaType type;
  size_t size;
  guint8 *buf;
};

G_DEFINE_BOXED_TYPE (WpSpaPodBuilder, wp_spa_pod_builder,
    wp_spa_pod_builder_ref, wp_spa_pod_builder_unref)

struct _WpSpaPodParser
{
  struct spa_pod_parser parser;
  struct spa_pod_frame frame;
  WpSpaType type;
  WpSpaPod *pod;
};

G_DEFINE_BOXED_TYPE (WpSpaPodParser, wp_spa_pod_parser,
    wp_spa_pod_parser_ref, wp_spa_pod_parser_unref)

static int
wp_spa_pod_builder_overflow (gpointer data, uint32_t size)
{
  WpSpaPodBuilder *self = data;
  const uint32_t next_size = self->size + WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE;
  const uint32_t new_size = size > next_size ? size : next_size;
  self->buf = g_realloc (self->buf, new_size);
  self->builder.data = self->buf;
  self->builder.size = new_size;
  self->size = new_size;
  return 0;
}

static const struct spa_pod_builder_callbacks builder_callbacks = {
  SPA_VERSION_POD_BUILDER_CALLBACKS,
  .overflow = wp_spa_pod_builder_overflow
};

static WpSpaPodBuilder *
wp_spa_pod_builder_new (size_t size, WpSpaType type)
{
  WpSpaPodBuilder *self = g_rc_box_new0 (WpSpaPodBuilder);
  self->size = size;
  self->buf = g_new0 (guint8, self->size);
  self->builder = SPA_POD_BUILDER_INIT (self->buf, self->size);
  self->type = type;

  spa_pod_builder_set_callbacks (&self->builder, &builder_callbacks, self);

  return self;
}

/*!
 * \brief Increases the reference count of a spa pod object
 * \ingroup wpspapod
 * \param self a spa pod object
 * \returns (transfer full): \a self with an additional reference count on it
 */
WpSpaPod *
wp_spa_pod_ref (WpSpaPod *self)
{
  g_ref_count_inc (&self->ref);
  return self;
}

static void
wp_spa_pod_free (WpSpaPod *self)
{
  g_clear_pointer (&self->builder, wp_spa_pod_builder_unref);
  self->pod = NULL;
  g_slice_free (WpSpaPod, self);
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 * \ingroup wpspapod
 * \param self (transfer full): a spa pod object
 */
void
wp_spa_pod_unref (WpSpaPod *self)
{
  if (g_ref_count_dec (&self->ref))
    wp_spa_pod_free (self);
}

static WpSpaPod *
wp_spa_pod_new (const struct spa_pod *pod, WpSpaPodType type, guint32 flags)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->flags = flags;
  self->type = type;

  /* Copy the reference if no ownership, otherwise copy the pod */
  if (self->flags & FLAG_NO_OWNERSHIP) {
    self->pod = (struct spa_pod *)pod;
  } else {
    self->builder = wp_spa_pod_builder_new (
      SPA_ROUND_UP_N (sizeof (*pod) + pod->size, 8), pod->type);
    self->pod = self->builder->builder.data;
    spa_pod_builder_primitive (&self->builder->builder, pod);
  }

  /* Set the prop table if it is an object */
  if (pod->type == SPA_TYPE_Object) {
    self->static_pod.data_property.table =
        wp_spa_type_get_values_table (((struct spa_pod_object *) pod)->body.type);
  }

  return self;
}

/*!
 * \brief Constructs a new WpSpaPod that wraps the given `spa_pod`.
 *
 * \ingroup wpspapod
 * \param pod a spa_pod
 * \returns a new WpSpaPod that references the data in \a pod. \a pod is not
 *   copied, so it needs to stay alive. The returned WpSpaPod can be modified
 *   by using the setter functions, in which case \a pod will be modified
 *   underneath.
 */
WpSpaPod *
wp_spa_pod_new_wrap (struct spa_pod *pod)
{
  return wp_spa_pod_new (pod, WP_SPA_POD_REGULAR, FLAG_NO_OWNERSHIP);
}

/*!
 * \brief Constructs a new immutable WpSpaPod that wraps the given `spa_pod`.
 *
 * \ingroup wpspapod
 * \param pod a constant spa_pod
 * \returns a new WpSpaPod that references the data in \a pod. \a pod is not
 *   copied, so it needs to stay alive. The returned WpSpaPod cannot be
 *   modified, unless it's copied first.
 */
WpSpaPod *
wp_spa_pod_new_wrap_const (const struct spa_pod *pod)
{
  return wp_spa_pod_new (pod, WP_SPA_POD_REGULAR,
      FLAG_NO_OWNERSHIP | FLAG_CONSTANT);
}

static WpSpaPod *
wp_spa_pod_new_property_wrap (WpSpaIdTable table, guint32 key, guint32 flags,
    struct spa_pod *pod)
{
  WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_PROPERTY, FLAG_NO_OWNERSHIP);
  self->static_pod.data_property.table = table;
  self->static_pod.data_property.key = key;
  self->static_pod.data_property.flags = flags;
  return self;
}

static WpSpaPod *
wp_spa_pod_new_control_wrap (guint32 offset, enum spa_control_type type,
    struct spa_pod *pod)
{
  WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_CONTROL, FLAG_NO_OWNERSHIP);
  self->static_pod.data_control.offset = offset;
  self->static_pod.data_control.type = type;
  return self;
}

#if 0
/* there is no use for these _const variants, but let's keep them just in case */

static WpSpaPod *
wp_spa_pod_new_property_wrap_const (WpSpaIdTable table, guint32 key,
    guint32 flags, const struct spa_pod *pod)
{
  WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_PROPERTY,
      FLAG_NO_OWNERSHIP | FLAG_CONSTANT);
  self->static_pod.data_property.table = table;
  self->static_pod.data_property.key = key;
  self->static_pod.data_property.flags = flags;
  return self;
}

static WpSpaPod *
wp_spa_pod_new_control_wrap_const (guint32 offset, enum spa_control_type type,
    const struct spa_pod *pod)
{
  WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_CONTROL,
      FLAG_NO_OWNERSHIP | FLAG_CONSTANT);
  self->static_pod.data_control.offset = offset;
  self->static_pod.data_control.type = type;
  return self;
}
#endif

static WpSpaPod *
wp_spa_pod_new_wrap_copy (const struct spa_pod *pod)
{
  return wp_spa_pod_new (pod, WP_SPA_POD_REGULAR, 0);
}

static WpSpaPod *
wp_spa_pod_new_property_wrap_copy (WpSpaIdTable table, guint32 key,
    guint32 flags, const struct spa_pod *pod)
{
  WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_PROPERTY, 0);
  self->static_pod.data_property.table = table;
  self->static_pod.data_property.key = key;
  self->static_pod.data_property.flags = flags;
  return self;
}

static WpSpaPod *
wp_spa_pod_new_control_wrap_copy (guint32 offset, enum spa_control_type type,
    const struct spa_pod *pod)
{
  WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_CONTROL, 0);
  self->static_pod.data_control.offset = offset;
  self->static_pod.data_control.type = type;
  return self;
}

/*!
 * \brief Converts a WpSpaPod pointer to a `struct spa_pod` one, for use with
 * native pipewire & spa functions. The returned pointer is owned by WpSpaPod
 * and may not be modified or freed.
 *
 * \ingroup wpspapod
 * \param self a spa pod object
 * \returns a const pointer to the underlying spa_pod structure
 */
const struct spa_pod *
wp_spa_pod_get_spa_pod (const WpSpaPod *self)
{
  return self->pod;
}

/*!
 * \brief Gets the SPA type of the spa pod.
 *
 * If the pod is an object or pointer, this will return the derived
 * object/pointer type directly.
 * If the pod is an object property or a control, this will return the type
 * of the contained value.
 *
 * \ingroup wpspapod
 * \param self a spa pod
 * \returns (transfer none): the type of the spa pod
 */
WpSpaType
wp_spa_pod_get_spa_type (WpSpaPod *self)
{
  g_return_val_if_fail (self != NULL, WP_SPA_TYPE_INVALID);

  if (wp_spa_pod_is_object (self) || wp_spa_pod_is_pointer (self))
    return SPA_POD_OBJECT_TYPE (self->pod);
  else
    return SPA_POD_TYPE (self->pod);
}

/*!
 * \brief If the pod is a Choice, this gets the choice type
 * (Range, Step, Enum, ...)
 *
 * \ingroup wpspapod
 * \param self a choice pod
 * \returns the choice type of the choice pod
 */
WpSpaIdValue
wp_spa_pod_get_choice_type (WpSpaPod *self)
{
  g_return_val_if_fail (wp_spa_pod_is_choice (self), NULL);
  return wp_spa_id_value_from_number (
      SPA_TYPE_INFO_Choice, SPA_POD_CHOICE_TYPE (self->pod));
}

/*!
 * \brief Copies a spa pod object
 *
 * \ingroup wpspapod
 * \param other a spa pod object
 * \returns (transfer full): The newly copied spa pod
 */
WpSpaPod *
wp_spa_pod_copy (WpSpaPod *other)
{
  g_return_val_if_fail (other, NULL);
  switch (other->type) {
  case WP_SPA_POD_PROPERTY:
    return wp_spa_pod_new_property_wrap_copy (
        other->static_pod.data_property.table,
        other->static_pod.data_property.key,
        other->static_pod.data_property.flags, other->pod);
  case WP_SPA_POD_CONTROL:
    return wp_spa_pod_new_control_wrap_copy (
        other->static_pod.data_control.offset,
        other->static_pod.data_control.type, other->pod);
  case WP_SPA_POD_REGULAR:
  default:
    break;
  }
  return wp_spa_pod_new_wrap_copy (other->pod);
}

/*!
 * \brief Checks if the pod is the unique owner of its data or not
 *
 * \ingroup wpspapod
 * \param self a spa pod object
 * \returns TRUE if the pod owns the data, FALSE otherwise.
 */
gboolean
wp_spa_pod_is_unique_owner (WpSpaPod *self)
{
  return g_ref_count_compare (&self->ref, 1) &&
      !(self->flags & FLAG_NO_OWNERSHIP);
}

/*!
 * \brief If \a self is not uniquely owned already, then it is unrefed and a
 * copy of it is returned instead. You should always consider \a self as unsafe
 * to use after this call and you should use the returned object instead.
 *
 * \ingroup wpspapod
 * \param self (transfer full): a spa pod object
 * \returns (transfer full): the uniquely owned spa pod object which may or may
 * not be the same as \a self.
 */
WpSpaPod *
wp_spa_pod_ensure_unique_owner (WpSpaPod *self)
{
  WpSpaPod *copy = NULL;

  if (wp_spa_pod_is_unique_owner (self))
    return self;

  copy = wp_spa_pod_copy (self);
  wp_spa_pod_unref (self);
  return copy;
}

/*!
 * \brief Creates a spa pod of type None
 * \ingroup wpspapod
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_none (void)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_none = SPA_POD_INIT_None();
  self->pod = &self->static_pod.pod_none;
  return self;
}

/*!
 * \brief Creates a spa pod of type boolean
 *
 * \ingroup wpspapod
 * \param value the boolean value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_boolean (gboolean value)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_bool = SPA_POD_INIT_Bool (value ? true : false);
  self->pod = &self->static_pod.pod_bool.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type Id
 *
 * \ingroup wpspapod
 * \param value the Id value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_id (guint32 value)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_id = SPA_POD_INIT_Id (value);
  self->pod = &self->static_pod.pod_id.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type int
 *
 * \ingroup wpspapod
 * \param value the int value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_int (gint32 value)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_int = SPA_POD_INIT_Int (value);
  self->pod = &self->static_pod.pod_int.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type long
 *
 * \ingroup wpspapod
 * \param value the long value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_long (gint64 value)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_long = SPA_POD_INIT_Long (value);
  self->pod = &self->static_pod.pod_long.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type float
 *
 * \ingroup wpspapod
 * \param value the float value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_float (float value)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_float = SPA_POD_INIT_Float (value);
  self->pod = &self->static_pod.pod_float.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type double
 *
 * \ingroup wpspapod
 * \param value the double value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_double (double value)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_double = SPA_POD_INIT_Double (value);
  self->pod = &self->static_pod.pod_double.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type string
 *
 * \ingroup wpspapod
 * \param value the string value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_string (const char *value)
{
  const uint32_t len = value ? strlen (value) : 0;
  const char *str = value ? value : "";
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;

  struct spa_pod_string p = SPA_POD_INIT_String (len + 1);
  self->builder = wp_spa_pod_builder_new (
      SPA_ROUND_UP_N (sizeof (p) + len + 1, 8), SPA_TYPE_String);

  self->pod = self->builder->builder.data;

  spa_pod_builder_raw (&self->builder->builder, &p, sizeof(p));
  spa_pod_builder_write_string (&self->builder->builder, str, len);
  return self;
}

/*!
 * \brief Creates a spa pod of type bytes
 *
 * \ingroup wpspapod
 * \param value the bytes value
 * \param len the length of the bytes value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_bytes (gconstpointer value, guint32 len)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  const struct spa_pod_bytes p = SPA_POD_INIT_Bytes (len);
  self->builder = wp_spa_pod_builder_new (
      SPA_ROUND_UP_N (sizeof (p) + p.pod.size, 8),
      SPA_TYPE_Bytes);

  self->pod = self->builder->builder.data;

  spa_pod_builder_raw (&self->builder->builder, &p, sizeof(p));
  spa_pod_builder_raw_padded (&self->builder->builder, value, len);
  return self;
}

/*!
 * \brief Creates a spa pod of type pointer
 *
 * \ingroup wpspapod
 * \param type_name the name of the type of the pointer
 * \param value the pointer value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_pointer (const char *type_name, gconstpointer value)
{
  WpSpaType type = wp_spa_type_from_name (type_name);
  g_return_val_if_fail (type != WP_SPA_TYPE_INVALID, NULL);

  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_pointer = SPA_POD_INIT_Pointer (type, value);
  self->pod = &self->static_pod.pod_pointer.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type Fd
 *
 * \ingroup wpspapod
 * \param value the Fd value
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_fd (gint64 value)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_fd = SPA_POD_INIT_Fd (value);
  self->pod = &self->static_pod.pod_fd.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type rectangle
 *
 * \ingroup wpspapod
 * \param width the width value of the rectangle
 * \param height the height value of the rectangle
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_rectangle (guint32 width, guint32 height)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_rectangle =
      SPA_POD_INIT_Rectangle (SPA_RECTANGLE (width, height));
  self->pod = &self->static_pod.pod_rectangle.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type fraction
 *
 * \ingroup wpspapod
 * \param num the numerator value of the fraction
 * \param denom the denominator value of the fraction
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_fraction (guint32 num, guint32 denom)
{
  WpSpaPod *self = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&self->ref);
  self->type = WP_SPA_POD_REGULAR;
  self->static_pod.pod_fraction =
      SPA_POD_INIT_Fraction (SPA_FRACTION (num, denom));
  self->pod = &self->static_pod.pod_fraction.pod;
  return self;
}

/*!
 * \brief Creates a spa pod of type choice
 *
 * \ingroup wpspapod
 * \param choice_type the name of the choice type ("Range", "Step", ...),
 * \param ... a list of choice values, followed by NULL
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_choice (const char *choice_type, ...)
{
  WpSpaPod *self;
  va_list args;

  va_start (args, choice_type);
  self = wp_spa_pod_new_choice_valist (choice_type, args);
  va_end (args);

  return self;
}

/*!
 * \brief This is the `va_list` version of wp_spa_pod_new_choice()
 *
 * \ingroup wpspapod
 * \param choice_type the name of the choice type ("Range", "Step", ...)
 * \param args the variable arguments passed to wp_spa_pod_new_choice()
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_choice_valist (const char *choice_type, va_list args)
{
  g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_choice (choice_type);
  wp_spa_pod_builder_add_valist (b, args);
  return wp_spa_pod_builder_end (b);
}

/*!
 * \brief Creates a spa pod of type object
 *
 * \ingroup wpspapod
 * \param type_name the type name of the object type
 * \param id_name the id name of the object,
 * \param ... a list of object properties with their values, followed by NULL
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_object (const char *type_name, const char *id_name, ...)
{
  WpSpaPod *self;
  va_list args;

  va_start (args, id_name);
  self = wp_spa_pod_new_object_valist (type_name, id_name, args);
  va_end (args);

  return self;
}

/*!
 * \brief This is the `va_list` version of wp_spa_pod_new_object()
 *
 * \ingroup wpspapod
 * \param type_name the type name of the object type
 * \param id_name the id name of the object
 * \param args the variable arguments passed to wp_spa_pod_new_object()
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_object_valist (const char *type_name, const char *id_name,
    va_list args)
{
  g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_object (type_name,
      id_name);
  wp_spa_pod_builder_add_valist (b, args);
  return wp_spa_pod_builder_end (b);
}

/*!
 * \brief Creates a spa pod of type sequence
 *
 * \ingroup wpspapod
 * \param unit the unit of the sequence
 * \param ... a list of sequence controls with their values, followed by NULL
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_sequence (guint unit, ...)
{
  WpSpaPod *self;
  va_list args;

  va_start(args, unit);
  self = wp_spa_pod_new_sequence_valist (unit, args);
  va_end(args);

  return self;
}

/*!
 * \brief This is the `va_list` version of wp_spa_pod_new_sequence()
 *
 * \ingroup wpspapod
 * \param unit the unit of the sequence
 * \param args the variable arguments passed to wp_spa_pod_new_sequence()
 * \returns (transfer full): The new spa pod
 */
WpSpaPod *
wp_spa_pod_new_sequence_valist (guint unit, va_list args)
{
  g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_sequence (unit);
  wp_spa_pod_builder_add_valist (b, args);
  return wp_spa_pod_builder_end (b);
}

/*!
 * \brief Checks wether the spa pod is of type none or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type none, FALSE otherwise
 */
gboolean
wp_spa_pod_is_none (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_none (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type boolean or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type boolean, FALSE otherwise
 */
gboolean
wp_spa_pod_is_boolean (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_bool (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type Id or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type Id, FALSE otherwise
 */
gboolean
wp_spa_pod_is_id (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_id (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type int or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type int, FALSE otherwise
 */
gboolean
wp_spa_pod_is_int (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_int (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type long or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type long, FALSE otherwise
 */
gboolean
wp_spa_pod_is_long (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_long (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type float or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type float, FALSE otherwise
 */
gboolean
wp_spa_pod_is_float (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_float (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type double or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type double, FALSE otherwise
 */
gboolean
wp_spa_pod_is_double (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_double (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type string or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type string, FALSE otherwise
 */
gboolean
wp_spa_pod_is_string (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_string (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type bytes or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type bytes, FALSE otherwise
 */
gboolean
wp_spa_pod_is_bytes (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_bytes (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type pointer or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type pointer, FALSE otherwise
 */
gboolean
wp_spa_pod_is_pointer (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_pointer (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type Fd or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type Fd, FALSE otherwise
 */
gboolean
wp_spa_pod_is_fd (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_fd (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type rectangle or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type rectangle, FALSE otherwise
 */
gboolean
wp_spa_pod_is_rectangle (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_rectangle (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type fraction or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type fraction, FALSE otherwise
 */
gboolean
wp_spa_pod_is_fraction (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_fraction (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type array or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type array, FALSE otherwise
 */
gboolean
wp_spa_pod_is_array (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_array (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type choice or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type choice, FALSE otherwise
 */
gboolean
wp_spa_pod_is_choice (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_choice (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type object or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type object, FALSE otherwise
 */
gboolean
wp_spa_pod_is_object (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_object (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type struct or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type struct, FALSE otherwise
 */
gboolean
wp_spa_pod_is_struct (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_struct (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type sequence or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type sequence, FALSE otherwise
 */
gboolean
wp_spa_pod_is_sequence (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_sequence (self->pod);
}

/*!
 * \brief Checks wether the spa pod is of type property or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type property, FALSE otherwise
 */
gboolean
wp_spa_pod_is_property (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_PROPERTY;
}

/*!
 * \brief Checks wether the spa pod is of type control or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \returns TRUE if it is of type control, FALSE otherwise
 */
gboolean
wp_spa_pod_is_control (WpSpaPod *self)
{
  return self->type == WP_SPA_POD_CONTROL;
}

/*!
 * \brief Gets the boolean value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the boolean value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_boolean (WpSpaPod *self, gboolean *value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);
  bool v = FALSE;
  const int res = spa_pod_get_bool (self->pod, &v);
  *value = v ? TRUE : FALSE;
  return res >= 0;
}

/*!
 * \brief Gets the Id value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the Id value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_id (WpSpaPod *self, guint32 *value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);
  uint32_t v = 0;
  const int res = spa_pod_get_id (self->pod, &v);
  *value = v;
  return res >= 0;
}

/*!
 * \brief Gets the int value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the int value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_int (WpSpaPod *self, gint32 *value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);
  return spa_pod_get_int (self->pod, value) >= 0;
}

/*!
 * \brief Gets the long value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the long value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_long (WpSpaPod *self, gint64 *value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);
  return spa_pod_get_long (self->pod, value) >= 0;
}

/*!
 * \brief Gets the float value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the float value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_float (WpSpaPod *self, float *value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);
  return spa_pod_get_float (self->pod, value) >= 0;
}

/*!
 * \brief Gets the double value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the double value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_double (WpSpaPod *self, double *value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);
  return spa_pod_get_double (self->pod, value) >= 0;
}

/*!
 * \brief Gets the string value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the string value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_string (WpSpaPod *self, const char **value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);
  return spa_pod_get_string (self->pod, value) >= 0;
}

/*!
 * \brief Gets the bytes value and its len of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the bytes value
 * \param len (out): the length of the bytes value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_bytes (WpSpaPod *self, gconstpointer *value, guint32 *len)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);
  g_return_val_if_fail (len, FALSE);
  return spa_pod_get_bytes (self->pod, value, len) >= 0;
}

/*!
 * \brief Gets the pointer value and its type name of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the pointer value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_pointer (WpSpaPod *self, gconstpointer *value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);

  guint32 type = 0;
  return spa_pod_get_pointer (self->pod, &type, value) >= 0;
}

/*!
 * \brief Gets the Fd value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value (out): the Fd value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_fd (WpSpaPod *self, gint64 *value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (value, FALSE);
  return spa_pod_get_fd (self->pod, value) >= 0;
}

/*!
 * \brief Gets the rectangle's width and height value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param width (out): the rectangle's width value
 * \param height (out): the rectangle's height value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_rectangle (WpSpaPod *self, guint32 *width, guint32 *height)
{
  g_return_val_if_fail (self, FALSE);
  struct spa_rectangle rectangle = { 0, };
  const gboolean res = spa_pod_get_rectangle (self->pod, &rectangle) >= 0;
  if (width)
    *width = rectangle.width;
  if (height)
    *height = rectangle.height;
  return res;
}

/*!
 * \brief Gets the fractions's numerator and denominator value of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param num (out): the fractions's numerator value
 * \param denom (out): the fractions's denominator value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_fraction (WpSpaPod *self, guint32 *num, guint32 *denom)
{
  g_return_val_if_fail (self, FALSE);
  struct spa_fraction fraction = { 0, };
  const gboolean res = spa_pod_get_fraction (self->pod, &fraction) >= 0;
  if (num)
    *num = fraction.num;
  if (denom)
    *denom = fraction.denom;
  return res;
}

/*!
 * \brief Sets a boolean value in the spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value the boolean value
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_boolean (WpSpaPod *self, gboolean value)
{
  g_return_val_if_fail (wp_spa_pod_is_boolean (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  ((struct spa_pod_bool *)self->pod)->value = value ? true : false;
  return TRUE;
}

/*!
 * \brief Sets an Id value in the spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value the Id value
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_id (WpSpaPod *self, guint32 value)
{
  g_return_val_if_fail (wp_spa_pod_is_id (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  ((struct spa_pod_id *)self->pod)->value = value;
  return TRUE;
}

/*!
 * \brief Sets an int value in the spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value the int value
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_int (WpSpaPod *self, gint32 value)
{
  g_return_val_if_fail (wp_spa_pod_is_int (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  ((struct spa_pod_int *)self->pod)->value = value;
  return TRUE;
}

/*!
 * \brief Sets a long value in the spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value the long value
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_long (WpSpaPod *self, gint64 value)
{
  g_return_val_if_fail (wp_spa_pod_is_long (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  ((struct spa_pod_long *)self->pod)->value = value;
  return TRUE;
}

/*!
 * \brief Sets a float value in the spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value the float value
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_float (WpSpaPod *self, float value)
{
  g_return_val_if_fail (wp_spa_pod_is_float (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  ((struct spa_pod_float *)self->pod)->value = value;
  return TRUE;
}

/*!
 * \brief Sets a double value in the spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value the double value
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_double (WpSpaPod *self, double value)
{
  g_return_val_if_fail (wp_spa_pod_is_double (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  ((struct spa_pod_double *)self->pod)->value = value;
  return TRUE;
}

/*!
 * \brief Sets a pointer value with its type name in the spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param type_name the name of the type of the pointer
 * \param value the pointer value
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_pointer (WpSpaPod *self, const char *type_name,
    gconstpointer value)
{
  WpSpaType type = wp_spa_type_from_name (type_name);

  g_return_val_if_fail (wp_spa_pod_is_pointer (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  g_return_val_if_fail (type != WP_SPA_TYPE_INVALID, FALSE);

  ((struct spa_pod_pointer *)self->pod)->body.type = type;
  ((struct spa_pod_pointer *)self->pod)->body.value = value;
  return TRUE;
}

/*!
 * \brief Sets a Fd value in the spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param value the Fd value
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_fd (WpSpaPod *self, gint64 value)
{
  g_return_val_if_fail (wp_spa_pod_is_fd (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  ((struct spa_pod_fd *)self->pod)->value = value;
  return TRUE;
}

/*!
 * \brief Sets the width and height values of a rectangle in the spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param width the width value of the rectangle
 * \param height the height value of the rectangle
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_rectangle (WpSpaPod *self, guint32 width, guint32 height)
{
  g_return_val_if_fail (wp_spa_pod_is_rectangle (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  ((struct spa_pod_rectangle *)self->pod)->value.width = width;
  ((struct spa_pod_rectangle *)self->pod)->value.height = height;
  return TRUE;
}

/*!
 * \brief Sets the numerator and denominator values of a fraction in the
 * spa pod object.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param num the numerator value of the farction
 * \param denom the denominator value of the fraction
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_fraction (WpSpaPod *self, guint32 num, guint32 denom)
{
  g_return_val_if_fail (wp_spa_pod_is_fraction (self), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
  ((struct spa_pod_fraction *)self->pod)->value.num = num;
  ((struct spa_pod_fraction *)self->pod)->value.denom = denom;
  return TRUE;
}

/*!
 * \brief Sets the value of a spa pod object in the current spa pod object.
 * The spa pod objects must be of the same value.
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param pod the pod with the value to be set
 * \returns TRUE if the value could be set, FALSE othewrise.
 */
gboolean
wp_spa_pod_set_pod (WpSpaPod *self, WpSpaPod *pod)
{
  g_return_val_if_fail (self->type == pod->type, FALSE);
  g_return_val_if_fail (SPA_POD_TYPE (self->pod) == SPA_POD_TYPE (pod->pod), FALSE);
  g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);

  switch (SPA_POD_TYPE (self->pod)) {
  case SPA_TYPE_None:
    break;
  case SPA_TYPE_Bool:
    ((struct spa_pod_bool *)self->pod)->value = ((struct spa_pod_bool *)pod->pod)->value;
    break;
  case SPA_TYPE_Id:
    ((struct spa_pod_id *)self->pod)->value = ((struct spa_pod_id *)pod->pod)->value;
    break;
  case SPA_TYPE_Int:
    ((struct spa_pod_int *)self->pod)->value = ((struct spa_pod_int *)pod->pod)->value;
    break;
  case SPA_TYPE_Long:
    ((struct spa_pod_long *)self->pod)->value = ((struct spa_pod_long *)pod->pod)->value;
    break;
  case SPA_TYPE_Float:
    ((struct spa_pod_float *)self->pod)->value = ((struct spa_pod_float *)pod->pod)->value;
    break;
  case SPA_TYPE_Double:
    ((struct spa_pod_double *)self->pod)->value = ((struct spa_pod_double *)pod->pod)->value;
    break;
  case SPA_TYPE_Pointer:
    ((struct spa_pod_pointer *)self->pod)->body.type = ((struct spa_pod_pointer *)pod->pod)->body.type;
    ((struct spa_pod_pointer *)self->pod)->body.value = ((struct spa_pod_pointer *)pod->pod)->body.value;
    break;
  case SPA_TYPE_Fd:
    ((struct spa_pod_fd *)self->pod)->value = ((struct spa_pod_fd *)pod->pod)->value;
    break;
  case SPA_TYPE_Rectangle:
    ((struct spa_pod_rectangle *)self->pod)->value.width = ((struct spa_pod_rectangle *)pod->pod)->value.width;
    ((struct spa_pod_rectangle *)self->pod)->value.height = ((struct spa_pod_rectangle *)pod->pod)->value.height;
    break;
  case SPA_TYPE_Fraction:
    ((struct spa_pod_fraction *)self->pod)->value.num = ((struct spa_pod_fraction *)pod->pod)->value.num;
    ((struct spa_pod_fraction *)self->pod)->value.denom = ((struct spa_pod_fraction *)pod->pod)->value.denom;
    break;
  default:
    g_return_val_if_fail (self->pod->size >= pod->pod->size, FALSE);
    memcpy (SPA_MEMBER (self->pod, sizeof (struct spa_pod), void),
        SPA_MEMBER (pod->pod, sizeof (struct spa_pod), void),
        SPA_MIN (self->pod->size, pod->pod->size));
    self->pod->type = pod->pod->type;
    self->pod->size = pod->pod->size;
    break;
  }

  switch (self->type) {
  case WP_SPA_POD_PROPERTY:
    self->static_pod.data_property.table = pod->static_pod.data_property.table;
    self->static_pod.data_property.key = pod->static_pod.data_property.key;
    self->static_pod.data_property.flags = pod->static_pod.data_property.flags;
    break;
  case WP_SPA_POD_CONTROL:
    self->static_pod.data_control.offset = pod->static_pod.data_control.offset;
    self->static_pod.data_control.type = pod->static_pod.data_control.type;
    break;
  case WP_SPA_POD_REGULAR:
  default:
    break;
  }

  return TRUE;
}

/*!
 * \brief Checks whether two spa pod objects have the same value or not
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param pod the pod with the value to be compared with
 * \returns TRUE if both spa pod objects have the same values, FALSE othewrise.
 */
gboolean
wp_spa_pod_equal (WpSpaPod *self, WpSpaPod *pod)
{
  g_return_val_if_fail (self != NULL, FALSE);
  g_return_val_if_fail (pod != NULL, FALSE);

  if (self->type != pod->type)
    return FALSE;
  if (SPA_POD_TYPE (self->pod) != SPA_POD_TYPE (pod->pod))
    return FALSE;

  switch (SPA_POD_TYPE (self->pod)) {
  case SPA_TYPE_None:
    break;
  case SPA_TYPE_Bool:
    if (((struct spa_pod_bool *)self->pod)->value != ((struct spa_pod_bool *)pod->pod)->value)
      return FALSE;
    break;
  case SPA_TYPE_Id:
    if (((struct spa_pod_id *)self->pod)->value != ((struct spa_pod_id *)pod->pod)->value)
      return FALSE;
    break;
  case SPA_TYPE_Int:
    if (((struct spa_pod_int *)self->pod)->value != ((struct spa_pod_int *)pod->pod)->value)
      return FALSE;
    break;
  case SPA_TYPE_Long:
    if (((struct spa_pod_long *)self->pod)->value != ((struct spa_pod_long *)pod->pod)->value)
      return FALSE;
    break;
  case SPA_TYPE_Float:
    if (((struct spa_pod_float *)self->pod)->value != ((struct spa_pod_float *)pod->pod)->value)
      return FALSE;
    break;
  case SPA_TYPE_Double:
    if (((struct spa_pod_double *)self->pod)->value != ((struct spa_pod_double *)pod->pod)->value)
      return FALSE;
    break;
  case SPA_TYPE_Pointer:
    if (((struct spa_pod_pointer *)self->pod)->body.type != ((struct spa_pod_pointer *)pod->pod)->body.type ||
        ((struct spa_pod_pointer *)self->pod)->body.value != ((struct spa_pod_pointer *)pod->pod)->body.value)
      return FALSE;
    break;
  case SPA_TYPE_Fd:
    if (((struct spa_pod_fd *)self->pod)->value != ((struct spa_pod_fd *)pod->pod)->value)
      return FALSE;
    break;
  case SPA_TYPE_Rectangle:
    if (((struct spa_pod_rectangle *)self->pod)->value.width != ((struct spa_pod_rectangle *)pod->pod)->value.width ||
        ((struct spa_pod_rectangle *)self->pod)->value.height != ((struct spa_pod_rectangle *)pod->pod)->value.height)
      return FALSE;
    break;
  case SPA_TYPE_Fraction:
    if (((struct spa_pod_fraction *)self->pod)->value.num != ((struct spa_pod_fraction *)pod->pod)->value.num ||
        ((struct spa_pod_fraction *)self->pod)->value.denom != ((struct spa_pod_fraction *)pod->pod)->value.denom)
      return FALSE;
    break;
  default:
    if (self->pod->size != pod->pod->size ||
        memcmp (SPA_MEMBER (self->pod, sizeof (struct spa_pod), void),
            SPA_MEMBER (pod->pod, sizeof (struct spa_pod), void),
            self->pod->size) != 0)
      return FALSE;
    break;
  }

  switch (self->type) {
  case WP_SPA_POD_PROPERTY:
    if (self->static_pod.data_property.table != pod->static_pod.data_property.table ||
        self->static_pod.data_property.key != pod->static_pod.data_property.key ||
        self->static_pod.data_property.flags != pod->static_pod.data_property.flags)
      return FALSE;
    break;
  case WP_SPA_POD_CONTROL:
    if (self->static_pod.data_control.offset != pod->static_pod.data_control.offset ||
        self->static_pod.data_control.type != pod->static_pod.data_control.type)
      return FALSE;
    break;
  case WP_SPA_POD_REGULAR:
  default:
    break;
  }

  return TRUE;
}

/*!
 * \brief Gets the object properties values of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param id_name (out): the id name of the object,
 * \param ... (out): the list of the object properties values, followed by NULL
 * \returns TRUE if the object properties values were obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_object (WpSpaPod *self, const char **id_name, ...)
{
  va_list args;
  gboolean res;
  va_start (args, id_name);
  res = wp_spa_pod_get_object_valist (self, id_name, args);
  va_end (args);
  return res;
}

/*!
 * \brief This is the `va_list` version of wp_spa_pod_get_object()
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param id_name (out): the id name of the object
 * \param args (out): the variable arguments passed to wp_spa_pod_get_object()
 * \returns TRUE if the object properties values were obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_object_valist (WpSpaPod *self, const char **id_name, va_list args)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (wp_spa_pod_is_object (self), FALSE);
  g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_object (self, id_name);
  const gboolean res = wp_spa_pod_parser_get_valist (p, args);
  wp_spa_pod_parser_end (p);
  return res;
}

/*!
 * \brief Gets the struct's values of a spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param ... (out): the list of the struct values, followed by NULL
 * \returns TRUE if the struct values were obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_struct (WpSpaPod *self, ...)
{
  va_list args;
  gboolean res;
  va_start (args, self);
  res = wp_spa_pod_get_struct_valist (self, args);
  va_end (args);
  return res;
}

/*!
 * \brief This is the `va_list` version of wp_spa_pod_get_struct()
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param args (out): the variable arguments passed to wp_spa_pod_get_struct()
 * \returns TRUE if the struct values were obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_struct_valist (WpSpaPod *self, va_list args)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (wp_spa_pod_is_struct (self), FALSE);
  g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_struct (self);
  const gboolean res = wp_spa_pod_parser_get_valist (p, args);
  wp_spa_pod_parser_end (p);
  return res;
}

/*!
 * \brief Gets the name, flags and spa pod value of a spa pod property
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param key (out) (optional): the name of the property
 * \param value (out) (optional): the spa pod value of the property
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_property (WpSpaPod *self, const char **key,
    WpSpaPod **value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (wp_spa_pod_is_property (self), FALSE);

  if (key) {
    WpSpaIdValue key_val = wp_spa_id_table_find_value (
        self->static_pod.data_property.table,
        self->static_pod.data_property.key);
    if (key_val) {
      *key = wp_spa_id_value_short_name (key_val);
    } else {
      g_snprintf (self->static_pod.data_property.id_key_name,
          WP_SPA_POD_ID_PROPERTY_NAME_MAX, "id-%08x",
          self->static_pod.data_property.key);
      *key = self->static_pod.data_property.id_key_name;
    }
  }
  if (value)
    *value = wp_spa_pod_new_wrap (self->pod);

  return TRUE;
}

/*!
 * \brief Gets the offset, type name and spa pod value of a spa pod control
 *
 * \ingroup wpspapod
 * \param self the spa pod object
 * \param offset (out) (optional): the offset of the control
 * \param ctl_type (out) (optional): the control type (Properties, Midi, ...)
 * \param value (out) (optional): the spa pod value of the control
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_get_control (WpSpaPod *self, guint32 *offset, const char **ctl_type,
    WpSpaPod **value)
{
  g_return_val_if_fail (self, FALSE);
  g_return_val_if_fail (wp_spa_pod_is_control (self), FALSE);

  if (offset)
    *offset = self->static_pod.data_control.offset;
  if (ctl_type) {
    WpSpaIdValue type_val = wp_spa_id_value_from_number (
        SPA_TYPE_INFO_Control, self->static_pod.data_control.type);
    g_return_val_if_fail (type_val != NULL, FALSE);
    *ctl_type = wp_spa_id_value_short_name (type_val);
  }
  if (value)
    *value = wp_spa_pod_new_wrap (self->pod);

  return TRUE;
}

/*!
 * \brief Gets the child of a spa pod choice object
 *
 * \ingroup wpspapod
 * \param self a spa pod choice object
 * \returns (transfer full): the child of the spa pod choice object
 */
WpSpaPod *
wp_spa_pod_get_choice_child (WpSpaPod *self)
{
  g_return_val_if_fail (wp_spa_pod_is_choice (self), NULL);
  return wp_spa_pod_new_wrap (SPA_POD_CHOICE_CHILD (self->pod));
}

/*!
 * \brief Gets the child of a spa pod array object
 *
 * \ingroup wpspapod
 * \param self a spa pod choice object
 * \returns (transfer full): the child of the spa pod array object
 */
WpSpaPod *
wp_spa_pod_get_array_child (WpSpaPod *self)
{
  g_return_val_if_fail (wp_spa_pod_is_array (self), NULL);
  return wp_spa_pod_new_wrap (SPA_POD_ARRAY_CHILD (self->pod));
}

/*!
 * \brief Fixates choices in an object pod so that they only have one value
 *
 * \ingroup wpspapod
 * \param self a spa pod
 * \returns TRUE if the pod was an object and it went through the fixation
 *   procedure, FALSE otherwise
 */
gboolean
wp_spa_pod_fixate (WpSpaPod *self)
{
  g_return_val_if_fail (self, FALSE);

  if (wp_spa_pod_is_object (self))
    return spa_pod_object_fixate ((struct spa_pod_object *) self->pod) == 0;
  return FALSE;
}

/*!
 * \brief Returns the intersection between \a self and \a filter
 *
 * This is typically used to intersect pods that describe formats, in order to
 * find a common format that is accceptable by both sides. For that purpose,
 * this is not exactly an intersection with its mathematical meaning.
 * Object properties can be thought of as format constraints. When one side does
 * not specify a specific property, it is considered to accept any value for it,
 * so the value of this property from the other side is added in the result.
 *
 * Both input pods are left unmodified after this function call.
 *
 * If NULL is passed in the \a filter, this function just copies \a self and
 * returns the copy.
 *
 * \param self the first pod
 * \param filter (nullable): the second pod
 * \return (transfer full) (nullable): a new pod that contains the intersection
 *   between \a self and \a filter, or NULL if the intersection was not possible
 *   to make
 */
WpSpaPod *
wp_spa_pod_filter (WpSpaPod *self, WpSpaPod *filter)
{
  char buffer[1024];
  struct spa_pod_builder b = SPA_POD_BUILDER_INIT(&buffer, sizeof(buffer));
  struct spa_pod *result = NULL;

  g_return_val_if_fail (self, NULL);

  if (spa_pod_filter(&b, &result, self->pod, filter ? filter->pod : NULL) >= 0)
    return wp_spa_pod_new_wrap_copy (result);
  return NULL;
}

/*!
 * \brief Increases the reference count of a spa pod builder
 *
 * \ingroup wpspapod
 * \param self a spa pod builder object
 * \returns (transfer full): \a self with an additional reference count on it
 */
WpSpaPodBuilder *
wp_spa_pod_builder_ref (WpSpaPodBuilder *self)
{
  return (WpSpaPodBuilder *) g_rc_box_acquire ((gpointer) self);
}

static void
wp_spa_pod_builder_free (WpSpaPodBuilder *self)
{
  g_clear_pointer (&self->buf, g_free);
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 *
 * \ingroup wpspapod
 * \param self (transfer full): a spa pod builder object
 */
void
wp_spa_pod_builder_unref (WpSpaPodBuilder *self)
{
  g_rc_box_release_full (self, (GDestroyNotify) wp_spa_pod_builder_free);
}

/*!
 * \brief Creates a spa pod builder of type array
 * \ingroup wpspapod
 * \returns (transfer full): the new spa pod builder
 */
WpSpaPodBuilder *
wp_spa_pod_builder_new_array (void)
{
  WpSpaPodBuilder *self = wp_spa_pod_builder_new (
      WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE, SPA_TYPE_Array);
  spa_pod_builder_push_array (&self->builder, &self->frame);
  return self;
}

/*!
 * \brief Creates a spa pod builder of type choice
 *
 * \ingroup wpspapod
 * \param choice_type the name of the choice type ("Range", "Step", ...)
 * \returns (transfer full): the new spa pod builder
 */
WpSpaPodBuilder *
wp_spa_pod_builder_new_choice (const char *choice_type)
{
  WpSpaPodBuilder *self = NULL;
  WpSpaIdValue type = wp_spa_id_value_from_short_name (
      SPA_TYPE_INFO_Choice, choice_type);
  g_return_val_if_fail (type != NULL, NULL);

  /* Construct the builder */
  self = wp_spa_pod_builder_new (WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE,
      SPA_TYPE_Choice);

  /* Push the array */
  spa_pod_builder_push_choice (&self->builder, &self->frame,
      wp_spa_id_value_number (type), 0);

  return self;
}

/*!
 * \brief Creates a spa pod builder of type object
 *
 * \ingroup wpspapod
 * \param type_name the type name of the object type
 * \param id_name the Id name of the object
 * \returns (transfer full): the new spa pod builder
 */
WpSpaPodBuilder *
wp_spa_pod_builder_new_object (const char *type_name, const char *id_name)
{
  WpSpaPodBuilder *self = NULL;
  WpSpaType type;
  WpSpaIdTable table;
  WpSpaIdValue id;

  /* Find the type */
  type = wp_spa_type_from_name (type_name);
  g_return_val_if_fail (wp_spa_type_is_object (type), NULL);

  /* Find the id */
  table = wp_spa_type_get_object_id_values_table (type);
  g_return_val_if_fail (table != NULL, NULL);

  id = wp_spa_id_table_find_value_from_short_name (table, id_name);
  g_return_val_if_fail (id != NULL, NULL);

  /* Construct the builder */
  self = wp_spa_pod_builder_new (WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE, type);

  /* Push the object */
  spa_pod_builder_push_object (&self->builder, &self->frame, type,
      wp_spa_id_value_number (id));

  return self;
}

/*!
 * \brief Creates a spa pod builder of type struct
 *
 * \ingroup wpspapod
 * \returns (transfer full): the new spa pod builder
 */
WpSpaPodBuilder *
wp_spa_pod_builder_new_struct (void)
{
  WpSpaPodBuilder *self = NULL;
  self = wp_spa_pod_builder_new (WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE,
      SPA_TYPE_Struct);
  spa_pod_builder_push_struct (&self->builder, &self->frame);
  return self;
}

/*!
 * \brief Creates a spa pod builder of type sequence
 *
 * \ingroup wpspapod
 * \returns (transfer full): the new spa pod builder
 */
WpSpaPodBuilder *
wp_spa_pod_builder_new_sequence (guint unit)
{
  WpSpaPodBuilder *self = NULL;
  self = wp_spa_pod_builder_new (WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE,
      SPA_TYPE_Sequence);
  spa_pod_builder_push_sequence (&self->builder, &self->frame, unit);
  return self;
}

/*!
 * \brief Adds a none value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 */

void
wp_spa_pod_builder_add_none (WpSpaPodBuilder *self)
{
  spa_pod_builder_none (&self->builder);
}

/*!
 * \brief Adds a boolean value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param value the boolean value
 */
void
wp_spa_pod_builder_add_boolean (WpSpaPodBuilder *self, gboolean value)
{
  spa_pod_builder_bool (&self->builder, value ? true : false);
}

/*!
 * \brief Adds a Id value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param value the Id value
 */
void
wp_spa_pod_builder_add_id (WpSpaPodBuilder *self, guint32 value)
{
  spa_pod_builder_id (&self->builder, value);
}

/*!
 * \brief Adds a int value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param value the int value
 */
void
wp_spa_pod_builder_add_int (WpSpaPodBuilder *self, gint32 value)
{
  spa_pod_builder_int (&self->builder, value);
}

/*!
 * \brief Adds a long value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param value the long value
 */
void
wp_spa_pod_builder_add_long (WpSpaPodBuilder *self, gint64 value)
{
  spa_pod_builder_long (&self->builder, value);
}

/*!
 * \brief Adds a float value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param value the float value
 */
void
wp_spa_pod_builder_add_float (WpSpaPodBuilder *self, float value)
{
  spa_pod_builder_float (&self->builder, value);
}

/*!
 * \brief Adds a double value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param value the double value
 */
void
wp_spa_pod_builder_add_double (WpSpaPodBuilder *self, double value)
{
  spa_pod_builder_double (&self->builder, value);
}

/*!
 * \brief Adds a string value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param value the string value
 */
void
wp_spa_pod_builder_add_string (WpSpaPodBuilder *self, const char *value)
{
  spa_pod_builder_string (&self->builder, value);
}

/*!
 * \brief Adds a bytes value with its length into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param value the bytes value
 * \param len the length of the bytes value
 */
void
wp_spa_pod_builder_add_bytes (WpSpaPodBuilder *self, gconstpointer value,
    guint32 len)
{
  spa_pod_builder_bytes (&self->builder, value, len);
}

/*!
 * \brief Adds a pointer value with its type name into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param type_name the type name that the pointer points to
 * \param value the pointer vaue
 */
void
wp_spa_pod_builder_add_pointer (WpSpaPodBuilder *self, const char *type_name,
    gconstpointer value)
{
  WpSpaType type = wp_spa_type_from_name (type_name);
  g_return_if_fail (wp_spa_type_parent (type) == SPA_TYPE_Pointer);
  spa_pod_builder_pointer (&self->builder, type, value);
}

/*!
 * \brief Adds a Fd value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param value the Fd value
 */
void
wp_spa_pod_builder_add_fd (WpSpaPodBuilder *self, gint64 value)
{
  spa_pod_builder_fd (&self->builder, value);
}

/*!
 * \brief Adds the width and height values of a rectangle into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param width the width value of the rectangle
 * \param height the height value of the rectangle
 */
void
wp_spa_pod_builder_add_rectangle (WpSpaPodBuilder *self, guint32 width,
    guint32 height)
{
  spa_pod_builder_rectangle (&self->builder, width, height);
}

/*!
 * \brief Adds the numerator and denominator values of a fraction into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param num the numerator value of the fraction
 * \param denom the denominator value of the fraction
 */
void
wp_spa_pod_builder_add_fraction (WpSpaPodBuilder *self, guint32 num,
    guint32 denom)
{
  spa_pod_builder_fraction (&self->builder, num, denom);
}

/*!
 * \brief Adds a pod value into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param pod the pod value
 */
void
wp_spa_pod_builder_add_pod (WpSpaPodBuilder *self, WpSpaPod *pod)
{
  spa_pod_builder_primitive (&self->builder, pod->pod);
}

/*!
 * \brief Adds a property into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param key the name of the property
 */
void
wp_spa_pod_builder_add_property (WpSpaPodBuilder *self, const char *key)
{
  guint key_id;
  if (g_str_has_prefix (key, "id-")) {
    g_return_if_fail (sscanf (key, "id-%08x", &key_id) == 1);
  } else {
    WpSpaIdTable table = wp_spa_type_get_values_table (self->type);
    WpSpaIdValue id = wp_spa_id_table_find_value_from_short_name (table, key);
    g_return_if_fail (id != NULL);
    key_id = wp_spa_id_value_number (id);
  }
  spa_pod_builder_prop (&self->builder, key_id, 0);
}

/*!
 * \brief Adds a property into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param id the id of the property
 */
void
wp_spa_pod_builder_add_property_id (WpSpaPodBuilder *self, guint32 id)
{
  spa_pod_builder_prop (&self->builder, id, 0);
}

/*!
 * \brief Adds a control into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param offset the offset of the control
 * \param ctl_type the type name of the control
 */
void
wp_spa_pod_builder_add_control (WpSpaPodBuilder *self, guint32 offset,
    const char *ctl_type)
{
  WpSpaIdValue id = wp_spa_id_value_from_short_name (
      SPA_TYPE_INFO_Control, ctl_type);
  g_return_if_fail (id != NULL);
  spa_pod_builder_control (&self->builder, offset, wp_spa_id_value_number (id));
}

/*!
 * \brief Adds a list of values into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param ... a list of additional values, followed by NULL
 */
void
wp_spa_pod_builder_add (WpSpaPodBuilder *self, ...)
{
  va_list args;
  va_start (args, self);
  wp_spa_pod_builder_add_valist (self, args);
  va_end (args);
}

/*!
 * \brief Adds a list of values into the builder
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \param args the variable arguments passed to wp_spa_pod_builder_add()
 */
void
wp_spa_pod_builder_add_valist (WpSpaPodBuilder *self, va_list args)
{
  WpSpaIdTable table = wp_spa_type_get_values_table (self->type);

  do {
    WpSpaIdValue key = NULL;
    const char *format;
    int n_values = 1;
    struct spa_pod_frame f;
    gboolean choice;

    if (wp_spa_type_is_object (self->type)) {
      guint key_id;
      const char *key_name = va_arg(args, const char *);
      if (!key_name)
        return;
      if (g_str_has_prefix (key_name, "id-")) {
        g_return_if_fail (sscanf (key_name, "id-%08x", &key_id) == 1);
      } else {
        key = wp_spa_id_table_find_value_from_short_name (table, key_name);
        g_return_if_fail (key != NULL);
        key_id = wp_spa_id_value_number (key);
      }
      spa_pod_builder_prop (&self->builder, key_id, 0);
    }
    else if (self->type == SPA_TYPE_Sequence) {
      guint32 offset = va_arg(args, uint32_t);
      if (offset == 0)
        return;
      const char *control_name = va_arg(args, const char *);
      if (!control_name)
        return;
      WpSpaIdValue type = wp_spa_id_value_from_short_name (
          SPA_TYPE_INFO_Control, control_name);
      g_return_if_fail (type != NULL);

      spa_pod_builder_control (&self->builder, offset,
          wp_spa_id_value_number (type));
    }

    if ((format = va_arg(args, const char *)) == NULL)
      break;

    choice = *format == '?';
    if (choice) {
      uint32_t type = spa_choice_from_id (*++format);
      if (*format != '\0')
        format++;
      spa_pod_builder_push_choice (&self->builder, &f, type, 0);
      n_values = va_arg(args, int);
    }
    while (n_values-- > 0) {
      switch (*format) {
      case 'P':  /* Pod */
      case 'V':  /* Choice */
      case 'O':  /* Object */
      case 'T': { /* Struct */
        WpSpaPod *arg = va_arg(args, WpSpaPod *);
        if (arg == NULL)
          spa_pod_builder_none (&self->builder);
        else
          spa_pod_builder_primitive (&self->builder, arg->pod);
        break;
      }
      case 'K': { /* Id as string - WirePlumber extension */
        const char * id = va_arg(args, const char *);
        if (key) {
          WpSpaIdTable id_table = NULL;
          wp_spa_id_value_get_value_type (key, &id_table);
          WpSpaIdValue id_val =
              wp_spa_id_table_find_value_from_short_name (id_table, id);
          spa_pod_builder_id (&self->builder, wp_spa_id_value_number (id_val));
        }
        break;
      }
      case 'b':
        spa_pod_builder_bool(&self->builder,
            va_arg(args, gboolean) ? true : false);
        break;
      default:
        SPA_POD_BUILDER_COLLECT(&self->builder, *format, args);
        break;
      }
    }

    if (choice)
      spa_pod_builder_pop (&self->builder, &f);

  } while (TRUE);
}

/*!
 * \brief Ends the builder process and returns the constructed spa pod object
 *
 * \ingroup wpspapod
 * \param self the spa pod builder object
 * \returns (transfer full): the constructed spa pod object
 */
WpSpaPod *
wp_spa_pod_builder_end (WpSpaPodBuilder *self)
{
  WpSpaPod *ret = NULL;

  /* Construct the pod */
  ret = g_slice_new0 (WpSpaPod);
  g_ref_count_init (&ret->ref);
  ret->type = WP_SPA_POD_REGULAR;
  ret->pod = spa_pod_builder_pop (&self->builder, &self->frame);
  ret->builder = wp_spa_pod_builder_ref (self);

  /* Also copy the specific object type if it is an object */
  if (spa_pod_is_object (ret->pod))
    ret->static_pod.data_property.table =
        wp_spa_type_get_values_table (ret->builder->type);

  return ret;
}

/*!
 * \brief Increases the reference count of a spa pod parser
 * \ingroup wpspapod
 * \param self a spa pod sparser object
 * \returns (transfer full): \a self with an additional reference count on it
 */
WpSpaPodParser *
wp_spa_pod_parser_ref (WpSpaPodParser *self)
{
  return (WpSpaPodParser *) g_rc_box_acquire ((gpointer) self);
}

static void
wp_spa_pod_parser_free (WpSpaPodParser *self)
{
  self->pod = NULL;
}

/*!
 * \brief Decreases the reference count on \a self and frees it when the ref
 * count reaches zero.
 *
 * \ingroup wpspapod
 * \param self (transfer full): a spa pod parser object
 */
void
wp_spa_pod_parser_unref (WpSpaPodParser *self)
{
  g_rc_box_release_full (self, (GDestroyNotify) wp_spa_pod_parser_free);
}

static WpSpaPodParser *
wp_spa_pod_parser_new (WpSpaPod *pod, guint32 type)
{
  WpSpaPodParser *self = g_rc_box_new0 (WpSpaPodParser);
  self->type = type;
  self->pod = pod;
  spa_pod_parser_pod (&self->parser, self->pod->pod);
  return self;
}

/*!
 * \brief Creates an object spa pod parser. The \a pod object must be valid for
 * the entire life-cycle of the returned parser.
 *
 * \ingroup wpspapod
 * \param pod the object spa pod to parse
 * \param id_name the Id name of the object
 * \returns (transfer full): The new spa pod parser
 */
WpSpaPodParser *
wp_spa_pod_parser_new_object (WpSpaPod *pod, const char **id_name)
{
  WpSpaPodParser *self = NULL;
  WpSpaType type = wp_spa_pod_get_spa_type (pod);
  guint32 id = SPA_ID_INVALID;

  g_return_val_if_fail (wp_spa_pod_is_object (pod), NULL);

  self = wp_spa_pod_parser_new (pod, type);
  spa_pod_parser_push_object (&self->parser, &self->frame, type, &id);
  if (id_name) {
    WpSpaIdTable table = wp_spa_type_get_object_id_values_table (type);
    *id_name = wp_spa_id_value_short_name (
        wp_spa_id_table_find_value (table, id));
  }
  return self;
}

/*!
 * \brief Creates an struct spa pod parser. The \a pod object must be valid for
 * the entire life-cycle of the returned parser.
 *
 * \ingroup wpspapod
 * \param pod the struct spa pod to parse
 * \returns (transfer full): The new spa pod parser
 */
WpSpaPodParser *
wp_spa_pod_parser_new_struct (WpSpaPod *pod)
{
  WpSpaPodParser *self = NULL;

  g_return_val_if_fail (wp_spa_pod_is_struct (pod), NULL);

  self = wp_spa_pod_parser_new (pod, SPA_TYPE_Struct);
  spa_pod_parser_push_struct (&self->parser, &self->frame);
  return self;
}

/*!
 * \brief Gets the boolean value from a spa pod parser
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the boolean value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_boolean (WpSpaPodParser *self, gboolean *value)
{
  g_return_val_if_fail (value, FALSE);
  bool v = FALSE;
  gboolean res = spa_pod_parser_get_bool (&self->parser, &v) >= 0;
  *value = v ? TRUE : FALSE;
  return res;
}

/*!
 * \brief Gets the Id value from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the Id value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_id (WpSpaPodParser *self, guint32 *value)
{
  g_return_val_if_fail (value, FALSE);
  return spa_pod_parser_get_id (&self->parser, value) >= 0;
}

/*!
 * \brief Gets the int value from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the int value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_int (WpSpaPodParser *self, gint32 *value)
{
  g_return_val_if_fail (value, FALSE);
  return spa_pod_parser_get_int (&self->parser, value) >= 0;
}

/*!
 * \brief Gets the long value from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the long value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_long (WpSpaPodParser *self, gint64 *value)
{
  g_return_val_if_fail (value, FALSE);
  return spa_pod_parser_get_long (&self->parser, value) >= 0;
}

/*!
 * \brief Gets the float value from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the float value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_float (WpSpaPodParser *self, float *value)
{
  g_return_val_if_fail (value, FALSE);
  return spa_pod_parser_get_float (&self->parser, value) >= 0;
}

/*!
 * \brief Gets the double value from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the double value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_double (WpSpaPodParser *self, double *value)
{
  g_return_val_if_fail (value, FALSE);
  return spa_pod_parser_get_double (&self->parser, value) >= 0;
}

/*!
 * \brief Gets the string value from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the string value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_string (WpSpaPodParser *self, const char **value)
{
  g_return_val_if_fail (value, FALSE);
  return spa_pod_parser_get_string (&self->parser, value) >= 0;
}

/*!
 * \brief Gets the bytes value and its length from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the bytes value
 * \param len (out): the length of the bytes value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_bytes (WpSpaPodParser *self, gconstpointer *value,
    guint32 *len)
{
  return spa_pod_parser_get_bytes (&self->parser, value, len) >= 0;
}

/*!
 * \brief Gets the pointer value and its type name from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the pointer value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_pointer (WpSpaPodParser *self, gconstpointer *value)
{
  g_return_val_if_fail (value, FALSE);
  guint32 type = 0;
  return spa_pod_parser_get_pointer (&self->parser, &type, value) >= 0;
}

/*!
 * \brief Gets the Fd value from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param value (out): the Fd value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_fd (WpSpaPodParser *self, gint64 *value)
{
  g_return_val_if_fail (value, FALSE);
  return spa_pod_parser_get_fd (&self->parser, value) >= 0;
}

/*!
 * \brief Gets the rectangle's width and height value from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param width (out): the rectangle's width value
 * \param height (out): the rectangle's height value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_rectangle (WpSpaPodParser *self, guint32 *width,
    guint32 *height)
{
  struct spa_rectangle r = { 0, };
  gboolean res = spa_pod_parser_get_rectangle (&self->parser, &r) >= 0;
  if (width)
    *width = r.width;
  if (height)
    *height = r.height;
  return res;
}

/*!
 * \brief Gets the fractions's numerator and denominator value from a spa pod
 * parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param num (out): the fractions's numerator value
 * \param denom (out): the fractions's denominator value
 * \returns TRUE if the value was obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_fraction (WpSpaPodParser *self, guint32 *num,
    guint32 *denom)
{
  struct spa_fraction f = { 0, };
  gboolean res = spa_pod_parser_get_fraction (&self->parser, &f) >= 0;
  if (num)
    *num = f.num;
  if (denom)
    *denom = f.denom;
  return res;
}

/*!
 * \brief Gets the spa pod value from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \returns (transfer full): The spa pod value or NULL if it could not be
 * obtained
 */
WpSpaPod *
wp_spa_pod_parser_get_pod (WpSpaPodParser *self)
{
  struct spa_pod *p = NULL;
  gboolean res = spa_pod_parser_get_pod (&self->parser, &p) >= 0;
  if (!res || !p)
    return NULL;

  return wp_spa_pod_new_wrap (p);
}

/*!
 * \brief Gets a list of values from a spa pod parser object
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param ... (out): a list of values to get, followed by NULL
 * \returns TRUE if the values were obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get (WpSpaPodParser *self, ...)
{
  gboolean res;
  va_list args;

  va_start (args, self);
  res = wp_spa_pod_parser_get_valist (self, args);
  va_end (args);

  return res;
}

/*!
 * \brief This is the `va_list` version of wp_spa_pod_parser_get()
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 * \param args the variable arguments passed to wp_spa_pod_parser_get()
 * \returns TRUE if the values were obtained, FALSE otherwise
 */
gboolean
wp_spa_pod_parser_get_valist (WpSpaPodParser *self, va_list args)
{
  const struct spa_pod_prop *prop = NULL;
  WpSpaIdTable table = wp_spa_type_get_values_table (self->type);

  do {
    WpSpaIdValue key = NULL;
    bool optional;
    const struct spa_pod *pod = NULL;
    const char *format;

    if (wp_spa_type_is_object (self->type)) {
      guint key_id;
      const struct spa_pod_object *object;
      const char *key_name = va_arg(args, const char *);
      if (!key_name)
        break;

      if (g_str_has_prefix (key_name, "id-")) {
        g_return_val_if_fail (sscanf (key_name, "id-%08x", &key_id) == 1, FALSE);
      } else {
        key = wp_spa_id_table_find_value_from_short_name (table, key_name);
        g_return_val_if_fail (key != NULL, FALSE);
        key_id = wp_spa_id_value_number (key);
      }

      object = (const struct spa_pod_object *)spa_pod_parser_frame
            (&self->parser, &self->frame);
      prop = spa_pod_object_find_prop (object, prop, key_id);
      pod = prop ? &prop->value : NULL;
    }

    if ((format = va_arg(args, char *)) == NULL)
      break;

    if (self->type == SPA_TYPE_Struct)
      pod = spa_pod_parser_next (&self->parser);

    if ((optional = (*format == '?')))
      format++;

    if (!pod || !spa_pod_parser_can_collect (pod, *format)) {
      if (!optional)
        return FALSE;

      SPA_POD_PARSER_SKIP (*format, args);
    } else {
      if (pod->type == SPA_TYPE_Choice && *format != 'V' &&
          SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_None)
        pod = SPA_POD_CHOICE_CHILD(pod);

      switch (*format) {
      case 'P':  /* Pod */
      case 'V':  /* Choice */
      case 'O':  /* Object */
      case 'T':  /* Struct */
        *va_arg(args, WpSpaPod**) = wp_spa_pod_new_wrap_copy (pod);
        break;
      case 'K': { /* Id as string - WirePlumber extension */
        const char ** idstr = va_arg(args, const char **);
        uint32_t id = SPA_POD_VALUE(struct spa_pod_id, pod);
        if (key) {
          WpSpaIdTable id_table = NULL;
          wp_spa_id_value_get_value_type (key, &id_table);
          WpSpaIdValue id_val = wp_spa_id_table_find_value (id_table, id);
          *idstr = wp_spa_id_value_short_name (id_val);
        }
        break;
      }
      case 'b':
        *va_arg(args, gboolean*) =
            SPA_POD_VALUE(struct spa_pod_bool, pod) ? TRUE : FALSE;
        break;
      default:
        SPA_POD_PARSER_COLLECT (pod, *format, args);
        break;
      }
    }
  } while (TRUE);

  return TRUE;
}

/*!
 * \brief Ends the parser process
 *
 * \ingroup wpspapod
 * \param self the spa pod parser object
 */
void
wp_spa_pod_parser_end (WpSpaPodParser *self)
{
  spa_pod_parser_pop (&self->parser, &self->frame);
}

struct _WpSpaPodIterator
{
  WpSpaPod *pod;
  union {
    gpointer value;                   /* Array and Choice */
    struct spa_pod *pod;              /* Struct */
    struct spa_pod_prop *prop;        /* Object */
    struct spa_pod_control *control;  /* Sequence */
  } curr;
};
typedef struct _WpSpaPodIterator WpSpaPodIterator;

static gboolean
wp_spa_pod_iterator_next_choice (WpSpaPodIterator *self, GValue *item)
{
  const struct spa_pod_choice *pod_choice =
      (const struct spa_pod_choice *) self->pod->pod;

  if (!self->curr.value)
    self->curr.value = SPA_MEMBER (&pod_choice->body, sizeof(struct spa_pod_choice_body), void);
  else
    self->curr.value = SPA_MEMBER (self->curr.value, pod_choice->body.child.size, void);

  if (self->curr.value >= SPA_MEMBER(&pod_choice->body, SPA_POD_BODY_SIZE (pod_choice), void))
    return FALSE;

  if (item) {
    g_value_init (item, G_TYPE_POINTER);
    g_value_set_pointer (item, self->curr.value);
  }
  return TRUE;
}

static gboolean
wp_spa_pod_iterator_next_array (WpSpaPodIterator *self, GValue *item)
{
  const struct spa_pod_array *pod_arr =
      (const struct spa_pod_array *) self->pod->pod;

  if (!self->curr.value)
    self->curr.value = SPA_MEMBER (&pod_arr->body, sizeof(struct spa_pod_array_body), void);
  else
    self->curr.value = SPA_MEMBER (self->curr.value, pod_arr->body.child.size, void);

  if (self->curr.value >= SPA_MEMBER(&pod_arr->body, SPA_POD_BODY_SIZE (pod_arr), void))
    return FALSE;

  if (item) {
    g_value_init (item, G_TYPE_POINTER);
    g_value_set_pointer (item, self->curr.value);
  }
  return TRUE;
}

static gboolean
wp_spa_pod_iterator_next_object (WpSpaPodIterator *self, GValue *item)
{
  const struct spa_pod_object *pod_obj =
      (const struct spa_pod_object *) self->pod->pod;

  if (!self->curr.prop)
    self->curr.prop = spa_pod_prop_first (&pod_obj->body);
  else
    self->curr.prop = spa_pod_prop_next (self->curr.prop);

  if (!spa_pod_prop_is_inside (&pod_obj->body, SPA_POD_BODY_SIZE (pod_obj),
      self->curr.prop))
    return FALSE;

  if (item) {
    g_value_init (item, WP_TYPE_SPA_POD);
    g_value_take_boxed (item, wp_spa_pod_new_property_wrap (
        self->pod->static_pod.data_property.table, self->curr.prop->key,
        self->curr.prop->flags, &self->curr.prop->value));
  }
  return TRUE;
}

static gboolean
wp_spa_pod_iterator_next_struct (WpSpaPodIterator *self, GValue *item)
{
  if (!self->curr.pod)
    self->curr.pod = SPA_POD_BODY (self->pod->pod);
  else
    self->curr.pod = spa_pod_next (self->curr.pod);

  if (!spa_pod_is_inside (SPA_POD_BODY (self->pod->pod),
      SPA_POD_BODY_SIZE (self->pod->pod), self->curr.pod))
    return FALSE;

  if (item) {
    g_value_init (item, WP_TYPE_SPA_POD);
    g_value_take_boxed (item, wp_spa_pod_new_wrap (self->curr.pod));
  }
  return TRUE;
}

static gboolean
wp_spa_pod_iterator_next_sequence (WpSpaPodIterator *self, GValue *item)
{
  const struct spa_pod_sequence *pod_seq =
      (const struct spa_pod_sequence *) self->pod->pod;

  if (!self->curr.control)
    self->curr.control = spa_pod_control_first (&pod_seq->body);
  else
    self->curr.control = spa_pod_control_next (self->curr.control);

  if (!spa_pod_control_is_inside (&pod_seq->body, SPA_POD_BODY_SIZE (pod_seq),
      self->curr.control))
    return FALSE;

  if (item) {
    g_value_init (item, WP_TYPE_SPA_POD);
    g_value_take_boxed (item, wp_spa_pod_new_control_wrap (
        self->curr.control->offset, self->curr.control->type,
        &self->curr.control->value));
  }
  return TRUE;
}


static void
wp_spa_pod_iterator_reset (WpIterator *iterator)
{
  WpSpaPodIterator *self = wp_iterator_get_user_data (iterator);
  self->curr.value = NULL;
  self->curr.pod = NULL;
  self->curr.prop = NULL;
  self->curr.control = NULL;
}

static gboolean
wp_spa_pod_iterator_next (WpIterator *iterator, GValue *item)
{
  WpSpaPodIterator *self = wp_iterator_get_user_data (iterator);

  switch (self->pod->pod->type) {
  case SPA_TYPE_Choice:
    return wp_spa_pod_iterator_next_choice (self, item);
  case SPA_TYPE_Array:
    return wp_spa_pod_iterator_next_array (self, item);
  case SPA_TYPE_Object:
    return wp_spa_pod_iterator_next_object (self, item);
  case SPA_TYPE_Struct:
    return wp_spa_pod_iterator_next_struct (self, item);
  case SPA_TYPE_Sequence:
    return wp_spa_pod_iterator_next_sequence (self, item);
  default:
    break;
  }

  return FALSE;
}

static gboolean
wp_spa_pod_iterator_fold (WpIterator *iterator, WpIteratorFoldFunc func,
    GValue *ret, gpointer data)
{
  WpSpaPodIterator *self = wp_iterator_get_user_data (iterator);

  wp_iterator_reset (iterator);

  switch (self->pod->pod->type) {
  case SPA_TYPE_Choice:
  {
    const struct spa_pod_choice *pod_choice =
        (const struct spa_pod_choice *) self->pod->pod;
    gpointer p = NULL;
    SPA_POD_CHOICE_FOREACH (pod_choice, p) {
      GValue v = G_VALUE_INIT;
      g_value_init (&v, G_TYPE_POINTER);
      g_value_set_pointer (&v, p);
      const gboolean res = func (&v, ret, data);
      g_value_unset (&v);
      if (!res)
        return FALSE;
    }
    break;
  }
  case SPA_TYPE_Array:
  {
    const struct spa_pod_array *pod_arr =
        (const struct spa_pod_array *) self->pod->pod;
    gpointer p = NULL;
    SPA_POD_ARRAY_FOREACH (pod_arr, p) {
      GValue v = G_VALUE_INIT;
      g_value_init (&v, G_TYPE_POINTER);
      g_value_set_pointer (&v, p);
      const gboolean res = func (&v, ret, data);
      g_value_unset (&v);
      if (!res)
        return FALSE;
    }
    break;
  }
  case SPA_TYPE_Object:
  {
    const struct spa_pod_object *pod_obj =
        (const struct spa_pod_object *) self->pod->pod;
    struct spa_pod_prop *p = NULL;
    SPA_POD_OBJECT_FOREACH (pod_obj, p) {
      GValue v = G_VALUE_INIT;
      g_value_init (&v, WP_TYPE_SPA_POD);
      g_value_take_boxed (&v, wp_spa_pod_new_property_wrap (
          self->pod->static_pod.data_property.table, p->key, p->flags,
          &p->value));
      const gboolean res = func (&v, ret, data);
      g_value_unset (&v);
      if (!res)
        return FALSE;
    }
    break;
  }
  case SPA_TYPE_Struct:
  {
    struct spa_pod *p = NULL;
    SPA_POD_STRUCT_FOREACH (self->pod->pod, p) {
      GValue v = G_VALUE_INIT;
      g_value_init (&v, WP_TYPE_SPA_POD);
      g_value_take_boxed (&v, wp_spa_pod_new_wrap (p));
      const gboolean res = func (&v, ret, data);
      g_value_unset (&v);
      if (!res)
        return FALSE;
    }
    break;
  }
  case SPA_TYPE_Sequence:
  {
    const struct spa_pod_sequence *pod_seq =
        (const struct spa_pod_sequence *) self->pod->pod;
    struct spa_pod_control *p = NULL;
    SPA_POD_SEQUENCE_FOREACH (pod_seq, p) {
      GValue v = G_VALUE_INIT;
      g_value_init (&v, WP_TYPE_SPA_POD);
      g_value_take_boxed (&v, wp_spa_pod_new_control_wrap (p->offset, p->type,
          &p->value));
      const gboolean res = func (&v, ret, data);
      g_value_unset (&v);
      if (!res)
        return FALSE;
    }
    break;
  }
  default:
    return FALSE;
  }

  return TRUE;
}

static void
wp_spa_pod_iterator_finalize (WpIterator *iterator)
{
  WpSpaPodIterator *self = wp_iterator_get_user_data (iterator);
  g_clear_pointer (&self->pod, wp_spa_pod_unref);
}

/*!
 * \brief Creates a new iterator for a spa pod object.
 *
 * \ingroup wpspapod
 * \param pod a spa pod object
 * \returns (transfer full): the new spa pod iterator
 */
WpIterator *
wp_spa_pod_new_iterator (WpSpaPod *pod)
{
  static const WpIteratorMethods methods = {
    .version = WP_ITERATOR_METHODS_VERSION,
    .reset = wp_spa_pod_iterator_reset,
    .next = wp_spa_pod_iterator_next,
    .fold = wp_spa_pod_iterator_fold,
    .foreach = NULL,
    .finalize = wp_spa_pod_iterator_finalize
  };
  WpIterator *it = wp_iterator_new (&methods, sizeof (WpSpaPodIterator));
  WpSpaPodIterator *self = wp_iterator_get_user_data (it);

  self->pod = wp_spa_pod_ref (pod);

  return it;
}
  07070100000095000081A4000000000000000000000001656CC35F00002B21000000000000000000000000000000000000002400000000wireplumber-0.4.17/lib/wp/spa-pod.h   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_SPA_POD_H__
#define __WIREPLUMBER_SPA_POD_H__

#include <gio/gio.h>

#include "defs.h"
#include "iterator.h"
#include "spa-type.h"

G_BEGIN_DECLS

struct spa_pod;

/*!
 * \brief The WpSpaPod GType
 * \ingroup wpspapod
 */
#define WP_TYPE_SPA_POD (wp_spa_pod_get_type ())
WP_API
GType wp_spa_pod_get_type (void);

typedef struct _WpSpaPod WpSpaPod;

WP_API
WpSpaPod *wp_spa_pod_ref (WpSpaPod *self);

WP_API
void wp_spa_pod_unref (WpSpaPod *self);

WP_API
WpSpaPod * wp_spa_pod_new_wrap (struct spa_pod *pod);

WP_API
WpSpaPod * wp_spa_pod_new_wrap_const (const struct spa_pod *pod);

WP_API
const struct spa_pod * wp_spa_pod_get_spa_pod (const WpSpaPod *self);

WP_API
WpSpaType wp_spa_pod_get_spa_type (WpSpaPod *self);

WP_API
WpSpaIdValue wp_spa_pod_get_choice_type (WpSpaPod *self);

WP_API
WpSpaPod *wp_spa_pod_copy (WpSpaPod *other);

WP_API
gboolean wp_spa_pod_is_unique_owner (WpSpaPod *self);

WP_API
WpSpaPod *wp_spa_pod_ensure_unique_owner (WpSpaPod *self);

WP_API
WpSpaPod *wp_spa_pod_new_none (void);

WP_API
WpSpaPod *wp_spa_pod_new_boolean (gboolean value);

WP_API
WpSpaPod *wp_spa_pod_new_id (guint32 value);

WP_API
WpSpaPod *wp_spa_pod_new_int (gint32 value);

WP_API
WpSpaPod *wp_spa_pod_new_long (gint64 value);

WP_API
WpSpaPod *wp_spa_pod_new_float (float value);

WP_API
WpSpaPod *wp_spa_pod_new_double (double value);

WP_API
WpSpaPod *wp_spa_pod_new_string (const char *value);

WP_API
WpSpaPod *wp_spa_pod_new_bytes (gconstpointer value, guint32 len);

WP_API
WpSpaPod *wp_spa_pod_new_pointer (const char *type_name, gconstpointer value);

WP_API
WpSpaPod *wp_spa_pod_new_fd (gint64 value);

WP_API
WpSpaPod *wp_spa_pod_new_rectangle (guint32 width, guint32 height);

WP_API
WpSpaPod *wp_spa_pod_new_fraction (guint32 num, guint32 denom);

WP_API
WpSpaPod *wp_spa_pod_new_choice (const char *choice_type, ...)
    G_GNUC_NULL_TERMINATED;

WP_API
WpSpaPod *wp_spa_pod_new_choice_valist (const char *choice_type, va_list args);

WP_API
WpSpaPod *wp_spa_pod_new_object (const char *type_name, const char *id_name,
    ...) G_GNUC_NULL_TERMINATED;

WP_API
WpSpaPod *wp_spa_pod_new_object_valist (const char *type_name,
    const char *id_name, va_list args);

WP_API
WpSpaPod *wp_spa_pod_new_sequence (guint unit, ...) G_GNUC_NULL_TERMINATED;

WP_API
WpSpaPod *wp_spa_pod_new_sequence_valist (guint unit, va_list args);

WP_API
gboolean wp_spa_pod_is_none (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_boolean (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_id (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_int (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_long (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_float (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_double (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_string (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_bytes (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_pointer (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_fd (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_rectangle (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_fraction (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_array (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_choice (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_object (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_struct (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_sequence (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_property (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_is_control (WpSpaPod *self);

WP_API
gboolean wp_spa_pod_get_boolean (WpSpaPod *self, gboolean *value);

WP_API
gboolean wp_spa_pod_get_id (WpSpaPod *self, guint32 *value);

WP_API
gboolean wp_spa_pod_get_int (WpSpaPod *self, gint32 *value);

WP_API
gboolean wp_spa_pod_get_long (WpSpaPod *self, gint64 *value);

WP_API
gboolean wp_spa_pod_get_float (WpSpaPod *self, float *value);

WP_API
gboolean wp_spa_pod_get_double (WpSpaPod *self, double *value);

WP_API
gboolean wp_spa_pod_get_string (WpSpaPod *self, const char **value);

WP_API
gboolean wp_spa_pod_get_bytes (WpSpaPod *self, gconstpointer *value,
    guint32 *len);

WP_API
gboolean wp_spa_pod_get_pointer (WpSpaPod *self, gconstpointer *value);

WP_API
gboolean wp_spa_pod_get_fd (WpSpaPod *self, gint64 *value);

WP_API
gboolean wp_spa_pod_get_rectangle (WpSpaPod *self, guint32 *width,
    guint32 *height);

WP_API
gboolean wp_spa_pod_get_fraction (WpSpaPod *self, guint32 *num, guint32 *denom);

WP_API
gboolean wp_spa_pod_set_boolean (WpSpaPod *self, gboolean value);

WP_API
gboolean wp_spa_pod_set_id (WpSpaPod *self, guint32 value);

WP_API
gboolean wp_spa_pod_set_int (WpSpaPod *self, gint32 value);

WP_API
gboolean wp_spa_pod_set_long (WpSpaPod *self, gint64 value);

WP_API
gboolean wp_spa_pod_set_float (WpSpaPod *self, float value);

WP_API
gboolean wp_spa_pod_set_double (WpSpaPod *self, double value);

WP_API
gboolean wp_spa_pod_set_pointer (WpSpaPod *self, const char *type_name,
   gconstpointer value);

WP_API
gboolean wp_spa_pod_set_fd (WpSpaPod *self, gint64 value);

WP_API
gboolean wp_spa_pod_set_rectangle (WpSpaPod *self, guint32 width,
    guint32 height);

WP_API
gboolean wp_spa_pod_set_fraction (WpSpaPod *self, guint32 num, guint32 denom);

WP_API
gboolean wp_spa_pod_set_pod (WpSpaPod *self, WpSpaPod *pod);

WP_API
gboolean wp_spa_pod_equal (WpSpaPod *self, WpSpaPod *pod);

WP_API
gboolean wp_spa_pod_get_object (WpSpaPod *self,
    const char **id_name, ...) G_GNUC_NULL_TERMINATED;

WP_API
gboolean wp_spa_pod_get_object_valist (WpSpaPod *self,
    const char **id_name, va_list args);

WP_API
gboolean wp_spa_pod_get_struct (WpSpaPod *self, ...) G_GNUC_NULL_TERMINATED;

WP_API
gboolean wp_spa_pod_get_struct_valist (WpSpaPod *self, va_list args);

WP_API
gboolean wp_spa_pod_get_property (WpSpaPod *self, const char **key,
    WpSpaPod **value);

WP_API
gboolean wp_spa_pod_get_control (WpSpaPod *self, guint32 *offset,
    const char **ctl_type, WpSpaPod **value);

WP_API
WpSpaPod *wp_spa_pod_get_choice_child (WpSpaPod *self);

WP_API
WpSpaPod *wp_spa_pod_get_array_child (WpSpaPod *self);

WP_API
WpIterator *wp_spa_pod_new_iterator (WpSpaPod *pod);

WP_API
gboolean wp_spa_pod_fixate (WpSpaPod *self);

WP_API
WpSpaPod *wp_spa_pod_filter (WpSpaPod *self, WpSpaPod *filter);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpSpaPod, wp_spa_pod_unref)


/*!
 * \brief The WpSpaPodBuilder GType
 * \ingroup wpspapod
 */
#define WP_TYPE_SPA_POD_BUILDER (wp_spa_pod_builder_get_type ())
WP_API
GType wp_spa_pod_builder_get_type (void);

typedef struct _WpSpaPodBuilder WpSpaPodBuilder;

WP_API
WpSpaPodBuilder *wp_spa_pod_builder_ref (WpSpaPodBuilder *self);

WP_API
void wp_spa_pod_builder_unref (WpSpaPodBuilder *self);

WP_API
WpSpaPodBuilder *wp_spa_pod_builder_new_array (void);

WP_API
WpSpaPodBuilder *wp_spa_pod_builder_new_choice (const char *choice_type);

WP_API
WpSpaPodBuilder *wp_spa_pod_builder_new_object (const char *type_name,
    const char *id_name);

WP_API
WpSpaPodBuilder *wp_spa_pod_builder_new_struct (void);

WP_API
WpSpaPodBuilder *wp_spa_pod_builder_new_sequence (guint unit);

WP_API
void wp_spa_pod_builder_add_none (WpSpaPodBuilder *self);

WP_API
void wp_spa_pod_builder_add_boolean (WpSpaPodBuilder *self, gboolean value);

WP_API
void wp_spa_pod_builder_add_id (WpSpaPodBuilder *self, guint32 value);

WP_API
void wp_spa_pod_builder_add_int (WpSpaPodBuilder *self, gint32 value);

WP_API
void wp_spa_pod_builder_add_long (WpSpaPodBuilder *self, gint64 value);

WP_API
void wp_spa_pod_builder_add_float (WpSpaPodBuilder *self, float value);

WP_API
void wp_spa_pod_builder_add_double (WpSpaPodBuilder *self, double value);

WP_API
void wp_spa_pod_builder_add_string (WpSpaPodBuilder *self, const char *value);

WP_API
void wp_spa_pod_builder_add_bytes (WpSpaPodBuilder *self, gconstpointer value,
    guint32 len);

WP_API
void wp_spa_pod_builder_add_pointer (WpSpaPodBuilder *self,
    const char *type_name, gconstpointer value);

WP_API
void wp_spa_pod_builder_add_fd (WpSpaPodBuilder *self, gint64 value);

WP_API
void wp_spa_pod_builder_add_rectangle (WpSpaPodBuilder *self, guint32 width,
    guint32 height);

WP_API
void wp_spa_pod_builder_add_fraction (WpSpaPodBuilder *self, guint32 num,
    guint32 denom);

WP_API
void wp_spa_pod_builder_add_pod (WpSpaPodBuilder *self, WpSpaPod *pod);

WP_API
void wp_spa_pod_builder_add_property (WpSpaPodBuilder *self, const char *key);

WP_API
void wp_spa_pod_builder_add_property_id (WpSpaPodBuilder *self, guint32 id);

WP_API
void wp_spa_pod_builder_add_control (WpSpaPodBuilder *self, guint32 offset,
    const char *ctl_type);

WP_API
void wp_spa_pod_builder_add (WpSpaPodBuilder *self, ...) G_GNUC_NULL_TERMINATED;

WP_API
void wp_spa_pod_builder_add_valist (WpSpaPodBuilder *self, va_list args);

WP_API
WpSpaPod *wp_spa_pod_builder_end (WpSpaPodBuilder *self);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpSpaPodBuilder, wp_spa_pod_builder_unref)


/*!
 * \brief The WpSpaPodParser GType
 * \ingroup wpspapod
 */
#define WP_TYPE_SPA_POD_PARSER (wp_spa_pod_parser_get_type ())
WP_API
GType wp_spa_pod_parser_get_type (void);

typedef struct _WpSpaPodParser WpSpaPodParser;

WP_API
WpSpaPodParser *wp_spa_pod_parser_ref (WpSpaPodParser *self);

WP_API
void wp_spa_pod_parser_unref (WpSpaPodParser *self);

WP_API
WpSpaPodParser *wp_spa_pod_parser_new_object (WpSpaPod *pod,
    const char **id_name);

WP_API
WpSpaPodParser *wp_spa_pod_parser_new_struct (WpSpaPod *pod);

WP_API
gboolean wp_spa_pod_parser_get_boolean (WpSpaPodParser *self, gboolean *value);

WP_API
gboolean wp_spa_pod_parser_get_id (WpSpaPodParser *self, guint32 *value);

WP_API
gboolean wp_spa_pod_parser_get_int (WpSpaPodParser *self, gint32 *value);

WP_API
gboolean wp_spa_pod_parser_get_long (WpSpaPodParser *self, gint64 *value);

WP_API
gboolean wp_spa_pod_parser_get_float (WpSpaPodParser *self, float *value);

WP_API
gboolean wp_spa_pod_parser_get_double (WpSpaPodParser *self, double *value);

WP_API
gboolean wp_spa_pod_parser_get_string (WpSpaPodParser *self,
    const char **value);

WP_API
gboolean wp_spa_pod_parser_get_bytes (WpSpaPodParser *self,
    gconstpointer *value, guint32 *len);

WP_API
gboolean wp_spa_pod_parser_get_pointer (WpSpaPodParser *self,
    gconstpointer *value);

WP_API
gboolean wp_spa_pod_parser_get_fd (WpSpaPodParser *self, gint64 *value);

WP_API
gboolean wp_spa_pod_parser_get_rectangle (WpSpaPodParser *self, guint32 *width,
    guint32 *height);

WP_API
gboolean wp_spa_pod_parser_get_fraction (WpSpaPodParser *self, guint32 *num,
    guint32 *denom);

WP_API
WpSpaPod *wp_spa_pod_parser_get_pod (WpSpaPodParser *self);

WP_API
gboolean wp_spa_pod_parser_get (WpSpaPodParser *self, ...)
    G_GNUC_NULL_TERMINATED;

WP_API
gboolean wp_spa_pod_parser_get_valist (WpSpaPodParser *self,
    va_list args);

WP_API
void wp_spa_pod_parser_end (WpSpaPodParser *self);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpSpaPodParser, wp_spa_pod_parser_unref)


G_END_DECLS

#endif
   07070100000096000081A4000000000000000000000001656CC35F000056C0000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/spa-type.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-spa-type"

#include "spa-type.h"

#include <spa/utils/type-info.h>
#include <spa/debug/types.h>
#include <pipewire/pipewire.h>

/*! \defgroup wpspatype WpSpaType
 *
 * Spa has a type system that is represented by a set of arrays that contain
 * `spa_type_info` structures. This type system is simple, yet complex to
 * work with for a couple of reasons.
 *
 * WirePlumber uses this API to access the spa type system, which makes some
 * things easier to understand and work with. The main benefit of using this
 * API is that it makes it easy to work with string representations of the
 * types, allowing easier access from script bindings.
 *
 * \b Type \b hierarchy
 *
 * On the top level, there is a list of types like Int, Bool, String, Id, Object.
 * These are called fundamental types (terms borrowed from GType).
 * Fundamental types can be derived and therefore we can have other types
 * that represent specific objects, for instance.
 *
 * Enum and flag types are all represented with `SPA_TYPE_Id`. These types
 * may have a list of possible values that one can select from (enums)
 * or combine (flags). These values are accessed with the WpSpaIdTable API.
 *
 * Object types can have fields. All objects always have a special "id" field,
 * which is an enum. Its possible values can be given by
 * wp_spa_type_get_object_id_values_table(). Optionally, objects can also have
 * other object-specific fields, which can be accessed with
 * wp_spa_type_get_values_table().
 *
 * Every object field or enum value is represented by a WpSpaIdValue. In the
 * case of object fields, each field can be of a specific type, which is
 * returned by wp_spa_id_value_get_value_type().
 */

static const WpSpaType SPA_TYPE_VENDOR_WirePlumber = 0x03000000;
static GArray *extra_types = NULL;
static GArray *extra_id_tables = NULL;

typedef struct {
  const char *name;
  const struct spa_type_info *values;
} WpSpaIdTableInfo;

static const WpSpaIdTableInfo static_id_tables[] = {
  { SPA_TYPE_INFO_Choice, spa_type_choice },
  { SPA_TYPE_INFO_Direction, spa_type_direction },
  { SPA_TYPE_INFO_ParamId, spa_type_param },
  { SPA_TYPE_INFO_MediaType, spa_type_media_type },
  { SPA_TYPE_INFO_MediaSubtype, spa_type_media_subtype },
  { SPA_TYPE_INFO_ParamAvailability, spa_type_param_availability },
  { SPA_TYPE_INFO_ParamPortConfigMode, spa_type_param_port_config_mode },
  { SPA_TYPE_INFO_VideoFormat, spa_type_video_format },
  { SPA_TYPE_INFO_AudioFormat, spa_type_audio_format },
  { SPA_TYPE_INFO_AudioFlags, spa_type_audio_flags },
  { SPA_TYPE_INFO_AudioChannel, spa_type_audio_channel },
  { SPA_TYPE_INFO_AudioIEC958Codec, spa_type_audio_iec958_codec },
  { SPA_TYPE_INFO_IO, spa_type_io },
  { SPA_TYPE_INFO_Control, spa_type_control },
  { SPA_TYPE_INFO_Data, spa_type_data_type },
  { SPA_TYPE_INFO_Meta, spa_type_meta_type },
  { SPA_TYPE_INFO_DeviceEventId, spa_type_device_event_id },
  { SPA_TYPE_INFO_NodeEvent, spa_type_node_event_id },
  { SPA_TYPE_INFO_NodeCommand, spa_type_node_command_id },
  { NULL, NULL }
};

GType wp_spa_type_get_type (void)
{
  static gsize id__volatile = 0;
  if (g_once_init_enter (&id__volatile)) {
    GType id = g_type_register_static_simple (
        G_TYPE_UINT, g_intern_static_string ("WpSpaType"),
        0, NULL, 0, NULL, 0);
    g_once_init_leave (&id__volatile, id);
  }
  return id__volatile;
}

G_DEFINE_POINTER_TYPE (WpSpaIdTable, wp_spa_id_table)

G_DEFINE_POINTER_TYPE (WpSpaIdValue, wp_spa_id_value)


static const struct spa_type_info *
wp_spa_type_info_find_by_type (WpSpaType type)
{
  const struct spa_type_info *info;

  g_return_val_if_fail (type != WP_SPA_TYPE_INVALID, NULL);
  g_return_val_if_fail (type != 0, NULL);

  if (extra_types)
    info = spa_debug_type_find (
        (const struct spa_type_info *) extra_types->data, type);
  else
    info = spa_debug_type_find (SPA_TYPE_ROOT, type);

  return info;
}

/* similar to spa_debug_type_find() and unlike spa_debug_type_find_type(),
   which steps into id values / object fields */
static const struct spa_type_info *
_spa_type_find_by_name (const struct spa_type_info * info, const char * name)
{
  const struct spa_type_info * res;

  while (info->name) {
    if (info->type == SPA_ID_INVALID) {
      if (info->values && (res = _spa_type_find_by_name (info->values, name)))
        return res;
    }
    if (strcmp (info->name, name) == 0)
      return info;
    info++;
  }
  return NULL;
}

static const struct spa_type_info *
wp_spa_type_info_find_by_name (const gchar *name)
{
  const struct spa_type_info *info = NULL;

  g_return_val_if_fail (name != NULL, NULL);

  if (extra_types)
    info = _spa_type_find_by_name (
        (const struct spa_type_info *) extra_types->data, name);
  else
    info = _spa_type_find_by_name (SPA_TYPE_ROOT, name);

  return info;
}

/*!
 * \brief Looks up the type id from a given type name
 *
 * \ingroup wpspatype
 * \param name the name to look up
 * \returns (transfer none): the corresponding type id or WP_SPA_TYPE_INVALID
 *   if not found
 */
WpSpaType
wp_spa_type_from_name (const gchar *name)
{
  const struct spa_type_info *info = wp_spa_type_info_find_by_name (name);
  return info ? info->type : WP_SPA_TYPE_INVALID;
}

/*!
 * \brief Gets the parent type of an SPA type
 *
 * \ingroup wpspatype
 * \param type a type id
 * \returns (transfer none): the direct parent type of the given \a type; if the
 *   type is fundamental (i.e. has no parent), the returned type is the same
 *   as \a type
 */
WpSpaType
wp_spa_type_parent (WpSpaType type)
{
  const struct spa_type_info *info = wp_spa_type_info_find_by_type (type);
  return info ? info->parent : WP_SPA_TYPE_INVALID;
}

/*!
 * \brief Gets the name of an SPA type
 *
 * \ingroup wpspatype
 * \param type a type id
 * \returns the complete name of the given \a type or NULL if \a type is invalid
 */
const gchar *
wp_spa_type_name (WpSpaType type)
{
  const struct spa_type_info *info = wp_spa_type_info_find_by_type (type);
  return info ? info->name : NULL;
}

/*!
 * \brief Checks if an SPA type is a fundamental type
 *
 * \ingroup wpspatype
 * \param type a type id
 * \returns TRUE if the \a type has no parent, FALSE otherwise
 */
gboolean
wp_spa_type_is_fundamental (WpSpaType type)
{
  const struct spa_type_info *info = wp_spa_type_info_find_by_type (type);
  return info ? (info->type == info->parent) : FALSE;
}

/*!
 * \brief Checks if an SPA type is an Id type
 *
 * \ingroup wpspatype
 * \param type a type id
 * \returns TRUE if the \a type is a SPA_TYPE_Id, FALSE otherwise
 */
gboolean
wp_spa_type_is_id (WpSpaType type)
{
  const struct spa_type_info *info = wp_spa_type_info_find_by_type (type);
  return info ? (info->parent == SPA_TYPE_Id) : FALSE;
}

/*!
 * \brief Checks if an SPA type is an Object type
 *
 * \ingroup wpspatype
 * \param type a type id
 * \returns TRUE if the \a type is a SPA_TYPE_Object, FALSE otherwise
 */
gboolean
wp_spa_type_is_object (WpSpaType type)
{
  const struct spa_type_info *info = wp_spa_type_info_find_by_type (type);
  return info ? (info->parent == SPA_TYPE_Object) : FALSE;
}

/*!
 * \brief Gets the table with the values that can be stored in the special "id"
 *   field of an object of the given \a type
 *
 * Object pods (see WpSpaPod) always have a special "id" field along with
 * other fields that can be defined. This "id" field can only store values
 * of a specific `SPA_TYPE_Id` type. This function returns the table that
 * contains the possible values for that field.
 *
 * \ingroup wpspatype
 * \param type the type id of an object type
 * \returns the table with the values that can be stored in the special "id"
 *   field of an object of the given \a type
 */
WpSpaIdTable
wp_spa_type_get_object_id_values_table (WpSpaType type)
{
  const struct spa_type_info *info = wp_spa_type_info_find_by_type (type);

  g_return_val_if_fail (info != NULL, NULL);
  g_return_val_if_fail (info->parent == SPA_TYPE_Object, NULL);
  g_return_val_if_fail (info->values != NULL, NULL);
  g_return_val_if_fail (info->values->name != NULL, NULL);
  g_return_val_if_fail (info->values->parent == SPA_TYPE_Id, NULL);

  return info->values->values;
}

/*!
 * \brief Gets the values table of an SPA type
 *
 * \ingroup wpspatype
 * \param type a type id
 * \returns the associated WpSpaIdTable that contains possible
 *   values or object fields for this type, or NULL
 */
WpSpaIdTable
wp_spa_type_get_values_table (WpSpaType type)
{
  const struct spa_type_info *info = wp_spa_type_info_find_by_type (type);

  g_return_val_if_fail (info != NULL, NULL);
  return info->values;
}

struct spa_type_info_iterator_data
{
  const struct spa_type_info *base;
  const struct spa_type_info *cur;
};

static void
spa_type_info_iterator_reset (WpIterator *it)
{
  struct spa_type_info_iterator_data *it_data = wp_iterator_get_user_data (it);
  it_data->cur = it_data->base;
}

static gboolean
spa_type_info_iterator_next (WpIterator *it, GValue *item)
{
  struct spa_type_info_iterator_data *it_data = wp_iterator_get_user_data (it);

  if (it_data->cur->name) {
    g_value_init (item, WP_TYPE_SPA_ID_VALUE);
    g_value_set_pointer (item, (gpointer) it_data->cur);
    it_data->cur++;
    return TRUE;
  }
  return FALSE;
}

static gboolean
spa_type_info_iterator_fold (WpIterator *it, WpIteratorFoldFunc func,
    GValue *ret, gpointer data)
{
  struct spa_type_info_iterator_data *it_data = wp_iterator_get_user_data (it);
  const struct spa_type_info *cur, *base;

  cur = base = it_data->base;

  while (cur->name) {
    g_auto (GValue) item = G_VALUE_INIT;
    g_value_init (&item, WP_TYPE_SPA_ID_VALUE);
    g_value_set_pointer (&item, (gpointer) cur);
    if (!func (&item, ret, data))
      return FALSE;
    cur++;
  }
  return TRUE;
}

static const WpIteratorMethods spa_type_info_iterator_methods = {
  .version = WP_ITERATOR_METHODS_VERSION,
  .reset = spa_type_info_iterator_reset,
  .next = spa_type_info_iterator_next,
  .fold = spa_type_info_iterator_fold,
};

/*!
 * \brief Finds a WpSpaIdTable given its name.
 *
 * This name can either be the full type name of an object type,
 * or the name of an enum (which is \b not(!!) a type).
 * For example, "Spa:Pod:Object:Param:Format" and "Spa:Enum:ParamId" are
 * both valid table names.
 *
 * \ingroup wpspatype
 * \param name the full name of an id table
 * \returns (nullable): the associated table, or NULL
 */
WpSpaIdTable
wp_spa_id_table_from_name (const gchar *name)
{
  g_return_val_if_fail (name != NULL, NULL);
  const WpSpaIdTableInfo *info = NULL;

  /* first look in dynamic id tables */
  if (extra_id_tables) {
    info = (const WpSpaIdTableInfo *) extra_id_tables->data;
    while (info && info->name) {
      if (strcmp (info->name, name) == 0)
        return info->values;
      info++;
    }
  }

  /* then look at the well-known static ones */
  info = static_id_tables;
  while (info && info->name) {
    if (strcmp (info->name, name) == 0)
      return info->values;
    info++;
  }

  /* then look into types, hoping to find an object type */
  const struct spa_type_info *tinfo = wp_spa_type_info_find_by_name (name);
  return tinfo ? tinfo->values : NULL;
}

/*!
 * \brief This function returns an iterator that allows you to iterate
 * through the values associated with this table.
 *
 * The items in the iterator are of type WpSpaIdValue.
 *
 * \ingroup wpspatype
 * \param table the id table
 * \returns a WpIterator that iterates over WpSpaIdValue items
 */
WpIterator *
wp_spa_id_table_new_iterator (WpSpaIdTable table)
{
  g_return_val_if_fail (table != NULL, NULL);

  WpIterator *it = wp_iterator_new (&spa_type_info_iterator_methods,
      sizeof (struct spa_type_info_iterator_data));
  struct spa_type_info_iterator_data *it_data = wp_iterator_get_user_data (it);
  it_data->base = (const struct spa_type_info *) table;
  it_data->cur = it_data->base;
  return it;
}

/*!
 * \brief Finds a value in an SPA Id table
 *
 * \ingroup wpspatype
 * \param table the id table
 * \param value a numeric value that is contained in the table
 * \returns (nullable): the WpSpaIdValue associated with \a value, or NULL
 */
WpSpaIdValue
wp_spa_id_table_find_value (WpSpaIdTable table, guint value)
{
  g_return_val_if_fail (table != NULL, NULL);

  const struct spa_type_info *info = table;
  while (info && info->name) {
    if (info->type == value)
      return info;
    info++;
  }
  return NULL;
}

/*!
 * \brief Finds a named value in an SPA Id table
 *
 * \ingroup wpspatype
 * \param table the id table
 * \param name the full name of a value that is contained in the table
 * \returns (nullable): the WpSpaIdValue associated with \a name, or NULL
 */
WpSpaIdValue
wp_spa_id_table_find_value_from_name (WpSpaIdTable table, const gchar * name)
{
  g_return_val_if_fail (table != NULL, NULL);

  const struct spa_type_info *info = table;
  while (info && info->name) {
    if (!strcmp (info->name, name))
      return info;
    info++;
  }
  return NULL;
}

/*!
 * \brief Finds a short named value in an SPA Id table
 *
 * \ingroup wpspatype
 * \param table the id table
 * \param short_name the short name of a value that is contained in the table
 * \returns (nullable): the WpSpaIdValue associated with \a short_name, or NULL
 */
WpSpaIdValue
wp_spa_id_table_find_value_from_short_name (WpSpaIdTable table,
    const gchar * short_name)
{
  g_return_val_if_fail (table != NULL, NULL);

  const struct spa_type_info *info = table;
  while (info && info->name) {
    if (!strcmp (spa_debug_type_short_name (info->name), short_name))
      return info;
    info++;
  }
  return NULL;
}

static WpSpaIdTable
wp_spa_id_name_find_id_table (const gchar * name)
{
  WpSpaIdTable table = NULL;
  g_autofree gchar *parent_name = g_strdup (name);
  gchar *h;

  if ((h = strrchr(parent_name, ':')) != NULL) {
    /* chop the enum name to get the type, ex:
       Spa:Enum:Direction:Input -> Spa:Enum:Direction */
    *h = '\0';
    table = wp_spa_id_table_from_name (parent_name);

    /* in some cases, the parent name is one layer further up, ex:
       Spa:Pod:Object:Param:Format:Audio:rate -> Spa:Pod:Object:Param:Format */
    if (!table && (h = strrchr(parent_name, ':')) != NULL) {
      *h = '\0';
      table = wp_spa_id_table_from_name (parent_name);
    }
  }
  return table;
}

/*!
 * \brief Looks up an id value (enum, flag or object field) directly from its
 * full name.
 *
 * For instance, "Spa:Enum:Direction:Input" will resolve to the
 * id value that represents "Input" in the "Spa:Enum:Direction" enum.
 *
 * \ingroup wpspatype
 * \param name the full name of an id value
 * \returns the id value for \a name, or NULL if no such id value was found
 */
WpSpaIdValue
wp_spa_id_value_from_name (const gchar * name)
{
  g_return_val_if_fail (name != NULL, NULL);

  WpSpaIdTable table = wp_spa_id_name_find_id_table (name);
  return wp_spa_id_table_find_value_from_name (table, name);
}

/*!
 * \brief Looks up an id value given its container \a table_name and its
 * \a short_name
 *
 * \ingroup wpspatype
 * \param table_name the name of the WpSpaIdTable to look up the value in
 * \param short_name the short name of the value to look up
 * \returns the id value or NULL if it was not found
 */
WpSpaIdValue
wp_spa_id_value_from_short_name (const gchar * table_name,
    const gchar * short_name)
{
  g_return_val_if_fail (table_name != NULL, NULL);
  g_return_val_if_fail (short_name != NULL, NULL);

  WpSpaIdTable table = wp_spa_id_table_from_name (table_name);
  return wp_spa_id_table_find_value_from_short_name (table, short_name);
}

/*!
 * \brief Looks up an id value given its container \a table_name and its numeric
 * representation, \a id
 *
 * \ingroup wpspatype
 * \param table_name the name of the WpSpaIdTable to look up the value in
 * \param id the numeric representation of the value to look up
 * \returns the id value or NULL if it was not found
 */
WpSpaIdValue
wp_spa_id_value_from_number (const gchar * table_name, guint id)
{
  g_return_val_if_fail (table_name != NULL, NULL);

  WpSpaIdTable table = wp_spa_id_table_from_name (table_name);
  return wp_spa_id_table_find_value (table, id);
}

/*!
 * \brief Gets the numeric value of an id value
 *
 * \ingroup wpspatype
 * \param id an id value
 * \returns the numeric representation of this id value
 */
guint
wp_spa_id_value_number (WpSpaIdValue id)
{
  g_return_val_if_fail (id != NULL, -1);

  const struct spa_type_info *info = id;
  return info->type;
}

/*!
 * \brief Gets the name of an id value
 *
 * \ingroup wpspatype
 * \param id an id value
 * \returns the full name of this id value
 */
const gchar *
wp_spa_id_value_name (WpSpaIdValue id)
{
  g_return_val_if_fail (id != NULL, NULL);

  const struct spa_type_info *info = id;
  return info->name;
}

/*!
 * \brief Gets the short name of an id value
 *
 * \ingroup wpspatype
 * \param id an id value
 * \returns the short name of this id value
 */
const gchar *
wp_spa_id_value_short_name (WpSpaIdValue id)
{
  g_return_val_if_fail (id != NULL, NULL);

  const struct spa_type_info *info = id;
  return spa_debug_type_short_name (info->name);
}

/*!
 * \brief Returns the value type associated with this WpSpaIdValue.
 *
 * This information is useful when \a id represents an object field,
 * which can take a value of an arbitrary type.
 *
 * When the returned type is (or is derived from) `SPA_TYPE_Id` or
 * `SPA_TYPE_Object`, \a table is set to point to the WpSpaIdTable
 * that contains the possible Id values / object fields.
 *
 * \ingroup wpspatype
 * \param id an id value
 * \param table (out) (optional): the associated WpSpaIdTable
 * \returns (transfer none): the value type associated with \a id
 */
WpSpaType
wp_spa_id_value_get_value_type (WpSpaIdValue id, WpSpaIdTable * table)
{
  g_return_val_if_fail (id != NULL, WP_SPA_TYPE_INVALID);

  const struct spa_type_info *info = id;

  if (table) {
    /* info->values has different semantics on Array types */
    if (info->values && info->parent != SPA_TYPE_Array) {
      *table = info->values;
    }
    /* derived object types normally don't have info->values directly set,
       so we need to look them up */
    else if (wp_spa_type_is_object (info->parent)) {
      WpSpaIdTable t = wp_spa_type_get_values_table (info->parent);
      if (t) *table = t;
    }
  }

  return info->parent;
}

/*!
 * \brief If the value type of \a id is `SPA_TYPE_Array`, this function
 * returns the type that is allowed to be contained inside the array.
 *
 * When the returned type is (or is derived from) `SPA_TYPE_Id` or
 * `SPA_TYPE_Object`, \a table is set to point to the WpSpaIdTable
 * that contains the possible Id values / object fields.
 *
 * \ingroup wpspatype
 * \param id an id value
 * \param table (out) (optional): the associated WpSpaIdTable
 * \returns (transfer none): the type that is allowed in the array, if \a id
 *   represents an object field that takes an array as value
 */
WpSpaType
wp_spa_id_value_array_get_item_type (WpSpaIdValue id, WpSpaIdTable * table)
{
  g_return_val_if_fail (id != NULL, WP_SPA_TYPE_INVALID);

  const struct spa_type_info *info = id;
  g_return_val_if_fail (info->parent == SPA_TYPE_Array, WP_SPA_TYPE_INVALID);

  return info->values ?
      wp_spa_id_value_get_value_type (info->values, table) :
      WP_SPA_TYPE_INVALID;
}

/*!
 * \brief Initializes the spa dynamic type registry.
 *
 * This allows registering new spa types at runtime. The spa type system
 * still works if this function is not called.
 *
 * Normally called by wp_init() when WP_INIT_SPA_TYPES is passed in its flags.
 *
 * \ingroup wpspatype
 */
void
wp_spa_dynamic_type_init (void)
{
  extra_types = g_array_new (TRUE, FALSE, sizeof (struct spa_type_info));
  extra_id_tables = g_array_new (TRUE, FALSE, sizeof (WpSpaIdTableInfo));

  /* init to chain up to spa types */
  struct spa_type_info info = {
      SPA_ID_INVALID, SPA_ID_INVALID, "spa_types", SPA_TYPE_ROOT
  };
  g_array_append_val (extra_types, info);
}

/*!
 * \brief Deinitializes the spa type registry.
 *
 * You do not need to ever call this, unless you want to free memory at the
 * end of the execution of a test, so that it doesn't show as leaked in
 * the memory profiler.
 *
 * \ingroup wpspatype
 */
void
wp_spa_dynamic_type_deinit (void)
{
  g_clear_pointer (&extra_types, g_array_unref);
  g_clear_pointer (&extra_id_tables, g_array_unref);
}

/*!
 * \brief Registers an additional type in the spa type system.
 *
 * This is useful to add a custom pod object type.
 *
 * Note that both \a name and \a values must be statically allocated, or
 * otherwise guaranteed to be kept in memory until wp_spa_dynamic_type_deinit()
 * is called. No memory copy is done by this function.
 *
 * \ingroup wpspatype
 * \param name the name of the type
 * \param parent the parent type
 * \param values an array of `spa_type_info` that contains the values of the type,
 *   used only for Object types
 * \returns (transfer none): the new type
 */
WpSpaType
wp_spa_dynamic_type_register (const gchar *name, WpSpaType parent,
    const struct spa_type_info * values)
{
  struct spa_type_info info;
  info.type = SPA_TYPE_VENDOR_WirePlumber + extra_types->len;
  info.name = name;
  info.parent = parent;
  info.values = values;
  g_array_append_val (extra_types, info);
  return info.type;
}

/*!
 * \brief Registers an additional WpSpaIdTable in the spa type system.
 *
 * This is useful to add custom enumeration types.
 *
 * Note that both \a name and \a values must be statically allocated, or
 * otherwise guaranteed to be kept in memory until wp_spa_dynamic_type_deinit()
 * is called. No memory copy is done by this function.
 *
 * \ingroup wpspatype
 * \param name the name of the id table
 * \param values an array of `spa_type_info` that contains the values of the table
 * \returns the new table
 */
WpSpaIdTable
wp_spa_dynamic_id_table_register (const gchar *name,
    const struct spa_type_info * values)
{
  WpSpaIdTableInfo info;
  info.name = name;
  info.values = values;
  g_array_append_val (extra_id_tables, info);
  return values;
}
07070100000097000081A4000000000000000000000001656CC35F00000C20000000000000000000000000000000000000002500000000wireplumber-0.4.17/lib/wp/spa-type.h  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_SPA_TYPE_H__
#define __WIREPLUMBER_SPA_TYPE_H__

#include "defs.h"
#include "iterator.h"

G_BEGIN_DECLS

/*!
 * \ingroup wpspatype
 */
typedef guint32 WpSpaType;
/*!
 * \ingroup wpspatype
 */
typedef gconstpointer WpSpaIdTable;
/*!
 * \ingroup wpspatype
 */
typedef gconstpointer WpSpaIdValue;
struct spa_type_info;

/* WpSpaType */

/*!
 * \brief The WpSpaType GType
 * \ingroup wpspatype
 */
#define WP_TYPE_SPA_TYPE (wp_spa_type_get_type ())
WP_API
GType wp_spa_type_get_type (void);

/*!
 * \brief Type id representing an invalid SPA type
 * \ingroup wpspatype
 */
static const WpSpaType WP_SPA_TYPE_INVALID = 0xffffffff;

WP_API
WpSpaType wp_spa_type_from_name (const gchar *name);

WP_API
WpSpaType wp_spa_type_parent (WpSpaType type);

WP_API
const gchar * wp_spa_type_name (WpSpaType type);

WP_API
gboolean wp_spa_type_is_fundamental (WpSpaType type);

WP_API
gboolean wp_spa_type_is_id (WpSpaType type);

WP_API
gboolean wp_spa_type_is_object (WpSpaType type);

WP_API
WpSpaIdTable wp_spa_type_get_object_id_values_table (WpSpaType type);

WP_API
WpSpaIdTable wp_spa_type_get_values_table (WpSpaType type);

/*!
 * \brief The WpSpaIdTable GType
 * \ingroup wpspatype
 */
#define WP_TYPE_SPA_ID_TABLE (wp_spa_id_table_get_type ())
WP_API
GType wp_spa_id_table_get_type (void);

WP_API
WpSpaIdTable wp_spa_id_table_from_name (const gchar *name);

WP_API
WpIterator * wp_spa_id_table_new_iterator (WpSpaIdTable table);

WP_API
WpSpaIdValue wp_spa_id_table_find_value (WpSpaIdTable table, guint value);

WP_API
WpSpaIdValue wp_spa_id_table_find_value_from_name (WpSpaIdTable table,
    const gchar * name);

WP_API
WpSpaIdValue wp_spa_id_table_find_value_from_short_name (WpSpaIdTable table,
    const gchar * short_name);

/*!
 * \brief The WpSpaIdValue GType
 * \ingroup wpspatype
 */
#define WP_TYPE_SPA_ID_VALUE (wp_spa_id_value_get_type ())
WP_API
GType wp_spa_id_value_get_type (void);

WP_API
WpSpaIdValue wp_spa_id_value_from_name (const gchar * name);

WP_API
WpSpaIdValue wp_spa_id_value_from_short_name (const gchar * table_name,
    const gchar * short_name);

WP_API
WpSpaIdValue wp_spa_id_value_from_number (const gchar * table_name, guint id);

WP_API
guint wp_spa_id_value_number (WpSpaIdValue id);

WP_API
const gchar * wp_spa_id_value_name (WpSpaIdValue id);

WP_API
const gchar * wp_spa_id_value_short_name (WpSpaIdValue id);

WP_API
WpSpaType wp_spa_id_value_get_value_type (WpSpaIdValue id, WpSpaIdTable *table);

WP_API
WpSpaType wp_spa_id_value_array_get_item_type (WpSpaIdValue id,
    WpSpaIdTable *table);


/* Dynamic type registration */

WP_API
void wp_spa_dynamic_type_init (void);

WP_API
void wp_spa_dynamic_type_deinit (void);

WP_API
WpSpaType wp_spa_dynamic_type_register (const gchar *name, WpSpaType parent,
    const struct spa_type_info * values);

WP_API
WpSpaIdTable wp_spa_dynamic_id_table_register (const gchar *name,
    const struct spa_type_info * values);

G_END_DECLS

#endif
07070100000098000081A4000000000000000000000001656CC35F000023A1000000000000000000000000000000000000002200000000wireplumber-0.4.17/lib/wp/state.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-state"

#include <stdio.h>
#include <errno.h>

#include "log.h"
#include "state.h"
#include "wp.h"

#define ESCAPED_CHARACTER '\\'

static char *
escape_string (const gchar *str)
{
  char *res = NULL;
  size_t str_size, i, j;

  g_return_val_if_fail (str, NULL);
  str_size = strlen (str);
  g_return_val_if_fail (str_size > 0, NULL);

  res = g_malloc_n ((str_size * 2) + 1, sizeof(gchar));

  j = 0;
  for (i = 0; i < str_size; i++) {
    switch (str[i]) {
      case ESCAPED_CHARACTER:
        res[j++] = ESCAPED_CHARACTER;
        res[j++] = ESCAPED_CHARACTER;
        break;
      case ' ':
        res[j++] = ESCAPED_CHARACTER;
        res[j++] = 's';
        break;
      case '=':
        res[j++] = ESCAPED_CHARACTER;
        res[j++] = 'e';
        break;
      case '[':
        res[j++] = ESCAPED_CHARACTER;
        res[j++] = 'o';
        break;
      case ']':
        res[j++] = ESCAPED_CHARACTER;
        res[j++] = 'c';
        break;
      default:
        res[j++] = str[i];
        break;
    }
  }
  res[j++] = '\0';

  return res;
}

static char *
compress_string (const gchar *str)
{
  char *res = NULL;
  size_t str_size, i, j;

  g_return_val_if_fail (str, NULL);
  str_size = strlen (str);
  g_return_val_if_fail (str_size > 0, NULL);

  res = g_malloc_n (str_size + 1, sizeof(gchar));

  j = 0;
  for (i = 0; i < str_size - 1; i++) {
    if (str[i] == ESCAPED_CHARACTER) {
      switch (str[i + 1]) {
        case ESCAPED_CHARACTER:
          res[j++] = ESCAPED_CHARACTER;
          break;
        case 's':
          res[j++] = ' ';
          break;
        case 'e':
          res[j++] = '=';
          break;
        case 'o':
          res[j++] = '[';
          break;
        case 'c':
          res[j++] = ']';
          break;
        default:
          res[j++] = str[i];
          break;
      }
      i++;
    } else {
      res[j++] = str[i];
    }
  }
  if (i < str_size)
    res[j++] = str[i];
  res[j++] = '\0';

  return res;
}

/*! \defgroup wpstate WpState */
/*!
 * \struct WpState
 *
 * The WpState class saves and loads properties from a file
 *
 * \gproperties
 * \gproperty{name, gchar *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The file name where the state will be stored.}
 */

enum {
  PROP_0,
  PROP_NAME,
};

struct _WpState
{
  GObject parent;

  /* Props */
  gchar *name;

  gchar *location;
  GKeyFile *keyfile;
};

G_DEFINE_TYPE (WpState, wp_state, G_TYPE_OBJECT)

/* Gets the full path to the WirePlumber XDG_STATE_HOME subdirectory */
static const gchar *
wp_get_xdg_state_dir (void)
{
  static gchar xdg_dir[PATH_MAX] = {0};
  if (xdg_dir[0] == '\0') {
    g_autofree gchar *path = NULL;
    g_autofree gchar *base = g_strdup (g_getenv ("XDG_STATE_HOME"));
    if (!base)
      base = g_build_filename (g_get_home_dir (), ".local", "state", NULL);

    path = g_build_filename (base, "wireplumber", NULL);
    (void) g_strlcpy (xdg_dir, path, sizeof (xdg_dir));
  }
  return xdg_dir;
}

static char *
get_new_location (const char *name)
{
  const gchar *path = wp_get_xdg_state_dir ();

  /* Create the directory if it doesn't exist */
  if (g_mkdir_with_parents (path, 0700) < 0)
    wp_warning ("failed to create directory %s: %s", path, g_strerror (errno));

  return g_build_filename (path, name, NULL);
}

static void
wp_state_ensure_location (WpState *self)
{
  if (!self->location)
    self->location = get_new_location (self->name);
  g_return_if_fail (self->location);
}

static void
wp_state_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpState *self = WP_STATE (object);

  switch (property_id) {
  case PROP_NAME:
    g_clear_pointer (&self->name, g_free);
    self->name = g_value_dup_string (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_state_get_property (GObject * object, guint property_id, GValue * value,
    GParamSpec * pspec)
{
  WpState *self = WP_STATE (object);

  switch (property_id) {
  case PROP_NAME:
    g_value_set_string (value, self->name);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_state_finalize (GObject * object)
{
  WpState * self = WP_STATE (object);

  g_clear_pointer (&self->name, g_free);
  g_clear_pointer (&self->location, g_free);

  G_OBJECT_CLASS (wp_state_parent_class)->finalize (object);
}

static void
wp_state_init (WpState * self)
{
}

static void
wp_state_class_init (WpStateClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;

  object_class->finalize = wp_state_finalize;
  object_class->set_property = wp_state_set_property;
  object_class->get_property = wp_state_get_property;

  g_object_class_install_property (object_class, PROP_NAME,
      g_param_spec_string ("name", "name",
          "The file name where the state will be stored", NULL,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}

/*!
 * \brief Constructs a new state object
 * \ingroup wpstate
 * \param name the state name
 * \returns (transfer full): the new WpState
 */
WpState *
wp_state_new (const gchar *name)
{
  g_return_val_if_fail (name, NULL);
  return g_object_new (wp_state_get_type (),
      "name", name,
      NULL);
}

/*!
 * \brief Gets the name of a state object
 * \ingroup wpstate
 * \param self the state
 * \returns the name of this state
 */
const gchar *
wp_state_get_name (WpState *self)
{
  g_return_val_if_fail (WP_IS_STATE (self), NULL);

  return self->name;
}

/*!
 * \brief Gets the location of a state object
 * \ingroup wpstate
 * \param self the state
 * \returns the location of this state
 */
const gchar *
wp_state_get_location (WpState *self)
{
  g_return_val_if_fail (WP_IS_STATE (self), NULL);
  wp_state_ensure_location (self);

  return self->location;
}

/*!
 * \brief Clears the state removing its file
 * \ingroup wpstate
 * \param self the state
 */
void
wp_state_clear (WpState *self)
{
  g_return_if_fail (WP_IS_STATE (self));
  wp_state_ensure_location (self);
  if (remove (self->location) < 0)
    wp_warning ("failed to remove %s: %s", self->location, g_strerror (errno));
}

/*!
 * \brief Saves new properties in the state, overwriting all previous data.
 * \ingroup wpstate
 * \param self the state
 * \param props (transfer none): the properties to save
 * \param error (out)(optional): return location for a GError, or NULL
 * \returns TRUE if the properties could be saved, FALSE otherwise
 */
gboolean
wp_state_save (WpState *self, WpProperties *props, GError ** error)
{
  g_autoptr (GKeyFile) keyfile = g_key_file_new ();
  g_autoptr (WpIterator) it = NULL;
  g_auto (GValue) item = G_VALUE_INIT;
  GError *err = NULL;

  g_return_val_if_fail (WP_IS_STATE (self), FALSE);
  g_return_val_if_fail (props, FALSE);
  wp_state_ensure_location (self);

  wp_info_object (self, "saving state into %s", self->location);

  /* Set the properties */
  for (it = wp_properties_new_iterator (props);
      wp_iterator_next (it, &item);
      g_value_unset (&item)) {
    WpPropertiesItem *pi = g_value_get_boxed (&item);
    const gchar *key = wp_properties_item_get_key (pi);
    const gchar *val = wp_properties_item_get_value (pi);
    g_autofree gchar *escaped_key = escape_string (key);
    if (escaped_key)
      g_key_file_set_string (keyfile, self->name, escaped_key, val);
  }

  if (!g_key_file_save_to_file (keyfile, self->location, &err)) {
    g_propagate_prefixed_error (error, err, "could not save %s: ", self->name);
    return FALSE;
  }

  return TRUE;
}

/*!
 * \brief Loads the state data from the file system
 *
 * This function will never fail. If it cannot load the state, for any reason,
 * it will simply return an empty WpProperties, behaving as if there was no
 * previous state stored.
 *
 * \ingroup wpstate
 * \param self the state
 * \returns (transfer full): a new WpProperties containing the state data
 */
WpProperties *
wp_state_load (WpState *self)
{
  g_autoptr (GKeyFile) keyfile = g_key_file_new ();
  g_autoptr (WpProperties) props = wp_properties_new_empty ();
  gchar ** keys = NULL;

  g_return_val_if_fail (WP_IS_STATE (self), NULL);
  wp_state_ensure_location (self);

  /* Open */
  if (!g_key_file_load_from_file (keyfile, self->location,
      G_KEY_FILE_NONE, NULL))
    return g_steal_pointer (&props);

  /* Load all keys */
  keys = g_key_file_get_keys (keyfile, self->name, NULL, NULL);
  if (!keys)
    return g_steal_pointer (&props);

  for (guint i = 0; keys[i]; i++) {
    g_autofree gchar *compressed_key = NULL;
    const gchar *key = keys[i];
    g_autofree gchar *val = NULL;
    val = g_key_file_get_string (keyfile, self->name, key, NULL);
    if (!val)
      continue;
    compressed_key = compress_string (key);
    if (compressed_key)
      wp_properties_set (props, compressed_key, val);
  }

  g_strfreev (keys);

  return g_steal_pointer (&props);
}
   07070100000099000081A4000000000000000000000001656CC35F00000331000000000000000000000000000000000000002200000000wireplumber-0.4.17/lib/wp/state.h /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_STATE_H__
#define __WIREPLUMBER_STATE_H__

#include "properties.h"

G_BEGIN_DECLS

/* WpState */

/*!
 * \brief The WpState GType
 * \ingroup wpstate
 */
#define WP_TYPE_STATE (wp_state_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpState, wp_state, WP, STATE, GObject)

WP_API
WpState * wp_state_new (const gchar *name);

WP_API
const gchar * wp_state_get_name (WpState *self);

WP_API
const gchar * wp_state_get_location (WpState *self);

WP_API
void wp_state_clear (WpState *self);

WP_API
gboolean wp_state_save (WpState *self, WpProperties *props, GError ** error);

WP_API
WpProperties * wp_state_load (WpState *self);

G_END_DECLS

#endif
   0707010000009A000081A4000000000000000000000001656CC35F0000434C000000000000000000000000000000000000002700000000wireplumber-0.4.17/lib/wp/transition.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-transition"

#include "transition.h"
#include "log.h"
#include "error.h"

/*! \defgroup wptransition Transitions */
/*!
 * \struct WpTransition
 *
 * A transition is an asynchronous operation, like GTask, that contains an
 * internal state machine, where a series of 'steps' are executed in order to
 * complete the operation.
 *
 * For every step, _WpTransitionClass::get_next_step()
 * is called in order to determine the next step to execute. Afterwards,
 * _WpTransitionClass::execute_step() is called
 * to perform any actions necessary to complete this step. When execution
 * of the step is done, the operation's code must call wp_transition_advance()
 * in order to continue to the next step. If an error occurs, the operation's
 * code must call wp_transition_return_error() instead, in which case the
 * transition completes immediately and wp_transition_had_error() returns TRUE.
 *
 * Typically, every step will start an asynchronous operation. Although is is
 * possible, the WpTransition base class does not expect
 * _WpTransitionClass::execute_step() to call wp_transition_advance() directly.
 * Instead, it is expected that wp_transition_advance() will be called from
 * the callback that the step's asynchronous operation will call when it is
 * completed.
 *
 * \gproperties

 * \gproperty{completed, gboolean, G_PARAM_READABLE,
 *   Whether the transition has completed\, meaning its callback (if set)
 *   has been invoked. This can only happen after the final step has been
 *   reached or wp_transition_return_error() has been called.
 *   \n
 *   This property is guaranteed to change from FALSE to TRUE exactly once.
 *   \n
 *   The GObject \c notify signal for this change is emitted in the same context
 *   as the transition’s callback\, immediately after that callback is invoked.}
 */

typedef struct _WpTransitionPrivate WpTransitionPrivate;
struct _WpTransitionPrivate
{
  /* source obj & callback */
  GObject *source_object;
  GCancellable *cancellable;
  GClosure *closure;

  /* GAsyncResult tag */
  gpointer tag;

  /* task data */
  gpointer data;
  GDestroyNotify data_destroy;

  /* state machine */
  gboolean started;
  guint step;
  GError *error;
};

enum {
  PROP_0,
  PROP_COMPLETED,
};

static void wp_transition_async_result_init (GAsyncResultIface *iface);

G_DEFINE_ABSTRACT_TYPE_WITH_CODE (WpTransition, wp_transition, G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, wp_transition_async_result_init)
    G_ADD_PRIVATE (WpTransition))

static void
wp_transition_init (WpTransition * self)
{
}

static void
wp_transition_finalize (GObject * object)
{
  WpTransition * self = WP_TRANSITION (object);
  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);

  if (priv->data && priv->data_destroy)
    priv->data_destroy (priv->data);

  g_clear_error (&priv->error);
  g_clear_pointer (&priv->closure, g_closure_unref);
  g_clear_object (&priv->cancellable);
  g_clear_object (&priv->source_object);

  G_OBJECT_CLASS (wp_transition_parent_class)->finalize (object);
}

static void
wp_transition_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpTransition * self = WP_TRANSITION (object);

  switch (property_id) {
  case PROP_COMPLETED:
    g_value_set_boolean (value, wp_transition_get_completed (self));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_transition_class_init (WpTransitionClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;

  object_class->finalize = wp_transition_finalize;
  object_class->get_property = wp_transition_get_property;

  g_object_class_install_property (object_class, PROP_COMPLETED,
      g_param_spec_boolean ("completed", "completed",
          "Whether the transition has completed", FALSE,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

static GObject *
get_source_object (GAsyncResult * res)
{
  WpTransitionPrivate *priv =
      wp_transition_get_instance_private (WP_TRANSITION (res));
  return priv->source_object ? g_object_ref (priv->source_object) : NULL;
}

static void
wp_transition_async_result_init (GAsyncResultIface * iface)
{
  iface->get_source_object = get_source_object;
  iface->get_user_data =
      (gpointer (*)(GAsyncResult *)) wp_transition_get_data;
  iface->is_tagged =
      (gboolean (*)(GAsyncResult *, gpointer)) wp_transition_is_tagged;
}

/*!
 * \brief Creates a WpTransition acting on \a source_object.
 *
 * When the transition is done, \a callback will be invoked.
 *
 * The transition does not automatically start executing steps. You must
 * call wp_transition_advance() after creating it in order to start it.
 *
 * \note The transition is automatically unref'ed after the \a callback
 * has been executed. If you wish to keep an additional reference on it,
 * you need to ref it explicitly.
 *
 * \ingroup wptransition
 * \param type the GType of the WpTransition subclass to instantiate
 * \param source_object (nullable) (type GObject): the GObject that owns this
 *   task, or NULL
 * \param cancellable (nullable): optional GCancellable
 * \param callback (scope async): a GAsyncReadyCallback
 * \param callback_data (closure): user data passed to \a callback
 * \returns (transfer none): the new transition
 */
WpTransition *
wp_transition_new (GType type,
    gpointer source_object, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer callback_data)
{
  return wp_transition_new_closure (type, source_object, cancellable,
      g_cclosure_new (G_CALLBACK (callback), callback_data, NULL));
}

/*!
 * \brief Creates a WpTransition acting on \a source_object.
 * When the transition is done, \a closure will be invoked.
 *
 * The transition does not automatically start executing steps. You must
 * call wp_transition_advance() after creating it in order to start it.
 *
 * Note that the transition is automatically unref'ed after the \a closure
 * has been executed. If you wish to keep an additional reference on it,
 * you need to ref it explicitly.
 *
 * \ingroup wptransition
 * \param type the GType of the WpTransition subclass to instantiate
 * \param source_object (nullable) (type GObject): the GObject that owns this
 *   task, or NULL
 * \param cancellable (nullable): optional GCancellable
 * \param closure (nullable): a GAsyncReadyCallback wrapped in a GClosure
 * \returns (transfer none): the new transition
 */
WpTransition *
wp_transition_new_closure (GType type, gpointer source_object,
    GCancellable * cancellable, GClosure * closure)
{
  g_return_val_if_fail (g_type_is_a (type, WP_TYPE_TRANSITION), NULL);
  g_return_val_if_fail (G_IS_OBJECT (source_object), NULL);

  WpTransition *self = g_object_new (type, NULL);
  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);

  priv->source_object = source_object ? g_object_ref (source_object) : NULL;
  priv->cancellable = cancellable ? g_object_ref (cancellable) : NULL;

  if (closure) {
    priv->closure = g_closure_ref (closure);
    g_closure_sink (closure);
    if (G_CLOSURE_NEEDS_MARSHAL (closure))
      g_closure_set_marshal (closure, g_cclosure_marshal_VOID__OBJECT);
  }

  return self;
}

/*!
 * \brief Gets the source object from the transition.
 *
 * Like g_async_result_get_source_object(), but does not ref the object.
 *
 * \ingroup wptransition
 * \param self the transition
 * \returns (transfer none) (type GObject): the source object
 */
gpointer
wp_transition_get_source_object (WpTransition * self)
{
  g_return_val_if_fail (WP_IS_TRANSITION (self), NULL);

  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);
  return priv->source_object;
}

/*!
 * \brief Checks if \a self has the given \a tag (generally a function pointer
 * indicating the function \a self was created by).
 *
 * \ingroup wptransition
 * \param self the transition
 * \param tag a tag
 * \returns TRUE if \a self has the indicated \a tag , FALSE if not.
 */
gboolean
wp_transition_is_tagged (WpTransition * self, gpointer tag)
{
  g_return_val_if_fail (WP_IS_TRANSITION (self), FALSE);

  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);
  return (priv->tag == tag);
}

/*!
 * \brief Gets \a self 's source tag.
 * \see wp_transition_set_source_tag().
 * \ingroup wptransition
 * \param self the transition
 * \returns (transfer none): the transition's source tag
 */
gpointer
wp_transition_get_source_tag (WpTransition * self)
{
  g_return_val_if_fail (WP_IS_TRANSITION (self), NULL);

  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);
  return priv->tag;
}

/*!
 * \brief Sets \a self 's source tag.
 *
 * You can use this to tag a transition's return
 * value with a particular pointer (usually a pointer to the function doing
 * the tagging) and then later check it using wp_transition_get_source_tag()
 * (or g_async_result_is_tagged()) in the transition's "finish" function,
 * to figure out if the response came from a particular place.

 * \ingroup wptransition
 * \param self the transition
 * \param tag an opaque pointer indicating the source of this transition
 */
void
wp_transition_set_source_tag (WpTransition * self, gpointer tag)
{
  g_return_if_fail (WP_IS_TRANSITION (self));

  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);
  priv->tag = tag;
}

/*!
 * \brief Gets \a self 's data.
 * \see wp_transition_set_data().
 * \ingroup wptransition
 * \param self the transition
 * \returns (transfer none): the transition's data
 */
gpointer
wp_transition_get_data (WpTransition * self)
{
  g_return_val_if_fail (WP_IS_TRANSITION (self), NULL);

  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);
  return priv->data;
}

/*!
 * \brief Sets \a self 's data (freeing the existing data, if any). This can be an
 * arbitrary user structure that holds data associated with this transition.
 *
 * \ingroup wptransition
 * \param self the transition
 * \param data (nullable): transition-specific user data
 * \param data_destroy (nullable): GDestroyNotify for \a data
 */
void
wp_transition_set_data (WpTransition * self, gpointer data,
    GDestroyNotify data_destroy)
{
  g_return_if_fail (WP_IS_TRANSITION (self));

  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);
  if (priv->data && priv->data_destroy)
    priv->data_destroy (priv->data);
  priv->data = data;
  priv->data_destroy = data_destroy;
}

/*!
 * \brief Checks if the transition completed.
 * \ingroup wptransition
 * \param self the transition
 * \returns TRUE if the transition has completed (with or without an error),
 *   FALSE otherwise
 */
gboolean
wp_transition_get_completed (WpTransition * self)
{
  g_return_val_if_fail (WP_IS_TRANSITION (self), FALSE);

  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);
  return (priv->step == WP_TRANSITION_STEP_NONE && priv->started) ||
         priv->step == WP_TRANSITION_STEP_ERROR;
}

/*!
 * \brief Checks if the transition completed with an error.
 * \ingroup wptransition
 * \param self the transition
 * \returns TRUE if the transition completed with an error, FALSE otherwise
 */
gboolean
wp_transition_had_error (WpTransition * self)
{
  g_return_val_if_fail (WP_IS_TRANSITION (self), FALSE);

  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);
  return priv->step == WP_TRANSITION_STEP_ERROR;
}

static void
wp_transition_return (WpTransition * self, WpTransitionPrivate *priv)
{
  if (priv->closure) {
    GValue values[2] = { G_VALUE_INIT, G_VALUE_INIT };
    g_value_init (&values[0], G_TYPE_OBJECT);
    g_value_init (&values[1], G_TYPE_OBJECT);
    g_value_set_object (&values[0], priv->source_object);
    g_value_set_object (&values[1], self);
    g_closure_invoke (priv->closure, NULL, 2, values, NULL);
    g_value_unset (&values[0]);
    g_value_unset (&values[1]);
  }

  g_object_notify (G_OBJECT (self), "completed");

  /* WARNING */
  g_object_unref (self);
}

/*!
 * \brief Advances the transition to the next step.
 *
 * This initially calls _WpTransitionClass::get_next_step()
 * in order to determine what the next step is.
 * If _WpTransitionClass::get_next_step() returns a step
 * different than the previous one, it calls
 * _WpTransitionClass::execute_step() to execute it.
 *
 * The very first time that _WpTransitionClass::get_next_step()
 * is called, its \a step parameter equals WP_TRANSITION_STEP_NONE.
 *
 * When _WpTransitionClass::get_next_step() returns
 * WP_TRANSITION_STEP_NONE this function completes the transition,
 * calling the transition's callback and then unref-ing the transition.
 *
 * When _WpTransitionClass::get_next_step() returns
 * WP_TRANSITION_STEP_ERROR, this function calls wp_transition_return_error(),
 * unless it has already been called directly by
 * _WpTransitionClass::get_next_step().
 *
 * In error conditions, _WpTransitionClass::execute_step()
 * is called once with \a step being WP_TRANSITION_STEP_ERROR, allowing the
 * implementation to rollback any changes or cancel underlying jobs, if necessary.
 *
 * \ingroup wptransition
 * \param self the transition
 */
void
wp_transition_advance (WpTransition * self)
{
  g_return_if_fail (WP_IS_TRANSITION (self));

  /* keep a reference to avoid issues when wp_transition_return_error() is
     called from within get_next_step() */
  g_autoptr (WpTransition) self_ref = g_object_ref (self);
  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);
  guint next_step;
  GError *error = NULL;

  priv->started = TRUE;

  if (g_cancellable_set_error_if_cancelled (priv->cancellable, &error)) {
    wp_transition_return_error (self, error);
    return;
  }

  /* find the next step */
  next_step = WP_TRANSITION_GET_CLASS (self)->get_next_step (self, priv->step);

  wp_trace_object (priv->source_object, "transition: %d -> %d", priv->step,
      next_step);

  if (next_step == WP_TRANSITION_STEP_ERROR) {
    /* return error if the callback didn't do it already */
    if (G_UNLIKELY (!priv->error)) {
        wp_transition_return_error (self, g_error_new (WP_DOMAIN_LIBRARY,
                WP_LIBRARY_ERROR_INVARIANT, "state machine error"));
    }
    return;
  }

  /* if we reached STEP_NONE again, that means we reached the next state */
  if (next_step == WP_TRANSITION_STEP_NONE) {
    /* complete the transition */
    priv->step = next_step;
    wp_transition_return (self, priv);
    return;
  }

  /* still at the same step, this means we are waiting for something */
  if (next_step == priv->step)
    return;

  wp_trace_object (priv->source_object, "transition: execute %d", next_step);

  /* execute the next step */
  priv->step = next_step;
  WP_TRANSITION_GET_CLASS (self)->execute_step (self, priv->step);
}

/*!
 * \brief Completes the transition with an error.
 *
 * This can be called anytime from within any virtual function or an async
 * job handler.
 *
 * \note In most cases this will also unref the transition, so it is
 * not safe to access it after this function has been called.
 *
 * \ingroup wptransition
 * \param self the transition
 * \param error (transfer full): a GError
 */
void
wp_transition_return_error (WpTransition * self, GError * error)
{
  g_return_if_fail (WP_IS_TRANSITION (self));

  WpTransitionPrivate *priv = wp_transition_get_instance_private (self);

  if (G_UNLIKELY (priv->error)) {
    wp_warning_object (self, "transition bailing out multiple times; "
        "old error was: %s", priv->error->message);
    g_clear_error (&priv->error);
  }

  priv->step = WP_TRANSITION_STEP_ERROR;
  priv->error = error;

  /* allow the implementation to rollback changes */
  if (WP_TRANSITION_GET_CLASS (self)->execute_step)
    WP_TRANSITION_GET_CLASS (self)->execute_step (self, priv->step);

  wp_transition_return (self, priv);
}

/*!
 * \brief Returns the final return status of the transition and its error,
 * if there was one.
 *
 * This is meant to be called from within the GAsyncReadyCallback
 * that was specified in wp_transition_new().
 *
 * \ingroup wptransition
 * \param res a transition, as a GAsyncResult
 * \param error (out) (optional): a location to return the transition's error,
 *   if any
 * \returns TRUE if the transition completed successfully, FALSE if there
 *   was an error
 */
gboolean
wp_transition_finish (GAsyncResult * res, GError ** error)
{
  g_return_val_if_fail (WP_IS_TRANSITION (res), FALSE);

  WpTransitionPrivate *priv =
      wp_transition_get_instance_private (WP_TRANSITION (res));
  if (priv->error) {
    g_propagate_error (error, priv->error);
    priv->error = NULL;
  } else if (!priv->started) {
    priv->step = WP_TRANSITION_STEP_ERROR;
    g_propagate_error (error, g_error_new (WP_DOMAIN_LIBRARY,
        WP_LIBRARY_ERROR_INVARIANT, "finished before starting"));
  }

  wp_trace_object (priv->source_object, "transition: finished %s",
      (priv->step == WP_TRANSITION_STEP_NONE) ? "ok" : "with error");

  return (priv->step == WP_TRANSITION_STEP_NONE);
}
0707010000009B000081A4000000000000000000000001656CC35F00000994000000000000000000000000000000000000002700000000wireplumber-0.4.17/lib/wp/transition.h    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_TRANSITION_H__
#define __WIREPLUMBER_TRANSITION_H__

#include <gio/gio.h>
#include "defs.h"

G_BEGIN_DECLS

/*!
 * \brief The WpTransition GType
 * \ingroup wptransition
 */
#define WP_TYPE_TRANSITION (wp_transition_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpTransition, wp_transition, WP, TRANSITION, GObject)

/*!
 * \brief Values for the \em steps of the implemented state machine
 * \ingroup wptransition
 */
typedef enum {
  /*! the initial and final step of the transition */
  WP_TRANSITION_STEP_NONE = 0,
  /*! returned by _WpTransitionClass::get_next_step() in case of an error */
  WP_TRANSITION_STEP_ERROR,
  /*! starting value for steps defined in subclasses */
  WP_TRANSITION_STEP_CUSTOM_START = 0x10
} WpTransitionStep;

struct _WpTransitionClass
{
  GObjectClass parent_class;

  /*! See wp_transition_advance() */
  guint (*get_next_step) (WpTransition * transition, guint step);
  /*! See wp_transition_advance() */
  void (*execute_step) (WpTransition * transition, guint step);

  /*< private >*/
  WP_PADDING(6)
};

WP_API
WpTransition * wp_transition_new (GType type,
    gpointer source_object, GCancellable * cancellable,
    GAsyncReadyCallback callback, gpointer callback_data);

WP_API
WpTransition * wp_transition_new_closure (GType type,
    gpointer source_object, GCancellable * cancellable, GClosure * closure);

/* source object */

WP_API
gpointer wp_transition_get_source_object (WpTransition * self);

/* tag */

WP_API
gboolean wp_transition_is_tagged (WpTransition * self, gpointer tag);

WP_API
gpointer wp_transition_get_source_tag (WpTransition * self);

WP_API
void wp_transition_set_source_tag (WpTransition * self, gpointer tag);

/* task data */

WP_API
gpointer wp_transition_get_data (WpTransition * self);

WP_API
void wp_transition_set_data (WpTransition * self, gpointer data,
    GDestroyNotify data_destroy);

/* state machine */

WP_API
gboolean wp_transition_get_completed (WpTransition * self);

WP_API
gboolean wp_transition_had_error (WpTransition * self);

WP_API
void wp_transition_advance (WpTransition * self);

WP_API
void wp_transition_return_error (WpTransition * self, GError * error);

/* result */

WP_API
gboolean wp_transition_finish (GAsyncResult * res, GError ** error);

G_END_DECLS

#endif
0707010000009C000081A4000000000000000000000001656CC35F00002A06000000000000000000000000000000000000001F00000000wireplumber-0.4.17/lib/wp/wp.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp"

#include "wp.h"
#include <pipewire/pipewire.h>
#include <libintl.h>

/*!
 * \defgroup wp Library Initialization
 * \{
 */

/*!
 * \brief Initializes WirePlumber and PipeWire underneath.
 *
 * \em flags can modify which parts are initialized, in cases where you want
 * to handle part of this initialization externally.
 *
 * \param flags initialization flags
 */
void
wp_init (WpInitFlags flags)
{
  if (flags & WP_INIT_SET_GLIB_LOG)
    g_log_set_writer_func (wp_log_writer_default, NULL, NULL);

  /* Initialize the logging system */
  wp_log_set_level (g_getenv ("WIREPLUMBER_DEBUG"));
  wp_info ("WirePlumber " WIREPLUMBER_VERSION " initializing");

  /* set PIPEWIRE_DEBUG and the spa_log interface that pipewire will use */
  if (flags & WP_INIT_SET_PW_LOG && !g_getenv ("WIREPLUMBER_NO_PW_LOG")) {
    if (g_getenv ("WIREPLUMBER_DEBUG")) {
      gchar lvl_str[2];
      g_snprintf (lvl_str, 2, "%d", wp_spa_log_get_instance ()->level);
      g_warn_if_fail (g_setenv ("PIPEWIRE_DEBUG", lvl_str, TRUE));
    }
    pw_log_set_level (wp_spa_log_get_instance ()->level);
    pw_log_set (wp_spa_log_get_instance ());
  }

  if (flags & WP_INIT_PIPEWIRE)
    pw_init (NULL, NULL);

  if (flags & WP_INIT_SPA_TYPES)
    wp_spa_dynamic_type_init ();

  bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");

  /* ensure WpProxy subclasses are loaded, which is needed to be able
    to autodetect the GType of proxies created through wp_proxy_new_global() */
  g_type_ensure (WP_TYPE_CLIENT);
  g_type_ensure (WP_TYPE_DEVICE);
  g_type_ensure (WP_TYPE_ENDPOINT);
  g_type_ensure (WP_TYPE_LINK);
  g_type_ensure (WP_TYPE_METADATA);
  g_type_ensure (WP_TYPE_NODE);
  g_type_ensure (WP_TYPE_PORT);
  g_type_ensure (WP_TYPE_FACTORY);
}

/*!
 * \brief Gets the WirePlumber library version
 * \returns WirePlumber library version
 *
 * \since 0.4.12
 */
const char *
wp_get_library_version (void)
{
  return WIREPLUMBER_VERSION;
}

/*!
 * \brief Gets the WirePlumber library API version
 * \returns WirePlumber library API version
 *
 * \since 0.4.12
 */
const char *
wp_get_library_api_version (void)
{
  return WIREPLUMBER_API_VERSION;
}

/*!
 * \brief Gets the WirePlumber module directory
 * \returns WirePlumber's module directory
 */
const gchar *
wp_get_module_dir (void)
{
  static const gchar *module_dir = NULL;
  if (!module_dir) {
    module_dir = g_getenv ("WIREPLUMBER_MODULE_DIR");
    if (!module_dir)
      module_dir = WIREPLUMBER_DEFAULT_MODULE_DIR;
  }
  return module_dir;
}

/*!
 * \brief Gets the full path to the WirePlumber configuration directory
 * \returns The WirePlumber configuration directory
 * \deprecated Use wp_find_file() instead
 */
const gchar *
wp_get_config_dir (void)
{
  static gchar config_dir[PATH_MAX] = {0};
  if (config_dir[0] == '\0') {
    g_autofree gchar *abspath;
    const gchar *path = g_getenv ("WIREPLUMBER_CONFIG_DIR");

    if (!path)
      path = WIREPLUMBER_DEFAULT_CONFIG_DIR;

    abspath = g_canonicalize_filename (path, NULL);
    (void) g_strlcpy (config_dir, abspath, sizeof (config_dir));
  }
  return config_dir;
}

/*!
 * \brief Gets full path to the WirePlumber data directory
 * \returns The WirePlumber data directory
 * \deprecated Use wp_find_file() instead
 */
const gchar *
wp_get_data_dir (void)
{
  static gchar data_dir[PATH_MAX] = {0};
  if (data_dir[0] == '\0') {
    g_autofree gchar *abspath;
    const char *path = g_getenv ("WIREPLUMBER_DATA_DIR");
    if (!path)
      path = WIREPLUMBER_DEFAULT_DATA_DIR;
    abspath = g_canonicalize_filename (path, NULL);
    (void) g_strlcpy (data_dir, abspath, sizeof (data_dir));
  }
  return data_dir;
}

/*! \} */

static gchar *
check_path (const gchar *basedir, const gchar *subdir, const gchar *filename)
{
  g_autofree gchar *path = g_build_filename (basedir,
                                             subdir ? subdir : filename,
                                             subdir ? filename : NULL,
                                             NULL);
  g_autofree gchar *abspath = g_canonicalize_filename (path, NULL);
  wp_trace ("checking %s", abspath);
  if (g_file_test (abspath, G_FILE_TEST_IS_REGULAR))
    return g_steal_pointer (&abspath);
  return NULL;
}

static GPtrArray *
lookup_dirs (guint flags)
{
  g_autoptr(GPtrArray) dirs = g_ptr_array_new_with_free_func (g_free);
  const gchar *dir;

  /* Compile the list of lookup directories in priority order:
   * - environment variables
   * - XDG config directories
   * - /etc/
   * - /usr/share/....
   *
   * Note that wireplumber environment variables *replace* other directories.
   */
  if ((flags & WP_LOOKUP_DIR_ENV_CONFIG) &&
      (dir = g_getenv ("WIREPLUMBER_CONFIG_DIR"))) {
    g_ptr_array_add (dirs, g_canonicalize_filename (dir, NULL));
  }
  else if ((flags & WP_LOOKUP_DIR_ENV_DATA) &&
      (dir = g_getenv ("WIREPLUMBER_DATA_DIR"))) {
    g_ptr_array_add (dirs, g_canonicalize_filename (dir, NULL));
  }
  else {
    if (flags & WP_LOOKUP_DIR_XDG_CONFIG_HOME) {
      dir = g_get_user_config_dir ();
      g_ptr_array_add (dirs, g_build_filename (dir, "wireplumber", NULL));
    }
    if (flags & WP_LOOKUP_DIR_ETC)
      g_ptr_array_add (dirs,
          g_canonicalize_filename (WIREPLUMBER_DEFAULT_CONFIG_DIR, NULL));
    if (flags & WP_LOOKUP_DIR_PREFIX_SHARE)
      g_ptr_array_add (dirs,
          g_canonicalize_filename(WIREPLUMBER_DEFAULT_DATA_DIR, NULL));
  }

  return g_steal_pointer (&dirs);
}

/*!
 * \brief Returns the full path of \a filename as found in
 * the hierarchy of configuration and data directories.
 *
 * \ingroup wp
 * \param dirs the directories to look into
 * \param filename the name of the file to search for
 * \param subdir (nullable): the name of the subdirectory to search in,
 *   inside the configuration directories
 * \returns (transfer full): An allocated string with the configuration
 *   file path or NULL if the file was not found.
 * \since 0.4.2
 */
gchar *
wp_find_file (WpLookupDirs dirs, const gchar *filename, const char *subdir)
{
  g_autoptr(GPtrArray) dir_paths = lookup_dirs (dirs);

  if (g_path_is_absolute (filename))
    return g_strdup (filename);

  for (guint i = 0; i < dir_paths->len; i++) {
    gchar *path = check_path (g_ptr_array_index (dir_paths, i),
                              subdir, filename);
    if (path)
      return path;
  }

  return NULL;
}

struct conffile_iterator_data
{
  GList *sorted_keys;
  GList *ptr;
  GHashTable *ht;
};

static void
conffile_iterator_reset (WpIterator *it)
{
  struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it);
  it_data->ptr = it_data->sorted_keys;
}

static gboolean
conffile_iterator_next (WpIterator *it, GValue *item)
{
  struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it);

  if (it_data->ptr) {
    const gchar *path = g_hash_table_lookup (it_data->ht, it_data->ptr->data);
    it_data->ptr = g_list_next (it_data->ptr);
    g_value_init (item, G_TYPE_STRING);
    g_value_set_string (item, path);
    return TRUE;
  }
  return FALSE;
}

static gboolean
conffile_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret,
    gpointer data)
{
  struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it);

  for (GList *ptr = it_data->sorted_keys; ptr != NULL; ptr = g_list_next (ptr)) {
    g_auto (GValue) item = G_VALUE_INIT;
    const gchar *path = g_hash_table_lookup (it_data->ht, ptr->data);
    g_value_init (&item, G_TYPE_STRING);
    g_value_set_string (&item, path);
    if (!func (&item, ret, data))
      return FALSE;
  }
  return TRUE;
}

static void
conffile_iterator_finalize (WpIterator *it)
{
  struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it);
  g_list_free (it_data->sorted_keys);
  g_hash_table_unref (it_data->ht);
}

static const WpIteratorMethods conffile_iterator_methods = {
  .version = WP_ITERATOR_METHODS_VERSION,
  .reset = conffile_iterator_reset,
  .next = conffile_iterator_next,
  .fold = conffile_iterator_fold,
  .finalize = conffile_iterator_finalize,
};

/*!
 * \brief Creates an iterator to iterate over configuration files in the
 * \a subdir of the configuration directories
 *
 * Files are sorted across the hierarchy of configuration and data
 * directories with files in higher-priority directories shadowing files in
 * lower-priority directories. Files are only checked for existence, a
 * caller must be able to handle read errors.
 *
 * \note the iterator may contain directories too; it is the responsibility
 * of the caller to ignore or recurse into those.
 *
 * \ingroup wp
 * \param dirs the directories to look into
 * \param subdir (nullable): the name of the subdirectory to search in,
 *   inside the configuration directories
 * \param suffix (nullable): The filename suffix, NULL matches all entries
 * \returns (transfer full): a new iterator iterating over strings which are
 *   absolute paths to the configuration files found
 * \since 0.4.2
 */
WpIterator *
wp_new_files_iterator (WpLookupDirs dirs, const gchar *subdir,
    const gchar *suffix)
{
  g_autoptr (GHashTable) ht =
      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
  g_autoptr (GPtrArray) dir_paths = NULL;

  if (subdir == NULL)
    subdir = ".";

  /* Note: this list is highest-priority first */
  dir_paths = lookup_dirs (dirs);

  /* Store all filenames with their full path in the hashtable, overriding
   * previous values. We need to run backwards through the list for that */
  for (guint i = dir_paths->len; i > 0; i--) {
    g_autofree gchar *dirpath =
        g_build_filename (g_ptr_array_index (dir_paths, i - 1), subdir, NULL);
    g_autoptr(GDir) dir = g_dir_open (dirpath, 0, NULL);

    wp_trace ("searching config dir: %s", dirpath);

    if (dir) {
      const gchar *filename;
      while ((filename = g_dir_read_name (dir))) {
        if (filename[0] == '.')
          continue;

        if (suffix && !g_str_has_suffix (filename, suffix))
          continue;

        g_hash_table_replace (ht, g_strdup (filename),
            g_build_filename (dirpath, filename, NULL));
      }
    }
  }

  /* Sort by filename */
  GList *keys = g_hash_table_get_keys (ht);
  keys = g_list_sort (keys, (GCompareFunc)g_strcmp0);

  /* Construct iterator */
  WpIterator *it = wp_iterator_new (&conffile_iterator_methods,
      sizeof (struct conffile_iterator_data));
  struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it);
  it_data->sorted_keys = keys;
  it_data->ht = g_hash_table_ref (ht);
  return g_steal_pointer (&it);
}
  0707010000009D000081A4000000000000000000000001656CC35F00000A20000000000000000000000000000000000000001F00000000wireplumber-0.4.17/lib/wp/wp.h    /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_WP_H__
#define __WIREPLUMBER_WP_H__

#include "client.h"
#include "component-loader.h"
#include "core.h"
#include "dbus.h"
#include "device.h"
#include "endpoint.h"
#include "error.h"
#include "global-proxy.h"
#include "iterator.h"
#include "link.h"
#include "log.h"
#include "metadata.h"
#include "module.h"
#include "node.h"
#include "object-interest.h"
#include "object-manager.h"
#include "object.h"
#include "plugin.h"
#include "port.h"
#include "properties.h"
#include "proxy.h"
#include "proxy-interfaces.h"
#include "session-item.h"
#include "si-factory.h"
#include "si-interfaces.h"
#include "spa-json.h"
#include "spa-pod.h"
#include "spa-type.h"
#include "state.h"
#include "transition.h"
#include "wpenums.h"
#include "wpversion.h"
#include "factory.h"

G_BEGIN_DECLS

/*!
 * \ingroup wp
 * Flags for wp_init()
 */
typedef enum {
  /*! Initialize PipeWire by calling pw_init() */
  WP_INIT_PIPEWIRE = (1<<0),
  /*! Initialize support for dynamic spa types.
   * See wp_spa_dynamic_type_init() */
  WP_INIT_SPA_TYPES = (1<<1),
  /*! Override PipeWire's logging system with WirePlumber's one */
  WP_INIT_SET_PW_LOG = (1<<2),
  /*! Set wp_log_writer_default() as GLib's default log writer function */
  WP_INIT_SET_GLIB_LOG = (1<<3),
  /*! Initialize all of the above */
  WP_INIT_ALL = 0xf,
} WpInitFlags;

WP_API
void wp_init (WpInitFlags flags);

WP_API
const char * wp_get_library_version (void);

WP_API
const char * wp_get_library_api_version (void);

WP_API
const gchar * wp_get_module_dir (void);

WP_API
G_DEPRECATED_FOR (wp_find_file)
const gchar * wp_get_config_dir (void);

WP_API
G_DEPRECATED_FOR (wp_find_file)
const gchar * wp_get_data_dir (void);

/*!
 * \brief Flags to specify lookup directories
 * \ingroup wp
 */
typedef enum { /*< flags >*/
  WP_LOOKUP_DIR_ENV_CONFIG = (1 << 0),       /*!< $WIREPLUMBER_CONFIG_DIR */
  WP_LOOKUP_DIR_ENV_DATA = (1 << 1),         /*!< $WIREPLUMBER_DATA_DIR */

  WP_LOOKUP_DIR_XDG_CONFIG_HOME = (1 << 10), /*!< XDG_CONFIG_HOME/wireplumber */
  WP_LOOKUP_DIR_ETC = (1 << 11),             /*!< ($prefix)/etc/wireplumber */
  WP_LOOKUP_DIR_PREFIX_SHARE = (1 << 12),    /*!< $prefix/share/wireplumber */
} WpLookupDirs;

WP_API
gchar * wp_find_file (WpLookupDirs dirs, const gchar *filename,
    const char *subdir);

WP_API
WpIterator * wp_new_files_iterator (WpLookupDirs dirs, const gchar *subdir,
    const gchar *suffix);

G_END_DECLS

#endif
0707010000009E000081A4000000000000000000000001656CC35F00000058000000000000000000000000000000000000002900000000wireplumber-0.4.17/lib/wp/wpversion.h.in  #define WIREPLUMBER_VERSION "@version@"
#define WIREPLUMBER_API_VERSION "@api_version@"
0707010000009F000081A4000000000000000000000001656CC35F00001561000000000000000000000000000000000000001F00000000wireplumber-0.4.17/meson.build    project('wireplumber', ['c'],
  version : '0.4.17',
  license : 'MIT',
  meson_version : '>= 0.59.0',
  default_options : [
    'warning_level=1',
    'buildtype=debugoptimized',
  ]
)

wireplumber_api_version = '0.4'
wireplumber_so_version = '0'

wireplumber_headers_dir = get_option('includedir') / 'wireplumber-' + wireplumber_api_version / 'wp'

wireplumber_bin_dir = get_option('prefix') / get_option('bindir')
wireplumber_module_dir = get_option('prefix') / get_option('libdir') / 'wireplumber-' + wireplumber_api_version
wireplumber_data_dir = get_option('prefix') / get_option('datadir') / 'wireplumber'
wireplumber_config_dir = get_option('prefix') / get_option('sysconfdir') / 'wireplumber'
wireplumber_locale_dir = get_option('prefix') / get_option('localedir')

cc = meson.get_compiler('c')

glib_req_version = '>= 2.62'
add_project_arguments([
    '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_62',
    '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_62',
  ], language: 'c'
)

add_project_arguments([
    '-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()),
  ], language: 'c'
)

build_modules = get_option('modules')
build_daemon = get_option('daemon')
if build_daemon and not build_modules
  error('\'modules\' option is required to be true when the \'daemon\' option is true')
endif
build_tools = get_option('tools')
if build_tools and not build_modules
  error('\'modules\' option is required to be true when the \'tools\' option is enabled')
endif

glib_dep = dependency('glib-2.0', version : glib_req_version)
gobject_dep = dependency('gobject-2.0', version : glib_req_version)
gmodule_dep = dependency('gmodule-2.0', version : glib_req_version)
gio_dep = dependency('gio-2.0', version : glib_req_version)
giounix_dep = dependency('gio-unix-2.0', version : glib_req_version)
spa_dep = dependency('libspa-0.2', version: '>= 0.2')
pipewire_dep = dependency('libpipewire-0.3', version: '>= 0.3.68')
mathlib = cc.find_library('m')
threads_dep = dependency('threads')
libintl_dep = dependency('intl')

if build_modules
  system_lua = get_option('system-lua')
  if system_lua
    if get_option('system-lua-version') != 'auto'
      lua_version_requested = get_option('system-lua-version')
      lua_dep = dependency('lua-' + lua_version_requested, required: false)
      if not lua_dep.found()
        lua_dep = dependency('lua' + lua_version_requested, required: false)
      endif

      if not lua_dep.found()
        error('Specified Lua version "' + lua_version_requested + '" not found')
      endif
    else
      lua_dep = dependency('lua-5.4', required: false)
      if not lua_dep.found()
        lua_dep = dependency('lua5.4', required: false)
      endif
      if not lua_dep.found()
        lua_dep = dependency('lua54', required: false)
      endif
      if not lua_dep.found()
        lua_dep = dependency('lua-5.3', required: false)
      endif
      if not lua_dep.found()
        lua_dep = dependency('lua5.3', required: false)
      endif
      if not lua_dep.found()
        lua_dep = dependency('lua53', required: false)
      endif
      if not lua_dep.found()
        lua_dep = dependency('lua', version: ['>=5.3.0'], required: false)
      endif
      if not lua_dep.found()
        error ('Could not find lua. Lua version 5.4 or 5.3 required')
      endif
    endif
  else
    lua_proj = subproject('lua', default_options: ['default_library=static'])
    lua_dep = lua_proj.get_variable('lua_dep')
  endif
  summary({'Lua version': lua_dep.version() + (system_lua ? ' (system)' : ' (built-in)')})
endif

if build_modules
  systemd = dependency('systemd', required: get_option('systemd'))
  libsystemd_dep = dependency('libsystemd',required: get_option('systemd'))
  libelogind_dep = dependency('libelogind', required: get_option('elogind'))
  summary({'systemd conf data': systemd.found(),
          'libsystemd': libsystemd_dep.found(),
          'libelogind': libelogind_dep.found()}, bool_yn: true)
endif

gnome = import('gnome')
pkgconfig = import('pkgconfig')
fs = import('fs')

wp_lib_include_dir = include_directories('lib')

common_flags = [
  '-fvisibility=hidden',
  '-Wsuggest-attribute=format',
  '-Wsign-compare',
  '-Wpointer-arith',
  '-Wpointer-sign',
  '-Wformat',
  '-Wformat-security',
  '-Wimplicit-fallthrough',
  '-Wmissing-braces',
  '-Wtype-limits',
  '-Wvariadic-macros',
  '-Wno-missing-field-initializers',
  '-Wno-unused-parameter',
  '-Wno-pedantic',
  '-Wold-style-declaration',
  '-Wunused-result',
]
add_project_arguments(cc.get_supported_arguments(common_flags), language: 'c')

subdir('lib')
subdir('docs')
if build_modules
  subdir('modules')
endif
subdir('src')
subdir('po')

if get_option('tests')
  subdir('tests')
endif

builddir = meson.project_build_root()
srcdir = meson.project_source_root()

conf_uninstalled = configuration_data()
conf_uninstalled.set('MESON', '')
conf_uninstalled.set('MESON_SOURCE_ROOT', srcdir)
conf_uninstalled.set('MESON_BUILD_ROOT', builddir)

wp_uninstalled = configure_file(
  input : 'wp-uninstalled.sh',
  output : 'wp-uninstalled.sh.in',
  configuration : conf_uninstalled,
)

wireplumber_uninstalled = custom_target('wp-uninstalled',
  output : 'wp-uninstalled.sh',
  input : wp_uninstalled,
  build_by_default : true,
  command : ['cp', '@INPUT@', '@OUTPUT@'],
)

devenv = environment({
  'WIREPLUMBER_MODULE_DIR': builddir / 'modules',
  'WIREPLUMBER_CONFIG_DIR': srcdir / 'src' / 'config',
  'WIREPLUMBER_DATA_DIR': srcdir / 'src',
})

meson.add_devenv(devenv)
   070701000000A0000081A4000000000000000000000001656CC35F00000743000000000000000000000000000000000000002500000000wireplumber-0.4.17/meson_options.txt  option('introspection', type : 'feature', value : 'auto',
       description : 'Generate gobject-introspection bindings')
option('doc', type : 'feature', value : 'auto',
       description: 'Enable documentation.')
option('modules', type : 'boolean', value: 'true',
       description : 'Build modules')
option('daemon', type : 'boolean', value: 'true',
       description : 'Build session manager daemon')
option('tools', type : 'boolean', value: 'true',
       description : 'Build CLI tools')
option('system-lua', type : 'boolean', value : 'false',
       description : 'Use lua from the system instead of the bundled one')
option('system-lua-version',
       type: 'string', value : 'auto',
       description: 'The system lua version to use or "auto" for auto-detection')
option('elogind',
	type: 'feature', value : 'auto',
	description: 'Enable elogind integration')
option('systemd',
       type: 'feature', value: 'auto',
       description: 'Enable installing systemd units & logind integration')
option('systemd-system-service',
       type : 'boolean', value : false,
       description: 'Install systemd system service file')
option('systemd-user-service',
       type : 'boolean', value : true,
       description: 'Install systemd user service file')
option('systemd-system-unit-dir',
       type : 'string',
       description : 'Directory for system systemd units')
option('systemd-user-unit-dir',
       type : 'string',
       description : 'Directory for user systemd units')
option('glib-supp', type : 'string', value : '',
       description: 'The glib.supp valgrind suppressions file to be used when running valgrind')
option('tests', type : 'boolean', value : 'true',
       description : 'Build the test suite')
option('dbus-tests', type : 'boolean', value : 'true',
       description: 'Enable running tests that need a dbus-daemon')
 070701000000A1000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001B00000000wireplumber-0.4.17/modules    070701000000A2000081A4000000000000000000000001656CC35F00001102000000000000000000000000000000000000002700000000wireplumber-0.4.17/modules/meson.build    common_c_args = [
  '-D_GNU_SOURCE',
  '-DG_LOG_USE_STRUCTURED',
]

shared_library(
  'wireplumber-module-metadata',
  [
    'module-metadata.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-metadata"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep],
)

shared_library(
  'wireplumber-module-default-profile',
  [
    'module-default-profile.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-default-profile"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep],
)

shared_library(
  'wireplumber-module-default-nodes',
  [
    'module-default-nodes.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-default-nodes"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep],
)

shared_library(
  'wireplumber-module-default-nodes-api',
  [
    'module-default-nodes-api.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-default-nodes-api"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep],
)

subdir('module-reserve-device')
shared_library(
  'wireplumber-module-reserve-device',
  [
    'module-reserve-device/plugin.c',
    'module-reserve-device/reserve-device.c',
    'module-reserve-device/transitions.c',
    reserve_device_interface_src,
    reserve_device_enums,
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-reserve-device"'],
  include_directories: reserve_device_includes,
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, giounix_dep],
)

shared_library(
  'wireplumber-module-portal-permissionstore',
  [
    'module-portal-permissionstore.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-portal-permissionstore"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, giounix_dep],
)

shared_library(
  'wireplumber-module-si-audio-adapter',
  [
    'module-si-audio-adapter.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-audio-adapter"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep],
)

shared_library(
  'wireplumber-module-si-audio-endpoint',
  [
    'module-si-audio-endpoint.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-audio-endpoint"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep],
)

shared_library(
  'wireplumber-module-si-node',
  [
    'module-si-node.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-node"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep],
)

shared_library(
  'wireplumber-module-si-standard-link',
  [
    'module-si-standard-link.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-standard-link"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep],
)

subdir('module-lua-scripting')
shared_library(
  'wireplumber-module-lua-scripting',
  [
    'module-lua-scripting/module.c',
    'module-lua-scripting/script.c',
    'module-lua-scripting/api/pod.c',
    'module-lua-scripting/api/json.c',
    'module-lua-scripting/api/api.c',
    'module-lua-scripting/api/config.c',
     m_lua_scripting_resources,
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-lua-scripting"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep, wplua_dep, libintl_dep],
)

shared_library(
  'wireplumber-module-mixer-api',
  [
    'module-mixer-api.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-mixer-api"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep, mathlib],
)

shared_library(
  'wireplumber-module-file-monitor-api',
  [
    'module-file-monitor-api.c',
  ],
  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-file-monitor-api"'],
  install : true,
  install_dir : wireplumber_module_dir,
  dependencies : [wp_dep, pipewire_dep],
)

if libsystemd_dep.found() or libelogind_dep.found()
  shared_library(
    'wireplumber-module-logind',
    [
      'module-logind.c',
    ],
    c_args : [common_c_args, '-DG_LOG_DOMAIN="m-logind"'],
    install : true,
    install_dir : wireplumber_module_dir,
    dependencies : [wp_dep, pipewire_dep, libsystemd_dep, libelogind_dep],
  )
endif
  070701000000A3000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000003000000000wireplumber-0.4.17/modules/module-default-nodes   070701000000A4000081A4000000000000000000000001656CC35F00002241000000000000000000000000000000000000003600000000wireplumber-0.4.17/modules/module-default-nodes-api.c /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <spa/utils/defs.h>
#include <pipewire/keys.h>
#include "module-default-nodes/common.h"

typedef struct _WpDefaultNode WpDefaultNode;
struct _WpDefaultNode
{
  gchar *value;
  gchar *config_value;
};

struct _WpDefaultNodesApi
{
  WpPlugin parent;

  WpDefaultNode defaults[N_DEFAULT_NODES];
  WpObjectManager *om;
};

enum {
  ACTION_GET_DEFAULT_NODE,
  ACTION_GET_DEFAULT_CONFIGURED_NODE_NAME,
  ACTION_SET_DEFAULT_CONFIGURED_NODE_NAME,
  SIGNAL_CHANGED,
  N_SIGNALS
};

static guint signals[N_SIGNALS] = {0};

G_DECLARE_FINAL_TYPE (WpDefaultNodesApi, wp_default_nodes_api,
                      WP, DEFAULT_NODES_API, WpPlugin)
G_DEFINE_TYPE (WpDefaultNodesApi, wp_default_nodes_api, WP_TYPE_PLUGIN)

static void
wp_default_nodes_api_init (WpDefaultNodesApi * self)
{
}

static void
sync_changed_notification (WpCore * core, GAsyncResult * res,
    WpDefaultNodesApi * self)
{
  g_autoptr (GError) error = NULL;
  if (!wp_core_sync_finish (core, res, &error)) {
    wp_warning_object (self, "core sync error: %s", error->message);
    return;
  }

  g_signal_emit (self, signals[SIGNAL_CHANGED], 0);
  return;
}

static void
schedule_changed_notification (WpDefaultNodesApi *self)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
  g_return_if_fail (core);
  wp_core_sync_closure (core, NULL, g_cclosure_new_object (
      G_CALLBACK (sync_changed_notification), G_OBJECT (self)));
}

static void
on_metadata_changed (WpMetadata *m, guint32 subject,
    const gchar *key, const gchar *type, const gchar *value, gpointer d)
{
  WpDefaultNodesApi * self = WP_DEFAULT_NODES_API (d);

  if (subject != 0)
    return;

  for (gint i = 0; i < N_DEFAULT_NODES; i++) {
    if (!g_strcmp0 (key, DEFAULT_KEY[i])) {
      g_clear_pointer (&self->defaults[i].value, g_free);

      if (value && !g_strcmp0 (type, "Spa:String:JSON")) {
        g_autoptr (WpSpaJson) json = wp_spa_json_new_from_string (value);
        g_autofree gchar *name = NULL;
        if (wp_spa_json_object_get (json, "name", "s", &name, NULL))
          self->defaults[i].value = g_strdup (name);
      }

      wp_debug_object (m, "changed '%s' -> '%s'", key,
          self->defaults[i].value);

      schedule_changed_notification (self);
      break;
    } else if (!g_strcmp0 (key, DEFAULT_CONFIG_KEY[i])) {
      g_clear_pointer (&self->defaults[i].config_value, g_free);

      if (value && !g_strcmp0 (type, "Spa:String:JSON")) {
        g_autoptr (WpSpaJson) json = wp_spa_json_new_from_string (value);
        g_autofree gchar *name = NULL;
        if (wp_spa_json_object_get (json, "name", "s", &name, NULL))
          self->defaults[i].config_value = g_strdup (name);
      }

      wp_debug_object (m, "changed '%s' -> '%s'", key,
          self->defaults[i].config_value);
      break;
    }
  }
}

static void
on_metadata_added (WpObjectManager *om, WpObject *obj, WpDefaultNodesApi * self)
{
  if (WP_IS_METADATA (obj)) {
    g_autoptr (WpIterator) it = wp_metadata_new_iterator (WP_METADATA (obj), 0);
    g_auto (GValue) val = G_VALUE_INIT;

    for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
      guint32 subject;
      const gchar *key, *type, *value;
      wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
      on_metadata_changed (WP_METADATA (obj), subject, key, type, value, self);
    }

    g_signal_connect_object (obj, "changed",
        G_CALLBACK (on_metadata_changed), self, 0);
  }
}

static void
on_om_installed (WpObjectManager * om, WpDefaultNodesApi * self)
{
  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_default_nodes_api_enable (WpPlugin * plugin, WpTransition * transition)
{
  WpDefaultNodesApi * self = WP_DEFAULT_NODES_API (plugin);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
  g_return_if_fail (core);

  /* Create the metadata object manager */
  self->om = wp_object_manager_new ();
  wp_object_manager_add_interest (self->om, WP_TYPE_METADATA,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s", "default",
      NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_NODE, NULL);
  wp_object_manager_request_object_features (self->om,
      WP_TYPE_METADATA, WP_OBJECT_FEATURES_ALL);
  wp_object_manager_request_object_features (self->om,
      WP_TYPE_NODE, WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
  g_signal_connect_object (self->om, "object-added",
      G_CALLBACK (on_metadata_added), self, 0);
  g_signal_connect_object (self->om, "installed",
      G_CALLBACK (on_om_installed), self, 0);
  wp_core_install_object_manager (core, self->om);
}

static void
wp_default_nodes_api_disable (WpPlugin * plugin)
{
  WpDefaultNodesApi * self = WP_DEFAULT_NODES_API (plugin);

  for (guint i = 0; i < N_DEFAULT_NODES; i++) {
    g_clear_pointer (&self->defaults[i].value, g_free);
    g_clear_pointer (&self->defaults[i].config_value, g_free);
  }
  g_clear_object (&self->om);
}

static guint
wp_default_nodes_api_get_default_node (WpDefaultNodesApi * self,
    const gchar * media_class)
{
  gint node_t = -1;
  for (gint i = 0; i < N_DEFAULT_NODES; i++) {
    if (!g_strcmp0 (media_class, NODE_TYPE_STR[i])) {
      node_t = i;
      break;
    }
  }

  if (node_t != -1 && self->defaults[node_t].value) {
    g_autoptr (WpIterator) it = NULL;
    g_auto (GValue) val = G_VALUE_INIT;
    it = wp_object_manager_new_filtered_iterator (self->om,
        WP_TYPE_NODE,
        WP_CONSTRAINT_TYPE_PW_PROPERTY,
        PW_KEY_NODE_NAME, "=s", self->defaults[node_t].value,
        NULL);
    for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
      WpNode *node = g_value_get_object (&val);
      const gchar *mc = wp_pipewire_object_get_property (
          WP_PIPEWIRE_OBJECT (node), PW_KEY_MEDIA_CLASS);
      if (!g_str_has_prefix (mc, "Stream/"))
        return wp_proxy_get_bound_id (WP_PROXY (node));
    }
  }
  return SPA_ID_INVALID;
}

static gchar *
wp_default_nodes_api_get_default_configured_node_name (WpDefaultNodesApi * self,
    const gchar * media_class)
{
  for (gint i = 0; i < N_DEFAULT_NODES; i++)
    if (!g_strcmp0 (media_class, NODE_TYPE_STR[i]) &&
        self->defaults[i].config_value)
      return g_strdup (self->defaults[i].config_value);

  return NULL;
}

static gboolean
wp_default_nodes_api_set_default_configured_node_name (WpDefaultNodesApi * self,
    const gchar * media_class, const gchar * name)
{
  g_autoptr (WpMetadata) m = wp_object_manager_lookup (self->om,
      WP_TYPE_METADATA, NULL);
  if (!m)
    return FALSE;

  for (gint i = 0; i < N_DEFAULT_NODES; i++) {
    if (!g_strcmp0 (media_class, NODE_TYPE_STR[i])) {
      if (name) {
        g_autofree gchar *v = g_strdup_printf ("{ \"name\": \"%s\" }", name);
        wp_metadata_set (m, 0, DEFAULT_CONFIG_KEY[i], "Spa:String:JSON", v);
      } else {
        wp_metadata_set (m, 0, DEFAULT_CONFIG_KEY[i], NULL, NULL);
      }
      return TRUE;
    }
  }

  return FALSE;
}

static void
wp_default_nodes_api_class_init (WpDefaultNodesApiClass * klass)
{
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  plugin_class->enable = wp_default_nodes_api_enable;
  plugin_class->disable = wp_default_nodes_api_disable;

  signals[ACTION_GET_DEFAULT_NODE] = g_signal_new_class_handler (
      "get-default-node", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_default_nodes_api_get_default_node,
      NULL, NULL, NULL,
      G_TYPE_UINT, 1, G_TYPE_STRING);

  signals[ACTION_GET_DEFAULT_CONFIGURED_NODE_NAME] = g_signal_new_class_handler (
      "get-default-configured-node-name", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_default_nodes_api_get_default_configured_node_name,
      NULL, NULL, NULL,
      G_TYPE_STRING, 1, G_TYPE_STRING);

  signals[ACTION_SET_DEFAULT_CONFIGURED_NODE_NAME] = g_signal_new_class_handler (
      "set-default-configured-node-name", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_default_nodes_api_set_default_configured_node_name,
      NULL, NULL, NULL,
      G_TYPE_BOOLEAN, 2, G_TYPE_STRING, G_TYPE_STRING);

  signals[SIGNAL_CHANGED] = g_signal_new (
      "changed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_plugin_register (g_object_new (wp_default_nodes_api_get_type (),
          "name", "default-nodes-api",
          "core", core,
          NULL));
  return TRUE;
}
   070701000000A5000081A4000000000000000000000001656CC35F000056DB000000000000000000000000000000000000003200000000wireplumber-0.4.17/modules/module-default-nodes.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <errno.h>
#include <pipewire/pipewire.h>
#include <pipewire/keys.h>

#include "module-default-nodes/common.h"

#define NAME "default-nodes"
#define DEFAULT_SAVE_INTERVAL_MS 1000
#define DEFAULT_USE_PERSISTENT_STORAGE TRUE
#define DEFAULT_AUTO_ECHO_CANCEL TRUE
#define DEFAULT_ECHO_CANCEL_SINK_NAME "echo-cancel-sink"
#define DEFAULT_ECHO_CANCEL_SOURCE_NAME "echo-cancel-source"
#define N_PREV_CONFIGS 16

enum {
  PROP_0,
  PROP_SAVE_INTERVAL_MS,
  PROP_USE_PERSISTENT_STORAGE,
  PROP_AUTO_ECHO_CANCEL,
  PROP_ECHO_CANCEL_SINK_NAME,
  PROP_ECHO_CANCEL_SOURCE_NAME,
};

typedef struct _WpDefaultNode WpDefaultNode;
struct _WpDefaultNode
{
  gchar *value;
  gchar *config_value;
  gchar *prev_config_value[N_PREV_CONFIGS];
};

struct _WpDefaultNodes
{
  WpPlugin parent;

  WpState *state;
  WpDefaultNode defaults[N_DEFAULT_NODES];
  WpObjectManager *metadata_om;
  WpObjectManager *rescan_om;
  GSource *timeout_source;

  /* properties */
  guint save_interval_ms;
  gboolean use_persistent_storage;
  gboolean auto_echo_cancel;
  gchar *echo_cancel_names[2];
};

G_DECLARE_FINAL_TYPE (WpDefaultNodes, wp_default_nodes,
                      WP, DEFAULT_NODES, WpPlugin)
G_DEFINE_TYPE (WpDefaultNodes, wp_default_nodes, WP_TYPE_PLUGIN)

static void
wp_default_nodes_init (WpDefaultNodes * self)
{
}

static void
clear_prev_config_values (WpDefaultNode *def)
{
  for (gint i = 0; i < N_PREV_CONFIGS; i++)
    g_clear_pointer (&def->prev_config_value[i], g_free);
}

static void
update_prev_config_values (WpDefaultNode *def)
{
  gint pos = N_PREV_CONFIGS - 1;

  if (!def->config_value)
    return;

  /* Find if the current configured value is already in the stack */
  for (gint i = 0; i < N_PREV_CONFIGS; ++i) {
    if (!g_strcmp0(def->config_value, def->prev_config_value[i])) {
      pos = i;
      break;
    }
  }

  if (pos == 0)
    return;

  /* Insert on top position */
  g_clear_pointer (&def->prev_config_value[pos], g_free);

  for (gint i = pos; i > 0; --i)
    def->prev_config_value[i] = def->prev_config_value[i-1];

  def->prev_config_value[0] = g_strdup(def->config_value);
}

static void
load_state (WpDefaultNodes * self)
{
  g_autoptr (WpProperties) props = wp_state_load (self->state);
  for (gint i = 0; i < N_DEFAULT_NODES; i++) {
    const gchar *value = wp_properties_get (props, DEFAULT_CONFIG_KEY[i]);

    self->defaults[i].config_value = g_strdup (value);

    for (gint j = 0; j < N_PREV_CONFIGS; ++j) {
      g_autofree gchar *key = g_strdup_printf("%s.%d", DEFAULT_CONFIG_KEY[i], j);

      value = wp_properties_get (props, key);
      self->defaults[i].prev_config_value[j] = g_strdup(value);
    }
  }
}

static gboolean
timeout_save_state_callback (WpDefaultNodes *self)
{
  g_autoptr (WpProperties) props = wp_properties_new_empty ();
  g_autoptr (GError) error = NULL;

  for (gint i = 0; i < N_DEFAULT_NODES; i++) {
    if (self->defaults[i].config_value)
      wp_properties_set (props, DEFAULT_CONFIG_KEY[i],
          self->defaults[i].config_value);

    for (gint j = 0; j < N_PREV_CONFIGS; ++j) {
      g_autofree gchar *key = g_strdup_printf("%s.%d", DEFAULT_CONFIG_KEY[i], j);

      wp_properties_set (props, key, self->defaults[i].prev_config_value[j]);
    }
  }

  if (!wp_state_save (self->state, props, &error))
    wp_warning_object (self, "%s", error->message);

  g_clear_pointer (&self->timeout_source, g_source_unref);
  return G_SOURCE_REMOVE;
}

static void
timer_start (WpDefaultNodes *self)
{
  if (!self->timeout_source && self->use_persistent_storage) {
    g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
    g_return_if_fail (core);

    /* Add the timeout callback */
    wp_core_timeout_add_closure (core, &self->timeout_source,
        self->save_interval_ms, g_cclosure_new_object (
            G_CALLBACK (timeout_save_state_callback), G_OBJECT (self)));
  }
}

static gboolean
node_has_available_routes (WpDefaultNodes * self, WpNode *node)
{
  const gchar *dev_id_str = wp_pipewire_object_get_property (
          WP_PIPEWIRE_OBJECT (node), PW_KEY_DEVICE_ID);
  const gchar *cpd_str = wp_pipewire_object_get_property (
          WP_PIPEWIRE_OBJECT (node), "card.profile.device");
  gint dev_id = dev_id_str ? atoi (dev_id_str) : -1;
  gint cpd = cpd_str ? atoi (cpd_str) : -1;
  g_autoptr (WpDevice) device = NULL;
  gint found = 0;

  if (dev_id == -1 || cpd == -1)
    return TRUE;

  /* Get the device */
  device = wp_object_manager_lookup (self->rescan_om, WP_TYPE_DEVICE,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=i", dev_id, NULL);
  if (!device)
    return TRUE;

  /* Check if the current device route supports the node card device profile */
  {
    g_autoptr (WpIterator) routes = NULL;
    g_auto (GValue) val = G_VALUE_INIT;
    routes = wp_pipewire_object_enum_params_sync (WP_PIPEWIRE_OBJECT (device),
        "Route", NULL);
    for (; wp_iterator_next (routes, &val); g_value_unset (&val)) {
      WpSpaPod *route = g_value_get_boxed (&val);
      gint route_device = -1;
      guint32 route_avail = SPA_PARAM_AVAILABILITY_unknown;

      if (!wp_spa_pod_get_object (route, NULL,
          "device", "i", &route_device,
          "available", "?I", &route_avail,
          NULL))
        continue;

      if (route_device != cpd)
        continue;

      if (route_avail == SPA_PARAM_AVAILABILITY_no)
        return FALSE;

      return TRUE;
    }
  }

  /* Check if available routes support the node card device profile */
  {
    g_autoptr (WpIterator) routes = NULL;
    g_auto (GValue) val = G_VALUE_INIT;
    routes = wp_pipewire_object_enum_params_sync (WP_PIPEWIRE_OBJECT (device),
        "EnumRoute", NULL);
    for (; wp_iterator_next (routes, &val); g_value_unset (&val)) {
      WpSpaPod *route = g_value_get_boxed (&val);
      guint32 route_avail = SPA_PARAM_AVAILABILITY_unknown;
      g_autoptr (WpSpaPod) route_devices = NULL;

      if (!wp_spa_pod_get_object (route, NULL,
          "available", "?I", &route_avail,
          "devices", "?P", &route_devices,
          NULL))
        continue;

      {
        g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (route_devices);
        g_auto (GValue) v = G_VALUE_INIT;
        for (; wp_iterator_next (it, &v); g_value_unset (&v)) {
          gint32 *d = (gint32 *)g_value_get_pointer (&v);
          if (d && *d == cpd) {
            found++;
            if (route_avail != SPA_PARAM_AVAILABILITY_no)
              return TRUE;
          }
        }
      }
    }
  }
  /* The node is part of a profile without routes so we assume it
   * is available. This can happen for Pro Audio profiles */
  if (found == 0)
    return TRUE;

  return FALSE;
}

static gboolean
is_echo_cancel_node (WpDefaultNodes * self, WpNode *node, WpDirection direction)
{
  const gchar *name = wp_pipewire_object_get_property (
      WP_PIPEWIRE_OBJECT (node), PW_KEY_NODE_NAME);
  const gchar *virtual_str = wp_pipewire_object_get_property (
      WP_PIPEWIRE_OBJECT (node), PW_KEY_NODE_VIRTUAL);
  gboolean virtual = virtual_str && pw_properties_parse_bool (virtual_str);

  if (!name || !virtual)
    return FALSE;

  return g_strcmp0 (name, self->echo_cancel_names[direction]) == 0;
}

static WpNode *
find_best_media_class_node (WpDefaultNodes * self, const gchar *media_class,
    const WpDefaultNode *def, WpDirection direction, gint *priority)
{
  g_autoptr (WpIterator) it = NULL;
  g_auto (GValue) val = G_VALUE_INIT;
  gint highest_prio = 0;
  WpNode *res = NULL;

  g_return_val_if_fail (media_class, NULL);

  it = wp_object_manager_new_filtered_iterator (self->rescan_om, WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "=s", media_class,
      NULL);

  for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
    WpNode *node = g_value_get_object (&val);
    g_autoptr (WpPort) port = wp_object_manager_lookup (self->rescan_om,
          WP_TYPE_PORT, WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_NODE_ID,
          "=u", wp_proxy_get_bound_id (WP_PROXY (node)),
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_PORT_DIRECTION,
          "=s", direction == WP_DIRECTION_INPUT ? "in" : "out",
          NULL);
    if (port) {
      const gchar *name = wp_pipewire_object_get_property (
          WP_PIPEWIRE_OBJECT (node), PW_KEY_NODE_NAME);
      const gchar *prio_str = wp_pipewire_object_get_property (
          WP_PIPEWIRE_OBJECT (node), PW_KEY_PRIORITY_SESSION);
      gint prio = prio_str ? atoi (prio_str) : -1;

      if (!node_has_available_routes (self, node))
        continue;

      if (self->auto_echo_cancel && is_echo_cancel_node (self, node, direction))
        prio += 10000;

      if (name && def->config_value && g_strcmp0 (name, def->config_value) == 0) {
        prio += 20000 * (N_PREV_CONFIGS + 1);
      } else if (name) {
        for (gint i = 0; i < N_PREV_CONFIGS; ++i) {
          if (!def->prev_config_value[i])
            continue;

          /* Match by name */
          if (g_strcmp0 (name, def->prev_config_value[i]) == 0) {
            prio += (N_PREV_CONFIGS - i) * 20000;
            break;
          }
        }
      }

      if (prio > highest_prio || res == NULL) {
        highest_prio = prio;
        res = node;
      }
    }
  }

  if (priority)
    *priority = highest_prio;
  return res;
}

static WpNode *
find_best_media_classes_node (WpDefaultNodes * self,
    const gchar **media_classes, const WpDefaultNode *def, WpDirection direction)
{
  gint highest_prio = -1;
  WpNode *res = NULL;
  for (guint i = 0; media_classes[i]; i++) {
    gint prio = -1;
    WpNode *node = find_best_media_class_node (self, media_classes[i],
        def, direction, &prio);
    if (node && (!res || prio > highest_prio)) {
      highest_prio = prio;
      res = node;
    }
  }
  return res;
}

static WpNode *
find_best_node (WpDefaultNodes * self, gint node_t)
{
  const WpDefaultNode *def = &self->defaults[node_t];

  switch (node_t) {
  case AUDIO_SINK: {
    const gchar *media_classes[] = {
        "Audio/Sink",
        "Audio/Duplex",
        NULL};
    return find_best_media_classes_node (self, media_classes, def,
        WP_DIRECTION_INPUT);
  }
  case AUDIO_SOURCE: {
    const gchar *media_classes[] = {
        "Audio/Source",
        "Audio/Source/Virtual",
        "Audio/Duplex",
        "Audio/Sink",
        NULL};
    return find_best_media_classes_node (self, media_classes, def,
        WP_DIRECTION_OUTPUT);
  }
  case VIDEO_SOURCE: {
    const gchar *media_classes[] = {
        "Video/Source",
        "Video/Source/Virtual",
        NULL};
    return find_best_media_classes_node (self, media_classes, def,
        WP_DIRECTION_OUTPUT);
  }
  default:
    break;
  }

  return NULL;
}

static void
reevaluate_default_node (WpDefaultNodes * self, WpMetadata *m, gint node_t)
{
  WpNode *node = NULL;
  const gchar *node_name = NULL;

  node = find_best_node (self, node_t);
  if (node)
    node_name = wp_pipewire_object_get_property (WP_PIPEWIRE_OBJECT (node),
        PW_KEY_NODE_NAME);

  /* store it in the metadata if it was changed */
  if (node && node_name &&
      g_strcmp0 (node_name, self->defaults[node_t].value) != 0)
  {
    g_autoptr (WpSpaJson) json = NULL;

    g_free (self->defaults[node_t].value);
    self->defaults[node_t].value = g_strdup (node_name);

    wp_info_object (self, "set default node for %s: %s",
        NODE_TYPE_STR[node_t], node_name);

    json = wp_spa_json_new_object ("name", "s", node_name, NULL);
    wp_metadata_set (m, 0, DEFAULT_KEY[node_t], "Spa:String:JSON",
        wp_spa_json_get_data (json));
  } else if (!node && self->defaults[node_t].value) {
    g_clear_pointer (&self->defaults[node_t].value, g_free);
    wp_info_object (self, "unset default node for %s", NODE_TYPE_STR[node_t]);
    wp_metadata_set (m, 0, DEFAULT_KEY[node_t], NULL, NULL);
  }
}

static void
sync_rescan (WpCore * core, GAsyncResult * res, WpDefaultNodes * self)
{
  g_autoptr (WpMetadata) metadata = NULL;
  g_autoptr (GError) error = NULL;

  if (!wp_core_sync_finish (core, res, &error)) {
    wp_warning_object (self, "core sync error: %s", error->message);
    return;
  }

  /* Get the metadata */
  metadata = wp_object_manager_lookup (self->metadata_om, WP_TYPE_METADATA,
      NULL);
  if (!metadata)
    return;

  wp_trace_object (self, "re-evaluating defaults");
  reevaluate_default_node (self, metadata, AUDIO_SINK);
  reevaluate_default_node (self, metadata, AUDIO_SOURCE);
  reevaluate_default_node (self, metadata, VIDEO_SOURCE);
}

static void
schedule_rescan (WpDefaultNodes * self)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
  g_return_if_fail (core);

  wp_debug_object (self, "scheduling default nodes rescan");
  wp_core_sync_closure (core, NULL, g_cclosure_new_object (
      G_CALLBACK (sync_rescan), G_OBJECT (self)));
}

static void
on_metadata_changed (WpMetadata *m, guint32 subject,
    const gchar *key, const gchar *type, const gchar *value, gpointer d)
{
  WpDefaultNodes * self = WP_DEFAULT_NODES (d);
  gint node_t = -1;

  if (subject == 0) {
    for (gint i = 0; i < N_DEFAULT_NODES; i++) {
      if (!g_strcmp0 (key, DEFAULT_CONFIG_KEY[i])) {
        node_t = i;
        break;
      }
    }
  }

  if (node_t != -1) {
    g_clear_pointer (&self->defaults[node_t].config_value, g_free);

    if (value && !g_strcmp0 (type, "Spa:String:JSON")) {
      g_autoptr (WpSpaJson) json = wp_spa_json_new_from_string (value);
      g_autofree gchar *name = NULL;
      if (wp_spa_json_object_get (json, "name", "s", &name, NULL)) {
        self->defaults[node_t].config_value = g_strdup (name);
        update_prev_config_values (&self->defaults[node_t]);
      }
    } else if (!value) {
      clear_prev_config_values (&self->defaults[node_t]);
    }

    wp_debug_object (m, "changed '%s' -> '%s'", key,
        self->defaults[node_t].config_value);

    /* schedule rescan */
    schedule_rescan (self);

    /* Save state after specific interval */
    timer_start (self);
  }
}

static void
on_object_added (WpObjectManager *om, WpPipewireObject *proxy, gpointer d)
{
  WpDefaultNodes * self = WP_DEFAULT_NODES (d);

  if (WP_IS_DEVICE (proxy)) {
    g_signal_connect_object (proxy, "params-changed",
        G_CALLBACK (schedule_rescan), self, G_CONNECT_SWAPPED);
  }
}

static void
on_metadata_added (WpObjectManager *om, WpMetadata *metadata, gpointer d)
{
  WpDefaultNodes * self = WP_DEFAULT_NODES (d);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
  g_return_if_fail (core);

  for (gint i = 0; i < N_DEFAULT_NODES; i++) {
    if (self->defaults[i].config_value) {
      g_autoptr (WpSpaJson) json = wp_spa_json_new_object (
          "name", "s", self->defaults[i].config_value, NULL);
      wp_metadata_set (metadata, 0, DEFAULT_CONFIG_KEY[i], "Spa:String:JSON",
          wp_spa_json_get_data (json));
    }
  }

  /* Handle the changed signal */
  g_signal_connect_object (metadata, "changed",
      G_CALLBACK (on_metadata_changed), self, 0);

  /* Create the rescan object manager */
  self->rescan_om = wp_object_manager_new ();
  wp_object_manager_add_interest (self->rescan_om, WP_TYPE_DEVICE, NULL);
  wp_object_manager_add_interest (self->rescan_om, WP_TYPE_NODE, NULL);
  wp_object_manager_add_interest (self->rescan_om, WP_TYPE_PORT, NULL);
  wp_object_manager_request_object_features (self->rescan_om, WP_TYPE_DEVICE,
      WP_OBJECT_FEATURES_ALL);
  wp_object_manager_request_object_features (self->rescan_om, WP_TYPE_NODE,
      WP_OBJECT_FEATURES_ALL);
  wp_object_manager_request_object_features (self->rescan_om, WP_TYPE_PORT,
      WP_OBJECT_FEATURES_ALL);
  g_signal_connect_object (self->rescan_om, "objects-changed",
      G_CALLBACK (schedule_rescan), self, G_CONNECT_SWAPPED);
  g_signal_connect_object (self->rescan_om, "object-added",
      G_CALLBACK (on_object_added), self, 0);
  wp_core_install_object_manager (core, self->rescan_om);
}

static void
wp_default_nodes_enable (WpPlugin * plugin, WpTransition * transition)
{
  WpDefaultNodes * self = WP_DEFAULT_NODES (plugin);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
  g_return_if_fail (core);

  if (self->use_persistent_storage) {
    self->state = wp_state_new (NAME);
    load_state (self);
  }

  /* Create the metadata object manager */
  self->metadata_om = wp_object_manager_new ();
  wp_object_manager_add_interest (self->metadata_om, WP_TYPE_METADATA,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s", "default",
      NULL);
  wp_object_manager_request_object_features (self->metadata_om,
      WP_TYPE_METADATA, WP_OBJECT_FEATURES_ALL);
  g_signal_connect_object (self->metadata_om, "object-added",
      G_CALLBACK (on_metadata_added), self, 0);
  wp_core_install_object_manager (core, self->metadata_om);

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_default_nodes_disable (WpPlugin * plugin)
{
  WpDefaultNodes * self = WP_DEFAULT_NODES (plugin);

  /* Clear the current timeout callback */
  if (self->timeout_source)
      g_source_destroy (self->timeout_source);
  g_clear_pointer (&self->timeout_source, g_source_unref);

  for (guint i = 0; i < N_DEFAULT_NODES; i++) {
    g_clear_pointer (&self->defaults[i].value, g_free);
    g_clear_pointer (&self->defaults[i].config_value, g_free);

    for (guint j = 0; j < N_PREV_CONFIGS; j++)
      g_clear_pointer (&self->defaults[i].prev_config_value[j], g_free);
  }

  g_clear_object (&self->metadata_om);
  g_clear_object (&self->rescan_om);
  g_clear_object (&self->state);
}

static void
wp_default_nodes_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpDefaultNodes * self = WP_DEFAULT_NODES (object);

  switch (property_id) {
  case PROP_SAVE_INTERVAL_MS:
    self->save_interval_ms = g_value_get_uint (value);
    break;
  case PROP_USE_PERSISTENT_STORAGE:
    self->use_persistent_storage =  g_value_get_boolean (value);
    break;
  case PROP_AUTO_ECHO_CANCEL:
    self->auto_echo_cancel = g_value_get_boolean (value);
    break;
  case PROP_ECHO_CANCEL_SINK_NAME:
    g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_INPUT], g_free);
    self->echo_cancel_names[WP_DIRECTION_INPUT] = g_value_dup_string (value);
    break;
  case PROP_ECHO_CANCEL_SOURCE_NAME:
    g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_OUTPUT], g_free);
    self->echo_cancel_names[WP_DIRECTION_OUTPUT] = g_value_dup_string (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_default_nodes_finalize (GObject * object)
{
  WpDefaultNodes * self = WP_DEFAULT_NODES (object);

  g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_INPUT], g_free);
  g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_OUTPUT], g_free);

  G_OBJECT_CLASS (wp_default_nodes_parent_class)->finalize (object);
}

static void
wp_default_nodes_class_init (WpDefaultNodesClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  object_class->finalize = wp_default_nodes_finalize;
  object_class->set_property = wp_default_nodes_set_property;

  plugin_class->enable = wp_default_nodes_enable;
  plugin_class->disable = wp_default_nodes_disable;

  g_object_class_install_property (object_class, PROP_SAVE_INTERVAL_MS,
      g_param_spec_uint ("save-interval-ms", "save-interval-ms",
          "save-interval-ms", 1, G_MAXUINT32, DEFAULT_SAVE_INTERVAL_MS,
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_USE_PERSISTENT_STORAGE,
      g_param_spec_boolean ("use-persistent-storage", "use-persistent-storage",
          "use-persistent-storage", DEFAULT_USE_PERSISTENT_STORAGE,
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_AUTO_ECHO_CANCEL,
      g_param_spec_boolean ("auto-echo-cancel", "auto-echo-cancel",
          "auto-echo-cancel", DEFAULT_AUTO_ECHO_CANCEL,
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_ECHO_CANCEL_SINK_NAME,
      g_param_spec_string ("echo-cancel-sink-name", "echo-cancel-sink-name",
          "echo-cancel-sink-name", DEFAULT_ECHO_CANCEL_SINK_NAME,
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_ECHO_CANCEL_SOURCE_NAME,
      g_param_spec_string ("echo-cancel-source-name", "echo-cancel-source-name",
          "echo-cancel-source-name", DEFAULT_ECHO_CANCEL_SOURCE_NAME,
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  guint save_interval_ms = DEFAULT_SAVE_INTERVAL_MS;
  gboolean use_persistent_storage = DEFAULT_USE_PERSISTENT_STORAGE;
  gboolean auto_echo_cancel = DEFAULT_AUTO_ECHO_CANCEL;
  const gchar *echo_cancel_sink_name = DEFAULT_ECHO_CANCEL_SINK_NAME;
  const gchar *echo_cancel_source_name = DEFAULT_ECHO_CANCEL_SOURCE_NAME;

  if (args) {
    g_variant_lookup (args, "save-interval-ms", "u", &save_interval_ms);
    g_variant_lookup (args, "use-persistent-storage", "b",
        &use_persistent_storage);
    g_variant_lookup (args, "auto-echo-cancel", "&s", &auto_echo_cancel);
    g_variant_lookup (args, "echo-cancel-sink-name", "&s",
        &echo_cancel_sink_name);
    g_variant_lookup (args, "echo-cancel-source-name", "&s",
        &echo_cancel_source_name);
  }

  wp_plugin_register (g_object_new (wp_default_nodes_get_type (),
          "name", NAME,
          "core", core,
          "save-interval-ms", save_interval_ms,
          "use-persistent-storage", use_persistent_storage,
          "auto-echo-cancel", auto_echo_cancel,
          "echo-cancel-sink-name", echo_cancel_sink_name,
          "echo-cancel-source-name", echo_cancel_source_name,
          NULL));
  return TRUE;
}
 070701000000A6000081A4000000000000000000000001656CC35F00000323000000000000000000000000000000000000003900000000wireplumber-0.4.17/modules/module-default-nodes/common.h  /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

enum {
  AUDIO_SINK,
  AUDIO_SOURCE,
  VIDEO_SOURCE,
  N_DEFAULT_NODES
};

static const gchar * DEFAULT_KEY[N_DEFAULT_NODES] = {
  [AUDIO_SINK] = "default.audio.sink",
  [AUDIO_SOURCE] = "default.audio.source",
  [VIDEO_SOURCE] = "default.video.source",
};

static const gchar * NODE_TYPE_STR[N_DEFAULT_NODES] = {
  [AUDIO_SINK] = "Audio/Sink",
  [AUDIO_SOURCE] = "Audio/Source",
  [VIDEO_SOURCE] = "Video/Source",
};

static const gchar * DEFAULT_CONFIG_KEY[N_DEFAULT_NODES] = {
  [AUDIO_SINK] = "default.configured.audio.sink",
  [AUDIO_SOURCE] = "default.configured.audio.source",
  [VIDEO_SOURCE] = "default.configured.video.source",
};
 070701000000A7000081A4000000000000000000000001656CC35F00002310000000000000000000000000000000000000003400000000wireplumber-0.4.17/modules/module-default-profile.c   ﻿/* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <pipewire/pipewire.h>

#define STATE_NAME "default-profile"
#define SAVE_INTERVAL_MS 1000

G_DEFINE_QUARK (wp-module-default-profile-profiles, profiles);

/* Signals */
enum
{
  SIGNAL_GET_PROFILE,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DECLARE_DERIVABLE_TYPE (WpDefaultProfile, wp_default_profile, WP,
    DEFAULT_PROFILE, WpPlugin)

struct _WpDefaultProfileClass
{
  WpPluginClass parent_class;

  gchar *(*get_profile) (WpDefaultProfile *self,
      WpPipewireObject *device);
};

typedef struct _WpDefaultProfilePrivate WpDefaultProfilePrivate;
struct _WpDefaultProfilePrivate
{
  WpState *state;
  WpProperties *profiles;
  GSource *timeout_source;

  WpObjectManager *devices_om;
};

G_DEFINE_TYPE_WITH_PRIVATE (WpDefaultProfile, wp_default_profile,
    WP_TYPE_PLUGIN)

static gint
find_device_profile (WpPipewireObject *device, const gchar *lookup_name)
{
  WpIterator *profiles = NULL;
  g_auto (GValue) item = G_VALUE_INIT;

  profiles = g_object_get_qdata (G_OBJECT (device), profiles_quark ());
  g_return_val_if_fail (profiles, -1);

  wp_iterator_reset (profiles);
  for (; wp_iterator_next (profiles, &item); g_value_unset (&item)) {
    WpSpaPod *pod = g_value_get_boxed (&item);
    gint index = 0;
    const gchar *name = NULL;

    /* Parse */
    if (!wp_spa_pod_get_object (pod, NULL,
        "index", "i", &index,
        "name", "s", &name,
        NULL))
      continue;

    if (g_strcmp0 (name, lookup_name) == 0) {
      g_value_unset (&item);
      return index;
    }
  }

  return -1;
}

static gboolean
timeout_save_callback (WpDefaultProfile *self)
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);
  g_autoptr (GError) error = NULL;

  if (!wp_state_save (priv->state, priv->profiles, &error))
    wp_warning_object (self, "%s", error->message);

  return G_SOURCE_REMOVE;
}

static void
timeout_save_profiles (WpDefaultProfile *self, guint ms)
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));

  g_return_if_fail (core);
  g_return_if_fail (priv->profiles);

  /* Clear the current timeout callback */
  if (priv->timeout_source)
      g_source_destroy (priv->timeout_source);
  g_clear_pointer (&priv->timeout_source, g_source_unref);

  /* Add the timeout callback */
  wp_core_timeout_add_closure (core, &priv->timeout_source, ms,
      g_cclosure_new_object (G_CALLBACK (timeout_save_callback),
      G_OBJECT (self)));
}

static gchar *
wp_default_profile_get_profile (WpDefaultProfile *self,
    WpPipewireObject *device)
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);
  const gchar *dev_name = NULL;
  const gchar *profile_name = NULL;

  g_return_val_if_fail (device, NULL);
  g_return_val_if_fail (priv->profiles, NULL);

  /* Get the device name */
  dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
  g_return_val_if_fail (dev_name, NULL);

  /* Get the profile */
  profile_name = wp_properties_get (priv->profiles, dev_name);
  return profile_name ? g_strdup (profile_name) : NULL;
}

static void
update_profile (WpDefaultProfile *self, WpPipewireObject *device,
    const gchar *new_profile)
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);
  const gchar *dev_name, *curr_profile = NULL;
  gint index;

  g_return_if_fail (new_profile);
  g_return_if_fail (priv->profiles);

  /* Get the device name */
  dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
  g_return_if_fail (dev_name);

  /* Check if the new profile is the same as the current one */
  curr_profile = wp_properties_get (priv->profiles, dev_name);
  if (curr_profile && g_strcmp0 (curr_profile, new_profile) == 0)
    return;

  /* Make sure the profile is valid */
  index = find_device_profile (device, new_profile);
  if (index < 0) {
    wp_info_object (self, "profile '%s' (%d) is not valid on device '%s'",
        new_profile, index, dev_name);
    return;
  }

  /* Otherwise update the profile and add timeout save callback */
  wp_properties_set (priv->profiles, dev_name, new_profile);
  timeout_save_profiles (self, SAVE_INTERVAL_MS);

  wp_info_object (self, "updated profile '%s' (%d) on device '%s'", new_profile,
      index, dev_name);
}

static void
handle_profile (WpDefaultProfile *self, WpPipewireObject * device,
    WpIterator *profiles)
{
  g_auto (GValue) item = G_VALUE_INIT;

  for (; wp_iterator_next (profiles, &item); g_value_unset (&item)) {
    WpSpaPod *pod = g_value_get_boxed (&item);
    const gchar *name = NULL;
    gint index = 0;
    gboolean save = FALSE;

    if (!wp_spa_pod_get_object (pod, NULL,
        "index", "i", &index,
        "name", "s", &name,
        "save", "?b", &save,
        NULL))
      continue;

    if (save)
      update_profile (self, device, name);
  }
}

static void
on_device_params_changed (WpPipewireObject * proxy, const gchar *param_name,
    WpDefaultProfile *self)
{
  g_autoptr (WpIterator) profiles = NULL;

  if (g_strcmp0 (param_name, "Profile") == 0) {
    profiles = wp_pipewire_object_enum_params_sync (proxy, "Profile", NULL);
    if (profiles)
      handle_profile (self, proxy, profiles);
  } else if (g_strcmp0 (param_name, "EnumProfile") == 0) {
    profiles = wp_pipewire_object_enum_params_sync (proxy, "EnumProfile", NULL);
    if (profiles)
      g_object_set_qdata_full (G_OBJECT (proxy), profiles_quark (),
          g_steal_pointer (&profiles), (GDestroyNotify) wp_iterator_unref);
  }
}

static void
on_device_added (WpObjectManager *om, WpPipewireObject *proxy, gpointer d)
{
  WpDefaultProfile *self = WP_DEFAULT_PROFILE (d);
  g_autoptr (WpIterator) profiles = NULL;

  g_signal_connect_object (proxy, "params-changed",
      G_CALLBACK (on_device_params_changed), self, 0);

  on_device_params_changed (proxy, "EnumProfile", self);
}

static void
wp_default_profile_enable (WpPlugin * plugin, WpTransition * transition)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
  WpDefaultProfile *self = WP_DEFAULT_PROFILE (plugin);
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);

  /* Create the devices object manager */
  priv->devices_om = wp_object_manager_new ();
  wp_object_manager_add_interest (priv->devices_om, WP_TYPE_DEVICE, NULL);
  wp_object_manager_request_object_features (priv->devices_om,
      WP_TYPE_DEVICE, WP_PIPEWIRE_OBJECT_FEATURES_ALL);
  g_signal_connect_object (priv->devices_om, "object-added",
      G_CALLBACK (on_device_added), self, 0);
  wp_core_install_object_manager (core, priv->devices_om);

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_default_profile_disable (WpPlugin * plugin)
{
  WpDefaultProfile *self = WP_DEFAULT_PROFILE (plugin);
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);

  g_clear_object (&priv->devices_om);
}

static void
wp_default_profile_finalize (GObject * object)
{
  WpDefaultProfile *self = WP_DEFAULT_PROFILE (object);
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);

  /* Clear the current timeout callback */
  if (priv->timeout_source)
      g_source_destroy (priv->timeout_source);
  g_clear_pointer (&priv->timeout_source, g_source_unref);

  g_clear_pointer (&priv->profiles, wp_properties_unref);
  g_clear_object (&priv->state);

  G_OBJECT_CLASS (wp_default_profile_parent_class)->finalize (object);
}

static void
wp_default_profile_init (WpDefaultProfile * self)
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);

  priv->state = wp_state_new (STATE_NAME);

  /* Load the saved profiles */
  priv->profiles = wp_state_load (priv->state);
}

static void
wp_default_profile_class_init (WpDefaultProfileClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  object_class->finalize = wp_default_profile_finalize;
  plugin_class->enable = wp_default_profile_enable;
  plugin_class->disable = wp_default_profile_disable;

  klass->get_profile = wp_default_profile_get_profile;

  /* Signals */
  signals[SIGNAL_GET_PROFILE] = g_signal_new ("get-profile",
      G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      G_STRUCT_OFFSET (WpDefaultProfileClass, get_profile), NULL, NULL,
      NULL, G_TYPE_STRING, 1, WP_TYPE_DEVICE);
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_plugin_register (g_object_new (wp_default_profile_get_type (),
      "name", STATE_NAME,
      "core", core,
      NULL));
  return TRUE;
}
070701000000A8000081A4000000000000000000000001656CC35F000016B2000000000000000000000000000000000000003500000000wireplumber-0.4.17/modules/module-file-monitor-api.c  /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <stdio.h>

#include <wp/wp.h>

struct _WpFileMonitorApi
{
  WpPlugin parent;

  GHashTable *monitors;
};

enum {
  ACTION_ADD_WATCH,
  ACTION_REMOVE_WATCH,
  SIGNAL_CHANGED,
  N_SIGNALS
};

static guint signals[N_SIGNALS] = {0};

G_DECLARE_FINAL_TYPE (WpFileMonitorApi, wp_file_monitor_api, WP,
    FILE_MONITOR_API, WpPlugin)
G_DEFINE_TYPE (WpFileMonitorApi, wp_file_monitor_api, WP_TYPE_PLUGIN)

static void
wp_file_monitor_api_init (WpFileMonitorApi * self)
{
  self->monitors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
      g_object_unref);
}

static void
wp_file_monitor_api_finalize (GObject * object)
{
  WpFileMonitorApi * self = WP_FILE_MONITOR_API (object);

  g_clear_pointer (&self->monitors, g_hash_table_unref);

  G_OBJECT_CLASS (wp_file_monitor_api_parent_class)->finalize (object);
}

static void
on_file_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other,
    GFileMonitorEvent evtype, gpointer data)
{
  WpFileMonitorApi * self = WP_FILE_MONITOR_API (data);

  g_autofree char *fpath = g_file_get_path (file);
  g_autofree char *opath = NULL;
  const gchar *evtype_str = NULL;

  if (other)
      opath = g_file_get_path (other);

  switch(evtype) {
    case G_FILE_MONITOR_EVENT_CHANGED:
      evtype_str = "changed";
      break;
    case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
      evtype_str = "changes-done-hint";
      break;
    case G_FILE_MONITOR_EVENT_DELETED:
      evtype_str = "deleted";
      break;
    case G_FILE_MONITOR_EVENT_CREATED:
      evtype_str = "created";
      break;
    case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
      evtype_str = "attribute-changed";
      break;
    case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
      evtype_str = "pre-unmount";
      break;
    case G_FILE_MONITOR_EVENT_UNMOUNTED:
      evtype_str = "unmounted";
      break;
    case G_FILE_MONITOR_EVENT_MOVED:
      evtype_str = "moved";
      break;
    case G_FILE_MONITOR_EVENT_RENAMED:
      evtype_str = "renamed";
      break;
    case G_FILE_MONITOR_EVENT_MOVED_IN:
      evtype_str = "moved-in";
      break;
    case G_FILE_MONITOR_EVENT_MOVED_OUT:
      evtype_str = "moved-out";
      break;
    default:
      wp_warning_object (self, "Unknown event type %d", evtype);
      break;
  }

  g_signal_emit (self, signals[SIGNAL_CHANGED], 0, fpath, opath, evtype_str);
}

static gboolean
wp_file_monitor_api_add_watch (WpFileMonitorApi * self, const gchar *path,
    const gchar *flags_str)
{
  g_autoptr (GError) e = NULL;
  g_autoptr (GFileMonitor) fm = NULL;
  g_autoptr (GFile) f = NULL;
  GFileMonitorFlags flags = G_FILE_MONITOR_NONE;

  /* don't do anything if the path is already being watched */
  if (g_hash_table_contains (self->monitors, path))
    return TRUE;

  /* get path */
  f = g_file_new_for_path (path);
  if (!f) {
    wp_warning_object (self, "Invalid path '%s'", path);
    return FALSE;
  }

  /* parse flags */
  for (guint i = 0; flags_str && i < strlen (flags_str); i++) {
    switch (flags_str[i]) {
      case 'o': flags |= G_FILE_MONITOR_WATCH_MOUNTS; break;
      case 's': flags |= G_FILE_MONITOR_SEND_MOVED; break;
      case 'h': flags |= G_FILE_MONITOR_WATCH_HARD_LINKS; break;
      case 'm': flags |= G_FILE_MONITOR_WATCH_MOVES; break;
      default:
        break;
    }
  }

  /* create the file monitor for that path */
  fm = g_file_monitor (f, flags, NULL, &e);
  if (e) {
    wp_warning_object (self, "Failed to add watch for path '%s': %s", path,
        e->message);
    return FALSE;
  }

  /* handle changed signal and add it to monitors table */
  g_signal_connect (fm, "changed", G_CALLBACK (on_file_monitor_changed), self);
  g_hash_table_insert (self->monitors, g_strdup (path), g_steal_pointer (&fm));
  return TRUE;
}

static void
wp_file_monitor_api_remove_watch (WpFileMonitorApi * self, const gchar *path)
{
  g_hash_table_remove (self->monitors, path);
}

static void
wp_file_monitor_api_enable (WpPlugin * plugin, WpTransition * transition)
{
  WpFileMonitorApi * self = WP_FILE_MONITOR_API (plugin);

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_file_monitor_api_disable (WpPlugin * plugin)
{
  WpFileMonitorApi * self = WP_FILE_MONITOR_API (plugin);

  g_hash_table_remove_all (self->monitors);
}

static void
wp_file_monitor_api_class_init (WpFileMonitorApiClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  object_class->finalize = wp_file_monitor_api_finalize;

  plugin_class->enable = wp_file_monitor_api_enable;
  plugin_class->disable = wp_file_monitor_api_disable;

  signals[ACTION_ADD_WATCH] = g_signal_new_class_handler (
      "add-watch", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_file_monitor_api_add_watch,
      NULL, NULL, NULL,
      G_TYPE_BOOLEAN, 2, G_TYPE_STRING, G_TYPE_STRING);

  signals[ACTION_REMOVE_WATCH] = g_signal_new_class_handler (
      "remove-watch", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_file_monitor_api_remove_watch,
      NULL, NULL, NULL,
      G_TYPE_NONE, 1, G_TYPE_STRING);

  signals[SIGNAL_CHANGED] = g_signal_new (
      "changed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
      G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_plugin_register (g_object_new (wp_file_monitor_api_get_type (),
          "name", "file-monitor-api",
          "core", core,
          NULL));
  return TRUE;
}
  070701000000A9000081A4000000000000000000000001656CC35F00000E7D000000000000000000000000000000000000002B00000000wireplumber-0.4.17/modules/module-logind.c    /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <glib-unix.h>

#include <systemd/sd-login.h>

#include <spa/utils/result.h>
#include <spa/utils/string.h>

#define NAME "logind"

struct _WpLogind
{
  WpPlugin parent;
  sd_login_monitor *monitor;
  GSource *source;
  char *state;
};

enum {
  ACTION_GET_STATE,
  SIGNAL_STATE_CHANGED,
  N_SIGNALS
};

static guint signals[N_SIGNALS] = {0};

G_DECLARE_FINAL_TYPE (WpLogind, wp_logind, WP, LOGIND, WpPlugin)
G_DEFINE_TYPE (WpLogind, wp_logind, WP_TYPE_PLUGIN)

static void
wp_logind_init (WpLogind * self)
{
}

static gchar *
wp_logind_get_state (WpLogind *self)
{
  return g_strdup (self->state);
}

static gboolean
wp_logind_source_ready (gint fd, GIOCondition condition, gpointer user_data)
{
  WpLogind *self = WP_LOGIND (user_data);
  sd_login_monitor_flush (self->monitor);
  {
    char *state = NULL;
    sd_uid_get_state (getuid(), &state);
    if (g_strcmp0 (state, self->state) != 0) {
      char *tmp = state;
      state = self->state;
      self->state = tmp;
      g_signal_emit (self, signals[SIGNAL_STATE_CHANGED], 0, self->state);
    }
    free (state);
  }
  return G_SOURCE_CONTINUE;
}

static void
wp_logind_enable (WpPlugin * plugin, WpTransition * transition)
{
  WpLogind *self = WP_LOGIND (plugin);
  int res = 0;

  if ((res = sd_login_monitor_new ("uid", &self->monitor)) < 0) {
    wp_transition_return_error (transition, g_error_new (G_IO_ERROR,
            g_io_error_from_errno (-res),
            "failed to start systemd logind monitor: %d (%s)",
            res, spa_strerror(res)));
    return;
  }

  if ((res = sd_uid_get_state (getuid(), &self->state)) < 0) {
    wp_transition_return_error (transition, g_error_new (G_IO_ERROR,
            g_io_error_from_errno (-res),
            "failed to get systemd login state: %d (%s)",
            res, spa_strerror(res)));
    g_clear_pointer (&self->monitor, sd_login_monitor_unref);
    return;
  }

  self->source = g_unix_fd_source_new (
      sd_login_monitor_get_fd (self->monitor),
      sd_login_monitor_get_events (self->monitor));
  g_source_set_callback (self->source, G_SOURCE_FUNC (wp_logind_source_ready),
      self, NULL);

  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
  GMainContext *context = wp_core_get_g_main_context (core);
  g_source_attach (self->source, context);

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_logind_disable (WpPlugin * plugin)
{
  WpLogind *self = WP_LOGIND (plugin);

  g_clear_pointer (&self->state, free);
  g_source_destroy (self->source);
  g_clear_pointer (&self->source, g_source_unref);
  g_clear_pointer (&self->monitor, sd_login_monitor_unref);
}

static void
wp_logind_class_init (WpLogindClass * klass)
{
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  plugin_class->enable = wp_logind_enable;
  plugin_class->disable = wp_logind_disable;

  signals[ACTION_GET_STATE] = g_signal_new_class_handler (
      "get-state", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_logind_get_state,
      NULL, NULL, NULL, G_TYPE_STRING, 0);

  signals[SIGNAL_STATE_CHANGED] = g_signal_new (
      "state-changed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_plugin_register (g_object_new (wp_logind_get_type (),
          "name", NAME,
          "core", core,
          NULL));
  return TRUE;
}
   070701000000AA000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000003000000000wireplumber-0.4.17/modules/module-lua-scripting   070701000000AB000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000003400000000wireplumber-0.4.17/modules/module-lua-scripting/api   070701000000AC000081A4000000000000000000000001656CC35F00009F90000000000000000000000000000000000000003A00000000wireplumber-0.4.17/modules/module-lua-scripting/api/api.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "lua.h"
#include <glib/gstdio.h>
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include <wplua/wplua.h>
#include <libintl.h>

#define URI_API "resource:///org/freedesktop/pipewire/wireplumber/m-lua-scripting/api.lua"

void wp_lua_scripting_pod_init (lua_State *L);
void wp_lua_scripting_json_init (lua_State *L);

/* helpers */

static WpCore *
get_wp_core (lua_State *L)
{
  WpCore *core = NULL;
  lua_pushliteral (L, "wireplumber_core");
  lua_gettable (L, LUA_REGISTRYINDEX);
  core = lua_touserdata (L, -1);
  lua_pop (L, 1);
  return core;
}

static WpCore *
get_wp_export_core (lua_State *L)
{
  WpCore *core = NULL;
  lua_pushliteral (L, "wireplumber_export_core");
  lua_gettable (L, LUA_REGISTRYINDEX);
  if (wplua_isobject (L, -1, WP_TYPE_CORE))
    core = wplua_toobject (L, -1);
  lua_pop (L, 1);
  return core ? core : get_wp_core(L);
}

/* GLib */

static int
glib_get_monotonic_time (lua_State *L)
{
  lua_pushinteger (L, g_get_monotonic_time ());
  return 1;
}

static int
glib_get_real_time (lua_State *L)
{
  lua_pushinteger (L, g_get_real_time ());
  return 1;
}

static gboolean
access_parse_mode (const gchar * mode_str, gint *mode)
{
  *mode = 0;

  if (!mode_str)
    return FALSE;
  else {
    for (guint i = 0; i < strlen (mode_str); i++) {
      switch (mode_str[i]) {
        case 'r': *mode |= R_OK; break;
        case 'w': *mode |= W_OK; break;
        case 'x': *mode |= X_OK; break;
        case 'f': *mode |= F_OK; break;
        case '-': break;
        default:
          return FALSE;
      }
    }
  }
  return TRUE;
}

static int
glib_access (lua_State *L)
{
  const gchar *filename = luaL_checkstring (L, 1);
  int mode = 0;
  if (!access_parse_mode (luaL_checkstring (L, 2), &mode))
      luaL_error (L, "invalid mode string: '%s'", lua_tostring (L, 2));
  lua_pushboolean (L, g_access (filename, mode) >= 0);
  return 1;
}

static const luaL_Reg glib_methods[] = {
  { "get_monotonic_time", glib_get_monotonic_time },
  { "get_real_time", glib_get_real_time },
  { "access", glib_access },
  { NULL, NULL }
};

/* GSource */

static int
source_destroy (lua_State *L)
{
  GSource *source = wplua_checkboxed (L, 1, G_TYPE_SOURCE);
  g_source_destroy (source);
  return 0;
}

static const luaL_Reg source_methods[] = {
  { "destroy", source_destroy },
  { NULL, NULL }
};

/* i18n */

static int
i18n_gettext (lua_State *L)
{
  const gchar * msgid = luaL_checkstring (L, 1);
  lua_pushstring (L, dgettext (GETTEXT_PACKAGE, msgid));
  return 1;
}

static int
i18n_ngettext (lua_State *L)
{
  const gchar * msgid = luaL_checkstring (L, 1);
  const gchar *msgid_plural = luaL_checkstring (L, 2);
  gulong n = luaL_checkinteger (L, 3);
  lua_pushstring (L, dngettext (GETTEXT_PACKAGE, msgid, msgid_plural, n));
  return 1;
}

static const luaL_Reg i18n_funcs[] = {
  { "gettext", i18n_gettext },
  { "ngettext", i18n_ngettext },
  { NULL, NULL }
};

/* WpCore */

static int
core_get_info (lua_State *L)
{
  WpCore * core = get_wp_core (L);
  g_autoptr (WpProperties) p = wp_core_get_remote_properties (core);

  lua_newtable (L);
  lua_pushinteger (L, wp_core_get_remote_cookie (core));
  lua_setfield (L, -2, "cookie");
  lua_pushstring (L, wp_core_get_remote_name (core));
  lua_setfield (L, -2, "name");
  lua_pushstring (L, wp_core_get_remote_user_name (core));
  lua_setfield (L, -2, "user_name");
  lua_pushstring (L, wp_core_get_remote_host_name (core));
  lua_setfield (L, -2, "host_name");
  lua_pushstring (L, wp_core_get_remote_version (core));
  lua_setfield (L, -2, "version");
  wplua_properties_to_table (L, p);
  lua_setfield (L, -2, "properties");
  return 1;
}

static int
core_get_vm_type (lua_State *L)
{
  WpCore * core = get_wp_core (L);
  g_autofree gchar *vm = wp_core_get_vm_type (core);
  lua_pushstring (L, vm);
  return 1;
}

static int
core_get_own_bound_id (lua_State *L)
{
  WpCore * core = get_wp_core (L);
  guint32 id = wp_core_get_own_bound_id (core);
  lua_pushinteger (L, id);
  return 1;
}

static int
core_idle_add (lua_State *L)
{
  GSource *source = NULL;
  luaL_checktype (L, 1, LUA_TFUNCTION);
  wp_core_idle_add_closure (get_wp_core (L), &source,
      wplua_function_to_closure (L, 1));
  wplua_pushboxed (L, G_TYPE_SOURCE, source);
  return 1;
}

static int
core_timeout_add (lua_State *L)
{
  GSource *source = NULL;
  lua_Integer timeout_ms = luaL_checkinteger (L, 1);
  luaL_checktype (L, 2, LUA_TFUNCTION);
  wp_core_timeout_add_closure (get_wp_core (L), &source, timeout_ms,
      wplua_function_to_closure (L, 2));
  wplua_pushboxed (L, G_TYPE_SOURCE, source);
  return 1;
}

static void
on_core_done (WpCore * core, GAsyncResult * res, GClosure * closure)
{
  g_autoptr (GError) error = NULL;
  GValue val = G_VALUE_INIT;
  int n_vals = 0;

  if (!wp_core_sync_finish (core, res, &error)) {
    g_value_init (&val, G_TYPE_STRING);
    g_value_set_string (&val, error->message);
    n_vals = 1;
  }
  g_closure_invoke (closure, NULL, n_vals, &val, NULL);
  g_value_unset (&val);
  g_closure_invalidate (closure);
  g_closure_unref (closure);
}

static int
core_sync (lua_State *L)
{
  luaL_checktype (L, 1, LUA_TFUNCTION);
  GClosure * closure = wplua_function_to_closure (L, 1);
  g_closure_sink (g_closure_ref (closure));
  wp_core_sync (get_wp_core (L), NULL, (GAsyncReadyCallback) on_core_done,
      closure);
  return 0;
}

static gboolean
core_disconnect (WpCore * core)
{
  wp_core_disconnect (core);
  return G_SOURCE_REMOVE;
}

static int
core_quit (lua_State *L)
{
  WpCore * core = get_wp_core (L);
  g_autoptr (WpProperties) p = wp_core_get_properties (core);
  const gchar *daemon = wp_properties_get (p, "wireplumber.daemon");
  if (!g_strcmp0 (daemon, "true")) {
    wp_warning ("script attempted to quit, but the engine is "
        "running in the wireplumber daemon; ignoring");
    return 0;
  }

  /* wp_core_disconnect() will immediately destroy the lua plugin
     and the lua engine, so we cannot call it directly */
  wp_core_idle_add (core, NULL, G_SOURCE_FUNC (core_disconnect), core, NULL);
  return 0;
}

#include "require.c"

static int
core_require_api (lua_State *L)
{
  WpCore * core = get_wp_core (L);
  g_autoptr (WpProperties) p = wp_core_get_properties (core);
  const gchar *daemon = wp_properties_get (p, "wireplumber.daemon");
  if (!g_strcmp0 (daemon, "true")) {
    wp_warning ("script attempted to load an API module, but the engine is "
        "running in the wireplumber daemon; ignoring");
    return 0;
  }
  return wp_require_api_transition_new_from_lua (L, core);
}

static const luaL_Reg core_funcs[] = {
  { "get_info", core_get_info },
  { "get_vm_type", core_get_vm_type },
  { "get_own_bound_id", core_get_own_bound_id },
  { "idle_add", core_idle_add },
  { "timeout_add", core_timeout_add },
  { "sync", core_sync },
  { "quit", core_quit },
  { "require_api", core_require_api },
  { NULL, NULL }
};

/* WpLog */

static int
log_log (lua_State *L, GLogLevelFlags lvl)
{
  lua_Debug ar = {0};
  const gchar *message, *tmp;
  gchar domain[25];
  gchar line_str[11];
  gconstpointer instance = NULL;
  GType type = G_TYPE_INVALID;
  int index = 1;

  if (!wp_log_level_is_enabled (lvl))
    return 0;

  g_warn_if_fail (lua_getstack (L, 1, &ar) == 1);
  g_warn_if_fail (lua_getinfo (L, "nSl", &ar) == 1);

  if (wplua_isobject (L, 1, G_TYPE_OBJECT)) {
    instance = wplua_toobject (L, 1);
    type = G_TYPE_FROM_INSTANCE (instance);
    index++;
  }
  else if (wplua_isboxed (L, 1, G_TYPE_BOXED)) {
    instance = wplua_toboxed (L, 1);
    type = wplua_gvalue_userdata_type (L, 1);
    index++;
  }

  message = luaL_checkstring (L, index);
  tmp = ar.source ? g_strrstr (ar.source, ".lua") : NULL;
  snprintf (domain, 25, "script/%.*s",
      tmp ? MIN((gint)(tmp - ar.source), 17) : 17,
      ar.source);
  snprintf (line_str, 11, "%d", ar.currentline);
  ar.name = ar.name ? ar.name : "chunk";

  wp_log_structured_standard (domain, lvl,
      ar.source, line_str, ar.name, type, instance, "%s", message);
  return 0;
}

static int
log_warning (lua_State *L) { return log_log (L, G_LOG_LEVEL_WARNING); }

static int
log_message (lua_State *L) { return log_log (L, G_LOG_LEVEL_MESSAGE); }

static int
log_info (lua_State *L) { return log_log (L, G_LOG_LEVEL_INFO); }

static int
log_debug (lua_State *L) { return log_log (L, G_LOG_LEVEL_DEBUG); }

static int
log_trace (lua_State *L) { return log_log (L, WP_LOG_LEVEL_TRACE); }

static const luaL_Reg log_funcs[] = {
  { "warning", log_warning },
  { "message", log_message },
  { "info", log_info },
  { "debug", log_debug },
  { "trace", log_trace },
  { NULL, NULL }
};

/* WpPlugin */

static int
plugin_find (lua_State *L)
{
  const char *name = luaL_checkstring (L, 1);
  WpPlugin *plugin = wp_plugin_find (get_wp_core (L), name);
  if (plugin)
    wplua_pushobject (L, plugin);
  else
    lua_pushnil (L);
  return 1;
}

static const luaL_Reg plugin_funcs[] = {
  { "find", plugin_find },
  { NULL, NULL }
};

/* WpObject */

static void
object_activate_done (WpObject *o, GAsyncResult * res, GClosure * closure)
{
  g_autoptr (GError) error = NULL;
  GValue val[2] = { G_VALUE_INIT, G_VALUE_INIT };
  int n_vals = 1;

  if (!wp_object_activate_finish (o, res, &error)) {
    wp_message_object (o, "%s", error->message);
    if (closure) {
      g_value_init (&val[1], G_TYPE_STRING);
      g_value_set_string (&val[1], error->message);
      n_vals = 2;
    }
  }
  if (closure) {
    g_value_init_from_instance (&val[0], o);
    g_closure_invoke (closure, NULL, n_vals, val, NULL);
    g_value_unset (&val[0]);
    g_value_unset (&val[1]);
    g_closure_invalidate (closure);
    g_closure_unref (closure);
  }
}

static int
object_activate (lua_State *L)
{
  WpObject *o = wplua_checkobject (L, 1, WP_TYPE_OBJECT);
  WpObjectFeatures features = luaL_checkinteger (L, 2);
  GClosure * closure = luaL_opt (L, wplua_checkclosure, 3, NULL);
  if (closure)
    g_closure_sink (g_closure_ref (closure));
  wp_object_activate (o, features, NULL,
      (GAsyncReadyCallback) object_activate_done, closure);
  return 0;
}

static int
object_deactivate (lua_State *L)
{
  WpObject *o = wplua_checkobject (L, 1, WP_TYPE_OBJECT);
  WpObjectFeatures features = luaL_checkinteger (L, 2);
  wp_object_deactivate (o, features);
  return 0;
}

static int
object_get_active_features (lua_State *L)
{
  WpObject *o = wplua_checkobject (L, 1, WP_TYPE_OBJECT);
  WpObjectFeatures features = wp_object_get_active_features (o);
  lua_pushinteger (L, features);
  return 1;
}

static int
object_get_supported_features (lua_State *L)
{
  WpObject *o = wplua_checkobject (L, 1, WP_TYPE_OBJECT);
  WpObjectFeatures features = wp_object_get_supported_features (o);
  lua_pushinteger (L, features);
  return 1;
}

static const luaL_Reg object_methods[] = {
  { "activate", object_activate },
  { "deactivate", object_deactivate },
  { "get_active_features", object_get_active_features },
  { "get_supported_features", object_get_supported_features },
  { NULL, NULL }
};

/* WpProxy */

static int
proxy_get_interface_type (lua_State *L)
{
  WpProxy * p = wplua_checkobject (L, 1, WP_TYPE_PROXY);
  guint32 version = 0;
  const gchar *type = wp_proxy_get_interface_type (p, &version);
  lua_pushstring (L, type);
  lua_pushinteger (L, version);
  return 2;
}

static const luaL_Reg proxy_methods[] = {
  { "get_interface_type", proxy_get_interface_type },
  { NULL, NULL }
};

/* WpGlobalProxy */

static int
global_proxy_request_destroy (lua_State *L)
{
  WpGlobalProxy * p = wplua_checkobject (L, 1, WP_TYPE_GLOBAL_PROXY);
  wp_global_proxy_request_destroy (p);
  return 0;
}

static const luaL_Reg global_proxy_methods[] = {
  { "request_destroy", global_proxy_request_destroy },
  { NULL, NULL }
};

/* WpIterator */

static int
iterator_next (lua_State *L)
{
  WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR);
  g_auto (GValue) v = G_VALUE_INIT;
  if (it && wp_iterator_next (it, &v)) {
    return wplua_gvalue_to_lua (L, &v);
  } else {
    lua_pushnil (L);
    return 1;
  }
}

static int
push_wpiterator (lua_State *L, WpIterator *it)
{
  lua_pushcfunction (L, iterator_next);
  wplua_pushboxed (L, WP_TYPE_ITERATOR, it);
  return 2;
}

/* Metadata WpIterator */

static int
metadata_iterator_next (lua_State *L)
{
  WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR);
  g_auto (GValue) item = G_VALUE_INIT;
  if (wp_iterator_next (it, &item)) {
    guint32 s = 0;
    const gchar *k = NULL, *t = NULL, *v = NULL;
    wp_metadata_iterator_item_extract (&item, &s, &k, &t, &v);
    lua_pushinteger (L, s);
    lua_pushstring (L, k);
    lua_pushstring (L, t);
    lua_pushstring (L, v);
    return 4;
  } else {
    lua_pushnil (L);
    return 1;
  }
}

static int
push_metadata_wpiterator (lua_State *L, WpIterator *it)
{
  lua_pushcfunction (L, metadata_iterator_next);
  wplua_pushboxed (L, WP_TYPE_ITERATOR, it);
  return 2;
}

/* WpObjectInterest */

static GVariant *
constraint_value_to_variant (lua_State *L, int idx)
{
  switch (lua_type (L, idx)) {
  case LUA_TBOOLEAN:
    return g_variant_new_boolean (lua_toboolean (L, idx));
  case LUA_TSTRING:
    return g_variant_new_string (lua_tostring (L, idx));
  case LUA_TNUMBER:
    if (lua_isinteger (L, idx))
      return g_variant_new_int64 (lua_tointeger (L, idx));
    else
      return g_variant_new_double (lua_tonumber (L, idx));
  default:
    return NULL;
  }
}

static void
object_interest_new_add_constraint (lua_State *L, GType type,
    WpObjectInterest *interest)
{
  int constraint_idx;
  WpConstraintType ctype;
  const gchar *subject;
  WpConstraintVerb verb;
  GVariant *value = NULL;

  constraint_idx = lua_absindex (L, -1);

  /* verify this is a Constraint{} */
  if (lua_type (L, constraint_idx) != LUA_TTABLE) {
    luaL_error (L, "Interest: expected Constraint at index %d",
        lua_tointeger (L, -2));
  }

  if (luaL_getmetafield (L, constraint_idx, "__name") == LUA_TNIL ||
      g_strcmp0 (lua_tostring (L, -1), "Constraint") != 0) {
    luaL_error (L, "Interest: expected Constraint at index %d",
        lua_tointeger (L, -2));
  }
  lua_pop (L, 1);

  /* get the constraint type */
  lua_pushliteral (L, "type");
  if (lua_gettable (L, constraint_idx) == LUA_TNUMBER)
    ctype = lua_tointeger (L, -1);
  else
    ctype = WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY;
  lua_pop (L, 1);

  /* get t[1] (the subject) and t[2] (the verb) */
  lua_geti (L, constraint_idx, 1);
  subject = lua_tostring (L, -1);

  lua_geti (L, constraint_idx, 2);
  verb = lua_tostring (L, -1)[0];

  switch (verb) {
  case WP_CONSTRAINT_VERB_EQUALS:
  case WP_CONSTRAINT_VERB_NOT_EQUALS:
  case WP_CONSTRAINT_VERB_MATCHES: {
    lua_geti (L, constraint_idx, 3);
    value = constraint_value_to_variant (L, -1);
    if (G_UNLIKELY (!value))
      luaL_error (L, "Constraint: bad value type");
    break;
  }
  case WP_CONSTRAINT_VERB_IN_RANGE: {
    GVariant *values[2];
    lua_geti (L, constraint_idx, 3);
    lua_geti (L, constraint_idx, 4);
    values[0] = constraint_value_to_variant (L, -2);
    values[1] = constraint_value_to_variant (L, -1);
    if (G_UNLIKELY (!values[0] || !values[1])) {
      g_clear_pointer (&values[0], g_variant_unref);
      g_clear_pointer (&values[1], g_variant_unref);
      luaL_error (L, "Constraint: bad value type");
    }
    value = g_variant_new_tuple (values, 2);
    break;
  }
  case WP_CONSTRAINT_VERB_IN_LIST: {
    GPtrArray *values =
        g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref);
    int i = 3;
    while (lua_geti (L, constraint_idx, i++) != LUA_TNIL) {
      GVariant *tmp = constraint_value_to_variant (L, -1);
      if (G_UNLIKELY (!tmp)) {
        g_ptr_array_unref (values);
        luaL_error (L, "Constraint: bad value type");
      }
      g_ptr_array_add (values, g_variant_ref_sink (tmp));
      lua_pop (L, 1);
    }
    value = g_variant_new_tuple ((GVariant **) values->pdata, values->len);
    g_ptr_array_unref (values);
    break;
  }
  default:
    break;
  }

  wp_object_interest_add_constraint (interest, ctype, subject, verb, value);
  lua_settop (L, constraint_idx);
}

static GType
parse_gtype (const gchar *str)
{
  g_autofree gchar *typestr = NULL;
  GType res = G_TYPE_INVALID;

  g_return_val_if_fail (str, res);

  /* "device" -> "WpDevice" */
  typestr = g_strdup_printf ("Wp%s", str);
  if (typestr[2] != 0) {
    typestr[2] = g_ascii_toupper (typestr[2]);
    res = g_type_from_name (typestr);
  }

  return res;
}

static int
object_interest_new_index (lua_State *L, int idx, GType def_type)
{
  WpObjectInterest *interest = NULL;
  GType type = def_type;

  luaL_checktype (L, idx, LUA_TTABLE);

  /* type = "string" */
  lua_pushliteral (L, "type");
  if (lua_gettable (L, idx) == LUA_TSTRING) {
    type = parse_gtype (lua_tostring (L, -1));
    if (type == G_TYPE_INVALID)
      luaL_error (L, "Interest: unknown type '%s'", lua_tostring (L, -1));
  }
  else if (def_type == G_TYPE_INVALID)
    luaL_error (L, "Interest: expected 'type' as string");
  lua_pop (L, 1);

  interest = wp_object_interest_new_type (type);
  wplua_pushboxed (L, WP_TYPE_OBJECT_INTEREST, interest);

  /* add constraints */
  lua_pushnil (L);
  while (lua_next (L, idx)) {
    /* if the key isn't "type" */
    if (!(lua_type (L, -2) == LUA_TSTRING &&
          !g_strcmp0 ("type", lua_tostring (L, -2))))
      object_interest_new_add_constraint (L, type, interest);
    lua_pop (L, 1);
  }

  return 1;
}

static int
object_interest_new (lua_State *L)
{
  return object_interest_new_index (L, 1, G_TYPE_INVALID);
}

static int
object_interest_matches (lua_State *L)
{
  WpObjectInterest *interest = wplua_checkboxed (L, 1, WP_TYPE_OBJECT_INTEREST);
  gboolean matches = FALSE;

  if (wplua_isobject (L, 2, G_TYPE_OBJECT)) {
    matches = wp_object_interest_matches (interest, wplua_toobject (L, 2));
  }
  else if (lua_istable (L, 2)) {
    g_autoptr (WpProperties) props = wplua_table_to_properties (L, 2);
    matches = wp_object_interest_matches (interest, props);
  } else
    luaL_argerror (L, 2, "expected GObject or table");

  lua_pushboolean (L, matches);
  return 1;
}

static const luaL_Reg object_interest_methods[] = {
  { "matches", object_interest_matches },
  { NULL, NULL }
};

static WpObjectInterest *
get_optional_object_interest (lua_State *L, int idx, GType def_type)
{
  if (lua_isnoneornil (L, idx))
    return NULL;
  else if (lua_isuserdata (L, idx))
    return wplua_checkboxed (L, idx, WP_TYPE_OBJECT_INTEREST);
  else if (lua_istable (L, idx)) {
    object_interest_new_index (L, idx, def_type);
    return wplua_toboxed (L, -1);
  } else {
    luaL_error (L, "expected Interest or none/nil");
    return NULL;
  }
}

/* WpObjectManager */

static int
object_manager_new (lua_State *L)
{
  WpObjectManager *om;

  /* validate arguments */
  luaL_checktype (L, 1, LUA_TTABLE);

  /* push to Lua asap to have a way to unref in case of error */
  om = wp_object_manager_new ();
  wplua_pushobject (L, om);

  lua_pushnil (L);
  while (lua_next (L, 1)) {
    WpObjectInterest *interest =
        wplua_checkboxed (L, -1, WP_TYPE_OBJECT_INTEREST);
    wp_object_manager_add_interest_full (om, wp_object_interest_ref (interest));
    lua_pop (L, 1);
  }

  /* request all the features for Lua scripts to make their job easier */
  wp_object_manager_request_object_features (om,
      WP_TYPE_OBJECT, WP_OBJECT_FEATURES_ALL);

  return 1;
}

static int
object_manager_activate (lua_State *L)
{
  WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER);
  wp_core_install_object_manager (get_wp_core (L), om);
  return 0;
}

static int
object_manager_get_n_objects (lua_State *L)
{
  WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER);
  lua_pushinteger (L, wp_object_manager_get_n_objects (om));
  return 1;
}

static int
object_manager_iterate (lua_State *L)
{
  WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER);
  WpObjectInterest *oi = get_optional_object_interest (L, 2, G_TYPE_OBJECT);
  WpIterator *it = oi ?
      wp_object_manager_new_filtered_iterator_full (om,
          wp_object_interest_ref (oi)) :
      wp_object_manager_new_iterator (om);
  return push_wpiterator (L, it);
}

static int
object_manager_lookup (lua_State *L)
{
  WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER);
  WpObjectInterest *oi = get_optional_object_interest (L, 2, G_TYPE_OBJECT);
  WpObject *o = oi ?
      wp_object_manager_lookup_full (om, wp_object_interest_ref (oi)) :
      wp_object_manager_lookup (om, G_TYPE_OBJECT, NULL);
  if (o) {
    wplua_pushobject (L, o);
    return 1;
  }
  return 0;
}

static const luaL_Reg object_manager_methods[] = {
  { "activate", object_manager_activate },
  { "get_n_objects", object_manager_get_n_objects },
  { "iterate", object_manager_iterate },
  { "lookup", object_manager_lookup },
  { NULL, NULL }
};

/* WpMetadata */

static int
metadata_iterate (lua_State *L)
{
  WpMetadata *metadata = wplua_checkobject (L, 1, WP_TYPE_METADATA);
  lua_Integer subject = luaL_checkinteger (L, 2);
  WpIterator *it = wp_metadata_new_iterator (metadata, subject);
  return push_metadata_wpiterator (L, it);
}

static int
metadata_find (lua_State *L)
{
  WpMetadata *metadata = wplua_checkobject (L, 1, WP_TYPE_METADATA);
  lua_Integer subject = luaL_checkinteger (L, 2);
  const char *key = luaL_checkstring (L, 3), *v = NULL, *t = NULL;
  v = wp_metadata_find (metadata, subject, key, &t);
  lua_pushstring (L, v);
  lua_pushstring (L, t);
  return 2;
}

static int
metadata_set (lua_State *L)
{
  WpMetadata *metadata = wplua_checkobject (L, 1, WP_TYPE_METADATA);
  lua_Integer subject = luaL_checkinteger (L, 2);
  const char *key = luaL_opt (L, luaL_checkstring, 3, NULL);
  const char *type = luaL_opt (L, luaL_checkstring, 4, NULL);
  const char *value = luaL_opt (L, luaL_checkstring, 5, NULL);
  wp_metadata_set (metadata, subject, key, type, value);
  return 0;
}

static const luaL_Reg metadata_methods[] = {
  { "iterate", metadata_iterate },
  { "find", metadata_find },
  { "set", metadata_set },
  { NULL, NULL }
};

/* WpImplMetadata */

static int
impl_metadata_new (lua_State *L)
{
  const char *name = luaL_checkstring (L, 1);
  WpProperties *properties = NULL;

  if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
    luaL_checktype (L, 2, LUA_TTABLE);
    properties = wplua_table_to_properties (L, 2);
  }

  WpImplMetadata *m = wp_impl_metadata_new_full (get_wp_core (L),
      name, properties);
  if (m)
    wplua_pushobject (L, m);
  return m ? 1 : 0;
}

/* WpEndpoint */

static const luaL_Reg endpoint_methods[] = {
  { NULL, NULL }
};

/* Device */

static int
device_new (lua_State *L)
{
  const char *factory = luaL_checkstring (L, 1);
  WpProperties *properties = NULL;

  if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
    luaL_checktype (L, 2, LUA_TTABLE);
    properties = wplua_table_to_properties (L, 2);
  }

  WpDevice *d = wp_device_new_from_factory (get_wp_export_core (L),
      factory, properties);
  if (d)
    wplua_pushobject (L, d);
  return d ? 1 : 0;
}

/* WpSpaDevice */

static int
spa_device_new (lua_State *L)
{
  const char *factory = luaL_checkstring (L, 1);
  WpProperties *properties = NULL;

  if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
    luaL_checktype (L, 2, LUA_TTABLE);
    properties = wplua_table_to_properties (L, 2);
  }

  WpSpaDevice *d = wp_spa_device_new_from_spa_factory (get_wp_export_core (L),
      factory, properties);
  if (d)
    wplua_pushobject (L, d);
  return d ? 1 : 0;
}

static int
spa_device_iterate_managed_objects (lua_State *L)
{
  WpSpaDevice *device = wplua_checkobject (L, 1, WP_TYPE_SPA_DEVICE);
  WpIterator *it = wp_spa_device_new_managed_object_iterator (device);
  return push_wpiterator (L, it);
}

static int
spa_device_get_managed_object (lua_State *L)
{
  WpSpaDevice *device = wplua_checkobject (L, 1, WP_TYPE_SPA_DEVICE);
  guint id = luaL_checkinteger (L, 2);
  GObject *obj = wp_spa_device_get_managed_object (device, id);
  if (obj)
    wplua_pushobject (L, obj);
  return obj ? 1 : 0;
}

static int
spa_device_store_managed_object (lua_State *L)
{
  WpSpaDevice *device = wplua_checkobject (L, 1, WP_TYPE_SPA_DEVICE);
  guint id = luaL_checkinteger (L, 2);
  GObject *obj = (lua_type (L, 3) != LUA_TNIL) ?
      g_object_ref (wplua_checkobject (L, 3, G_TYPE_OBJECT)) : NULL;

  wp_spa_device_store_managed_object (device, id, obj);
  return 0;
}

static const luaL_Reg spa_device_methods[] = {
  { "iterate_managed_objects", spa_device_iterate_managed_objects },
  { "get_managed_object", spa_device_get_managed_object },
  { "store_managed_object", spa_device_store_managed_object },
  { NULL, NULL }
};

/* Node */

static int
node_new (lua_State *L)
{
  const char *factory = luaL_checkstring (L, 1);
  WpProperties *properties = NULL;

  if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
    luaL_checktype (L, 2, LUA_TTABLE);
    properties = wplua_table_to_properties (L, 2);
  }

  WpNode *d = wp_node_new_from_factory (get_wp_export_core (L),
      factory, properties);
  if (d)
    wplua_pushobject (L, d);
  return d ? 1 : 0;
}

static int
node_get_state (lua_State *L)
{
  WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
  const gchar *error = NULL;
  WpNodeState state = wp_node_get_state (node, &error);
  wplua_enum_to_lua (L, state, WP_TYPE_NODE_STATE);
  lua_pushstring (L, error ? error : "");
  return 2;
}

static int
node_get_n_input_ports (lua_State *L)
{
  WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
  guint max = 0;
  guint ports = wp_node_get_n_input_ports (node, &max);
  lua_pushinteger (L, ports);
  lua_pushinteger (L, max);
  return 2;
}

static int
node_get_n_output_ports (lua_State *L)
{
  WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
  guint max = 0;
  guint ports = wp_node_get_n_output_ports (node, &max);
  lua_pushinteger (L, ports);
  lua_pushinteger (L, max);
  return 2;
}

static int
node_get_n_ports (lua_State *L)
{
  WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
  guint ports = wp_node_get_n_ports (node);
  lua_pushinteger (L, ports);
  return 1;
}

static int
node_iterate_ports (lua_State *L)
{
  WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
  WpObjectInterest *oi = get_optional_object_interest (L, 2, WP_TYPE_PORT);
  WpIterator *it = oi ?
      wp_node_new_ports_filtered_iterator_full (node,
          wp_object_interest_ref (oi)) :
      wp_node_new_ports_iterator (node);
  return push_wpiterator (L, it);
}

static int
node_lookup_port (lua_State *L)
{
  WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
  WpObjectInterest *oi = get_optional_object_interest (L, 2, WP_TYPE_PORT);
  WpPort *port = oi ?
      wp_node_lookup_port_full (node, wp_object_interest_ref (oi)) :
      wp_node_lookup_port (node, G_TYPE_OBJECT, NULL);
  if (port) {
    wplua_pushobject (L, port);
    return 1;
  }
  return 0;
}

static int
node_send_command (lua_State *L)
{
  WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
  const char *command = luaL_checkstring (L, 2);
  wp_node_send_command (node, command);
  return 0;
}

static const luaL_Reg node_methods[] = {
  { "get_state", node_get_state },
  { "get_n_input_ports", node_get_n_input_ports },
  { "get_n_output_ports", node_get_n_output_ports },
  { "get_n_ports", node_get_n_ports },
  { "iterate_ports", node_iterate_ports },
  { "lookup_port", node_lookup_port },
  { "send_command", node_send_command },
  { NULL, NULL }
};

/* ImplNode */

static int
impl_node_new (lua_State *L)
{
  const char *factory = luaL_checkstring (L, 1);
  WpProperties *properties = NULL;

  if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
    luaL_checktype (L, 2, LUA_TTABLE);
    properties = wplua_table_to_properties (L, 2);
  }

  WpImplNode *d = wp_impl_node_new_from_pw_factory (get_wp_export_core (L),
     factory, properties);
  if (d)
    wplua_pushobject (L, d);
  return d ? 1 : 0;
}

/* Port */

static int
port_get_direction (lua_State *L)
{
  WpPort *port = wplua_checkobject (L, 1, WP_TYPE_PORT);
  WpDirection direction = wp_port_get_direction (port);
  wplua_enum_to_lua (L, direction, WP_TYPE_DIRECTION);
  return 1;
}

static const luaL_Reg port_methods[] = {
  { "get_direction", port_get_direction },
  { NULL, NULL }
};

/* Link */

static int
link_new (lua_State *L)
{
  const char *factory = luaL_checkstring (L, 1);
  WpProperties *properties = NULL;

  if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
    luaL_checktype (L, 2, LUA_TTABLE);
    properties = wplua_table_to_properties (L, 2);
  }

  WpLink *l = wp_link_new_from_factory (get_wp_core (L), factory, properties);
  if (l)
    wplua_pushobject (L, l);
  return l ? 1 : 0;
}

/* Client */

static gboolean
client_parse_permissions (const gchar * perms_str, guint32 *perms)
{
  *perms = 0;

  if (!perms_str)
    return FALSE;
  else if (g_strcmp0 (perms_str, "all") == 0)
    *perms = PW_PERM_ALL;
  else {
    for (guint i = 0; i < strlen (perms_str); i++) {
      switch (perms_str[i]) {
        case 'r': *perms |= PW_PERM_R; break;
        case 'w': *perms |= PW_PERM_W; break;
        case 'x': *perms |= PW_PERM_X; break;
        case 'm': *perms |= PW_PERM_M; break;
        case '-': break;
        default:
          return FALSE;
      }
    }
  }
  return TRUE;
}

static int
client_update_permissions (lua_State *L)
{
  WpClient *client = wplua_checkobject (L, 1, WP_TYPE_CLIENT);
  g_autoptr (GArray) arr = NULL;

  luaL_checktype (L, 2, LUA_TTABLE);

  lua_pushnil(L);
  while (lua_next (L, -2)) {
    struct pw_permission perm = {0};

    if (lua_type (L, -2) == LUA_TSTRING &&
        (!g_ascii_strcasecmp (lua_tostring(L, -2), "any") ||
         !g_ascii_strcasecmp (lua_tostring(L, -2), "all")))
      perm.id = PW_ID_ANY;
    else if (lua_isinteger (L, -2))
      perm.id = lua_tointeger (L, -2);
    else
      luaL_error (L, "invalid key for permissions array");

    if (!client_parse_permissions (lua_tostring (L, -1), &perm.permissions))
      luaL_error (L, "invalid permission string: '%s'", lua_tostring (L, -1));

    if (!arr)
      arr = g_array_new (FALSE, FALSE, sizeof (struct pw_permission));

    g_array_append_val (arr, perm);
    lua_pop (L, 1);
  }

  wp_client_update_permissions_array (client, arr->len,
      (const struct pw_permission *) arr->data);
  return 0;
}

static int
client_send_error (lua_State *L)
{
  WpClient *client = wplua_checkobject (L, 1, WP_TYPE_CLIENT);
  guint id = luaL_checkinteger (L, 2);
  int res = luaL_checkinteger (L, 3);
  const char *message = luaL_checkstring (L, 4);
  wp_client_send_error (client, id, res, message);
  return 0;
}

static const luaL_Reg client_methods[] = {
  { "update_permissions", client_update_permissions },
  { "send_error", client_send_error },
  { NULL, NULL }
};

/* WpSessionItem */

static int
session_item_new (lua_State *L)
{
  const char *type = luaL_checkstring (L, 1);
  WpSessionItem *si = wp_session_item_make (get_wp_core (L), type);
  if (si)
    wplua_pushobject (L, si);
  return si ? 1 : 0;
}

static int
session_item_get_associated_proxy (lua_State *L)
{
  WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
  const char *typestr = luaL_checkstring (L, 2);
  WpProxy *proxy = wp_session_item_get_associated_proxy (si,
      parse_gtype (typestr));
  if (proxy)
    wplua_pushobject (L, proxy);
  return proxy ? 1 : 0;
}

static int
session_item_reset (lua_State *L)
{
  WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
  wp_session_item_reset (si);
  return 0;
}

static int
session_item_configure (lua_State *L)
{
  WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
  WpProperties *props = wp_properties_new_empty ();

  /* validate arguments */
  luaL_checktype (L, 2, LUA_TTABLE);

  /* build the configuration properties */
  lua_pushnil (L);
  while (lua_next (L, 2)) {
    const gchar *key = NULL;
    g_autofree gchar *var = NULL;

    switch (lua_type (L, -1)) {
      case LUA_TBOOLEAN:
        var = g_strdup_printf ("%u", lua_toboolean (L, -1));
        break;
      case LUA_TNUMBER:
        if (lua_isinteger (L, -1))
          var = g_strdup_printf ("%lld", lua_tointeger (L, -1));
        else
          var = g_strdup_printf ("%f", lua_tonumber (L, -1));
        break;
      case LUA_TSTRING:
        var = g_strdup (lua_tostring (L, -1));
        break;
      case LUA_TUSERDATA: {
        GValue *v = lua_touserdata (L, -1);
        gpointer p = g_value_peek_pointer (v);
        var = g_strdup_printf ("%p", p);
        break;
      }
      default:
        luaL_error (L, "configure does not support lua type ",
            lua_typename(L, lua_type(L, -1)));
        break;
    }

    key = luaL_tolstring (L, -2, NULL);
    wp_properties_set (props, key, var);
    lua_pop (L, 2);
  }

  lua_pushboolean (L, wp_session_item_configure (si, props));
  return 1;
}

static int
session_item_register (lua_State *L)
{
  WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
  wp_session_item_register (g_object_ref (si));
  return 0;
}

static int
session_item_remove (lua_State *L)
{
  WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
  wp_session_item_remove (si);
  return 0;
}

static const luaL_Reg session_item_methods[] = {
  { "get_associated_proxy", session_item_get_associated_proxy },
  { "reset", session_item_reset },
  { "configure", session_item_configure },
  { "register", session_item_register },
  { "remove", session_item_remove },
  { NULL, NULL }
};

/* WpSiAdapter */

static int
si_adapter_get_ports_format (lua_State *L)
{
  WpSiAdapter *adapter = wplua_checkobject (L, 1, WP_TYPE_SI_ADAPTER);
  const gchar *mode = NULL;
  WpSpaPod *format = wp_si_adapter_get_ports_format (adapter, &mode);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, format);
  lua_pushstring (L, mode);
  return 2;
}

static void
si_adapter_set_ports_format_done (WpObject *o, GAsyncResult * res,
    GClosure * closure)
{
  g_autoptr (GError) error = NULL;
  GValue val[2] = { G_VALUE_INIT, G_VALUE_INIT };
  int n_vals = 1;

  if (!wp_si_adapter_set_ports_format_finish (WP_SI_ADAPTER (o), res, &error)) {
    wp_message_object (o, "%s", error->message);
    if (closure) {
      g_value_init (&val[1], G_TYPE_STRING);
      g_value_set_string (&val[1], error->message);
      n_vals = 2;
    }
  }
  if (closure) {
    g_value_init_from_instance (&val[0], o);
    g_closure_invoke (closure, NULL, n_vals, val, NULL);
    g_value_unset (&val[0]);
    g_value_unset (&val[1]);
    g_closure_invalidate (closure);
    g_closure_unref (closure);
  }
}

static int
si_adapter_set_ports_format (lua_State *L)
{
  WpSiAdapter *adapter = wplua_checkobject (L, 1, WP_TYPE_SI_ADAPTER);
  WpSpaPod *format = wplua_checkboxed (L, 2, WP_TYPE_SPA_POD);
  const gchar *mode = luaL_checkstring (L, 3);
  GClosure * closure = luaL_opt (L, wplua_checkclosure, 4, NULL);
  if (closure)
    g_closure_sink (g_closure_ref (closure));
  wp_si_adapter_set_ports_format (adapter, wp_spa_pod_ref (format), mode,
      (GAsyncReadyCallback) si_adapter_set_ports_format_done, closure);
  return 0;
}

static const luaL_Reg si_adapter_methods[] = {
  { "get_ports_format", si_adapter_get_ports_format },
  { "set_ports_format", si_adapter_set_ports_format },
  { NULL, NULL }
};

/* WpPipewireObject */

static int
pipewire_object_iterate_params (lua_State *L)
{
  WpPipewireObject *pwobj = wplua_checkobject (L, 1, WP_TYPE_PIPEWIRE_OBJECT);
  const gchar *id = luaL_checkstring (L, 2);
  WpIterator *it = wp_pipewire_object_enum_params_sync (pwobj, id, NULL);
  return push_wpiterator (L, it);
}

static int
pipewire_object_set_param (lua_State *L)
{
  WpPipewireObject *pwobj = wplua_checkobject (L, 1, WP_TYPE_PIPEWIRE_OBJECT);
  const gchar *id = luaL_checkstring (L, 2);
  WpSpaPod *pod = wplua_checkboxed (L, 3, WP_TYPE_SPA_POD);
  wp_pipewire_object_set_param (pwobj, id, 0, wp_spa_pod_ref (pod));
  return 0;
}

static const luaL_Reg pipewire_object_methods[] = {
  { "iterate_params", pipewire_object_iterate_params },
  { "set_param" , pipewire_object_set_param },
  { "set_params" , pipewire_object_set_param }, /* deprecated, compat only */
  { NULL, NULL }
};

/* WpState */

static int
state_new (lua_State *L)
{
  const gchar *name = luaL_checkstring (L, 1);
  WpState *state = wp_state_new (name);
  wplua_pushobject (L, state);
  return 1;
}

static int
state_clear (lua_State *L)
{
  WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
  wp_state_clear (state);
  return 0;
}

static int
state_save (lua_State *L)
{
  WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
  luaL_checktype (L, 2, LUA_TTABLE);
  g_autoptr (WpProperties) props = wplua_table_to_properties (L, 2);
  g_autoptr (GError) error = NULL;
  gboolean saved = wp_state_save (state, props, &error);
  lua_pushboolean (L, saved);
  lua_pushstring (L, error ? error->message : "");
  return 2;
}

static int
state_load (lua_State *L)
{
  WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
  g_autoptr (WpProperties) props = wp_state_load (state);
  wplua_properties_to_table (L, props);
  return 1;
}

static const luaL_Reg state_methods[] = {
  { "clear", state_clear },
  { "save" , state_save },
  { "load" , state_load },
  { NULL, NULL }
};

/* ImplModule */

static int
impl_module_new (lua_State *L)
{
  const char *name, *args = NULL;
  WpProperties *properties = NULL;

  name = luaL_checkstring (L, 1);

  if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL)
    args = luaL_checkstring (L, 2);

  if (lua_type (L, 3) != LUA_TNONE && lua_type (L, 3) != LUA_TNIL) {
    luaL_checktype (L, 3, LUA_TTABLE);
    properties = wplua_table_to_properties (L, 3);
  }

  bool load_file = false; // Load args as file path
  if (lua_type (L, 4) != LUA_TNONE && lua_type (L, 4) != LUA_TNIL) {
    luaL_checktype (L, 4, LUA_TBOOLEAN);
    load_file = lua_toboolean(L, 4);
  }

  WpImplModule *m = NULL;
  if (load_file) {
    m = wp_impl_module_load_file (get_wp_export_core (L),
      name, args, properties);
  } else {
    m = wp_impl_module_load (get_wp_export_core (L),
      name, args, properties);
  }

  if (m) {
    wplua_pushobject (L, m);
    return 1;
  } else {
    return 0;
  }
}

void
wp_lua_scripting_api_init (lua_State *L)
{
  g_autoptr (GError) error = NULL;

  luaL_newlib (L, glib_methods);
  lua_setglobal (L, "GLib");

  luaL_newlib (L, i18n_funcs);
  lua_setglobal (L, "I18n");

  luaL_newlib (L, log_funcs);
  lua_setglobal (L, "WpLog");

  luaL_newlib (L, core_funcs);
  lua_setglobal (L, "WpCore");

  luaL_newlib (L, plugin_funcs);
  lua_setglobal (L, "WpPlugin");

  wp_lua_scripting_pod_init (L);
  wp_lua_scripting_json_init (L);

  wplua_register_type_methods (L, G_TYPE_SOURCE,
      NULL, source_methods);
  wplua_register_type_methods (L, WP_TYPE_OBJECT,
      NULL, object_methods);
  wplua_register_type_methods (L, WP_TYPE_PROXY,
      NULL, proxy_methods);
  wplua_register_type_methods (L, WP_TYPE_GLOBAL_PROXY,
      NULL, global_proxy_methods);
  wplua_register_type_methods (L, WP_TYPE_OBJECT_INTEREST,
      object_interest_new, object_interest_methods);
  wplua_register_type_methods (L, WP_TYPE_OBJECT_MANAGER,
      object_manager_new, object_manager_methods);
  wplua_register_type_methods (L, WP_TYPE_METADATA,
      NULL, metadata_methods);
  wplua_register_type_methods (L, WP_TYPE_IMPL_METADATA,
      impl_metadata_new, NULL);
  wplua_register_type_methods (L, WP_TYPE_ENDPOINT,
      NULL, endpoint_methods);
  wplua_register_type_methods (L, WP_TYPE_DEVICE,
      device_new, NULL);
  wplua_register_type_methods (L, WP_TYPE_SPA_DEVICE,
      spa_device_new, spa_device_methods);
  wplua_register_type_methods (L, WP_TYPE_NODE,
      node_new, node_methods);
  wplua_register_type_methods (L, WP_TYPE_IMPL_NODE,
      impl_node_new, NULL);
  wplua_register_type_methods (L, WP_TYPE_PORT,
      NULL, port_methods);
  wplua_register_type_methods (L, WP_TYPE_LINK,
      link_new, NULL);
  wplua_register_type_methods (L, WP_TYPE_CLIENT,
      NULL, client_methods);
  wplua_register_type_methods (L, WP_TYPE_SESSION_ITEM,
      session_item_new, session_item_methods);
  wplua_register_type_methods (L, WP_TYPE_SI_ADAPTER,
      NULL, si_adapter_methods);
  wplua_register_type_methods (L, WP_TYPE_PIPEWIRE_OBJECT,
      NULL, pipewire_object_methods);
  wplua_register_type_methods (L, WP_TYPE_STATE,
      state_new, state_methods);
  wplua_register_type_methods (L, WP_TYPE_IMPL_MODULE,
      impl_module_new, NULL);

  if (!wplua_load_uri (L, URI_API, &error) ||
      !wplua_pcall (L, 0, 0, &error)) {
    wp_critical ("Failed to load api: %s", error->message);
  }
}
070701000000AD000081A4000000000000000000000001656CC35F0000166E000000000000000000000000000000000000003C00000000wireplumber-0.4.17/modules/module-lua-scripting/api/api.lua   -- WirePlumber
--
-- This file contains the API that is made available to the Lua scripts
--
-- Copyright © 2020 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT

local function Constraint (spec)
  assert (type(spec[1]) == "string", "Constraint: expected subject as string");
  assert (type(spec[2]) == "string", "Constraint: expected verb as string");

  local subject = spec[1]
  local verb = spec[2]
  local verbs = {
    ["="] = "equals",
    ["!"] = "not-equals",
    ["c"] = "in-list",
    ["~"] = "in-range",
    ["#"] = "matches",
    ["+"] = "is-present",
    ["-"] = "is-absent"
  }

  -- check and convert verb to its short version
  local verb_is_valid = false
  for k, v in pairs(verbs) do
    if verb == k or verb == v then
      verb = k
      spec[2] = k
      verb_is_valid = true
      break
    end
  end
  assert (verb_is_valid, "Constraint: invalid verb '" .. verb .. "'")

  -- check and convert type to its integer value
  local type = spec["type"]
  if type then
    local valid_types = { "pw-global", "pw", "gobject" }
    local type_is_valid = false

    for i, v in ipairs(valid_types) do
      if type == v then
        spec["type"] = i
        type_is_valid = true
        break
      end
    end

    assert(type_is_valid, "Constraint: invalid subject type '" .. type .. "'")
  end

  -- check if we got the right amount of values
  if verb == "=" or verb == "!" or verb == "#" then
    assert (spec[3] ~= nil,
      "Constraint: " .. verbs[verb] .. ": expected constraint value")
  elseif verb == "c" then
    assert (spec[3] ~= nil,
      "Constraint: " .. verbs[verb] .. ": expected at least one constraint value")
  elseif verb == "~" then
    assert (spec[3] ~= nil and spec[4] ~= nil,
      "Constraint: " .. verbs[verb] .. ": expected two values")
  else
    assert (spec[3] == nil,
      "Constraint: " .. verbs[verb] .. ": expected no value, but there is one")
  end

  return debug.setmetatable(spec, { __name = "Constraint" })
end

local function dump_table(t, indent)
  local indent_str = ""
  indent = indent or 1
  for i = 1, indent, 1 do
    indent_str = indent_str .. "\t"
  end

  local kvpairs = {}
  for k, v in pairs(t) do
    table.insert(kvpairs, { k, v })
  end

  table.sort(kvpairs, function (lhs, rhs)
    local left_key, right_key = lhs[1], rhs[1]

    -- If the types are different, we sort by the type
    -- in alphabetical order. This means that numbers
    -- come before before strings, etc
    if type(left_key) ~= type(right_key) then
      return type(left_key) < type(right_key)
    end

    local key_type = type(left_key)

    -- Only numbers and strings have a well-defined order
    -- that's guaranteed to fulfill the requirements of
    -- table.sort (strict weak order)
    if key_type == "number" or key_type == "string" then
      return left_key < right_key
    end

    -- At this point, we have no good way to order the objects.
    -- We can't just do `left_key < right_key`, because this may fail
    -- if there's no `__lt` metamethod, and even if there is one,
    -- it might not be a strict weak order. (The Lua reference does
    -- not say what happens if the order is not strict weak, so it's
    -- undefined behaviour)

    -- That said, it's always mathematically "permitted" to return `false`,
    -- in which case, since both x < y and y < x are false, the elements
    -- are considered "equivalent" and may appear in any order in relation
    -- to each other. The elements are still sorted in relation to the
    -- *other* keys.
    return false

    -- To be a strict weak order, if x and y are equivalent, and y and z
    -- are equivalent, then x and z must be equivalent too. Otherwise the
    -- ordering is only a strict *partial* order.

    -- Note that the Lua 5.3 reference states that the order merely has to
    -- be a strict *partial* order, but since all weak orders are partial
    -- orders, this is not a problem.
  end)

  for _, pair in ipairs(kvpairs) do
    local k, v = table.unpack(pair)

    if (type(v) == "table") then
      print (indent_str .. tostring(k) .. ": ")
      dump_table(v, indent + 1)
    else
      print (indent_str .. tostring(k) .. ": " .. tostring(v))
    end
  end
end

local Debug = {
  dump_table = dump_table,
}

local Id = {
  INVALID = 0xffffffff,
  ANY = 0xffffffff,
}

local Features = {
  PipewireObject = {
    MINIMAL = 0x11,
  },
  ALL = 0xffffffff,
}

local Feature = {
  Proxy = {
    BOUND             = 1,
  },
  PipewireObject = {
    INFO              = (1 << 4),
    PARAM_PROPS       = (1 << 5),
    PARAM_FORMAT      = (1 << 6),
    PARAM_PROFILE     = (1 << 7),
    PARAM_PORT_CONFIG = (1 << 8),
    PARAM_ROUTE       = (1 << 9),
  },
  SpaDevice = {
    ENABLED           = (1 << 16),
  },
  Node = {
    PORTS             = (1 << 16),
  },
  Session = {
    ENDPOINTS         = (1 << 16),
    LINKS             = (1 << 17),
  },
  Endpoint = {
    STREAMS           = (1 << 16),
  },
  Metadata = {
    DATA              = (1 << 16),
  },
  SessionItem = {
    ACTIVE            = (1 << 0),
    EXPORTED          = (1 << 1),
  },
}

SANDBOX_EXPORT = {
  Debug = Debug,
  Id = Id,
  Features = Features,
  Feature = Feature,
  GLib = GLib,
  I18n = I18n,
  Log = WpLog,
  Core = WpCore,
  Plugin = WpPlugin,
  ObjectManager = WpObjectManager_new,
  Interest = WpObjectInterest_new,
  SessionItem = WpSessionItem_new,
  Constraint = Constraint,
  Device = WpDevice_new,
  SpaDevice = WpSpaDevice_new,
  Node = WpNode_new,
  LocalNode = WpImplNode_new,
  Link = WpLink_new,
  Pod = WpSpaPod,
  Json = WpSpaJson,
  State = WpState_new,
  LocalModule = WpImplModule_new,
  ImplMetadata = WpImplMetadata_new,
}
  070701000000AE000081A4000000000000000000000001656CC35F0000130A000000000000000000000000000000000000003D00000000wireplumber-0.4.17/modules/module-lua-scripting/api/config.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <wplua/wplua.h>

static gboolean
load_components (lua_State *L, WpCore * core, GError ** error)
{
  lua_getglobal (L, "SANDBOX_COMMON_ENV");

  switch (lua_getfield (L, -1, "components")) {
  case LUA_TTABLE:
    break;
  case LUA_TNIL:
    wp_debug ("no components specified");
    goto done;
  default:
    g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
        "Expected 'components' to be a table");
    return FALSE;
  }

  lua_pushnil (L);
  while (lua_next (L, -2)) {
    /* value must be a table */
    if (lua_type (L, -1) != LUA_TTABLE) {
      g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
          "'components' must be a table with tables as values");
      return FALSE;
    }

    /* record indexes to the current key and value of the components table */
    int key = lua_absindex (L, -2);
    int table = lua_absindex (L, -1);

    /* get component */
    if (lua_geti (L, table, 1) != LUA_TSTRING) {
      g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
          "components['%s'] has a non-string or unspecified component name",
          lua_tostring (L, key));
      return FALSE;
    }
    const char * component = lua_tostring (L, -1);

    /* get component type */
    if (lua_getfield (L, table, "type") != LUA_TSTRING) {
      g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
          "components['%s'] has a non-string or unspecified component type",
          lua_tostring (L, key));
      return FALSE;
    }
    const char * type = lua_tostring (L, -1);

    /* optional component arguments */
    GVariant *args = NULL;
    if (lua_getfield (L, table, "args") == LUA_TTABLE) {
      args = wplua_lua_to_gvariant (L, -1);
    }

    gboolean optional = FALSE;
    if (lua_getfield (L, table, "optional") == LUA_TBOOLEAN) {
      optional = lua_toboolean (L, -1);
    }

    wp_debug ("load component: %s (%s) optional(%s)",
     component, type, (optional ? "true" : "false"));

    g_autoptr (GError) load_error = NULL;
    if (!wp_core_load_component (core, component, type, args, &load_error)) {
      if (!optional) {
        g_propagate_error (error, g_steal_pointer (&load_error));
        return FALSE;
      } else {
        wp_message ("%s", load_error->message);
      }
    }

    /* clear the stack up to the key */
    lua_settop (L, key);
  }

done:
  lua_pop (L, 2); /* pop components & SANDBOX_COMMON_ENV */
  return TRUE;
}

static gboolean
load_file (const GValue *item, GValue *ret, gpointer data)
{
  lua_State *L = data;
  const gchar *path = g_value_get_string (item);
  g_autoptr (GError) error = NULL;
  int nargs;

  if (g_file_test (path, G_FILE_TEST_IS_DIR))
    return TRUE;

  wp_info ("loading config file: %s", path);

  nargs = wplua_push_sandbox (L);
  if (!wplua_load_path (L, path, &error) ||
      !wplua_pcall (L, nargs, 0, &error)) {
    lua_settop (L, 0);
    g_value_unset (ret);
    g_value_init (ret, G_TYPE_ERROR);
    g_value_take_boxed (ret, g_steal_pointer (&error));
    return FALSE;
  }

  g_value_set_int (ret, g_value_get_int (ret) + 1);
  return TRUE;
}

#define CONFIG_DIRS_LOOKUP_SET \
    (WP_LOOKUP_DIR_ENV_CONFIG | \
     WP_LOOKUP_DIR_XDG_CONFIG_HOME | \
     WP_LOOKUP_DIR_ETC | \
     WP_LOOKUP_DIR_PREFIX_SHARE)

gboolean
wp_lua_scripting_load_configuration (const gchar * conf_file,
    WpCore * core, GError ** error)
{
  g_autoptr (lua_State) L = wplua_new ();
  g_autofree gchar * path = NULL;
  g_autoptr (WpIterator) it = NULL;
  g_auto (GValue) fold_ret = G_VALUE_INIT;
  gint nfiles = 0;

  wplua_enable_sandbox (L, 0);

  /* load conf_file itself */
  path = wp_find_file (CONFIG_DIRS_LOOKUP_SET, conf_file, NULL);
  if (path) {
    wp_info ("loading config file: %s", path);
    int nargs = wplua_push_sandbox (L);
    if (!wplua_load_path (L, path, error) ||
        !wplua_pcall (L, nargs, 0, error)) {
      lua_settop (L, 0);
      return FALSE;
    }
    nfiles = 1;
  }
  g_clear_pointer (&path, g_free);

  path = g_strdup_printf ("%s.d", conf_file);
  it = wp_new_files_iterator (CONFIG_DIRS_LOOKUP_SET, path, ".lua");

  g_value_init (&fold_ret, G_TYPE_INT);
  g_value_set_int (&fold_ret, nfiles);
  if (!wp_iterator_fold (it, load_file, &fold_ret, L)) {
    if (error && G_VALUE_HOLDS (&fold_ret, G_TYPE_ERROR))
      *error = g_value_dup_boxed (&fold_ret);
    return FALSE;
  }
  nfiles = g_value_get_int (&fold_ret);

  if (nfiles == 0) {
    g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
        "Could not locate configuration file '%s'", conf_file);
    return FALSE;
  }

  if (!load_components (L, core, error))
    return FALSE;

  return TRUE;
}
  070701000000AF000081A4000000000000000000000001656CC35F000000CA000000000000000000000000000000000000004200000000wireplumber-0.4.17/modules/module-lua-scripting/api/gresource.xml <?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/org/freedesktop/pipewire/wireplumber/m-lua-scripting/">
    <file compressed="true">api.lua</file>
  </gresource>
</gresources>
  070701000000B0000081A4000000000000000000000001656CC35F000023FB000000000000000000000000000000000000003B00000000wireplumber-0.4.17/modules/module-lua-scripting/api/json.c    /* WirePlumber
 *
 * Copyright © 2022 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <wplua/wplua.h>

/* API */

static int
spa_json_get_data (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  lua_pushstring (L, wp_spa_json_get_data (json));
  return 1;
}

static int
spa_json_get_size (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  lua_pushinteger (L, wp_spa_json_get_size (json));
  return 1;
}

static int
spa_json_to_string (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  /* Instead of using wp_spa_json_to_string() and lua_pushstring, we can avoid
   * an extra allocation if we use lua_pushlstring with wp_spa_json_get_data()
   * and wp_spa_json_get_size () */
  lua_pushlstring (L, wp_spa_json_get_data (json), wp_spa_json_get_size (json));
  return 1;
}

static int
spa_json_is_null (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  lua_pushboolean (L, wp_spa_json_is_null (json));
  return 1;
}

static int
spa_json_is_boolean (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  lua_pushboolean (L, wp_spa_json_is_boolean (json));
  return 1;
}

static int
spa_json_is_int (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  lua_pushboolean (L, wp_spa_json_is_int (json));
  return 1;
}

static int
spa_json_is_float (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  lua_pushboolean (L, wp_spa_json_is_float (json));
  return 1;
}

static int
spa_json_is_string (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  lua_pushboolean (L, wp_spa_json_is_string (json));
  return 1;
}

static int
spa_json_is_array (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  lua_pushboolean (L, wp_spa_json_is_array (json));
  return 1;
}

static int
spa_json_is_object (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  lua_pushboolean (L, wp_spa_json_is_object (json));
  return 1;
}

static void
push_luajson (lua_State *L, WpSpaJson *json, gint n_recursions)
{
  /* Null */
  if (wp_spa_json_is_null (json)) {
    lua_pushnil (L);
  }

  /* Boolean */
  else if (wp_spa_json_is_boolean (json)) {
    gboolean value = FALSE;
    g_warn_if_fail (wp_spa_json_parse_boolean (json, &value));
    lua_pushboolean (L, value);
  }

  /* Int */
  else if (wp_spa_json_is_int (json)) {
    gint value = 0;
    g_warn_if_fail (wp_spa_json_parse_int (json, &value));
    lua_pushinteger (L, value);
  }

  /* Float */
  else if (wp_spa_json_is_float (json)) {
    float value = 0;
    g_warn_if_fail (wp_spa_json_parse_float (json, &value));
    lua_pushnumber (L, value);
  }

  /* Array */
  else if (wp_spa_json_is_array (json) && n_recursions > 0) {
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
    guint i = 1;
    lua_newtable (L);
    for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
      WpSpaJson *j = g_value_get_boxed (&item);
      push_luajson (L, j, n_recursions - 1);
      lua_rawseti (L, -2, i++);
    }
  }

  /* Object */
  else if (wp_spa_json_is_object (json) && n_recursions > 0) {
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
    lua_newtable (L);
    for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
      WpSpaJson *key = g_value_get_boxed (&item);
      g_autofree gchar *key_str = NULL;
      WpSpaJson *value = NULL;
      key_str = wp_spa_json_parse_string (key);
      g_warn_if_fail (key_str);
      g_value_unset (&item);
      if (!wp_iterator_next (it, &item))
        break;
      value = g_value_get_boxed (&item);
      push_luajson (L, value, n_recursions - 1);
      lua_setfield (L, -2, key_str);
    }
  }

  /* Otherwise alwyas parse as String to allow parsing strings without quotes */
  else {
    g_autofree gchar *value = wp_spa_json_parse_string (json);
    g_warn_if_fail (value);
    lua_pushstring (L, value);
  }
}

static int
spa_json_parse (lua_State *L)
{
  WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
  gint n_recursions = luaL_opt (L, luaL_checkinteger, 2, INT_MAX);
  push_luajson (L, json, n_recursions);
  return 1;
}

/* Raw */

static int
spa_json_raw_new (lua_State *L)
{
  const gchar *value = lua_tostring (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_JSON, wp_spa_json_new_from_string (value));
  return 1;
}

/* None */

static int
spa_json_null_new (lua_State *L)
{
  wplua_pushboxed (L, WP_TYPE_SPA_JSON, wp_spa_json_new_null ());
  return 1;
}

/* Boolean */

static int
spa_json_boolean_new (lua_State *L)
{
  gboolean value = lua_toboolean (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_JSON, wp_spa_json_new_boolean (value));
  return 1;
}

/* Int */

static int
spa_json_int_new (lua_State *L)
{
  gint64 value = lua_tointeger (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_JSON, wp_spa_json_new_int (value));
  return 1;
}

/* Float */

static int
spa_json_float_new (lua_State *L)
{
  float value = lua_tonumber (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_JSON, wp_spa_json_new_float (value));
  return 1;
}

/* String */

static int
spa_json_string_new (lua_State *L)
{
  const gchar *value = lua_tostring (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_JSON, wp_spa_json_new_string (value));
  return 1;
}

/* Array */

static int
spa_json_array_new (lua_State *L)
{
  g_autoptr (WpSpaJsonBuilder) builder = wp_spa_json_builder_new_array ();

  luaL_checktype (L, 1, LUA_TTABLE);

  lua_pushnil (L);
  while (lua_next (L, -2)) {
    /* We only add table values with integer keys */
    if (lua_isinteger (L, -2)) {
      switch (lua_type (L, -1)) {
        case LUA_TBOOLEAN:
          wp_spa_json_builder_add_boolean (builder, lua_toboolean (L, -1));
          break;
        case LUA_TNUMBER:
          if (lua_isinteger (L, -1))
            wp_spa_json_builder_add_int (builder, lua_tointeger (L, -1));
          else
            wp_spa_json_builder_add_float (builder, lua_tonumber (L, -1));
          break;
        case LUA_TSTRING:
          wp_spa_json_builder_add_string (builder, lua_tostring (L, -1));
          break;
        case LUA_TUSERDATA: {
          WpSpaJson *json = wplua_checkboxed (L, -1, WP_TYPE_SPA_JSON);
          wp_spa_json_builder_add_json (builder, json);
          break;
        }
        default:
          luaL_error (L, "Json does not support lua type ",
              lua_typename(L, lua_type(L, -1)));
          break;
      }
    }
    lua_pop (L, 1);
  }

  wplua_pushboxed (L, WP_TYPE_SPA_JSON, wp_spa_json_builder_end (builder));
  return 1;
}

/* Object */

static int
spa_json_object_new (lua_State *L)
{
  g_autoptr (WpSpaJsonBuilder) builder = wp_spa_json_builder_new_object ();

  luaL_checktype (L, 1, LUA_TTABLE);

  lua_pushnil (L);
  while (lua_next (L, -2)) {
    /* We only add table values with string keys */
    if (lua_type (L, -2) == LUA_TSTRING) {
      wp_spa_json_builder_add_property (builder, lua_tostring (L, -2));

      switch (lua_type (L, -1)) {
        case LUA_TBOOLEAN:
          wp_spa_json_builder_add_boolean (builder, lua_toboolean (L, -1));
          break;
        case LUA_TNUMBER:
          if (lua_isinteger (L, -1))
            wp_spa_json_builder_add_int (builder, lua_tointeger (L, -1));
          else
            wp_spa_json_builder_add_float (builder, lua_tonumber (L, -1));
          break;
        case LUA_TSTRING:
          wp_spa_json_builder_add_string (builder, lua_tostring (L, -1));
          break;
        case LUA_TUSERDATA: {
          WpSpaJson *json = wplua_checkboxed (L, -1, WP_TYPE_SPA_JSON);
          wp_spa_json_builder_add_json (builder, json);
          break;
        }
        default:
          luaL_error (L, "Json does not support lua type ",
              lua_typename(L, lua_type(L, -1)));
          break;
      }
    }

    lua_pop (L, 1);
  }

  wplua_pushboxed (L, WP_TYPE_SPA_JSON, wp_spa_json_builder_end (builder));
  return 1;
}

/* Init */

static const luaL_Reg spa_json_methods[] = {
  { "get_data", spa_json_get_data },
  { "get_size", spa_json_get_size },
  { "to_string", spa_json_to_string },
  { "is_null", spa_json_is_null },
  { "is_boolean", spa_json_is_boolean },
  { "is_int", spa_json_is_int },
  { "is_float", spa_json_is_float },
  { "is_string", spa_json_is_string },
  { "is_array", spa_json_is_array },
  { "is_object", spa_json_is_object },
  { "parse", spa_json_parse },
  { NULL, NULL }
};

static const luaL_Reg spa_json_constructors[] = {
  { "Raw", spa_json_raw_new },
  { "Null", spa_json_null_new },
  { "Boolean", spa_json_boolean_new },
  { "Int", spa_json_int_new },
  { "Float", spa_json_float_new },
  { "String", spa_json_string_new },
  { "Array", spa_json_array_new },
  { "Object", spa_json_object_new },
  { NULL, NULL }
};

void
wp_lua_scripting_json_init (lua_State *L)
{
  luaL_newlib (L, spa_json_constructors);
  lua_setglobal (L, "WpSpaJson");

  wplua_register_type_methods (L, WP_TYPE_SPA_JSON, NULL, spa_json_methods);
}
 070701000000B1000081A4000000000000000000000001656CC35F000000C1000000000000000000000000000000000000004000000000wireplumber-0.4.17/modules/module-lua-scripting/api/meson.build   m_lua_scripting_resources = gnome.compile_resources(
    'm-lua-scripting-resources',
    'gresource.xml',
    source_dir: meson.current_source_dir(),
    c_name: '_m_lua_scripting_resources')
   070701000000B2000081A4000000000000000000000001656CC35F00008151000000000000000000000000000000000000003A00000000wireplumber-0.4.17/modules/module-lua-scripting/api/pod.c /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <wplua/wplua.h>

#include <spa/utils/type.h>

#define MAX_LUA_TYPES 9

/* Builder */

typedef gboolean (*primitive_lua_add_func) (WpSpaPodBuilder *, WpSpaIdValue,
    lua_State *, int);

struct primitive_lua_type {
  WpSpaType primitive_type;
  primitive_lua_add_func primitive_lua_add_funcs[MAX_LUA_TYPES];
};

static inline gboolean
builder_add_boolean_lua_boolean (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  wp_spa_pod_builder_add_boolean (b, lua_toboolean (L, idx));
  return TRUE;
}

static inline gboolean
builder_add_boolean_lua_number (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  if (lua_isinteger (L, idx)) {
    wp_spa_pod_builder_add_boolean (b, lua_tointeger (L, idx) > 0);
    return TRUE;
  }
  return FALSE;
}

static inline gboolean
builder_add_boolean_lua_string (WpSpaPodBuilder *b, WpSpaIdValue key_id,
   lua_State *L, int idx)
{
  const gchar *value = lua_tostring (L, idx);
  wp_spa_pod_builder_add_boolean (b,
     g_strcmp0 (value, "true") == 0 || g_strcmp0 (value, "1") == 0);
  return TRUE;
}

static inline gboolean
builder_add_id_lua_number (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  if (lua_isinteger (L, idx)) {
    wp_spa_pod_builder_add_id (b, lua_tointeger (L, idx));
    return TRUE;
  }
  return FALSE;
}

static inline gboolean
builder_add_id_lua_string (WpSpaPodBuilder *b, WpSpaIdValue key_id,
   lua_State *L, int idx)
{
  const gchar *value = lua_tostring (L, idx);
  WpSpaIdTable id_table = NULL;
  WpSpaIdValue id_val = NULL;
  if (key_id) {
    wp_spa_id_value_get_value_type (key_id, &id_table);
    if (id_table) {
      id_val = wp_spa_id_table_find_value_from_short_name (id_table, value);
      wp_spa_pod_builder_add_id (b, wp_spa_id_value_number (id_val));
      return TRUE;
    }
  }
  return FALSE;
}

static inline gboolean
builder_add_int_lua_boolean (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  gboolean value = lua_toboolean (L, idx);
  wp_spa_pod_builder_add_int (b, value ? 1 : 0);
  return TRUE;
}

static inline gboolean
builder_add_int_lua_number (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  if (lua_isinteger (L, idx)) {
    wp_spa_pod_builder_add_int (b, lua_tointeger (L, idx));
    return TRUE;
  }
  return FALSE;
}

static inline gboolean
builder_add_int_lua_string (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  const gchar *value = lua_tostring (L, idx);
  wp_spa_pod_builder_add_int (b, strtol (value, NULL, 10));
  return TRUE;
}

static inline gboolean
builder_add_long_lua_boolean (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  gboolean value = lua_toboolean (L, idx);
  wp_spa_pod_builder_add_long (b, value ? 1 : 0);
  return TRUE;
}

static inline gboolean
builder_add_long_lua_number (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  if (lua_isinteger (L, idx)) {
    wp_spa_pod_builder_add_long (b, lua_tointeger (L, idx));
    return TRUE;
  }
  return FALSE;
}

static inline gboolean
builder_add_long_lua_string (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  const gchar *value = lua_tostring (L, idx);
  wp_spa_pod_builder_add_long (b, strtol (value, NULL, 10));
  return TRUE;
}

static inline gboolean
builder_add_float_lua_boolean (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  gboolean value = lua_toboolean (L, idx);
  wp_spa_pod_builder_add_float (b, value ? 1.0f : 0.0f);
  return TRUE;
}

static inline gboolean
builder_add_float_lua_number (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  if (lua_isnumber (L, idx) && !lua_isinteger (L, idx)) {
    wp_spa_pod_builder_add_float (b, lua_tonumber (L, idx));
    return TRUE;
  }
  return FALSE;
}

static inline gboolean
builder_add_double_lua_boolean (WpSpaPodBuilder *b, WpSpaIdValue key_id,
   lua_State *L, int idx)
{
  gboolean value = lua_toboolean (L, idx);
  wp_spa_pod_builder_add_double (b, value ? 1.0f : 0.0f);
  return TRUE;
}

static inline gboolean
builder_add_double_lua_number (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  if (lua_isnumber (L, idx) && !lua_isinteger (L, idx)) {
    wp_spa_pod_builder_add_double (b, lua_tonumber (L, idx));
    return TRUE;
  }
  return FALSE;
}

static inline gboolean
builder_add_string_lua_boolean (WpSpaPodBuilder *b, WpSpaIdValue key_id,
   lua_State *L, int idx)
{
  gboolean value = lua_toboolean (L, idx);
  wp_spa_pod_builder_add_string (b, value ? "true" : "false");
  return TRUE;
}

static inline gboolean
builder_add_string_lua_number (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  g_autofree gchar *value = NULL;
  value = lua_isinteger (L, idx) ?
      g_strdup_printf ("%lld", lua_tointeger (L, idx)) :
      g_strdup_printf ("%f", lua_tonumber (L, idx));
  wp_spa_pod_builder_add_string (b, value);
  return TRUE;
}

static inline gboolean
builder_add_string_lua_string (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  const gchar *value = lua_tostring (L, idx);
  wp_spa_pod_builder_add_string (b, value);
  return TRUE;
}

static inline gboolean
builder_add_bytes_lua_number (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  if (lua_isinteger (L, idx)) {
    gint64 value = lua_tointeger (L, idx);
    wp_spa_pod_builder_add_bytes (b, (gconstpointer)&value, sizeof (value));
  } else {
    double value = lua_tonumber (L, idx);
    wp_spa_pod_builder_add_bytes (b, (gconstpointer)&value, sizeof (value));
  }
  return TRUE;
}

static inline gboolean
builder_add_bytes_lua_string (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  const gchar *value = lua_tostring (L, idx);
  wp_spa_pod_builder_add_bytes (b, (gconstpointer)value, strlen (value));
  return TRUE;
}

static inline gboolean
builder_add_fd_lua_number (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  if (lua_isinteger (L, idx)) {
    wp_spa_pod_builder_add_fd (b, lua_tointeger (L, idx));
    return TRUE;
  }
  return FALSE;
}

static inline gboolean
builder_add_fd_lua_string (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  const gchar *value = lua_tostring (L, idx);
  wp_spa_pod_builder_add_fd (b, strtol (value, NULL, 10));
  return TRUE;
}

static inline gboolean
is_pod_type_compatible (WpSpaType type, WpSpaPod *pod)
{
  /* Check if the pod type matches */
  if (type == wp_spa_pod_get_spa_type (pod))
    return TRUE;

  /* Otherwise, check if the child type of Choice pod matches */
  if (wp_spa_pod_is_choice (pod)) {
    g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
    WpSpaType child_type = wp_spa_pod_get_spa_type (child);
    if (type == child_type)
      return TRUE;
  }

  return FALSE;
}

static inline gboolean
builder_add_lua_userdata (WpSpaPodBuilder *b, WpSpaIdValue key_id,
    lua_State *L, int idx)
{
  WpSpaPod *pod = wplua_checkboxed (L, idx, WP_TYPE_SPA_POD);
  if (pod) {
    if (key_id) {
      WpSpaType prop_type = wp_spa_id_value_get_value_type (key_id, NULL);
      if (is_pod_type_compatible (prop_type, pod)) {
        wp_spa_pod_builder_add_pod (b, pod);
        return TRUE;
      }
    } else {
      wp_spa_pod_builder_add_pod (b, pod);
      return TRUE;
    }
  }
  return FALSE;
}

static const struct primitive_lua_type primitive_lua_types[] = {
  {SPA_TYPE_Bool, {
    [LUA_TBOOLEAN] = builder_add_boolean_lua_boolean,
    [LUA_TNUMBER] = builder_add_boolean_lua_number,
    [LUA_TSTRING] = builder_add_boolean_lua_string,
  }},
  {SPA_TYPE_Id, {
    [LUA_TNUMBER] = builder_add_id_lua_number,
    [LUA_TSTRING] = builder_add_id_lua_string,
  }},
  {SPA_TYPE_Int, {
    [LUA_TBOOLEAN] = builder_add_int_lua_boolean,
    [LUA_TNUMBER] = builder_add_int_lua_number,
    [LUA_TSTRING] = builder_add_int_lua_string,
  }},
  {SPA_TYPE_Long, {
    [LUA_TBOOLEAN] = builder_add_long_lua_boolean,
    [LUA_TNUMBER] = builder_add_long_lua_number,
    [LUA_TSTRING] = builder_add_long_lua_string,
  }},
  {SPA_TYPE_Float, {
    [LUA_TBOOLEAN] = builder_add_float_lua_boolean,
    [LUA_TNUMBER] = builder_add_float_lua_number,
  }},
  {SPA_TYPE_Double, {
    [LUA_TBOOLEAN] = builder_add_double_lua_boolean,
    [LUA_TNUMBER] = builder_add_double_lua_number,
  }},
  {SPA_TYPE_String, {
    [LUA_TBOOLEAN] = builder_add_string_lua_boolean,
    [LUA_TNUMBER] = builder_add_string_lua_number,
    [LUA_TSTRING] = builder_add_string_lua_string,
  }},
  {SPA_TYPE_Bytes, {
    [LUA_TNUMBER] = builder_add_bytes_lua_number,
    [LUA_TSTRING] = builder_add_bytes_lua_string,
  }},
  {SPA_TYPE_Fd, {
    [LUA_TNUMBER] = builder_add_fd_lua_number,
    [LUA_TSTRING] = builder_add_fd_lua_string,
  }},
  {0, {}},
};

static gboolean
builder_add_key (WpSpaPodBuilder *b, WpSpaIdTable table, lua_State *L, int idx)
{
  /* Number */
  if (lua_type (L, -1) == LUA_TNUMBER) {
    wp_spa_pod_builder_add_id (b, lua_tonumber (L, idx));
    return TRUE;
  }

  /* String */
  else if (lua_type (L, -1) == LUA_TSTRING) {
    const gchar *key = lua_tostring (L, idx);
    WpSpaIdValue val = wp_spa_id_table_find_value_from_short_name (table, key);
    if (val) {
      wp_spa_pod_builder_add_id (b, wp_spa_id_value_number (val));
      return TRUE;
    }
  }

  return FALSE;
}

static gboolean
builder_add_value (WpSpaPodBuilder *b, WpSpaType array_type, lua_State *L,
    int idx)
{
  int ltype = lua_type (L, idx);
  guint i;

  if (ltype < 0 || ltype >= MAX_LUA_TYPES)
    return FALSE;

  for (i = 0; primitive_lua_types[i].primitive_type; i++) {
    const struct primitive_lua_type *t = primitive_lua_types + i;
    if (t->primitive_type == array_type) {
      primitive_lua_add_func f = t->primitive_lua_add_funcs[ltype];
      if (f) {
        return f (b, NULL, L, idx);
      }
    }
  }
  return FALSE;
}

static void
builder_add_table (lua_State *L, WpSpaPodBuilder *builder)
{
  const gchar *type_name = NULL;
  WpSpaType type = WP_SPA_TYPE_INVALID;
  WpSpaIdTable table = NULL;

  luaL_checktype (L, 1, LUA_TTABLE);

  lua_pushnil (L);
  while (lua_next (L, 1)) {
    /* First filed is always the item type or table */
    if (type == WP_SPA_TYPE_INVALID && !table) {
      if (lua_type (L, -1) == LUA_TSTRING) {
        type_name = lua_tostring (L, -1);
        type = wp_spa_type_from_name (type_name);
        if (type == WP_SPA_TYPE_INVALID) {
          table = wp_spa_id_table_from_name (type_name);
          if (!table)
            luaL_error (L, "Unknown type '%s'", type_name);
        }
      } else {
        luaL_error (L,
            "must have the item type or table on its first field");
      }
    }

    /* Add remaining table key elements */
    else if (table) {
      if (!builder_add_key (builder, table, L, -1))
        luaL_error (L, "key could not be added");
    }

    /* Add remaining value elements */
    else if (type != WP_SPA_TYPE_INVALID) {
      if (!builder_add_value (builder, type, L, -1))
        luaL_error (L, "value could not be added");
    }

    lua_pop (L, 1);
  }
}

/* None */

static int
spa_pod_none_new (lua_State *L)
{
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_none ());
  return 1;
}

/* Boolean */

static int
spa_pod_boolean_new (lua_State *L)
{
  gboolean value = lua_toboolean (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_boolean (value));
  return 1;
}

/* Id */

static int
spa_pod_id_new (lua_State *L)
{
  /* Id number */
  if (lua_type (L, 1) == LUA_TNUMBER) {
    gint64 value = lua_tointeger (L, 1);
    wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_id (value));
    return 1;
  }

  /* Table name and key name */
  else if (lua_type (L, 1) == LUA_TSTRING) {
    const gchar *table_name = lua_tostring (L, 1);
    const gchar *key_name = lua_tostring (L, 2);
    WpSpaIdTable table = NULL;
    WpSpaIdValue value = NULL;
    table = wp_spa_id_table_from_name (table_name);
    if (!table)
      luaL_error (L, "table '%s' does not exist", table_name);
    value = wp_spa_id_table_find_value_from_short_name (table, key_name);
    if (!value)
      luaL_error (L, "key '%s' does not exist in '%s'", key_name, table_name);
    wplua_pushboxed (L, WP_TYPE_SPA_POD,
        wp_spa_pod_new_id (wp_spa_id_value_number (value)));
    return 1;
  }

  /* Error */
  luaL_error (L, "Invalid parameters");
  return 0;
}

/* Int */

static int
spa_pod_int_new (lua_State *L)
{
  gint64 value = lua_tointeger (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_int (value));
  return 1;
}

/* Long */

static int
spa_pod_long_new (lua_State *L)
{
  gint64 value = lua_tointeger (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_long (value));
  return 1;
}

/* Float */

static int
spa_pod_float_new (lua_State *L)
{
  float value = lua_tonumber (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_float (value));
  return 1;
}

/* Double */

static int
spa_pod_double_new (lua_State *L)
{
  double value = lua_tonumber (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_double (value));
  return 1;
}

/* String */

static int
spa_pod_string_new (lua_State *L)
{
  const gchar *value = lua_tostring (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_string (value));
  return 1;
}

/* Bytes */

static int
spa_pod_bytes_new (lua_State *L)
{
  switch (lua_type (L, 1)) {
    case LUA_TNUMBER: {
      if (lua_isinteger (L, 1)) {
        guint64 value = lua_tointeger (L, 1);
        wplua_pushboxed (L, WP_TYPE_SPA_POD,
            wp_spa_pod_new_bytes (&value, sizeof (guint64)));
      } else {
        double value = lua_tonumber (L, 1);
        wplua_pushboxed (L, WP_TYPE_SPA_POD,
            wp_spa_pod_new_bytes (&value, sizeof (double)));
      }
      return 1;
    }
    case LUA_TSTRING: {
      const gchar *str = lua_tostring (L, 1);
      wplua_pushboxed (L, WP_TYPE_SPA_POD,
          wp_spa_pod_new_bytes (str, strlen (str)));
      return 1;
    }
    default:
      luaL_error (L, "Only number and strings are valid for bytes pod");
      break;
  }
  return 0;
}

/* Pointer */

static int
spa_pod_pointer_new (lua_State *L)
{
  const gchar *type = lua_tostring (L, 1);
  gconstpointer value = lua_touserdata (L, 2);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_pointer (type, value));
  return 1;
}

/* Fd */

static int
spa_pod_fd_new (lua_State *L)
{
  gint64 value = lua_tointeger (L, 1);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_fd (value));
  return 1;
}

/* Rectangle */

static int
spa_pod_rectangle_new (lua_State *L)
{
  gint64 width = lua_tointeger (L, 1);
  gint64 height = lua_tointeger (L, 2);
  wplua_pushboxed (L, WP_TYPE_SPA_POD,
      wp_spa_pod_new_rectangle (width, height));
  return 1;
}

/* Fraction */

static int
spa_pod_fraction_new (lua_State *L)
{
  gint64 num = lua_tointeger (L, 1);
  gint64 denom = lua_tointeger (L, 2);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_new_fraction (num, denom));
  return 1;
}


/* Object */

static gboolean
object_add_property (WpSpaPodBuilder *b, WpSpaIdTable table,
    const gchar *key, lua_State *L, int idx)
{
  guint i;
  WpSpaIdValue prop_id = NULL;
  int ltype = lua_type (L, idx);

  if (ltype < 0 || ltype >= MAX_LUA_TYPES)
    return FALSE;

  /* Check if we can add primitive property directly from LUA type */
  prop_id = wp_spa_id_table_find_value_from_short_name (table, key);
  if (prop_id) {
    WpSpaType prop_type = wp_spa_id_value_get_value_type (prop_id, NULL);
    if (prop_type != WP_SPA_TYPE_INVALID) {
      for (i = 0; primitive_lua_types[i].primitive_type; i++) {
        const struct primitive_lua_type *t = primitive_lua_types + i;
        if (t->primitive_type == prop_type) {
          primitive_lua_add_func f = t->primitive_lua_add_funcs[ltype];
          if (f) {
            wp_spa_pod_builder_add_property (b, key);
            return f (b, prop_id, L, idx);
          }
        }
      }
    }
  }

  /* Otherwise just add pod property */
  if (lua_type (L, idx) == LUA_TUSERDATA) {
    wp_spa_pod_builder_add_property (b, key);
    return builder_add_lua_userdata (b, prop_id, L, idx);
  }

  return FALSE;
}

static int
spa_pod_object_new (lua_State *L)
{
  g_autoptr (WpSpaPodBuilder) builder = NULL;
  const gchar *fields[2] = { NULL, NULL };  // type_name, name_id
  WpSpaType object_type = WP_SPA_TYPE_INVALID;
  WpSpaIdTable table = NULL;

  luaL_checktype (L, 1, LUA_TTABLE);

  lua_geti (L, 1, 1);
  fields[0] = lua_tostring (L, -1);
  lua_geti (L, 1, 2);
  fields[1] = lua_tostring (L, -1);

  object_type = wp_spa_type_from_name (fields[0]);
  if (object_type == WP_SPA_TYPE_INVALID)
    luaL_error (L, "Invalid object type '%s'", fields[0]);

  table = wp_spa_type_get_values_table (object_type);
  if (!table)
    luaL_error (L, "Object type '%s' has incomplete type information",
        fields[0]);

  builder = wp_spa_pod_builder_new_object (fields[0], fields[1]);
  if (!builder)
    luaL_error (L, "Could not create pod object");

  lua_pop (L, 2);

  lua_pushnil(L);
  while (lua_next (L, -2)) {
    /* Remaining fields with string keys are the object properties */
    if (lua_type (L, -2) == LUA_TSTRING) {
      const gchar *key = lua_tostring (L, -2);
      if (!object_add_property (builder, table, key, L, -1))
        luaL_error (L, "Property '%s' could not be added", key);
    }

    lua_pop (L, 1);
  }

  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_builder_end (builder));
  return 1;
}

/* Struct */

static int
spa_pod_struct_new (lua_State *L)
{
  g_autoptr (WpSpaPodBuilder) builder = NULL;

  luaL_checktype (L, 1, LUA_TTABLE);

  builder = wp_spa_pod_builder_new_struct ();

  lua_pushnil (L);
  while (lua_next (L, 1)) {
    switch (lua_type (L, -1)) {
      case LUA_TBOOLEAN:
        wp_spa_pod_builder_add_boolean (builder, lua_toboolean (L, -1));
        break;
      case LUA_TNUMBER:
        if (lua_isinteger (L, -1))
          wp_spa_pod_builder_add_long (builder, lua_tointeger (L, -1));
        else
          wp_spa_pod_builder_add_double (builder, lua_tonumber (L, -1));
        break;
      case LUA_TSTRING:
        wp_spa_pod_builder_add_string (builder, lua_tostring (L, -1));
        break;
      case LUA_TUSERDATA: {
        WpSpaPod *pod = wplua_checkboxed (L, -1, WP_TYPE_SPA_POD);
        wp_spa_pod_builder_add_pod (builder, pod);
        break;
      }
      default:
        luaL_error (L, "Struct does not support lua type ",
            lua_typename(L, lua_type(L, -1)));
        break;
    }
    lua_pop (L, 1);
  }

  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_builder_end (builder));
  return 1;
}

/* Sequence */

static int
spa_pod_sequence_new (lua_State *L)
{
  g_autoptr (WpSpaPodBuilder) builder = NULL;

  luaL_checktype (L, 1, LUA_TTABLE);

  builder = wp_spa_pod_builder_new_sequence (0);

  lua_pushnil (L);
  while (lua_next (L, -2)) {
    guint32 offset = 0;
    const gchar *type_name = NULL;
    WpSpaPod *value = NULL;

    /* Read Control */
    if (lua_istable(L, -1)) {
      lua_pushnil (L);
      while (lua_next (L, -2)) {
        const gchar *key = lua_tostring (L, -2);
        if (g_strcmp0 (key, "offset") == 0) {
          offset = lua_tointeger (L, -1);
        } else if (!type_name && g_strcmp0 (key, "typename") == 0) {
          type_name = lua_tostring (L, -1);
        } else if (!value && g_strcmp0 (key, "value") == 0) {
          switch (lua_type (L, -1)) {
	    case LUA_TBOOLEAN:
	      value = wp_spa_pod_new_boolean (lua_toboolean (L, -1));
              break;
	    case LUA_TNUMBER:
              if (lua_isinteger (L, -1))
	        value = wp_spa_pod_new_long (lua_tointeger (L, -1));
	      else
                value = wp_spa_pod_new_double (lua_tonumber (L, -1));
	      break;
	    case LUA_TSTRING:
	      value = wp_spa_pod_new_string (lua_tostring (L, -1));
	      break;
	    case LUA_TUSERDATA: {
              value = wplua_checkboxed (L, -1, WP_TYPE_SPA_POD);
	      break;
	    }
	    default: {
              luaL_error (L, "Control value does not support lua type ",
	          lua_typename(L, lua_type(L, -1)));
	      value = NULL;
	      break;
	    }
	  }
        }
        lua_pop(L, 1);
      }
    }

    /* Add control */
    if (type_name && value) {
      wp_spa_pod_builder_add_control (builder, offset, type_name);
      wp_spa_pod_builder_add_pod (builder, value);
      wp_spa_pod_unref (value);
    }

    lua_pop(L, 1);
  }

  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_builder_end (builder));
  return 1;
}

/* Array */

static int
spa_pod_array_new (lua_State *L)
{
  g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_array ();
  builder_add_table (L, builder);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_builder_end (builder));
  return 1;
}

/* Choice */

static int
spa_pod_choice_none_new (lua_State *L)
{
  g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_choice ("None");
  builder_add_table (L, builder);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_builder_end (builder));
  return 1;
}

static int
spa_pod_choice_range_new (lua_State *L)
{
  g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_choice ("Range");
  builder_add_table (L, builder);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_builder_end (builder));
  return 1;
}

static int
spa_pod_choice_step_new (lua_State *L)
{
  g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_choice ("Step");
  builder_add_table (L, builder);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_builder_end (builder));
  return 1;
}

static int
spa_pod_choice_enum_new (lua_State *L)
{
  g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_choice ("Enum");
  builder_add_table (L, builder);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_builder_end (builder));
  return 1;
}

static int
spa_pod_choice_flags_new (lua_State *L)
{
  g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_choice ("Flags");
  builder_add_table (L, builder);
  wplua_pushboxed (L, WP_TYPE_SPA_POD, wp_spa_pod_builder_end (builder));
  return 1;
}

/* API */

static int
spa_pod_get_type_name (lua_State *L)
{
  WpSpaPod *pod = wplua_checkboxed (L, 1, WP_TYPE_SPA_POD);
  lua_pushstring (L, wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
  return 1;
}

static void
push_primitive_values (lua_State *L, WpSpaPod *pod, WpSpaType type,
    guint start_index, WpSpaIdTable idtable)
{
  g_auto (GValue) item = G_VALUE_INIT;
  g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
  for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
    gpointer p = g_value_get_pointer (&item);
    if (!p)
      continue;
    switch (type) {
    case SPA_TYPE_Bool:
      lua_pushboolean (L, *(gboolean *)p);
      break;
    case SPA_TYPE_Id: {
      WpSpaIdValue idval = NULL;
      if (idtable)
        idval = wp_spa_id_table_find_value (idtable, *(guint32 *)p);
      if (idval)
        lua_pushstring (L, wp_spa_id_value_short_name (idval));
      else
        lua_pushinteger (L, *(guint32 *)p);
      break;
    }
    case SPA_TYPE_Int:
      lua_pushinteger (L, *(gint *)p);
      break;
    case SPA_TYPE_Long:
      lua_pushinteger (L, *(long *)p);
      break;
    case SPA_TYPE_Float:
      lua_pushnumber (L, *(float *)p);
      break;
    case SPA_TYPE_Double:
      lua_pushnumber (L, *(double *)p);
      break;
    case SPA_TYPE_Fd:
      lua_pushnumber (L, *(gint64 *)p);
      break;
    default:
      continue;
    }
    lua_rawseti (L, -2, start_index++);
  }
}

static void
push_luapod (lua_State *L, WpSpaPod *pod, WpSpaIdValue field_idval)
{
  /* None */
  if (wp_spa_pod_is_none (pod)) {
    lua_pushnil (L);
  }

  /* Boolean */
  else if (wp_spa_pod_is_boolean (pod)) {
    gboolean value = FALSE;
    g_warn_if_fail (wp_spa_pod_get_boolean (pod, &value));
    lua_pushboolean (L, value);
  }

  /* Id */
  else if (wp_spa_pod_is_id (pod)) {
    guint32 value = 0;
    WpSpaIdTable idtable = NULL;
    WpSpaIdValue idval = NULL;
    g_warn_if_fail (wp_spa_pod_get_id (pod, &value));
    if (field_idval && SPA_TYPE_Id ==
            wp_spa_id_value_get_value_type (field_idval, &idtable)) {
      idval = wp_spa_id_table_find_value (idtable, value);
    }
    if (idval)
      lua_pushstring (L, wp_spa_id_value_short_name (idval));
    else
      lua_pushinteger (L, value);
  }

  /* Int */
  else if (wp_spa_pod_is_int (pod)) {
    gint value = 0;
    g_warn_if_fail (wp_spa_pod_get_int (pod, &value));
    lua_pushinteger (L, value);
  }

  /* Long */
  else if (wp_spa_pod_is_long (pod)) {
    gint64 value = 0;
    wp_spa_pod_get_long (pod, &value);
    lua_pushinteger (L, value);
  }

  /* Float */
  else if (wp_spa_pod_is_float (pod)) {
    float value = 0;
    g_warn_if_fail (wp_spa_pod_get_float (pod, &value));
    lua_pushnumber (L, value);
  }

  /* Double */
  else if (wp_spa_pod_is_double (pod)) {
    double value = 0;
    g_warn_if_fail (wp_spa_pod_get_double (pod, &value));
    lua_pushnumber (L, value);
  }

  /* String */
  else if (wp_spa_pod_is_string (pod)) {
    const gchar *value = NULL;
    g_warn_if_fail (wp_spa_pod_get_string (pod, &value));
    lua_pushstring (L, value);
  }

  /* Bytes */
  else if (wp_spa_pod_is_bytes (pod)) {
    gconstpointer value = NULL;
    guint32 size = 0;
    g_warn_if_fail (wp_spa_pod_get_bytes (pod, &value, &size));
    char str[size + 1];
    for (guint i = 0; i < size; i++)
      str[i] = ((const gchar *)value)[i];
    str[size] = '\0';
    lua_pushstring (L, str);
  }

  /* Pointer */
  else if (wp_spa_pod_is_pointer (pod)) {
    gconstpointer value = NULL;
    g_warn_if_fail (wp_spa_pod_get_pointer (pod, &value));
    if (!value)
      lua_pushnil (L);
    else
      lua_pushlightuserdata (L, (gpointer)value);
  }

  /* Fd */
  else if (wp_spa_pod_is_fd (pod)) {
    gint64 value = 0;
    g_warn_if_fail (wp_spa_pod_get_fd (pod, &value));
    lua_pushinteger (L, value);
  }

  /* Rectangle */
  else if (wp_spa_pod_is_rectangle (pod)) {
    guint32 width = 0, height = 0;
    g_warn_if_fail (wp_spa_pod_get_rectangle (pod, &width, &height));
    lua_newtable (L);
    lua_pushstring (L, "Rectangle");
    lua_setfield (L, -2, "pod_type");
    lua_pushinteger (L, width);
    lua_setfield (L, -2, "width");
    lua_pushinteger (L, height);
    lua_setfield (L, -2, "height");
  }

  /* Fraction */
  else if (wp_spa_pod_is_fraction (pod)) {
    guint32 num = 0, denom = 0;
    g_warn_if_fail (wp_spa_pod_get_fraction (pod, &num, &denom));
    lua_newtable (L);
    lua_pushstring (L, "Fraction");
    lua_setfield (L, -2, "pod_type");
    lua_pushinteger (L, num);
    lua_setfield (L, -2, "num");
    lua_pushinteger (L, denom);
    lua_setfield (L, -2, "denom");
  }

  /* Object */
  else if (wp_spa_pod_is_object (pod)) {
    WpSpaType type = wp_spa_pod_get_spa_type (pod);
    WpSpaIdTable values_table = wp_spa_type_get_values_table (type);
    const gchar *id_name = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpIterator) it = NULL;
    g_warn_if_fail (wp_spa_pod_get_object (pod, &id_name, NULL));
    lua_newtable (L);
    lua_pushstring (L, "Object");
    lua_setfield (L, -2, "pod_type");
    lua_pushstring (L, id_name);
    lua_setfield (L, -2, "object_id");
    it = wp_spa_pod_new_iterator (pod);
    lua_newtable (L);
    for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
      WpSpaPod *prop = g_value_get_boxed (&item);
      const gchar *key = NULL;
      g_autoptr (WpSpaPod) val = NULL;
      //FIXME: this is suboptimal because _get_property() converts
      // the key to a short name and we convert it back
      g_warn_if_fail (wp_spa_pod_get_property (prop, &key, &val));
      if (key) {
        push_luapod (L, val,
            wp_spa_id_table_find_value_from_short_name (values_table, key));
        lua_setfield (L, -2, key);
      }
    }
    lua_setfield (L, -2, "properties");
  }

  /* Struct */
  else if (wp_spa_pod_is_struct (pod)) {
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    guint i = 1;
    lua_newtable (L);
    lua_pushstring (L, "Struct");
    lua_setfield (L, -2, "pod_type");
    for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
      WpSpaPod *val = g_value_get_boxed (&item);
      push_luapod (L, val, NULL);
      lua_rawseti (L, -2, i++);
    }
  }

  /* Sequence */
  else if (wp_spa_pod_is_sequence (pod)) {
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    guint i = 1;
    lua_newtable (L);
    lua_pushstring (L, "Sequence");
    lua_setfield (L, -2, "pod_type");
    for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
      WpSpaPod *control = g_value_get_boxed (&item);
      guint32 offset = 0;
      const char *type_name = NULL;
      g_autoptr (WpSpaPod) val = NULL;
      g_warn_if_fail (wp_spa_pod_get_control (control, &offset, &type_name,
          &val));
      lua_newtable (L);
      lua_pushinteger (L, offset);
      lua_setfield (L, -2, "offset");
      lua_pushstring (L, type_name);
      lua_setfield (L, -2, "typename");
      push_luapod (L, val, NULL);
      lua_setfield (L, -2, "value");
      lua_rawseti (L, -2, i++);
    }
  }

  /* Array */
  else if (wp_spa_pod_is_array (pod)) {
    g_autoptr (WpSpaPod) child = wp_spa_pod_get_array_child (pod);
    WpSpaType type = wp_spa_pod_get_spa_type (child);
    WpSpaIdTable idtable = NULL;
    if (field_idval && type == SPA_TYPE_Id && SPA_TYPE_Array ==
            wp_spa_id_value_get_value_type (field_idval, &idtable))
        wp_spa_id_value_array_get_item_type (field_idval, &idtable);
    lua_newtable (L);
    lua_pushstring (L, "Array");
    lua_setfield (L, -2, "pod_type");
    lua_pushstring (L, wp_spa_type_name (type));
    lua_setfield (L, -2, "value_type");
    push_primitive_values (L, pod, type, 1, idtable);
  }

  /* Choice */
  else if (wp_spa_pod_is_choice (pod)) {
    g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
    WpSpaType type = wp_spa_pod_get_spa_type (child);
    g_autofree const gchar *choice_type = g_strdup_printf ("Choice.%s",
        wp_spa_id_value_short_name (wp_spa_pod_get_choice_type (pod)));
    WpSpaIdTable idtable = NULL;
    if (field_idval && type == SPA_TYPE_Id)
      wp_spa_id_value_get_value_type (field_idval, &idtable);
    lua_newtable (L);
    lua_pushstring (L, choice_type);
    lua_setfield (L, -2, "pod_type");
    lua_pushstring (L, wp_spa_type_name (type));
    lua_setfield (L, -2, "value_type");
    push_primitive_values (L, pod, type, 1, idtable);
  }

  /* Error */
  else {
    luaL_error (L, "Unsupported pod type ",
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
  }
}

static int
spa_pod_parse (lua_State *L)
{
  WpSpaPod *pod = wplua_checkboxed (L, 1, WP_TYPE_SPA_POD);
  push_luapod (L, pod, NULL);
  return 1;
}

static int
spa_pod_fixate (lua_State *L)
{
  WpSpaPod *pod = wplua_checkboxed (L, 1, WP_TYPE_SPA_POD);
  gboolean res = wp_spa_pod_fixate (pod);
  lua_pushboolean (L, res);
  return 1;
}

static inline WpSpaPod *
_checkpod (lua_State *L, int n)
{
  return wplua_checkboxed (L, n, WP_TYPE_SPA_POD);
}

static int
spa_pod_filter (lua_State *L)
{
  WpSpaPod *pod = wplua_checkboxed (L, 1, WP_TYPE_SPA_POD);
  WpSpaPod *filter = luaL_opt (L, _checkpod, 2, NULL);
  WpSpaPod *result = wp_spa_pod_filter (pod, filter);
  if (result) {
    wplua_pushboxed (L, WP_TYPE_SPA_POD, result);
    return 1;
  }
  return 0;
}

static const luaL_Reg spa_pod_methods[] = {
  { "get_type_name", spa_pod_get_type_name },
  { "parse", spa_pod_parse },
  { "fixate", spa_pod_fixate },
  { "filter", spa_pod_filter },
  { NULL, NULL }
};

static const luaL_Reg spa_pod_constructors[] = {
  { "None", spa_pod_none_new },
  { "Boolean", spa_pod_boolean_new },
  { "Id", spa_pod_id_new },
  { "Int", spa_pod_int_new },
  { "Long", spa_pod_long_new },
  { "Float", spa_pod_float_new },
  { "Double", spa_pod_double_new },
  { "String", spa_pod_string_new },
  { "Bytes", spa_pod_bytes_new },
  { "Pointer", spa_pod_pointer_new },
  { "Fd", spa_pod_fd_new },
  { "Rectangle", spa_pod_rectangle_new },
  { "Fraction", spa_pod_fraction_new },
  { "Object", spa_pod_object_new },
  { "Struct", spa_pod_struct_new },
  { "Sequence", spa_pod_sequence_new },
  { "Array", spa_pod_array_new },
  { NULL, NULL }
};

static const luaL_Reg spa_pod_choice_constructors[] = {
  { "None", spa_pod_choice_none_new },
  { "Range", spa_pod_choice_range_new },
  { "Step", spa_pod_choice_step_new },
  { "Enum", spa_pod_choice_enum_new },
  { "Flags", spa_pod_choice_flags_new },
  { NULL, NULL }
};

/* Init */

void
wp_lua_scripting_pod_init (lua_State *L)
{
  luaL_newlib (L, spa_pod_constructors);
  luaL_newlib (L, spa_pod_choice_constructors);
  lua_setfield (L, -2, "Choice");
  lua_setglobal (L, "WpSpaPod");

  wplua_register_type_methods (L, WP_TYPE_SPA_POD, NULL, spa_pod_methods);
}
   070701000000B3000081A4000000000000000000000001656CC35F0000172D000000000000000000000000000000000000003E00000000wireplumber-0.4.17/modules/module-lua-scripting/api/require.c /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <wplua/wplua.h>

struct _WpRequireApiTransition
{
  WpTransition parent;
  GPtrArray *apis;
  guint pending_plugins;
};

enum {
  STEP_LOAD_MODULES = WP_TRANSITION_STEP_CUSTOM_START,
  STEP_ACTIVATE_PLUGINS,
};

G_DECLARE_FINAL_TYPE (WpRequireApiTransition, wp_require_api_transition,
                      WP, REQUIRE_API_TRANSITION, WpTransition)
G_DEFINE_TYPE (WpRequireApiTransition, wp_require_api_transition, WP_TYPE_TRANSITION)

static void
wp_require_api_transition_init (WpRequireApiTransition * self)
{
  self->apis = g_ptr_array_new_with_free_func (g_free);
}

static void
wp_require_api_transition_finalize (GObject * object)
{
  WpRequireApiTransition *self = WP_REQUIRE_API_TRANSITION (object);

  g_clear_pointer (&self->apis, g_ptr_array_unref);

  G_OBJECT_CLASS (wp_require_api_transition_parent_class)->finalize (object);
}

static guint
wp_require_api_transition_get_next_step (WpTransition * transition, guint step)
{
  WpRequireApiTransition *self = WP_REQUIRE_API_TRANSITION (transition);

  switch (step) {
  case WP_TRANSITION_STEP_NONE: return STEP_LOAD_MODULES;
  case STEP_LOAD_MODULES:       return STEP_ACTIVATE_PLUGINS;
  case STEP_ACTIVATE_PLUGINS:
    return (self->pending_plugins > 0) ?
        STEP_ACTIVATE_PLUGINS : WP_TRANSITION_STEP_NONE;
  default:
    g_return_val_if_reached (WP_TRANSITION_STEP_ERROR);
  }
}

static void
on_plugin_activated (WpObject * p, GAsyncResult * res,
    WpRequireApiTransition *self)
{
  GError *error = NULL;

  if (!wp_object_activate_finish (p, res, &error)) {
    wp_transition_return_error (WP_TRANSITION (self), error);
    return;
  }

  --self->pending_plugins;
  wp_transition_advance (WP_TRANSITION (self));
}

static void
wp_require_api_transition_execute_step (WpTransition * transition, guint step)
{
  WpRequireApiTransition *self = WP_REQUIRE_API_TRANSITION (transition);
  WpCore *core = wp_transition_get_source_object (transition);

  switch (step) {
  case STEP_LOAD_MODULES:
  {
    for (guint i = 0; i < self->apis->len; i++) {
      const gchar *api_name = g_ptr_array_index (self->apis, i);
      g_autoptr (WpPlugin) plugin = wp_plugin_find (core, api_name);
      if (!plugin) {
        GError *error = NULL;
        gchar module_name[50];

        g_snprintf (module_name, sizeof (module_name),
            "libwireplumber-module-%s", api_name);

        if (!wp_core_load_component (core, module_name, "module", NULL, &error)) {
          wp_transition_return_error (transition, error);
          return;
        }

        plugin = wp_plugin_find (core, api_name);
        if (!plugin) {
          wp_transition_return_error (transition, g_error_new (
              WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
              "API '%s' was not found in module '%s'", api_name, module_name));
          return;
        }
      }
    }
    wp_transition_advance (transition);
    break;
  }

  case STEP_ACTIVATE_PLUGINS:
    wp_debug_object (self, "Activating plugins...");

    for (guint i = 0; i < self->apis->len; i++) {
      const gchar *api_name = g_ptr_array_index (self->apis, i);
      g_autoptr (WpPlugin) plugin = wp_plugin_find (core, api_name);

      self->pending_plugins++;
      wp_object_activate (WP_OBJECT (plugin), WP_PLUGIN_FEATURE_ENABLED, NULL,
          (GAsyncReadyCallback) on_plugin_activated, self);
    }
    wp_transition_advance (transition);
    break;

  case WP_TRANSITION_STEP_ERROR:
    break;

  default:
    g_assert_not_reached ();
  }
}

static void
wp_require_api_transition_class_init (WpRequireApiTransitionClass * klass)
{
  GObjectClass * object_class = (GObjectClass *) klass;
  WpTransitionClass * transition_class = (WpTransitionClass *) klass;

  object_class->finalize = wp_require_api_transition_finalize;

  transition_class->get_next_step = wp_require_api_transition_get_next_step;
  transition_class->execute_step = wp_require_api_transition_execute_step;
}

static void
on_require_api_transition_done (WpCore * core, GAsyncResult * res, gpointer data)
{
  g_autoptr (GClosure) closure = data;
  g_autoptr (GError) error = NULL;

  if (!wp_transition_finish (res, &error)) {
    wp_warning ("Core.require_api failed: %s", error->message);
    wp_core_idle_add (core, NULL, G_SOURCE_FUNC (core_disconnect), core, NULL);
    return;
  }

  WpRequireApiTransition *t = WP_REQUIRE_API_TRANSITION (res);
  g_autoptr (GArray) params = g_array_new (FALSE, TRUE, sizeof (GValue));

  g_array_set_clear_func (params, (GDestroyNotify) g_value_unset);
  g_array_set_size (params, t->apis->len);

  for (guint i = 0; i < t->apis->len; i++) {
    const gchar *api_name = g_ptr_array_index (t->apis, i);
    g_autoptr (WpPlugin) plugin = wp_plugin_find (core, api_name);
    g_value_init_from_instance (&g_array_index (params, GValue, i), plugin);
  }

  g_closure_invoke (closure, NULL,
      params->len, (const GValue *) params->data, NULL);
  g_closure_invalidate (closure);
}

static int
wp_require_api_transition_new_from_lua (lua_State *L, WpCore * core)
{
  int n_args = lua_gettop (L);

  wp_info("n_args = %d", n_args);

  for (int i = 1; i < n_args; i++)
    luaL_checktype (L, i, LUA_TSTRING);
  luaL_checktype (L, n_args, LUA_TFUNCTION);

  GClosure *closure = wplua_function_to_closure (L, n_args);
  g_closure_ref (closure);
  g_closure_sink (closure);

  WpRequireApiTransition *t = (WpRequireApiTransition *)
      wp_transition_new (wp_require_api_transition_get_type (), core, NULL,
          (GAsyncReadyCallback) on_require_api_transition_done, closure);

  for (int i = 1; i < n_args; i++) {
    const char * api_name = lua_tostring (L, i);
    g_ptr_array_add (t->apis, g_strdup_printf ("%s-api", api_name));
  }

  wp_transition_advance (WP_TRANSITION (t));
  return 0;
}
   070701000000B4000081A4000000000000000000000001656CC35F0000004C000000000000000000000000000000000000003C00000000wireplumber-0.4.17/modules/module-lua-scripting/meson.build   wplua_include_dir = include_directories('.')

subdir('wplua')
subdir('api')
070701000000B5000081A4000000000000000000000001656CC35F00001C34000000000000000000000000000000000000003900000000wireplumber-0.4.17/modules/module-lua-scripting/module.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <wplua/wplua.h>
#include <pipewire/keys.h>

#include "script.h"

void wp_lua_scripting_api_init (lua_State *L);
gboolean wp_lua_scripting_load_configuration (const gchar * conf_file,
    WpCore * core, GError ** error);

struct _WpLuaScriptingPlugin
{
  WpComponentLoader parent;

  GPtrArray *scripts; /* element-type: WpPlugin* */
  lua_State *L;
};

static int
wp_lua_scripting_package_loader (lua_State *L)
{
  luaL_checktype (L, 2, LUA_TFUNCTION);
  wplua_push_sandbox (L);
  lua_pushvalue (L, 2);
  lua_call (L, 1, 1);
  return 1;
}

static int
wp_lua_scripting_package_searcher (lua_State *L)
{
  const gchar *name = luaL_checkstring (L, 1);
  g_autoptr (GError) error = NULL;
  g_autofree gchar *filename = g_strdup_printf ("%s.lua", name);
  g_autofree gchar *script = wp_find_file (
      WP_LOOKUP_DIR_ENV_DATA |
      WP_LOOKUP_DIR_XDG_CONFIG_HOME |
      WP_LOOKUP_DIR_ETC |
      WP_LOOKUP_DIR_PREFIX_SHARE,
      filename, "scripts/lib");

  if (!script)  {
    lua_pushliteral (L, "script not found");
    return 1;
  }

  /* 1. loader (function) */
  lua_pushcfunction (L, wp_lua_scripting_package_loader);

  /* 2. loader data (param to 1) */
  if (!wplua_load_path (L, script, &error)) {
    lua_pop (L, 1);
    lua_pushstring (L, error->message);
    return 1;
  }

  /* 3. script path */
  lua_pushstring (L, script);
  return 3;
}

static void
wp_lua_scripting_enable_package_searcher (lua_State *L)
{
  /* table.insert(package.searchers, 2, wp_lua_scripting_package_searcher) */
  lua_getglobal (L, "table");
  lua_getfield (L, -1, "insert");
  lua_remove (L, -2);
  lua_getglobal (L, "package");
  lua_getfield (L, -1, "searchers");
  lua_remove (L, -2);
  lua_pushinteger (L, 2);
  lua_pushcfunction (L, wp_lua_scripting_package_searcher);
  lua_call (L, 3, 0);
}

G_DECLARE_FINAL_TYPE (WpLuaScriptingPlugin, wp_lua_scripting_plugin,
                      WP, LUA_SCRIPTING_PLUGIN, WpComponentLoader)
G_DEFINE_TYPE (WpLuaScriptingPlugin, wp_lua_scripting_plugin,
               WP_TYPE_COMPONENT_LOADER)

static void
wp_lua_scripting_plugin_init (WpLuaScriptingPlugin * self)
{
  self->scripts = g_ptr_array_new_with_free_func (g_object_unref);
}

static void
wp_lua_scripting_plugin_finalize (GObject * object)
{
  WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (object);

  g_clear_pointer (&self->scripts, g_ptr_array_unref);

  G_OBJECT_CLASS (wp_lua_scripting_plugin_parent_class)->finalize (object);
}

static void
wp_lua_scripting_plugin_enable (WpPlugin * plugin, WpTransition * transition)
{
  WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (plugin);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
  WpCore *export_core;

  /* init lua engine */
  self->L = wplua_new ();

  lua_pushliteral (self->L, "wireplumber_core");
  lua_pushlightuserdata (self->L, core);
  lua_settable (self->L, LUA_REGISTRYINDEX);

  /* initialize secondary connection to pipewire */
  export_core = g_object_get_data (G_OBJECT (core), "wireplumber.export-core");
  if (export_core) {
    lua_pushliteral (self->L, "wireplumber_export_core");
    wplua_pushobject (self->L, g_object_ref (export_core));
    lua_settable (self->L, LUA_REGISTRYINDEX);
  }

  wp_lua_scripting_api_init (self->L);
  wp_lua_scripting_enable_package_searcher (self->L);
  wplua_enable_sandbox (self->L, WP_LUA_SANDBOX_ISOLATE_ENV);

  /* register scripts that were queued in for loading */
  for (guint i = 0; i < self->scripts->len; i++) {
    WpPlugin *script = g_ptr_array_index (self->scripts, i);
    g_object_set (script, "lua-engine", self->L, NULL);
    wp_plugin_register (g_object_ref (script));
  }
  g_ptr_array_set_size (self->scripts, 0);

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_lua_scripting_plugin_disable (WpPlugin * plugin)
{
  WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (plugin);
  g_clear_pointer (&self->L, wplua_unref);
}

static gboolean
wp_lua_scripting_plugin_supports_type (WpComponentLoader * cl,
    const gchar * type)
{
  return (!g_strcmp0 (type, "script/lua") || !g_strcmp0 (type, "config/lua"));
}

static gchar *
find_script (const gchar * script, WpCore *core)
{
  g_autoptr (WpProperties) p = wp_core_get_properties (core);
  const gchar *str = wp_properties_get (p, "wireplumber.daemon");
  gboolean daemon = !g_strcmp0 (str, "true");

  if ((!daemon || g_path_is_absolute (script)) &&
      g_file_test (script, G_FILE_TEST_IS_REGULAR))
    return g_strdup (script);

  return wp_find_file (WP_LOOKUP_DIR_ENV_DATA |
                       WP_LOOKUP_DIR_XDG_CONFIG_HOME |
                       WP_LOOKUP_DIR_ETC |
                       WP_LOOKUP_DIR_PREFIX_SHARE,
                       script, "scripts");
}

static gboolean
wp_lua_scripting_plugin_load (WpComponentLoader * cl, const gchar * component,
    const gchar * type, GVariant * args, GError ** error)
{
  WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (cl);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (cl));

  /* interpret component as a script */
  if (!g_strcmp0 (type, "script/lua")) {
    g_autofree gchar *filename = NULL;
    g_autofree gchar *pluginname = NULL;
    g_autoptr (WpPlugin) script = NULL;

    filename = find_script (component, core);
    if (!filename) {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
          "Could not locate script '%s'", component);
      return FALSE;
    }

    pluginname = g_strdup_printf ("script:%s", component);

    script = g_object_new (WP_TYPE_LUA_SCRIPT,
        "core", core,
        "name", pluginname,
        "filename", filename,
        "arguments", args,
        NULL);

    if (self->L) {
      g_object_set (script, "lua-engine", self->L, NULL);
      wp_plugin_register (g_steal_pointer (&script));
    } else {
      /* keep in a list and delay registering until the plugin is enabled */
      g_ptr_array_add (self->scripts, g_steal_pointer (&script));
    }
    return TRUE;
  }
  /* interpret component as a configuration file */
  else if (!g_strcmp0 (type, "config/lua")) {
    return wp_lua_scripting_load_configuration (component, core, error);
  }

  g_return_val_if_reached (FALSE);
}

static void
wp_lua_scripting_plugin_class_init (WpLuaScriptingPluginClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpPluginClass *plugin_class = (WpPluginClass *) klass;
  WpComponentLoaderClass *cl_class = (WpComponentLoaderClass *) klass;

  object_class->finalize = wp_lua_scripting_plugin_finalize;

  plugin_class->enable = wp_lua_scripting_plugin_enable;
  plugin_class->disable = wp_lua_scripting_plugin_disable;

  cl_class->supports_type = wp_lua_scripting_plugin_supports_type;
  cl_class->load = wp_lua_scripting_plugin_load;
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_plugin_register (g_object_new (wp_lua_scripting_plugin_get_type (),
          "name", "lua-scripting",
          "core", core,
          NULL));
  return TRUE;
}
070701000000B6000081A4000000000000000000000001656CC35F00001EC8000000000000000000000000000000000000003900000000wireplumber-0.4.17/modules/module-lua-scripting/script.c  /* WirePlumber
 *
 * Copyright © 2022 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "script.h"
#include <pipewire/keys.h>

/*
 * This is a WpPlugin subclass that wraps a single lua script and acts like
 * a handle for that script. When enabled, through the WpObject activation
 * mechanism, the script is executed. It then provides an API for the script
 * to declare when it has finished its activation procedure, which can be
 * asynchronous (this is Script.finish_activation in Lua).
 * When disabled, this class destroys the global environment that was used
 * in the Lua engine for excecuting that script, effectively destroying all
 * objects that were held in Lua as global variables.
 */

struct _WpLuaScript
{
  WpPlugin parent;

  lua_State *L;
  gchar *filename;
  GVariant *args;
};

enum {
  PROP_0,
  PROP_LUA_ENGINE,
  PROP_FILENAME,
  PROP_ARGUMENTS,
};

G_DEFINE_TYPE (WpLuaScript, wp_lua_script, WP_TYPE_PLUGIN)

static void
wp_lua_script_init (WpLuaScript * self)
{
}

static void
wp_lua_script_cleanup (WpLuaScript * self)
{
  /* LUA_REGISTRYINDEX[self] = nil */
  if (self->L) {
    lua_pushnil (self->L);
    lua_rawsetp (self->L, LUA_REGISTRYINDEX, self);
  }
}

static void
wp_lua_script_finalize (GObject * object)
{
  WpLuaScript *self = WP_LUA_SCRIPT (object);

  wp_lua_script_cleanup (self);
  g_clear_pointer (&self->L, wplua_unref);
  g_clear_pointer (&self->filename, g_free);
  g_clear_pointer (&self->args, g_variant_unref);

  G_OBJECT_CLASS (wp_lua_script_parent_class)->finalize (object);
}

static void
wp_lua_script_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpLuaScript *self = WP_LUA_SCRIPT (object);

  switch (property_id) {
  case PROP_LUA_ENGINE:
    g_return_if_fail (self->L == NULL);
    self->L = g_value_get_pointer (value);
    if (self->L)
      self->L = wplua_ref (self->L);
    break;
  case PROP_FILENAME:
    self->filename = g_value_dup_string (value);
    break;
  case PROP_ARGUMENTS:
    self->args = g_value_dup_variant (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static gboolean
wp_lua_script_check_async_activation (WpLuaScript * self)
{
  gboolean ret;
  lua_rawgetp (self->L, LUA_REGISTRYINDEX, self);
  lua_pushliteral (self->L, "Script");
  lua_gettable (self->L, -2);
  lua_pushliteral (self->L, "async_activation");
  lua_gettable (self->L, -2);
  ret = lua_toboolean (self->L, -1);
  lua_pop (self->L, 3);
  return ret;
}

static void
wp_lua_script_detach_transition (WpLuaScript * self)
{
  lua_rawgetp (self->L, LUA_REGISTRYINDEX, self);
  lua_pushliteral (self->L, "Script");
  lua_gettable (self->L, -2);
  lua_pushliteral (self->L, "__transition");
  lua_pushnil (self->L);
  lua_settable (self->L, -3);
  lua_pop (self->L, 2);
}

static int
script_finish_activation (lua_State * L)
{
  WpLuaScript *self;

  luaL_checktype (L, 1, LUA_TTABLE);

  lua_pushliteral (L, "__self");
  lua_gettable (L, 1);
  luaL_checktype (L, -1, LUA_TLIGHTUSERDATA);
  self = WP_LUA_SCRIPT ((gpointer) lua_topointer (L, -1));
  lua_pop (L, 2);

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
  return 0;
}

static int
script_finish_activation_with_error (lua_State * L)
{
  WpTransition *transition = NULL;
  const char *msg = NULL;

  luaL_checktype (L, 1, LUA_TTABLE);
  msg = luaL_checkstring (L, 2);

  lua_pushliteral (L, "__transition");
  lua_gettable (L, 1);
  if (lua_type (L, -1) == LUA_TLIGHTUSERDATA)
    transition = WP_TRANSITION ((gpointer) lua_topointer (L, -1));
  lua_pop (L, 2);

  if (transition)
    wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY,
        WP_LIBRARY_ERROR_OPERATION_FAILED, "%s", msg));
  return 0;
}

static const luaL_Reg script_api_methods[] = {
  { "finish_activation", script_finish_activation },
  { "finish_activation_with_error", script_finish_activation_with_error },
  { NULL, NULL }
};

static int
wp_lua_script_sandbox (lua_State *L)
{
  luaL_checktype (L, 1, LUA_TLIGHTUSERDATA); // self
  luaL_checktype (L, 2, LUA_TLIGHTUSERDATA); // transition
  luaL_checktype (L, 3, LUA_TFUNCTION); // the script chunk

  /* create unique environment for this script */
  lua_getglobal (L, "create_sandbox_env");
  lua_call (L, 0, 1);

  /* create "Script" API */
  lua_pushliteral (L, "Script");
  luaL_newlib (L, script_api_methods);
  lua_pushliteral (L, "__self");
  lua_pushvalue (L, 1);
  lua_settable (L, -3);
  lua_pushliteral (L, "__transition");
  lua_pushvalue (L, 2);
  lua_settable (L, -3);
  lua_settable (L, -3);

  /* store the environment */
  /* LUA_REGISTRYINDEX[self] = env */
  lua_pushvalue (L, 1); // self
  lua_pushvalue (L, -2); // the table returned by create_sandbox_env
  lua_rawset (L, LUA_REGISTRYINDEX);

  /* set it as the 1st upvalue (_ENV) on the loaded script chunk (at index 3) */
  lua_setupvalue (L, 3, 1);

  /* anything remaining on the stack are function arguments */
  int nargs = lua_gettop (L) - 3;

  /* execute script */
  lua_call (L, nargs, 0);
  return 0;
}

static void
wp_lua_script_enable (WpPlugin * plugin, WpTransition * transition)
{
  WpLuaScript *self = WP_LUA_SCRIPT (plugin);
  g_autoptr (GError) error = NULL;
  int top, nargs = 3;

  if (!self->L) {
    error = g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
        "No lua state open; lua-scripting plugin is not enabled");
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  top = lua_gettop (self->L);
  lua_pushcfunction (self->L, wp_lua_script_sandbox);
  lua_pushlightuserdata (self->L, self);
  lua_pushlightuserdata (self->L, transition);

  /* load script */
  if (!wplua_load_path (self->L, self->filename, &error)) {
    lua_settop (self->L, top);
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  /* push script arguments */
  if (self->args) {
    wplua_gvariant_to_lua (self->L, self->args);
    nargs++;
  }

  /* execute script */
  if (!wplua_pcall (self->L, nargs, 0, &error)) {
    lua_settop (self->L, top);
    wp_transition_return_error (transition, g_steal_pointer (&error));
    wp_lua_script_cleanup (self);
    return;
  }

  if (!wp_lua_script_check_async_activation (self)) {
    wp_lua_script_detach_transition (self);
    wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
  } else {
    g_signal_connect_object (transition, "notify::completed",
        (GCallback) wp_lua_script_detach_transition, self, G_CONNECT_SWAPPED);
  }

  lua_settop (self->L, top);
}

static void
wp_lua_script_disable (WpPlugin * plugin)
{
  WpLuaScript *self = WP_LUA_SCRIPT (plugin);
  wp_lua_script_cleanup (self);
}

static void
wp_lua_script_class_init (WpLuaScriptClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  object_class->finalize = wp_lua_script_finalize;
  object_class->set_property = wp_lua_script_set_property;

  plugin_class->enable = wp_lua_script_enable;
  plugin_class->disable = wp_lua_script_disable;

  g_object_class_install_property (object_class, PROP_LUA_ENGINE,
      g_param_spec_pointer ("lua-engine", "lua-engine", "lua-engine",
          G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_FILENAME,
      g_param_spec_string ("filename", "filename", "filename", NULL,
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_ARGUMENTS,
      g_param_spec_variant ("arguments", "arguments", "arguments",
          G_VARIANT_TYPE_VARDICT, NULL,
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
070701000000B7000081A4000000000000000000000001656CC35F000001CB000000000000000000000000000000000000003900000000wireplumber-0.4.17/modules/module-lua-scripting/script.h  /* WirePlumber
 *
 * Copyright © 2022 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __MODULE_LUA_SCRIPTING_SCRIPT_H__
#define __MODULE_LUA_SCRIPTING_SCRIPT_H__

#include <wp/wp.h>
#include <wplua/wplua.h>

G_BEGIN_DECLS

#define WP_TYPE_LUA_SCRIPT (wp_lua_script_get_type ())
G_DECLARE_FINAL_TYPE (WpLuaScript, wp_lua_script, WP, LUA_SCRIPT, WpPlugin)

G_END_DECLS

#endif
 070701000000B8000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000003600000000wireplumber-0.4.17/modules/module-lua-scripting/wplua 070701000000B9000081A4000000000000000000000001656CC35F00000A5A000000000000000000000000000000000000003E00000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/boxed.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "wplua.h"
#include "private.h"
#include <wp/wp.h>

static lua_CFunction
find_method_in_luaL_Reg (luaL_Reg *reg, const gchar *method)
{
  if (reg) {
    while (reg->name) {
      if (!g_strcmp0 (method, reg->name))
        return reg->func;
      reg++;
    }
  }
  return NULL;
}

static int
_wplua_gboxed___index (lua_State *L)
{
  luaL_argcheck (L, wplua_isboxed (L, 1, G_TYPE_BOXED), 1,
      "expected userdata storing GValue<GBoxed>");
  GValue *obj_v = lua_touserdata (L, 1);
  const gchar *key = luaL_checkstring (L, 2);
  lua_CFunction func = NULL;
  GHashTable *vtables;

  lua_pushliteral (L, "wplua_vtables");
  lua_gettable (L, LUA_REGISTRYINDEX);
  vtables = wplua_toboxed (L, -1);
  lua_pop (L, 1);

  /* search in registered vtables */
  if (!func) {
    GType type = G_VALUE_TYPE (obj_v);
    while (!func && type) {
      luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (type));
      func = find_method_in_luaL_Reg (reg, key);
      type = g_type_parent (type);
    }
  }

  if (func) {
    lua_pushcfunction (L, func);
    return 1;
  }
  return 0;
}

void
_wplua_init_gboxed (lua_State *L)
{
  static const luaL_Reg gboxed_meta[] = {
    { "__gc", _wplua_gvalue_userdata___gc },
    { "__eq", _wplua_gvalue_userdata___eq },
    { "__index", _wplua_gboxed___index },
    { NULL, NULL }
  };

  luaL_newmetatable (L, "GBoxed");
  luaL_setfuncs (L, gboxed_meta, 0);
  lua_pop (L, 1);
}

void
wplua_pushboxed (lua_State * L, GType type, gpointer object)
{
  g_return_if_fail (G_TYPE_FUNDAMENTAL (type) == G_TYPE_BOXED);

  GValue *v = _wplua_pushgvalue_userdata (L, type);
  wp_trace_boxed (type, object, "pushing to Lua, v=%p", v);
  g_value_take_boxed (v, object);

  luaL_getmetatable (L, "GBoxed");
  lua_setmetatable (L, -2);
}

gpointer
wplua_toboxed (lua_State *L, int idx)
{
  g_return_val_if_fail (_wplua_isgvalue_userdata (L, idx, G_TYPE_BOXED), NULL);
  return g_value_get_boxed ((GValue *) lua_touserdata (L, idx));
}

gpointer
wplua_checkboxed (lua_State *L, int idx, GType type)
{
  if (G_UNLIKELY (!_wplua_isgvalue_userdata (L, idx, type))) {
    wp_critical ("expected userdata storing GValue<%s>", g_type_name (type));
    luaL_argerror (L, idx, "expected userdata storing GValue<GBoxed>");
  }
  return g_value_get_boxed ((GValue *) lua_touserdata (L, idx));
}

gboolean
wplua_isboxed (lua_State *L, int idx, GType type)
{
  if (!g_type_is_a (type, G_TYPE_BOXED)) return FALSE;
  return _wplua_isgvalue_userdata (L, idx, type);
}
  070701000000BA000081A4000000000000000000000001656CC35F00001288000000000000000000000000000000000000004000000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/closure.c   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "wplua.h"
#include "private.h"
#include <wp/wp.h>

/* This structure is added to a lua global and it's only referenced from there;
   When the lua_State closes, it is unrefed and its finalize function below
   invalidates all the closures so that nothing attempts to call into a lua
   function after the state is closed */
typedef struct _WpLuaClosureStore WpLuaClosureStore;
struct _WpLuaClosureStore
{
  GPtrArray *closures;
};

static WpLuaClosureStore *
_wplua_closure_store_new (void)
{
  WpLuaClosureStore *self = g_rc_box_new (WpLuaClosureStore);
  self->closures = g_ptr_array_new ();
  return self;
}

static void
_wplua_closure_store_finalize (WpLuaClosureStore * self)
{
  for (guint i = self->closures->len; i > 0; i--) {
    GClosure *c = g_ptr_array_index (self->closures, i-1);
    g_closure_ref (c);
    g_closure_invalidate (c);
    g_ptr_array_remove_index_fast (self->closures, i-1);
    g_closure_unref (c);
  }
  g_ptr_array_unref (self->closures);
}

static WpLuaClosureStore *
_wplua_closure_store_ref (WpLuaClosureStore * self)
{
  return g_rc_box_acquire (self);
}

static void
_wplua_closure_store_unref (WpLuaClosureStore * self)
{
  g_rc_box_release_full (self, (GDestroyNotify) _wplua_closure_store_finalize);
}

G_DEFINE_BOXED_TYPE(WpLuaClosureStore, _wplua_closure_store,
    _wplua_closure_store_ref, _wplua_closure_store_unref)


typedef struct _WpLuaClosure WpLuaClosure;
struct _WpLuaClosure
{
  GClosure closure;
  int func_ref;
  GPtrArray *closures;
};

static void
_wplua_closure_marshal (GClosure *closure, GValue *return_value,
    guint n_param_values, const GValue *param_values,
    gpointer invocation_hint, gpointer marshal_data)
{
  static int reentrant = 0;
  lua_State *L = closure->data;
  int func_ref = ((WpLuaClosure *) closure)->func_ref;

  /* invalid closure, skip it */
  if (func_ref == LUA_NOREF || func_ref == LUA_REFNIL)
    return;

  /* stop the garbage collector */
  if (reentrant == 0)
    lua_gc (L, LUA_GCSTOP, 0);

  /* push the function */
  lua_rawgeti (L, LUA_REGISTRYINDEX, func_ref);

  /* push arguments */
  for (guint i = 0; i < n_param_values; i++)
    wplua_gvalue_to_lua (L, &param_values[i]);

  /* call in protected mode */
  reentrant++;
  int res = _wplua_pcall (L, n_param_values, return_value ? 1 : 0);
  reentrant--;

  /* handle the result */
  if (res == LUA_OK && return_value) {
    wplua_lua_to_gvalue (L, -1, return_value);
    lua_pop (L, 1);
  }

  /* clean up */
  lua_gc (L, LUA_GCCOLLECT, 0);
  if (reentrant == 0)
    lua_gc (L, LUA_GCRESTART, 0);
}

static void
_wplua_closure_invalidate (lua_State *L, WpLuaClosure *c)
{
  wp_trace_boxed (G_TYPE_CLOSURE, c, "invalidated");
  luaL_unref (L, LUA_REGISTRYINDEX, c->func_ref);
  c->func_ref = LUA_NOREF;
}

static void
_wplua_closure_finalize (lua_State *L, WpLuaClosure *c)
{
  g_ptr_array_remove_fast (c->closures, c);
  g_ptr_array_unref (c->closures);
}

GClosure *
wplua_checkclosure (lua_State *L, int idx)
{
  luaL_checktype (L, idx, LUA_TFUNCTION);
  return wplua_function_to_closure (L, idx);
}

/**
 * wplua_function_to_closure:
 *
 * Make a GClosure out of a Lua function at index @em idx
 *
 * Returns: (transfer floating): the new closure
 */
GClosure *
wplua_function_to_closure (lua_State *L, int idx)
{
  g_return_val_if_fail (lua_isfunction(L, idx), NULL);

  GClosure *c = g_closure_new_simple (sizeof (WpLuaClosure), L);
  WpLuaClosure *wlc = (WpLuaClosure *) c;
  WpLuaClosureStore *store;

  lua_pushvalue (L, idx);
  wlc->func_ref = luaL_ref (L, LUA_REGISTRYINDEX);

  wp_trace_boxed (G_TYPE_CLOSURE, c, "created, func_ref = %d", wlc->func_ref);

  g_closure_set_marshal (c, _wplua_closure_marshal);
  g_closure_add_invalidate_notifier (c, L,
      (GClosureNotify) _wplua_closure_invalidate);
  g_closure_add_finalize_notifier (c, L,
      (GClosureNotify) _wplua_closure_finalize);

  /* keep a weak ref of the closure in the store's array,
     so that we can invalidate the closure when lua_State closes;
     keep a strong ref of the array in the closure so that
     _wplua_closure_finalize() works even after the state is closed */
  lua_pushliteral (L, "wplua_closures");
  lua_gettable (L, LUA_REGISTRYINDEX);
  store = wplua_toboxed (L, -1);
  lua_pop (L, 1);

  g_ptr_array_add (store->closures, c);
  wlc->closures = g_ptr_array_ref (store->closures);

  return c;
}

void
_wplua_init_closure (lua_State *L)
{
  lua_pushliteral (L, "wplua_closures");
  wplua_pushboxed (L,
      _wplua_closure_store_get_type (),
      _wplua_closure_store_new ());
  lua_settable (L, LUA_REGISTRYINDEX);
}
070701000000BB000081A4000000000000000000000001656CC35F000000C4000000000000000000000000000000000000004400000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/gresource.xml   <?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/org/freedesktop/pipewire/wireplumber/wplua/">
    <file compressed="true">sandbox.lua</file>
  </gresource>
</gresources>
070701000000BC000081A4000000000000000000000001656CC35F000002E2000000000000000000000000000000000000004200000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/meson.build wplua_lib_sources = [
  'boxed.c',
  'closure.c',
  'object.c',
  'userdata.c',
  'value.c',
  'wplua.c',
]

wplua_resources = gnome.compile_resources(
    'wplua-resources',
    'gresource.xml',
    c_name: '_wplua',
    extra_args: '--manual-register',
    source_dir: meson.current_source_dir())

wplua_lib = static_library('wplua-' + wireplumber_api_version,
  [ wplua_lib_sources, wplua_resources ],
  c_args : [
    '-D_GNU_SOURCE',
    '-DG_LOG_USE_STRUCTURED',
    '-DG_LOG_DOMAIN="wplua"',
  ],
  install: false,
  include_directories: wplua_include_dir,
  dependencies : [wp_dep, lua_dep],
)

wplua_dep = declare_dependency(
  link_with: wplua_lib,
  include_directories: wplua_include_dir,
  dependencies: [wp_dep, lua_dep],
)
  070701000000BD000081A4000000000000000000000001656CC35F00001A4F000000000000000000000000000000000000003F00000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/object.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "wplua.h"
#include "private.h"
#include <wp/wp.h>

static int
_wplua_gobject_call (lua_State *L)
{
  GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
  const char *sig_name = lua_tostring (L, 2);
  guint n_params = lua_gettop (L) - 2;
  GSignalQuery query;
  guint sig_id = 0;
  GQuark detail = 0;

  if (G_UNLIKELY (!g_signal_parse_name (sig_name, G_TYPE_FROM_INSTANCE (obj),
                                        &sig_id, &detail, FALSE)))
    luaL_error (L, "unknown signal '%s::%s'", G_OBJECT_TYPE_NAME (obj),
        sig_name);

  g_signal_query (sig_id, &query);

  if (G_UNLIKELY (!(query.signal_flags & G_SIGNAL_ACTION)))
    luaL_error (L, "lua code is not allowed to emit non-action signal '%s::%s'",
        G_OBJECT_TYPE_NAME (obj), sig_name);

  if (G_UNLIKELY (query.n_params > n_params))
    luaL_error (L, "not enough arguments for '%s::%s': expected %d, got %d",
        G_OBJECT_TYPE_NAME (obj), sig_name, query.n_params, n_params);

  GValue ret = G_VALUE_INIT;
  GValue *vals = g_newa (GValue, n_params + 1);
  memset (vals, 0, sizeof (GValue) * (n_params + 1));

  if (query.return_type != G_TYPE_NONE)
    g_value_init (&ret, query.return_type);

  g_value_init_from_instance (&vals[0], obj);
  for (guint i = 0; i < n_params; i++) {
    g_value_init (&vals[i+1], query.param_types[i]);
    wplua_lua_to_gvalue (L, i+3, &vals[i+1]);
  }

  g_signal_emitv (vals, sig_id, detail, &ret);

  for (guint i = 0; i < n_params + 1; i++) {
    g_value_unset (&vals[i]);
  }

  int n_ret = 0;
  if (query.return_type != G_TYPE_NONE)
    n_ret = wplua_gvalue_to_lua (L, &ret);

  g_value_unset (&ret);
  return n_ret;
}

static int
_wplua_gobject_connect (lua_State *L)
{
  GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
  const char *sig_name = luaL_checkstring (L, 2);
  luaL_checktype (L, 3, LUA_TFUNCTION);

  guint sig_id = 0;
  GQuark detail = 0;

  if (G_UNLIKELY (!g_signal_parse_name (sig_name, G_TYPE_FROM_INSTANCE (obj),
                                        &sig_id, &detail, FALSE)))
    luaL_error (L, "unknown signal '%s::%s'", G_OBJECT_TYPE_NAME (obj),
        sig_name);

  GClosure *closure = wplua_function_to_closure (L, 3);
  gulong handler =
      g_signal_connect_closure_by_id (obj, sig_id, detail, closure, FALSE);

  lua_pushinteger (L, handler);
  return 1;
}

static lua_CFunction
find_method_in_luaL_Reg (luaL_Reg *reg, const gchar *method)
{
  if (reg) {
    while (reg->name) {
      if (!g_strcmp0 (method, reg->name))
        return reg->func;
      reg++;
    }
  }
  return NULL;
}

static int
_wplua_gobject___index (lua_State *L)
{
  GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
  const gchar *key = luaL_checkstring (L, 2);
  lua_CFunction func = NULL;
  GHashTable *vtables;

  lua_pushliteral (L, "wplua_vtables");
  lua_gettable (L, LUA_REGISTRYINDEX);
  vtables = wplua_toboxed (L, -1);
  lua_pop (L, 1);

  if (!g_strcmp0 (key, "call"))
    func = _wplua_gobject_call;
  else if (!g_strcmp0 (key, "connect"))
    func = _wplua_gobject_connect;

  /* search in registered vtables */
  if (!func) {
    GType type = G_TYPE_FROM_INSTANCE (obj);
    while (!func && type) {
      luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (type));
      func = find_method_in_luaL_Reg (reg, key);
      type = g_type_parent (type);
    }
  }

  /* search in registered vtables of interfaces */
  if (!func) {
    g_autofree GType *interfaces =
        g_type_interfaces (G_TYPE_FROM_INSTANCE (obj), NULL);
    GType *type = interfaces;
    while (!func && *type) {
      luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (*type));
      func = find_method_in_luaL_Reg (reg, key);
      type++;
    }
  }

  if (func) {
    lua_pushcfunction (L, func);
    return 1;
  }
  else {
    /* search in properties */
    GObjectClass *klass = G_OBJECT_GET_CLASS (obj);
    GParamSpec *pspec = g_object_class_find_property (klass, key);
    if (pspec && (pspec->flags & G_PARAM_READABLE)) {
      g_auto (GValue) v = G_VALUE_INIT;
      g_value_init (&v, pspec->value_type);
      g_object_get_property (obj, key, &v);
      return wplua_gvalue_to_lua (L, &v);
    }
  }

  return 0;
}

static int
_wplua_gobject___newindex (lua_State *L)
{
  GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
  const gchar *key = luaL_checkstring (L, 2);

  /* search in properties */
  GObjectClass *klass = G_OBJECT_GET_CLASS (obj);
  GParamSpec *pspec = g_object_class_find_property (klass, key);
  if (pspec && (pspec->flags & G_PARAM_WRITABLE)) {
    g_auto (GValue) v = G_VALUE_INIT;
    g_value_init (&v, pspec->value_type);
    wplua_lua_to_gvalue (L, 3, &v);
    g_object_set_property (obj, key, &v);
  } else {
    luaL_error (L, "attempted to assign unknown or non-writable property '%s'",
        key);
  }
  return 0;
}

static int
_wplua_gobject__tostring (lua_State *L)
{
  GObject *obj;
  gchar *str;

  obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
  str = g_strdup_printf (WP_OBJECT_FORMAT, WP_OBJECT_ARGS (obj));
  lua_pushstring (L, str);
  g_free (str);
  return 1;
}

void
_wplua_init_gobject (lua_State *L)
{
  static const luaL_Reg gobject_meta[] = {
    { "__gc", _wplua_gvalue_userdata___gc },
    { "__eq", _wplua_gvalue_userdata___eq },
    { "__index", _wplua_gobject___index },
    { "__newindex", _wplua_gobject___newindex },
    { "__tostring", _wplua_gobject__tostring },
    { NULL, NULL }
  };

  luaL_newmetatable (L, "GObject");
  luaL_setfuncs (L, gobject_meta, 0);
  lua_pop (L, 1);
}

void
wplua_pushobject (lua_State * L, gpointer object)
{
  g_return_if_fail (G_IS_OBJECT (object));

  GValue *v = _wplua_pushgvalue_userdata (L, G_TYPE_FROM_INSTANCE (object));
  wp_trace_object (object, "pushing to Lua, v=%p", v);
  g_value_take_object (v, object);

  luaL_getmetatable (L, "GObject");
  lua_setmetatable (L, -2);
}

gpointer
wplua_toobject (lua_State *L, int idx)
{
  g_return_val_if_fail (_wplua_isgvalue_userdata (L, idx, G_TYPE_OBJECT), NULL);
  return g_value_get_object ((GValue *) lua_touserdata (L, idx));
}

gpointer
wplua_checkobject (lua_State *L, int idx, GType type)
{
  if (G_UNLIKELY (!_wplua_isgvalue_userdata (L, idx, type))) {
    wp_critical ("expected userdata storing GValue<%s>", g_type_name (type));
    luaL_argerror (L, idx, "expected userdata storing GValue<GObject>");
  }
  return g_value_get_object ((GValue *) lua_touserdata (L, idx));
}

gboolean
wplua_isobject (lua_State *L, int idx, GType type)
{
  if (!g_type_is_a (type, G_TYPE_OBJECT)) return FALSE;
  return _wplua_isgvalue_userdata (L, idx, type);
}
 070701000000BE000081A4000000000000000000000001656CC35F000002FC000000000000000000000000000000000000004000000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/private.h   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WPLUA_PRIVATE_H__
#define __WPLUA_PRIVATE_H__

#include "wplua.h"

G_BEGIN_DECLS

/* boxed.c */
void _wplua_init_gboxed (lua_State *L);

/* closure.c */
void _wplua_init_closure (lua_State *L);

/* object.c */
void _wplua_init_gobject (lua_State *L);

/* userdata.c */
GValue * _wplua_pushgvalue_userdata (lua_State * L, GType type);
gboolean _wplua_isgvalue_userdata (lua_State *L, int idx, GType type);

int _wplua_gvalue_userdata___gc (lua_State *L);
int _wplua_gvalue_userdata___eq (lua_State *L);

/* wplua.c */
int _wplua_pcall (lua_State *L, int nargs, int nret);

G_END_DECLS

#endif
070701000000BF000081A4000000000000000000000001656CC35F00000ABD000000000000000000000000000000000000004200000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/sandbox.lua -- WirePlumber
--
-- Copyright © 2020 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- Based on https://github.com/kikito/sandbox.lua
-- Copyright © 2013 Enrique García Cota
--
-- SPDX-License-Identifier: MIT

local SANDBOX_CONFIG = ...
local SANDBOX_ENV = {}

function create_sandbox_env()
  local function populate_env(id)
    local module, method = id:match('([^%.]+)%.([^%.]+)')
    if module then
      SANDBOX_ENV[module]         = SANDBOX_ENV[module] or {}
      SANDBOX_ENV[module][method] = _G[module][method]
    else
      SANDBOX_ENV[id] = _G[id]
    end
  end

  if not SANDBOX_ENV._VERSION then
    -- List of exported functions and packages
    ([[ _VERSION assert error ipairs   next pairs  tonumber
        pcall    select print tostring type xpcall require
        table    string math  package  utf8 debug  coroutine
        os.clock  os.difftime os.time  os.date     os.getenv
    ]]):gsub('%S+', populate_env)

    -- Additionally export everything in SANDBOX_EXPORT
    if type(SANDBOX_EXPORT) == "table" then
      for k, v in pairs(SANDBOX_EXPORT) do
        SANDBOX_ENV[k] = v
      end
    end

    -- Additionally protect packages from malicious scripts trying to override methods
    for k, v in pairs(SANDBOX_ENV) do
      if type(v) == "table" then
        SANDBOX_ENV[k] = setmetatable({}, {
          __index = v,
          __newindex = function(_, attr_name, _)
            error('Can not modify ' .. k .. '.' .. attr_name .. '. Protected by the sandbox.')
          end
        })
      end
    end
  end

  -- chunk's environment will be an empty table with __index
  -- to access our SANDBOX_ENV (without being able to write it)
  return setmetatable({}, {
    __index = SANDBOX_ENV,
  })
end

if SANDBOX_CONFIG["isolate_env"] then
  -- in isolate_env mode, use a separate enviornment for each loaded chunk and
  -- store all of them in a global table so that they are not garbage collected
  SANDBOX_ENV_LIST = {}

  function sandbox(chunk, ...)
    local env = create_sandbox_env()
    -- store the chunk's environment so that it is not garbage collected
    table.insert(SANDBOX_ENV_LIST, env)
    -- set it as the chunk's 1st upvalue (__ENV)
    debug.setupvalue(chunk, 1, env)
    -- execute the chunk
    return chunk(...)
  end
else
  -- in common_env mode, use the same environment for all loaded chunks
  -- chunk's environment will be an empty table with __index
  -- to access our SANDBOX_ENV (without being able to write it)
  SANDBOX_COMMON_ENV = create_sandbox_env()

  function sandbox(chunk, ...)
    -- set it as the chunk's 1st upvalue (__ENV)
    debug.setupvalue(chunk, 1, SANDBOX_COMMON_ENV)
    -- execute the chunk
    return chunk(...)
  end
end
   070701000000C0000081A4000000000000000000000001656CC35F000006B3000000000000000000000000000000000000004100000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/userdata.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "wplua.h"
#include "private.h"
#include <wp/wp.h>

GValue *
_wplua_pushgvalue_userdata (lua_State * L, GType type)
{
  GValue *v = lua_newuserdata (L, sizeof (GValue));
  memset (v, 0, sizeof (GValue));
  g_value_init (v, type);
  return v;
}

gboolean
_wplua_isgvalue_userdata (lua_State *L, int idx, GType type)
{
  GValue *v;

  if (!lua_isuserdata (L, idx))
    return FALSE;
  if (lua_rawlen (L, idx) != sizeof (GValue))
    return FALSE;
  if (!(v = lua_touserdata (L, idx)))
    return FALSE;
  if (type != G_TYPE_NONE && !g_type_is_a (G_VALUE_TYPE (v), type))
    return FALSE;

  return TRUE;
}

GType
wplua_gvalue_userdata_type (lua_State *L, int idx)
{
  GValue *v;

  if (!lua_isuserdata (L, idx))
    return G_TYPE_INVALID;
  if (lua_rawlen (L, idx) != sizeof (GValue))
    return G_TYPE_INVALID;
  if (!(v = lua_touserdata (L, idx)))
    return G_TYPE_INVALID;

  return G_VALUE_TYPE (v);
}

int
_wplua_gvalue_userdata___gc (lua_State *L)
{
  GValue *v = lua_touserdata (L, 1);
  wp_trace_boxed (G_VALUE_TYPE (v), g_value_peek_pointer (v),
      "collected, v=%p", v);
  g_value_unset (v);
  return 0;
}

int
_wplua_gvalue_userdata___eq (lua_State *L)
{
  if (_wplua_isgvalue_userdata (L, 1, G_TYPE_NONE) &&
      _wplua_isgvalue_userdata (L, 2, G_TYPE_NONE)) {
    GValue *v1 = lua_touserdata (L, 1);
    GValue *v2 = lua_touserdata (L, 2);
    gpointer p1 = g_value_peek_pointer (v1);
    gpointer p2 = g_value_peek_pointer (v2);
    lua_pushboolean (L, (p1 == p2));
  } else {
    lua_pushboolean (L, FALSE);
  }
  return 1;
}
 070701000000C1000081A4000000000000000000000001656CC35F000027C6000000000000000000000000000000000000003E00000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/value.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "wplua.h"
#include "private.h"
#include <wp/wp.h>

WpProperties *
wplua_table_to_properties (lua_State *L, int idx)
{
  WpProperties *p = wp_properties_new_empty ();
  const gchar *key, *value;
  int table = lua_absindex (L, idx);

  lua_pushnil(L);
  while (lua_next (L, table) != 0) {
    /* copy key & value to convert them to string */
    key = luaL_tolstring (L, -2, NULL);
    value = luaL_tolstring (L, -2, NULL);
    wp_properties_set (p, key, value);
    lua_pop (L, 3);
  }

  /* sort, because the lua table has a random order and it's too messy to read */
  wp_properties_sort (p);

  return p;
}

void
wplua_properties_to_table (lua_State *L, WpProperties *p)
{
  lua_newtable (L);
  if (p) {
    g_autoptr (WpIterator) it = wp_properties_new_iterator (p);
    GValue v = G_VALUE_INIT;
    const gchar *key, *value;

    while (wp_iterator_next (it, &v)) {
      WpPropertiesItem *pi = g_value_get_boxed (&v);
      key = wp_properties_item_get_key (pi);
      value = wp_properties_item_get_value (pi);
      lua_pushstring (L, key);
      lua_pushstring (L, value);
      lua_settable (L, -3);
      g_value_unset (&v);
    }
  }
}

GVariant *
wplua_lua_to_gvariant (lua_State *L, int idx)
{
  switch (lua_type (L, idx)) {
  case LUA_TNIL:
    return g_variant_new ("()");
  case LUA_TBOOLEAN:
    return g_variant_new_boolean (lua_toboolean (L, idx));
  case LUA_TNUMBER:
    if (lua_isinteger (L, idx))
      return g_variant_new_int64 (lua_tointeger (L, idx));
    else
      return g_variant_new_double (lua_tonumber (L, idx));
  case LUA_TSTRING:
    return g_variant_new_string (lua_tostring (L, idx));
  case LUA_TTABLE: {
    GVariantBuilder b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
    const gchar *key;
    int table = lua_absindex (L, idx);

    lua_pushnil (L);
    while (lua_next (L, table) != 0) {
      /* copy key to convert it to string */
      lua_pushvalue (L, -2);
      key = lua_tostring (L, -1);
      g_variant_builder_add (&b, "{sv}", key, wplua_lua_to_gvariant (L, -2));
      lua_pop (L, 2);
    }
    return g_variant_builder_end (&b);
  }
  default:
    wp_warning ("skipping bad value (its type cannot be represented in GVariant)");
    return NULL;
  }
}

void
wplua_gvariant_to_lua (lua_State *L, GVariant  *variant)
{
  if (variant == NULL || g_variant_is_of_type (variant, G_VARIANT_TYPE_UNIT)) {
    lua_pushnil (L);
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT16)) {
    lua_pushinteger (L, g_variant_get_int16 (variant));
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT32)) {
    lua_pushinteger (L, g_variant_get_int32 (variant));
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT64)) {
    lua_pushinteger (L, g_variant_get_int64 (variant));
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT16)) {
    lua_pushinteger (L, g_variant_get_uint16 (variant));
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32)) {
    lua_pushinteger (L, g_variant_get_uint32 (variant));
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT64)) {
    lua_pushinteger (L, g_variant_get_uint64 (variant));
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_DOUBLE)) {
    lua_pushnumber (L, g_variant_get_double (variant));
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN)) {
    lua_pushboolean (L, g_variant_get_boolean (variant));
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) {
    lua_pushstring (L, g_variant_get_string (variant, NULL));
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT)) {
    g_autoptr (GVariant) v = g_variant_get_variant (variant);
    wplua_gvariant_to_lua (L, v);
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_DICTIONARY)) {
    gsize n_children, i;
    n_children = g_variant_n_children (variant);
    lua_createtable (L, 0, n_children);
    for (i = 0; i < n_children; i++) {
      g_autoptr (GVariant) key, value;
      g_variant_get_child (variant, i, "{@?@*}", &key, &value);
      wplua_gvariant_to_lua (L, key);
      /* if the key is a string convertible to integer, convert it */
      if (lua_type (L, -1) == LUA_TSTRING) {
        int isnum = 0;
        lua_Integer num = lua_tointegerx (L, -1, &isnum);
        if (isnum) {
          lua_pop (L, 1);
          lua_pushinteger (L, num);
        }
      }
      wplua_gvariant_to_lua (L, value);
      lua_settable (L, -3);
    }
  }
  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_ARRAY)) {
    gsize n_children, i;
    n_children = g_variant_n_children (variant);
    lua_createtable (L, n_children, 0);
    for (i = 0; i < n_children; i++) {
      g_autoptr (GVariant) value;
      value = g_variant_get_child_value (variant, i);
      wplua_gvariant_to_lua (L, value);
      lua_seti (L, -2, i + 1);
    }
  }
  else {
    g_autofree gchar *type_name =
        g_variant_type_dup_string (g_variant_get_type (variant));
    wp_warning ("Unhandled GVariant type %s", type_name);
    lua_pushnil (L);
  }
}

gint
wplua_lua_to_enum (lua_State *L, int idx, GType enum_type)
{
  if (lua_type (L, idx) == LUA_TSTRING) {
    g_autoptr (GEnumClass) klass = g_type_class_ref (enum_type);
    GEnumValue *value = g_enum_get_value_by_nick (klass, lua_tostring (L, idx));
    if (value)
      return value->value;
    else
      luaL_error (L, "Invalid enum value '%s'", lua_tostring (L, idx));
  }
  return lua_tointeger (L, idx);
}

void
wplua_enum_to_lua (lua_State *L, gint enum_val, GType enum_type)
{
  g_autoptr (GEnumClass) klass = g_type_class_ref (enum_type);
  GEnumValue *value = g_enum_get_value (klass, enum_val);
  if (value)
    lua_pushstring (L, value->value_nick);
  else
    lua_pushinteger (L, enum_val);
}

void
wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v)
{
  switch (g_type_fundamental (G_VALUE_TYPE (v))) {
  case G_TYPE_CHAR:
    if (lua_type (L, idx) == LUA_TSTRING)
      g_value_set_schar (v, *lua_tostring (L, idx));
    else
      g_value_set_schar (v, lua_tointeger (L, idx));
    break;
  case G_TYPE_UCHAR:
    g_value_set_uchar (v, lua_tointeger (L, idx));
    break;
  case G_TYPE_INT:
    g_value_set_int (v, lua_tointeger (L, idx));
    break;
  case G_TYPE_UINT:
    g_value_set_uint (v, lua_tointeger (L, idx));
    break;
  case G_TYPE_LONG:
    g_value_set_long (v, lua_tointeger (L, idx));
    break;
  case G_TYPE_ULONG:
    g_value_set_ulong (v, lua_tointeger (L, idx));
    break;
  case G_TYPE_INT64:
    g_value_set_int64 (v, lua_tointeger (L, idx));
    break;
  case G_TYPE_UINT64:
    g_value_set_uint64 (v, lua_tonumber (L, idx));
    break;
  case G_TYPE_FLOAT:
    g_value_set_float (v, lua_tonumber (L, idx));
    break;
  case G_TYPE_DOUBLE:
    g_value_set_double (v, lua_tonumber (L, idx));
    break;
  case G_TYPE_BOOLEAN:
    g_value_set_boolean (v, lua_toboolean (L, idx));
    break;
  case G_TYPE_STRING:
    g_value_set_string (v, lua_tostring (L, idx));
    break;
  case G_TYPE_POINTER:
    if (lua_type (L, idx) == LUA_TLIGHTUSERDATA)
      g_value_set_pointer (v, lua_touserdata (L, idx));
    break;
  case G_TYPE_BOXED:
    if (_wplua_isgvalue_userdata (L, idx, G_VALUE_TYPE (v)))
      g_value_set_boxed (v, wplua_toboxed (L, idx));
    /* table -> WpProperties */
    else if (lua_istable (L, idx) && G_VALUE_TYPE (v) == WP_TYPE_PROPERTIES)
      g_value_take_boxed (v, wplua_table_to_properties (L, idx));
    break;
  case G_TYPE_OBJECT:
  case G_TYPE_INTERFACE:
    if (_wplua_isgvalue_userdata (L, idx, G_VALUE_TYPE (v)))
      g_value_set_object (v, wplua_toobject (L, idx));
    break;
  case G_TYPE_ENUM:
    g_value_set_enum (v, wplua_lua_to_enum (L, idx, G_VALUE_TYPE (v)));
    break;
  case G_TYPE_FLAGS:
    g_value_set_flags (v, lua_tointeger (L, idx));
    break;
  case G_TYPE_VARIANT:
    g_value_set_variant (v, wplua_lua_to_gvariant (L, idx));
    break;
  default:
    break;
  }
}

int
wplua_gvalue_to_lua (lua_State *L, const GValue *v)
{
  switch (g_type_fundamental (G_VALUE_TYPE (v))) {
  case G_TYPE_CHAR:
    lua_pushinteger (L, g_value_get_schar (v));
    break;
  case G_TYPE_UCHAR:
    lua_pushinteger (L, g_value_get_uchar (v));
    break;
  case G_TYPE_INT:
    lua_pushinteger (L, g_value_get_int (v));
    break;
  case G_TYPE_UINT:
    lua_pushinteger (L, g_value_get_uint (v));
    break;
  case G_TYPE_LONG:
    lua_pushinteger (L, g_value_get_long (v));
    break;
  case G_TYPE_ULONG:
    lua_pushinteger (L, g_value_get_ulong (v));
    break;
  case G_TYPE_INT64:
    lua_pushinteger (L, g_value_get_int64 (v));
    break;
  case G_TYPE_UINT64:
    lua_pushnumber (L, g_value_get_uint64 (v));
    break;
  case G_TYPE_FLOAT:
    lua_pushnumber (L, g_value_get_float (v));
    break;
  case G_TYPE_DOUBLE:
    lua_pushnumber (L, g_value_get_double (v));
    break;
  case G_TYPE_BOOLEAN:
    lua_pushboolean (L, g_value_get_boolean (v));
    break;
  case G_TYPE_STRING:
    lua_pushstring (L, g_value_get_string (v));
    break;
  case G_TYPE_POINTER:
    lua_pushlightuserdata (L, g_value_get_pointer (v));
    break;
  case G_TYPE_BOXED:
    if (G_VALUE_TYPE (v) == WP_TYPE_PROPERTIES)
      wplua_properties_to_table (L, g_value_get_boxed (v));
    else
      wplua_pushboxed (L, G_VALUE_TYPE (v), g_value_dup_boxed (v));
    break;
  case G_TYPE_OBJECT:
  case G_TYPE_INTERFACE: {
    GObject *object = g_value_dup_object (v);
    if (object)
      wplua_pushobject (L, object);
    else
      lua_pushnil (L);
    break;
  }
  case G_TYPE_ENUM:
    wplua_enum_to_lua (L, g_value_get_enum (v), G_VALUE_TYPE (v));
    break;
  case G_TYPE_FLAGS:
    /* FIXME: push as userdata with methods */
    lua_pushinteger (L, g_value_get_flags (v));
    break;
  case G_TYPE_PARAM: {
    GParamSpec *pspec = g_value_get_param (v);
    lua_pushstring (L, pspec->name);
    break;
  }
  case G_TYPE_VARIANT: {
    GVariant *variant = g_value_get_variant (v);
    wplua_gvariant_to_lua (L, variant);
    break;
  }
  default:
    lua_pushnil (L);
    break;
  }
  return 1;
}
  070701000000C2000081A4000000000000000000000001656CC35F00001CCA000000000000000000000000000000000000003E00000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/wplua.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "wplua.h"
#include "private.h"
#include <wp/wp.h>

#define URI_SANDBOX "resource:///org/freedesktop/pipewire/wireplumber/wplua/sandbox.lua"

extern void _wplua_register_resource (void);

G_DEFINE_QUARK (wplua, wp_domain_lua);

static void
_wplua_openlibs (lua_State *L)
{
  /* http://www.lua.org/manual/5.3/manual.html#luaL_requiref
   * http://www.lua.org/source/5.3/linit.c.html */
  static const luaL_Reg loadedlibs[] = {
    {"_G", luaopen_base},
    {LUA_LOADLIBNAME, luaopen_package},
    {LUA_COLIBNAME, luaopen_coroutine},
    {LUA_TABLIBNAME, luaopen_table},
    /* {LUA_IOLIBNAME, luaopen_io}, */
    {LUA_OSLIBNAME, luaopen_os},
    {LUA_STRLIBNAME, luaopen_string},
    {LUA_MATHLIBNAME, luaopen_math},
    {LUA_UTF8LIBNAME, luaopen_utf8},
    {LUA_DBLIBNAME, luaopen_debug},
    {NULL, NULL}
  };
  const luaL_Reg *lib;

  for (lib = loadedlibs; lib->func; lib++) {
    luaL_requiref (L, lib->name, lib->func, 1);
    lua_pop (L, 1);
  }
}

static int
_wplua_errhandler (lua_State *L)
{
  luaL_traceback (L, L, NULL, 1);
  wp_warning ("%s\n%s", lua_tostring (L, -2), lua_tostring (L, -1));
  lua_pop (L, 2);
  return 0;
}

int
_wplua_pcall (lua_State *L, int nargs, int nret)
{
  int hpos = lua_gettop (L) - nargs;
  int ret = LUA_OK;

  lua_pushcfunction (L, _wplua_errhandler);
  lua_insert (L, hpos);

  ret = lua_pcall (L, nargs, nret, hpos);
  switch (ret) {
  case LUA_ERRMEM:
    wp_critical ("not enough memory");
    break;
  case LUA_ERRERR:
    wp_critical ("error running the message handler");
    break;
  default:
    break;
  }

  lua_remove (L, hpos);
  return ret;
}

lua_State *
wplua_new (void)
{
  static gboolean resource_registered = FALSE;
  lua_State *L = luaL_newstate ();

  wp_debug ("initializing lua_State %p", L);

  if (!resource_registered) {
    _wplua_register_resource ();
    resource_registered = TRUE;
  }

  _wplua_openlibs (L);
  _wplua_init_gboxed (L);
  _wplua_init_gobject (L);
  _wplua_init_closure (L);

  {
    GHashTable *t = g_hash_table_new (g_direct_hash, g_direct_equal);
    lua_pushliteral (L, "wplua_vtables");
    wplua_pushboxed (L, G_TYPE_HASH_TABLE, t);
    lua_settable (L, LUA_REGISTRYINDEX);
  }

  /* refcount */
  lua_pushinteger (L, 1);
  lua_rawsetp (L, LUA_REGISTRYINDEX, L);

  return L;
}

lua_State *
wplua_ref (lua_State *L)
{
  lua_Integer refcount;
  lua_rawgetp (L, LUA_REGISTRYINDEX, L);
  refcount = lua_tointeger (L, -1);
  lua_pushinteger (L, refcount + 1);
  lua_rawsetp (L, LUA_REGISTRYINDEX, L);
  lua_pop (L, 1);
  return L;
}

void
wplua_unref (lua_State * L)
{
  lua_Integer refcount;
  lua_rawgetp (L, LUA_REGISTRYINDEX, L);
  refcount = lua_tointeger (L, -1);
  if (refcount > 1) {
    lua_pushinteger (L, refcount - 1);
    lua_rawsetp (L, LUA_REGISTRYINDEX, L);
    lua_pop (L, 1);
  } else {
    wp_debug ("closing lua_State %p", L);
    lua_close (L);
  }
}

void
wplua_enable_sandbox (lua_State * L, WpLuaSandboxFlags flags)
{
  g_autoptr (GError) error = NULL;
  wp_debug ("enabling Lua sandbox");

  if (!wplua_load_uri (L, URI_SANDBOX, &error)) {
    wp_critical ("Failed to load sandbox: %s", error->message);
    return;
  }

  lua_newtable (L);
  lua_pushliteral (L, "isolate_env");
  lua_pushboolean (L, (flags & WP_LUA_SANDBOX_ISOLATE_ENV));
  lua_settable (L, -3);

  if (!wplua_pcall (L, 1, 0, &error)) {
    wp_critical ("Failed to load sandbox: %s", error->message);
  }
}

int
wplua_push_sandbox (lua_State * L)
{
  return (lua_getglobal (L, "sandbox") == LUA_TFUNCTION) ? 1 : 0;
}

void
wplua_register_type_methods (lua_State * L, GType type,
    lua_CFunction constructor, const luaL_Reg * methods)
{
  g_return_if_fail (L != NULL);
  g_return_if_fail (G_TYPE_FUNDAMENTAL (type) == G_TYPE_OBJECT ||
                    G_TYPE_FUNDAMENTAL (type) == G_TYPE_BOXED ||
                    G_TYPE_FUNDAMENTAL (type) == G_TYPE_INTERFACE);

  /* register methods */
  if (methods) {
    GHashTable *vtables;

    lua_pushliteral (L, "wplua_vtables");
    lua_gettable (L, LUA_REGISTRYINDEX);
    vtables = wplua_toboxed (L, -1);
    lua_pop (L, 1);

    wp_debug ("Registering methods for '%s'", g_type_name (type));

    if (G_UNLIKELY (g_hash_table_contains (vtables, GUINT_TO_POINTER (type)))) {
      wp_critical ("type '%s' was already registered", g_type_name (type));
      return;
    }

    g_hash_table_insert (vtables, GUINT_TO_POINTER (type), (gpointer) methods);
  }

  /* register constructor */
  if (constructor) {
    luaL_Buffer b;

    wp_debug ("Registering class for '%s'", g_type_name (type));

    luaL_buffinit (L, &b);
    luaL_addstring (&b, g_type_name (type));
    luaL_addchar (&b, '_');
    luaL_addstring (&b, "new");
    luaL_pushresult (&b);
    lua_pushcfunction (L, constructor);
    lua_setglobal (L, lua_tostring (L, -2));
    lua_pop (L, 1);
  }
}

static gboolean
_wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
    const gchar * name, GError **error)
{
  int ret;

  /* skip shebang, if present */
  if (g_str_has_prefix (buf, "#!/")) {
    const char *tmp = strchr (buf, '\n');
    size -= (tmp - buf);
    buf = tmp;
  }

  ret = luaL_loadbuffer (L, buf, size, name);
  if (ret != LUA_OK) {
    g_set_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_COMPILATION,
        "Failed to compile: %s", lua_tostring (L, -1));
    lua_pop (L, 1);
    return FALSE;
  }
  return TRUE;
}

gboolean
wplua_load_buffer (lua_State * L, const gchar *buf, gsize size, GError **error)
{
  g_return_val_if_fail (L != NULL, FALSE);
  g_return_val_if_fail (buf != NULL, FALSE);
  g_return_val_if_fail (size != 0, FALSE);

  g_autofree gchar *name =
      g_strdup_printf ("buffer@%p;size=%" G_GSIZE_FORMAT, buf, size);
  return _wplua_load_buffer (L, buf, size, name, error);
}

gboolean
wplua_load_uri (lua_State * L, const gchar *uri, GError **error)
{
  g_autoptr (GFile) file = NULL;
  g_autoptr (GBytes) bytes = NULL;
  g_autoptr (GError) err = NULL;
  g_autofree gchar *name = NULL;
  gconstpointer data;
  gsize size;

  g_return_val_if_fail (L != NULL, FALSE);
  g_return_val_if_fail (uri != NULL, FALSE);

  file = g_file_new_for_uri (uri);
  if (!(bytes = g_file_load_bytes (file, NULL, NULL, &err))) {
    g_propagate_prefixed_error (error, err, "Failed to load '%s':", uri);
    err = NULL;
    return FALSE;
  }

  name = g_path_get_basename (uri);
  data = g_bytes_get_data (bytes, &size);
  return _wplua_load_buffer (L, data, size, name, error);
}

gboolean
wplua_load_path (lua_State * L, const gchar *path, GError **error)
{
  g_autofree gchar *abs_path = NULL;
  g_autofree gchar *uri = NULL;

  g_return_val_if_fail (L != NULL, FALSE);
  g_return_val_if_fail (path != NULL, FALSE);

  if (!g_path_is_absolute (path)) {
    g_autofree gchar *cwd = g_get_current_dir ();
    abs_path = g_build_filename (cwd, path, NULL);
  }

  if (!(uri = g_filename_to_uri (abs_path ? abs_path : path, NULL, error)))
    return FALSE;

  return wplua_load_uri (L, uri, error);
}

gboolean
wplua_pcall (lua_State * L, int nargs, int nres, GError **error)
{
  int ret = _wplua_pcall (L, nargs, nres);
  if (ret != LUA_OK) {
    g_set_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_RUNTIME,
        "Lua runtime error");
    return FALSE;
  }
  return TRUE;
}
  070701000000C3000081A4000000000000000000000001656CC35F00000C35000000000000000000000000000000000000003E00000000wireplumber-0.4.17/modules/module-lua-scripting/wplua/wplua.h /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WPLUA_H__
#define __WPLUA_H__

#include <wp/wp.h>

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

G_BEGIN_DECLS

/**
 * WP_DOMAIN_LUA:
 *
 * @brief A <a href="https://developer.gnome.org/glib/stable/glib-Error-Reporting.html#GError">
 * GError</a> domain for errors that occurred within the context of the
 * WirePlumber lua library.
 */
#define WP_DOMAIN_LUA (wp_domain_lua_quark ())
GQuark wp_domain_lua_quark (void);

/**
 * WpLuaError:
 *
 * @brief
 * @em WP_LUA_ERROR_COMPILATION: a compilation error, i.e. invalid Lua code
 * @em WP_LUA_ERROR_RUNTIME: a runtime error, i.e. misbehaving Lua code
 *
 * Error codes that can appear in a
 * <a href="https://developer.gnome.org/glib/stable/glib-Error-Reporting.html#GError">
 * GError</a> when the error domain is %WP_DOMAIN_LUA
 */
typedef enum {
  WP_LUA_ERROR_COMPILATION,
  WP_LUA_ERROR_RUNTIME,
} WpLuaError;

typedef enum {
  WP_LUA_SANDBOX_ISOLATE_ENV = 1,
} WpLuaSandboxFlags;

lua_State * wplua_new (void);
lua_State * wplua_ref (lua_State *L);
void wplua_unref (lua_State * L);

void wplua_enable_sandbox (lua_State * L, WpLuaSandboxFlags flags);
int wplua_push_sandbox (lua_State * L);

void wplua_register_type_methods (lua_State * L, GType type,
    lua_CFunction constructor, const luaL_Reg * methods);

GType wplua_gvalue_userdata_type (lua_State *L, int idx);

/* push -> transfer full; get -> transfer none */
void wplua_pushobject (lua_State * L, gpointer object);
gpointer wplua_toobject (lua_State *L, int idx);
gpointer wplua_checkobject (lua_State *L, int idx, GType type);
gboolean wplua_isobject (lua_State *L, int idx, GType type);

/* push -> transfer full; get -> transfer none */
void wplua_pushboxed (lua_State * L, GType type, gpointer object);
gpointer wplua_toboxed (lua_State *L, int idx);
gpointer wplua_checkboxed (lua_State *L, int idx, GType type);
gboolean wplua_isboxed (lua_State *L, int idx, GType type);

/* transfer floating */
GClosure * wplua_checkclosure (lua_State *L, int idx);
GClosure * wplua_function_to_closure (lua_State *L, int idx);

void wplua_enum_to_lua (lua_State *L, gint enum_val, GType enum_type);
gint wplua_lua_to_enum (lua_State *L, int idx, GType enum_type);

void wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v);
int wplua_gvalue_to_lua (lua_State *L, const GValue *v);

GVariant * wplua_lua_to_gvariant (lua_State *L, int idx);
void wplua_gvariant_to_lua (lua_State *L, GVariant *p);

WpProperties * wplua_table_to_properties (lua_State *L, int idx);
void wplua_properties_to_table (lua_State *L, WpProperties *p);

gboolean wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
    GError **error);
gboolean wplua_load_uri (lua_State * L, const gchar *uri, GError **error);
gboolean wplua_load_path (lua_State * L, const gchar *path, GError **error);

gboolean wplua_pcall (lua_State * L, int nargs, int nres, GError **error);

G_DEFINE_AUTOPTR_CLEANUP_FUNC(lua_State, wplua_unref)

G_END_DECLS

#endif
   070701000000C4000081A4000000000000000000000001656CC35F000008A5000000000000000000000000000000000000002D00000000wireplumber-0.4.17/modules/module-metadata.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Raghavendra Rao <raghavendra.rao@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <pipewire/pipewire.h>

struct _WpMetadataPlugin
{
  WpPlugin parent;
  WpImplMetadata *metadata;
};

G_DECLARE_FINAL_TYPE (WpMetadataPlugin, wp_metadata_plugin,
                      WP, METADATA_PLUGIN, WpPlugin)
G_DEFINE_TYPE (WpMetadataPlugin, wp_metadata_plugin, WP_TYPE_PLUGIN)

static void
wp_metadata_plugin_init (WpMetadataPlugin * self)
{
}

static void
on_metadata_activated (GObject * obj, GAsyncResult * res, gpointer user_data)
{
  WpTransition * transition = WP_TRANSITION (user_data);
  WpMetadataPlugin * self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (WP_OBJECT (obj), res, &error)) {
    g_clear_object (&self->metadata);
    g_prefix_error (&error, "Failed to activate WpImplMetadata: ");
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_metadata_plugin_enable (WpPlugin * plugin, WpTransition * transition)
{
  WpMetadataPlugin * self = WP_METADATA_PLUGIN (plugin);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
  g_return_if_fail (core);

  self->metadata = wp_impl_metadata_new (core);
  wp_object_activate (WP_OBJECT (self->metadata),
        WP_OBJECT_FEATURES_ALL, NULL, on_metadata_activated, transition);
}

static void
wp_metadata_plugin_disable (WpPlugin * plugin)
{
  WpMetadataPlugin * self = WP_METADATA_PLUGIN (plugin);

  g_clear_object (&self->metadata);
}

static void
wp_metadata_plugin_class_init (WpMetadataPluginClass * klass)
{
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  plugin_class->enable = wp_metadata_plugin_enable;
  plugin_class->disable = wp_metadata_plugin_disable;
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_plugin_register (g_object_new (wp_metadata_plugin_get_type (),
          "name", "metadata",
          "core", core,
          NULL));
  return TRUE;
}
   070701000000C5000081A4000000000000000000000001656CC35F00004935000000000000000000000000000000000000002E00000000wireplumber-0.4.17/modules/module-mixer-api.c /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <math.h>
#include <pipewire/pipewire.h>
#include <spa/pod/iter.h>
#include <spa/param/audio/raw.h>

struct volume {
  uint8_t channels;
  float values[SPA_AUDIO_MAX_CHANNELS];
};

struct channel_map {
  uint8_t channels;
  uint32_t map[SPA_AUDIO_MAX_CHANNELS];
};

struct node_info {
  guint32 seq;

  guint32 device_id;
  gint32 route_index;
  gint32 route_device;

  struct volume volume;
  struct volume monitorVolume;
  struct channel_map map;
  bool mute;
  float svolume;
  float base;
  float step;
};

struct _WpMixerApi
{
  WpPlugin parent;
  WpObjectManager *om;
  GHashTable *node_infos;
  guint32 seq;

  /* properties */
  gint scale;
};

enum {
  ACTION_SET_VOLUME,
  ACTION_GET_VOLUME,
  SIGNAL_CHANGED,
  N_SIGNALS
};

enum {
  PROP_0,
  PROP_SCALE,
};

static guint signals[N_SIGNALS] = {0};

G_DECLARE_FINAL_TYPE (WpMixerApi, wp_mixer_api, WP, MIXER_API, WpPlugin)
G_DEFINE_TYPE (WpMixerApi, wp_mixer_api, WP_TYPE_PLUGIN)

enum {
  SCALE_LINEAR,
  SCALE_CUBIC,
};

static GType
wp_mixer_api_volume_scale_enum_get_type (void)
{
  static gsize gtype_id = 0;
  static const GEnumValue values[] = {
    { (gint) SCALE_LINEAR, "SCALE_LINEAR", "linear" },
    { (gint) SCALE_CUBIC, "SCALE_CUBIC", "cubic" },
    { 0, NULL, NULL }
  };
  if (g_once_init_enter (&gtype_id)) {
    GType new_type = g_enum_register_static (
        g_intern_static_string ("WpMixerApiVolumeScale"), values);
    g_once_init_leave (&gtype_id, new_type);
  }
  return (GType) gtype_id;
}

static void
wp_mixer_api_init (WpMixerApi * self)
{
}

static void
wp_mixer_api_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpMixerApi *self = WP_MIXER_API (object);

  switch (property_id) {
  case PROP_SCALE:
    g_value_set_enum (value, self->scale);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_mixer_api_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpMixerApi *self = WP_MIXER_API (object);

  switch (property_id) {
  case PROP_SCALE:
    self->scale = g_value_get_enum (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static gboolean
node_info_fill (struct node_info * info, WpSpaPod * props)
{
  g_autoptr (WpSpaPod) channelVolumes = NULL;
  g_autoptr (WpSpaPod) channelMap = NULL;
  g_autoptr (WpSpaPod) monitorVolumes = NULL;

  if (!wp_spa_pod_get_object (props, NULL,
          "mute", "b", &info->mute,
          "channelVolumes", "P", &channelVolumes,
          NULL))
    return FALSE;

  /* default values */
  info->svolume = 1.0;
  info->base = 1.0;
  info->step = 1.0 / 65536.0;

  wp_spa_pod_get_object (props, NULL,
      "channelMap", "?P", &channelMap,
      "volumeBase", "?f", &info->base,
      "volumeStep", "?f", &info->step,
      "volume",     "?f", &info->svolume,
      "monitorVolumes", "?P", &monitorVolumes,
      NULL);

  info->volume.channels = spa_pod_copy_array (
      wp_spa_pod_get_spa_pod (channelVolumes), SPA_TYPE_Float,
      info->volume.values, SPA_AUDIO_MAX_CHANNELS);

  if (channelMap)
    info->map.channels = spa_pod_copy_array (
        wp_spa_pod_get_spa_pod (channelMap), SPA_TYPE_Id,
        info->map.map, SPA_AUDIO_MAX_CHANNELS);

  if (monitorVolumes)
    info->monitorVolume.channels = spa_pod_copy_array (
        wp_spa_pod_get_spa_pod (monitorVolumes), SPA_TYPE_Float,
        info->monitorVolume.values, SPA_AUDIO_MAX_CHANNELS);

  return TRUE;
}

static void
collect_node_info (WpMixerApi * self, struct node_info *info,
    WpPipewireObject * node)
{
  g_autoptr (WpPipewireObject) dev = NULL;
  const gchar *str = NULL;
  gboolean have_volume = FALSE;

  info->device_id = SPA_ID_INVALID;
  info->route_index = -1;
  info->route_device = -1;

  if ((str = wp_pipewire_object_get_property (node, PW_KEY_DEVICE_ID))) {
    dev = wp_object_manager_lookup (self->om, WP_TYPE_DEVICE,
        WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=s", str, NULL);
  }

  if (dev && (str = wp_pipewire_object_get_property (node, "card.profile.device"))) {
    gint32 p_device = atoi (str);
    g_autoptr (WpIterator) it = NULL;
    g_auto (GValue) val = G_VALUE_INIT;

    it = wp_pipewire_object_enum_params_sync (dev, "Route", NULL);
    for (; it && wp_iterator_next (it, &val); g_value_unset (&val)) {
      WpSpaPod *param = g_value_get_boxed (&val);
      gint32 r_index = -1, r_device = -1;
      g_autoptr (WpSpaPod) props = NULL;

      if (!wp_spa_pod_get_object (param, NULL,
              "index", "i", &r_index,
              "device", "i", &r_device,
              "props", "P", &props,
              NULL))
        continue;
      if (r_device != p_device)
        continue;

      if (props && node_info_fill (info, props)) {
        info->device_id = wp_proxy_get_bound_id (WP_PROXY (dev));
        info->route_index = r_index;
        info->route_device = r_device;
        have_volume = TRUE;
        g_value_unset (&val);
        break;
      }
    }
  }

  if (!have_volume) {
    g_autoptr (WpIterator) it = NULL;
    g_auto (GValue) val = G_VALUE_INIT;

    it = wp_pipewire_object_enum_params_sync (node, "Props", NULL);
    for (; it && wp_iterator_next (it, &val); g_value_unset (&val)) {
      WpSpaPod *param = g_value_get_boxed (&val);
      if (node_info_fill (info, param)) {
        g_value_unset (&val);
        break;
      }
    }
  }
}

static void on_objects_changed (WpObjectManager * om, WpMixerApi * self);

static void
on_sync_done (WpCore * core, GAsyncResult * res, WpMixerApi * self)
{
  g_autoptr (GError) error = NULL;
  if (!wp_core_sync_finish (core, res, &error))
    wp_warning_object (core, "sync error: %s", error->message);
  if (self->om) {
    on_objects_changed (self->om, self);
  }
}

static void
on_params_changed (WpPipewireObject * obj, const gchar * param_name,
    WpMixerApi * self)
{
  if ((WP_IS_NODE (obj) && !g_strcmp0 (param_name, "Props")) ||
      (WP_IS_DEVICE (obj) && !g_strcmp0 (param_name, "Route"))) {
    g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
    wp_core_sync (core, NULL, (GAsyncReadyCallback) on_sync_done, self);
  }
}

static void
on_objects_changed (WpObjectManager * om, WpMixerApi * self)
{
  g_autoptr (WpIterator) it =
      wp_object_manager_new_filtered_iterator (om, WP_TYPE_NODE, NULL);
  g_auto (GValue) val = G_VALUE_INIT;
  GHashTableIter infos_it;
  struct node_info *info;
  struct node_info old;

  self->seq++;

  for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
    WpPipewireObject *node = g_value_get_object (&val);
    guint id = wp_proxy_get_bound_id (WP_PROXY (node));

    info = g_hash_table_lookup (self->node_infos, GUINT_TO_POINTER (id));
    if (!info) {
      info = g_slice_new0 (struct node_info);
      g_hash_table_insert (self->node_infos, GUINT_TO_POINTER (id), info);
    }
    info->seq = self->seq;

    old = *info;
    collect_node_info (self, info, node);
    if (memcmp (&old, info, sizeof (struct node_info)) != 0) {
      wp_debug_object (self, "node %u changed volume props", id);
      g_signal_emit (self, signals[SIGNAL_CHANGED], 0, id);
    }
  }

  /* remove node_info of nodes that were removed from the object manager */
  g_hash_table_iter_init (&infos_it, self->node_infos);
  while (g_hash_table_iter_next (&infos_it, NULL, (gpointer *) &info)) {
    if (info->seq != self->seq)
      g_hash_table_iter_remove (&infos_it);
  }
}

static void
on_object_added (WpObjectManager * om, WpProxy * obj, WpMixerApi * self)
{
  g_signal_connect (obj, "params-changed", G_CALLBACK (on_params_changed), self);
}

static void
on_object_removed (WpObjectManager * om, WpProxy * obj, WpMixerApi * self)
{
  g_signal_handlers_disconnect_by_func (obj, G_CALLBACK (on_params_changed), self);
}

static void
node_info_free (gpointer info)
{
  g_slice_free (struct node_info, info);
}

static void
on_om_installed (WpObjectManager * om, WpMixerApi * self)
{
  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_mixer_api_enable (WpPlugin * plugin, WpTransition * transition)
{
  WpMixerApi * self = WP_MIXER_API (plugin);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
  g_return_if_fail (core);

  self->node_infos = g_hash_table_new_full (g_direct_hash, g_direct_equal,
      NULL, node_info_free);

  self->om = wp_object_manager_new ();
  wp_object_manager_add_interest (self->om, WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "#s", "*Audio*",
      NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_DEVICE,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "=s", "Audio/Device",
      NULL);
  wp_object_manager_request_object_features (self->om,
      WP_TYPE_GLOBAL_PROXY, WP_OBJECT_FEATURES_ALL);
  g_signal_connect_object (self->om, "objects-changed",
      G_CALLBACK (on_objects_changed), self, 0);
  g_signal_connect_object (self->om, "object-added",
      G_CALLBACK (on_object_added), self, 0);
  g_signal_connect_object (self->om, "object-removed",
      G_CALLBACK (on_object_removed), self, 0);
  g_signal_connect_object (self->om, "installed",
      G_CALLBACK (on_om_installed), self, 0);
  wp_core_install_object_manager (core, self->om);
}

static void
wp_mixer_api_disable (WpPlugin * plugin)
{
  WpMixerApi * self = WP_MIXER_API (plugin);

  {
    g_autoptr (WpIterator) it = wp_object_manager_new_iterator (self->om);
    g_auto (GValue) val = G_VALUE_INIT;

    for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
      WpProxy *obj = g_value_get_object (&val);
      on_object_removed (self->om, obj, self);
    }
  }

  g_clear_object (&self->om);
  g_clear_pointer (&self->node_infos, g_hash_table_unref);
}

static inline gdouble
volume_from_linear (float vol, gint scale)
{
  if (vol <= 0.0f)
    return 0.0;
  else if (scale == SCALE_CUBIC)
    return cbrt(vol);
  else
    return vol;
}

static inline float
volume_to_linear (gdouble vol, gint scale)
{
  if (vol <= 0.0f)
    return 0.0;
  else if (scale == SCALE_CUBIC)
    return vol * vol * vol;
  else
    return vol;
}

static gboolean
wp_mixer_api_set_volume (WpMixerApi * self, guint32 id, GVariant * vvolume)
{
  struct node_info *info = self->node_infos ?
      g_hash_table_lookup (self->node_infos, GUINT_TO_POINTER (id)) : NULL;
  struct volume new_volume = {0};
  struct volume new_monVolume = {0};
  gboolean has_mute = FALSE;
  gboolean mute = FALSE;
  WpSpaIdTable t_audioChannel =
      wp_spa_id_table_from_name ("Spa:Enum:AudioChannel");

  if (!info || !vvolume)
    return FALSE;

  if (g_variant_is_of_type (vvolume, G_VARIANT_TYPE_DOUBLE)) {
    gdouble val = g_variant_get_double (vvolume);
    new_volume = info->volume;
    for (uint i = 0; i < new_volume.channels; i++)
      new_volume.values[i] = volume_to_linear (val, self->scale);
  }
  else if (g_variant_is_of_type (vvolume, G_VARIANT_TYPE_VARDICT)) {
    GVariantIter *iter;
    const gchar *idx_str;
    GVariant *v;
    gdouble val;

    has_mute = g_variant_lookup (vvolume, "mute", "b", &mute);

    if (g_variant_lookup (vvolume, "volume", "d", &val)) {
      new_volume = info->volume;
      for (uint i = 0; i < new_volume.channels; i++)
        new_volume.values[i] = volume_to_linear (val, self->scale);
    }

    if (g_variant_lookup (vvolume, "monitorVolume", "d", &val)) {
      new_monVolume = info->monitorVolume;
      for (uint i = 0; i < new_monVolume.channels; i++)
        new_monVolume.values[i] = volume_to_linear (val, self->scale);
    }

    if (g_variant_lookup (vvolume, "channelVolumes", "a{sv}", &iter)) {
      /* keep the existing volume values for unspecified channels */
      new_volume = info->volume;
      new_monVolume = info->monitorVolume;

      while (g_variant_iter_loop (iter, "{&sv}", &idx_str, &v)) {
        guint index = atoi (idx_str);
        const gchar *channel_str = NULL;
        WpSpaIdValue channel = NULL;

        if (g_variant_lookup (v, "channel", "&s", &channel_str)) {
          channel = wp_spa_id_table_find_value_from_short_name (
              t_audioChannel, channel_str);
          if (!channel)
            wp_message_object (self, "invalid channel: %s", channel_str);
        }

        if (channel) {
          for (uint i = 0; i < info->map.channels; i++)
            if (info->map.map[i] == wp_spa_id_value_number (channel)) {
              index = i;
              break;
            }
        }

        if (index >= MIN(new_volume.channels, SPA_AUDIO_MAX_CHANNELS)) {
          wp_message_object (self, "invalid channel index: %u", index);
          continue;
        }

        if (g_variant_lookup (v, "volume", "d", &val)) {
          new_volume.values[index] = volume_to_linear (val, self->scale);
        }
        if (g_variant_lookup (v, "monitorVolume", "d", &val)) {
          new_monVolume.values[index] = volume_to_linear (val, self->scale);
        }
      }
      g_variant_iter_free (iter);
    }
  } else {
    return FALSE;
  }

  /* set param */
  g_autoptr (WpSpaPod) props = NULL;
  g_autoptr (WpSpaPodBuilder) b =
      wp_spa_pod_builder_new_object ("Spa:Pod:Object:Param:Props", "Props");

  if (new_volume.channels > 0)
    wp_spa_pod_builder_add (b, "channelVolumes", "a",
        sizeof(float), SPA_TYPE_Float,
        new_volume.channels, new_volume.values, NULL);
  if (new_monVolume.channels > 0)
    wp_spa_pod_builder_add (b, "monitorVolumes", "a",
        sizeof(float), SPA_TYPE_Float,
        new_monVolume.channels, new_monVolume.values, NULL);
  if (has_mute)
    wp_spa_pod_builder_add (b, "mute", "b", mute, NULL);

  props = wp_spa_pod_builder_end (b);

  if (info->device_id != SPA_ID_INVALID) {
    WpPipewireObject *device = wp_object_manager_lookup (self->om,
        WP_TYPE_DEVICE, WP_CONSTRAINT_TYPE_G_PROPERTY,
        "bound-id", "=u", info->device_id, NULL);
    g_return_val_if_fail (device != NULL, FALSE);

    wp_pipewire_object_set_param (device, "Route", 0, wp_spa_pod_new_object (
        "Spa:Pod:Object:Param:Route", "Route",
        "index", "i", info->route_index,
        "device", "i", info->route_device,
        "props", "P", props,
        "save", "b", true,
        NULL));
  } else {
    WpPipewireObject *node = wp_object_manager_lookup (self->om,
        WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY,
        "bound-id", "=u", id, NULL);
    g_return_val_if_fail (node != NULL, FALSE);

    wp_pipewire_object_set_param (node, "Props", 0, g_steal_pointer (&props));
  }

  return TRUE;
}

static GVariant *
wp_mixer_api_get_volume (WpMixerApi * self, guint32 id)
{
  struct node_info *info = self->node_infos ?
      g_hash_table_lookup (self->node_infos, GUINT_TO_POINTER (id)) : NULL;
  g_auto (GVariantBuilder) b =
      G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  g_auto (GVariantBuilder) b_vol =
      G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  WpSpaIdTable t_audioChannel =
      wp_spa_id_table_from_name ("Spa:Enum:AudioChannel");

  if (!info)
    return NULL;

  g_variant_builder_add (&b, "{sv}", "id", g_variant_new_uint32 (id));
  g_variant_builder_add (&b, "{sv}", "mute", g_variant_new_boolean (info->mute));
  g_variant_builder_add (&b, "{sv}", "base", g_variant_new_double (info->base));
  g_variant_builder_add (&b, "{sv}", "step", g_variant_new_double (info->step));
  g_variant_builder_add (&b, "{sv}", "volume", g_variant_new_double (
          volume_from_linear ((info->volume.channels > 0) ?
              info->volume.values[0] : info->svolume, self->scale)));
  if (info->monitorVolume.channels > 0) {
    g_variant_builder_add (&b, "{sv}", "monitorVolume", g_variant_new_double (
          volume_from_linear (info->monitorVolume.values[0], self->scale)));
  }

  for (guint i = 0; i < info->volume.channels; i++) {
    gchar index_str[10];
    g_auto (GVariantBuilder) b_vol_nested =
        G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

    g_variant_builder_add (&b_vol_nested, "{sv}",
        "volume", g_variant_new_double (
            volume_from_linear (info->volume.values[i], self->scale)));

    if (i < info->map.channels) {
      WpSpaIdValue v =
          wp_spa_id_table_find_value (t_audioChannel, info->map.map[i]);
      if (v) {
        const gchar *channel_str = wp_spa_id_value_short_name (v);
        g_variant_builder_add (&b_vol_nested, "{sv}",
          "channel", g_variant_new_string (channel_str));
      }
    }

    if (i < info->monitorVolume.channels) {
      g_variant_builder_add (&b_vol_nested, "{sv}",
          "monitorVolume", g_variant_new_double (
              volume_from_linear (info->monitorVolume.values[i], self->scale)));
    }

    g_snprintf (index_str, 10, "%u", i);
    g_variant_builder_add (&b_vol, "{sv}", index_str,
        g_variant_builder_end (&b_vol_nested));
  }

  g_variant_builder_add (&b, "{sv}",
      "channelVolumes", g_variant_builder_end (&b_vol));
  return g_variant_builder_end (&b);
}

static void
wp_mixer_api_class_init (WpMixerApiClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  object_class->set_property = wp_mixer_api_set_property;
  object_class->get_property = wp_mixer_api_get_property;

  plugin_class->enable = wp_mixer_api_enable;
  plugin_class->disable = wp_mixer_api_disable;

  g_object_class_install_property (object_class, PROP_SCALE,
      g_param_spec_enum ("scale", "scale", "scale",
          wp_mixer_api_volume_scale_enum_get_type (),
          SCALE_LINEAR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  signals[ACTION_SET_VOLUME] = g_signal_new_class_handler (
      "set-volume", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_mixer_api_set_volume,
      NULL, NULL, NULL,
      G_TYPE_BOOLEAN, 2, G_TYPE_UINT, G_TYPE_VARIANT);

  signals[ACTION_GET_VOLUME] = g_signal_new_class_handler (
      "get-volume", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_mixer_api_get_volume,
      NULL, NULL, NULL,
      G_TYPE_VARIANT, 1, G_TYPE_UINT);

  signals[SIGNAL_CHANGED] = g_signal_new (
      "changed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_plugin_register (g_object_new (wp_mixer_api_get_type (),
          "name", "mixer-api",
          "core", core,
          NULL));
  return TRUE;
}
   070701000000C6000081A4000000000000000000000001656CC35F00002378000000000000000000000000000000000000003B00000000wireplumber-0.4.17/modules/module-portal-permissionstore.c    /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>

#define DBUS_INTERFACE_NAME "org.freedesktop.impl.portal.PermissionStore"
#define DBUS_OBJECT_PATH "/org/freedesktop/impl/portal/PermissionStore"

enum
{
  ACTION_GET_DBUS,
  ACTION_LOOKUP,
  ACTION_SET,
  SIGNAL_CHANGED,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

struct _WpPortalPermissionStorePlugin
{
  WpPlugin parent;

  WpDbus *dbus;
  guint signal_id;
};

G_DECLARE_FINAL_TYPE (WpPortalPermissionStorePlugin,
    wp_portal_permissionstore_plugin, WP, PORTAL_PERMISSIONSTORE_PLUGIN,
    WpPlugin)
G_DEFINE_TYPE (WpPortalPermissionStorePlugin, wp_portal_permissionstore_plugin,
    WP_TYPE_PLUGIN)

static gpointer
wp_portal_permissionstore_plugin_get_dbus (WpPortalPermissionStorePlugin *self)
{
  return self->dbus ? g_object_ref (self->dbus) : NULL;
}

static GVariant *
wp_portal_permissionstore_plugin_lookup (WpPortalPermissionStorePlugin *self,
    const gchar *table, const gchar *id)
{
  g_autoptr (GDBusConnection) conn = NULL;
  g_autoptr (GError) error = NULL;
  g_autoptr (GVariant) res = NULL;
  GVariant *permissions = NULL, *data = NULL;

  conn = wp_dbus_get_connection (self->dbus);
  g_return_val_if_fail (conn, NULL);

  /* Lookup */
  res = g_dbus_connection_call_sync (conn, DBUS_INTERFACE_NAME,
      DBUS_OBJECT_PATH, DBUS_INTERFACE_NAME, "Lookup",
      g_variant_new ("(ss)", table, id), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL,
      &error);
  if (error) {
    wp_warning_object (self, "Failed to call Lookup: %s", error->message);
    return NULL;
  }

  /* Get the permissions */
  g_variant_get (res, "(@a{sas}@v)", &permissions, &data);

  return permissions ? g_variant_ref (permissions) : NULL;
}

static void
wp_portal_permissionstore_plugin_set (WpPortalPermissionStorePlugin *self,
    const gchar *table, gboolean create, const gchar *id, GVariant *permissions)
{
  g_autoptr (GDBusConnection) conn = NULL;
  g_autoptr (GError) error = NULL;
  g_autoptr (GVariant) res = NULL;
  GVariant *data = NULL;

  conn = wp_dbus_get_connection (self->dbus);
  g_return_if_fail (conn);

  /* Set */
  res = g_dbus_connection_call_sync (conn, DBUS_INTERFACE_NAME,
      DBUS_OBJECT_PATH, DBUS_INTERFACE_NAME, "Set",
      g_variant_new ("(sbs@a{sas}@v)", table, id, permissions, data), NULL,
      G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
  if (error)
    wp_warning_object (self, "Failed to call Set: %s", error->message);
}

static void
wp_portal_permissionstore_plugin_changed (GDBusConnection *connection,
    const gchar *sender_name, const gchar *object_path,
    const gchar *interface_name, const gchar *signal_name,
    GVariant *parameters, gpointer user_data)
{
  WpPortalPermissionStorePlugin *self =
      WP_PORTAL_PERMISSIONSTORE_PLUGIN (user_data);
  const char *table = NULL, *id = NULL;
  gboolean deleted = FALSE;
  GVariant *permissions = NULL, *data = NULL;

  g_return_if_fail (parameters);
  g_variant_get (parameters, "(ssb@v@a{sas})", &table, &id, &deleted, &data,
      &permissions);

  g_signal_emit (self, signals[SIGNAL_CHANGED], 0, table, id, deleted,
      permissions);
}

static void
clear_signal (WpPortalPermissionStorePlugin *self)
{
  g_autoptr (GDBusConnection) conn = NULL;

  conn = wp_dbus_get_connection (self->dbus);
  if (conn && self->signal_id > 0) {
    g_dbus_connection_signal_unsubscribe (conn, self->signal_id);
    self->signal_id = 0;
  }
}

static void
on_dbus_state_changed (GObject * obj, GParamSpec * spec,
    WpPortalPermissionStorePlugin *self)
{
  WpDBusState state = wp_dbus_get_state (self->dbus);

  switch (state) {
    case WP_DBUS_STATE_CONNECTED: {
      g_autoptr (GDBusConnection) conn = NULL;

      conn = wp_dbus_get_connection (self->dbus);
      g_return_if_fail (conn);

      self->signal_id = g_dbus_connection_signal_subscribe (conn,
          DBUS_INTERFACE_NAME, DBUS_INTERFACE_NAME, "Changed", NULL, NULL,
          G_DBUS_SIGNAL_FLAGS_NONE, wp_portal_permissionstore_plugin_changed,
          self, NULL);
      break;
    }

    case WP_DBUS_STATE_CONNECTING:
    case WP_DBUS_STATE_CLOSED:
      clear_signal (self);
      break;

    default:
      break;
  }
}

static void
wp_portal_permissionstore_plugin_init (WpPortalPermissionStorePlugin * self)
{
}

static void
wp_portal_permissionstore_plugin_constructed (GObject *object)
{
  WpPortalPermissionStorePlugin *self =
      WP_PORTAL_PERMISSIONSTORE_PLUGIN (object);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));

  self->dbus = wp_dbus_get_instance (core, G_BUS_TYPE_SESSION);
  g_signal_connect_object (self->dbus, "notify::state",
      G_CALLBACK (on_dbus_state_changed), self, 0);

  G_OBJECT_CLASS (wp_portal_permissionstore_plugin_parent_class)->constructed (
      object);
}

static void
wp_portal_permissionstore_plugin_finalize (GObject * object)
{
  WpPortalPermissionStorePlugin *self =
      WP_PORTAL_PERMISSIONSTORE_PLUGIN (object);

  g_clear_object (&self->dbus);

  G_OBJECT_CLASS (wp_portal_permissionstore_plugin_parent_class)->finalize (
      object);
}

static void
on_dbus_activated (GObject * obj, GAsyncResult * res, gpointer user_data)
{
  WpTransition * transition = WP_TRANSITION (user_data);
  WpPortalPermissionStorePlugin * self =
      wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (WP_OBJECT (obj), res, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_portal_permissionstore_plugin_enable (WpPlugin * plugin,
    WpTransition * transition)
{
  WpPortalPermissionStorePlugin *self =
      WP_PORTAL_PERMISSIONSTORE_PLUGIN (plugin);

  /* make sure dbus always activated */
  g_return_if_fail (self->dbus);
  wp_object_activate (WP_OBJECT (self->dbus), WP_OBJECT_FEATURES_ALL, NULL,
      (GAsyncReadyCallback) on_dbus_activated, transition);
}

static void
wp_portal_permissionstore_plugin_disable (WpPlugin * plugin)
{
  WpPortalPermissionStorePlugin *self =
      WP_PORTAL_PERMISSIONSTORE_PLUGIN (plugin);

  clear_signal (self);

  wp_object_update_features (WP_OBJECT (self), 0, WP_PLUGIN_FEATURE_ENABLED);
}

static void
wp_portal_permissionstore_plugin_class_init (
    WpPortalPermissionStorePluginClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  object_class->constructed = wp_portal_permissionstore_plugin_constructed;
  object_class->finalize = wp_portal_permissionstore_plugin_finalize;

  plugin_class->enable = wp_portal_permissionstore_plugin_enable;
  plugin_class->disable = wp_portal_permissionstore_plugin_disable;

  /**
   * WpPortalPermissionStorePlugin::get-dbus:
   *
   * Returns: (transfer full): the dbus object
   */
  signals[ACTION_GET_DBUS] = g_signal_new_class_handler (
      "get-dbus", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_portal_permissionstore_plugin_get_dbus,
      NULL, NULL, NULL,
      G_TYPE_OBJECT, 0);

  /**
   * WpPortalPermissionStorePlugin::lookup:
   *
   * @brief
   * @em table: the table name
   * @em id: the Id name
   *
   * Returns: (transfer full): the GVariant with permissions
   */
  signals[ACTION_LOOKUP] = g_signal_new_class_handler (
      "lookup", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_portal_permissionstore_plugin_lookup,
      NULL, NULL, NULL, G_TYPE_VARIANT,
      2, G_TYPE_STRING, G_TYPE_STRING);

  /**
   * WpPortalPermissionStorePlugin::set:
   *
   * @brief
   * @em table: the table name
   * @em create: whether to create the table if it does not exist
   * @em id: the Id name
   * @em permissions: the permissions
   *
   * Sets the permissions in the permission store
   */
  signals[ACTION_SET] = g_signal_new_class_handler (
      "set", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_portal_permissionstore_plugin_set,
      NULL, NULL, NULL, G_TYPE_NONE,
      4, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_VARIANT);

  /**
   * WpPortalPermissionStorePlugin::changed:
   *
   * @brief
   * @em table: the table name
   * @em id: the Id name
   * @em deleted: whether the permission was deleted or not
   * @em permissions: the GVariant with permissions
   *
   * Signaled when the permissions changed
   */
  signals[SIGNAL_CHANGED] = g_signal_new (
      "changed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0,
      NULL, NULL, NULL, G_TYPE_NONE, 4,
      G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_VARIANT);
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_plugin_register (g_object_new (wp_portal_permissionstore_plugin_get_type(),
      "name", "portal-permissionstore",
      "core", core,
      NULL));
  return TRUE;
}
070701000000C7000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000003100000000wireplumber-0.4.17/modules/module-reserve-device  070701000000C8000081A4000000000000000000000001656CC35F00000177000000000000000000000000000000000000003D00000000wireplumber-0.4.17/modules/module-reserve-device/meson.build  reserve_device_interface_src = gnome.gdbus_codegen('reserve-device-interface',
  sources: 'org.freedesktop.ReserveDevice1.xml',
  interface_prefix : 'org.freedesktop.ReserveDevice1.',
  namespace : 'Wp'
)

reserve_device_enums = gnome.mkenums_simple('reserve-device-enums',
  sources: [ 'plugin.h', 'reserve-device.h' ],
)

reserve_device_includes = include_directories('.')
 070701000000C9000081A4000000000000000000000001656CC35F00000193000000000000000000000000000000000000005400000000wireplumber-0.4.17/modules/module-reserve-device/org.freedesktop.ReserveDevice1.xml   <node>
 <interface name="org.freedesktop.ReserveDevice1">
  <method name="RequestRelease">
   <arg name="priority" type="i" direction="in"/>
   <arg name="result" type="b" direction="out"/>
  </method>
  <property name="Priority" type="i" access="read"/>
  <property name="ApplicationName" type="s" access="read"/>
  <property name="ApplicationDeviceName" type="s" access="read"/>
 </interface>
</node>
 070701000000CA000081A4000000000000000000000001656CC35F00001DA0000000000000000000000000000000000000003A00000000wireplumber-0.4.17/modules/module-reserve-device/plugin.c /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "plugin.h"
#include "reserve-device.h"
#include "reserve-device-enums.h"

G_DEFINE_TYPE (WpReserveDevicePlugin, wp_reserve_device_plugin, WP_TYPE_PLUGIN)

enum
{
  ACTION_CREATE_RESERVATION,
  ACTION_DESTROY_RESERVATION,
  ACTION_GET_RESERVATION,
  ACTION_GET_DBUS,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static void
rd_unref (gpointer data)
{
  WpReserveDevice *rd = data;
  g_signal_emit_by_name (rd, "release");
  g_object_unref (rd);
}

static void
clear_reservation (WpReserveDevicePlugin *self)
{
  g_hash_table_remove_all (self->reserve_devices);
  g_clear_object (&self->manager);
}

static void
on_dbus_state_changed (GObject * obj, GParamSpec * spec,
    WpReserveDevicePlugin *self)
{
  WpDBusState state = wp_dbus_get_state (self->dbus);
  switch (state) {
    case WP_DBUS_STATE_CONNECTED: {
      g_autoptr (GDBusConnection) conn = NULL;

      conn = wp_dbus_get_connection (self->dbus);
      g_return_if_fail (conn);

      self->manager = g_dbus_object_manager_server_new (
          FDO_RESERVE_DEVICE1_PATH);
      g_dbus_object_manager_server_set_connection (self->manager, conn);
      break;
    }

    case WP_DBUS_STATE_CONNECTING:
    case WP_DBUS_STATE_CLOSED:
      clear_reservation (self);
      break;

    default:
      break;
  }
}

static void
wp_reserve_device_plugin_init (WpReserveDevicePlugin * self)
{
}

static void
wp_reserve_device_plugin_constructed (GObject *object)
{
  WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_PLUGIN (object);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));

  self->reserve_devices = g_hash_table_new_full (g_str_hash, g_str_equal,
      NULL, rd_unref);

  self->dbus = wp_dbus_get_instance (core, G_BUS_TYPE_SESSION);
  g_signal_connect_object (self->dbus, "notify::state",
       G_CALLBACK (on_dbus_state_changed), self, 0);

  G_OBJECT_CLASS (wp_reserve_device_plugin_parent_class)->constructed (object);
}

static void
wp_reserve_device_plugin_finalize (GObject * object)
{
  WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_PLUGIN (object);

  g_clear_pointer (&self->reserve_devices, g_hash_table_unref);
  g_clear_object (&self->dbus);

  G_OBJECT_CLASS (wp_reserve_device_plugin_parent_class)->finalize (object);
}

static void
on_dbus_activated (GObject * obj, GAsyncResult * res, gpointer user_data)
{
  WpTransition * transition = WP_TRANSITION (user_data);
  WpReserveDevicePlugin * self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (WP_OBJECT (obj), res, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}

static void
wp_reserve_device_plugin_enable (WpPlugin * plugin, WpTransition * transition)
{
  WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_PLUGIN (plugin);

  /* make sure dbus always activated */
  g_return_if_fail (self->dbus);
  wp_object_activate (WP_OBJECT (self->dbus), WP_OBJECT_FEATURES_ALL, NULL,
      (GAsyncReadyCallback) on_dbus_activated, transition);
}

static void
wp_reserve_device_plugin_disable (WpPlugin * plugin)
{
  WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_PLUGIN (plugin);

  clear_reservation (self);

  wp_object_update_features (WP_OBJECT (self), 0, WP_PLUGIN_FEATURE_ENABLED);
}

static gpointer
wp_reserve_device_plugin_create_reservation (WpReserveDevicePlugin *self,
    const gchar *name, const gchar *app_name, const gchar *app_dev_name,
    gint priority)
{
  WpDBusState state = wp_dbus_get_state (self->dbus);
  if (state != WP_DBUS_STATE_CONNECTED) {
    wp_message_object (self, "not connected to D-Bus");
    return NULL;
  }

  WpReserveDevice *rd = g_object_new (wp_reserve_device_get_type (),
      "plugin", self,
      "name", name,
      "application-name", app_name,
      "application-device-name", app_dev_name,
      "priority", priority,
      NULL);

  /* use rd->name to avoid copying @em name again */
  g_hash_table_replace (self->reserve_devices, rd->name, rd);

  return g_object_ref (rd);
}

static void
wp_reserve_device_plugin_destroy_reservation (WpReserveDevicePlugin *self,
    const gchar *name)
{
  WpDBusState state = wp_dbus_get_state (self->dbus);
  if (state != WP_DBUS_STATE_CONNECTED) {
    wp_message_object (self, "not connected to D-Bus");
    return;
  }
  g_hash_table_remove (self->reserve_devices, name);
}

static gpointer
wp_reserve_device_plugin_get_reservation (WpReserveDevicePlugin *self,
    const gchar *name)
{
  WpDBusState state = wp_dbus_get_state (self->dbus);
  if (state != WP_DBUS_STATE_CONNECTED) {
    wp_message_object (self, "not connected to D-Bus");
    return NULL;
  }

  WpReserveDevice *rd = g_hash_table_lookup (self->reserve_devices, name);
  return rd ? g_object_ref (rd) : NULL;
}

static gpointer
wp_reserve_device_plugin_get_dbus (WpReserveDevicePlugin *self)
{
  return self->dbus ? g_object_ref (self->dbus) : NULL;
}

static void
wp_reserve_device_plugin_class_init (WpReserveDevicePluginClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  object_class->constructed = wp_reserve_device_plugin_constructed;
  object_class->finalize = wp_reserve_device_plugin_finalize;

  plugin_class->enable = wp_reserve_device_plugin_enable;
  plugin_class->disable = wp_reserve_device_plugin_disable;

  /**
   * WpReserveDevicePlugin::create-reservation:
   *
   * @brief
   * @em name:
   * @em app_name:
   * @em app_dev_name:
   * @em priority:
   *
   * Returns: (transfer full): the reservation object
   */
  signals[ACTION_CREATE_RESERVATION] = g_signal_new_class_handler (
      "create-reservation", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_reserve_device_plugin_create_reservation,
      NULL, NULL, NULL,
      G_TYPE_OBJECT, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);

  /**
   * WpReserveDevicePlugin::destroy-reservation:
   *
   * @brief
   * @em name:
   *
   */
  signals[ACTION_DESTROY_RESERVATION] = g_signal_new_class_handler (
      "destroy-reservation", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_reserve_device_plugin_destroy_reservation,
      NULL, NULL, NULL,
      G_TYPE_NONE, 1, G_TYPE_STRING);

  /**
   * WpReserveDevicePlugin::get-reservation:
   *
   * @brief
   * @em name:
   *
   * Returns: (transfer full): the reservation object
   */
  signals[ACTION_GET_RESERVATION] = g_signal_new_class_handler (
      "get-reservation", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_reserve_device_plugin_get_reservation,
      NULL, NULL, NULL,
      G_TYPE_OBJECT, 1, G_TYPE_STRING);

  /**
   * WpReserveDevicePlugin::get-dbus:
   *
   * Returns: (transfer full): the dbus object
   */
  signals[ACTION_GET_DBUS] = g_signal_new_class_handler (
      "get-dbus", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_reserve_device_plugin_get_dbus,
      NULL, NULL, NULL,
      G_TYPE_OBJECT, 0);

}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_plugin_register (g_object_new (wp_reserve_device_plugin_get_type (),
      "name", "reserve-device",
      "core", core,
      NULL));
  return TRUE;
}
070701000000CB000081A4000000000000000000000001656CC35F0000035D000000000000000000000000000000000000003A00000000wireplumber-0.4.17/modules/module-reserve-device/plugin.h /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_RESERVE_DEVICE_PLUGIN_H__
#define __WIREPLUMBER_RESERVE_DEVICE_PLUGIN_H__

#include <wp/wp.h>

G_BEGIN_DECLS

#define FDO_RESERVE_DEVICE1_SERVICE "org.freedesktop.ReserveDevice1"
#define FDO_RESERVE_DEVICE1_PATH "/org/freedesktop/ReserveDevice1"

typedef enum {
  WP_DBUS_CONNECTION_STATE_CLOSED = 0,
  WP_DBUS_CONNECTION_STATE_CONNECTING,
  WP_DBUS_CONNECTION_STATE_CONNECTED,
} WpDBusConnectionState;

G_DECLARE_FINAL_TYPE (WpReserveDevicePlugin, wp_reserve_device_plugin,
    WP, RESERVE_DEVICE_PLUGIN, WpPlugin)

struct _WpReserveDevicePlugin
{
  WpPlugin parent;

  WpDbus *dbus;
  GHashTable *reserve_devices;
  GDBusObjectManagerServer *manager;
};

G_END_DECLS

#endif
   070701000000CC000081A4000000000000000000000001656CC35F00004154000000000000000000000000000000000000004200000000wireplumber-0.4.17/modules/module-reserve-device/reserve-device.c /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "reserve-device.h"
#include "plugin.h"
#include "transitions.h"
#include "reserve-device-interface.h"
#include "reserve-device-enums.h"

/*
 * WpReserveDevice:
 */
G_DEFINE_TYPE (WpReserveDevice, wp_reserve_device, G_TYPE_OBJECT)

enum
{
  ACTION_ACQUIRE,
  ACTION_RELEASE,
  ACTION_DENY_RELEASE,
  SIGNAL_RELEASE_REQUESTED,
  LAST_SIGNAL
};

enum
{
  PROP_0,
  PROP_PLUGIN,
  PROP_NAME,
  PROP_APP_NAME,
  PROP_APP_DEV_NAME,
  PROP_PRIORITY,
  PROP_STATE,
  PROP_OWNER_APP_NAME,
};

static guint signals[LAST_SIGNAL] = { 0 };

static void
wp_reserve_device_init (WpReserveDevice * self)
{
  g_weak_ref_init (&self->plugin, NULL);
  g_weak_ref_init (&self->transition, NULL);
}

static void
on_got_proxy (GObject * src, GAsyncResult * res, WpReserveDevice *self)
{
  g_autoptr (GError) error = NULL;
  g_autoptr (WpOrgFreedesktopReserveDevice1) proxy =
      wp_org_freedesktop_reserve_device1_proxy_new_finish (res, &error);
  if (!proxy) {
    wp_info_object (self, "%s: Could not get proxy of remote reservation: %s",
        self->name, error->message);
    return;
  }

  wp_debug_object (self, "%s owned by: %s", self->name,
      wp_org_freedesktop_reserve_device1_get_application_name (proxy));

  /* ensure that we are still busy and there is no owner_app_name */
  if (self->state == WP_RESERVE_DEVICE_STATE_BUSY && !self->owner_app_name) {
    self->owner_app_name =
        wp_org_freedesktop_reserve_device1_dup_application_name (proxy);
    g_object_notify (G_OBJECT (self), "owner-application-name");
  }
}

static void
update_owner_app_name (WpReserveDevice *self)
{
  if (self->state == WP_RESERVE_DEVICE_STATE_BUSY && !self->owner_app_name) {
    /* create proxy */
    g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
    g_autoptr (GDBusConnection) conn = wp_dbus_get_connection (plugin->dbus);
    wp_org_freedesktop_reserve_device1_proxy_new (conn,
        G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
        G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
        self->service_name, self->object_path, NULL,
        (GAsyncReadyCallback) on_got_proxy, self);
  }
  else if (self->state != WP_RESERVE_DEVICE_STATE_BUSY && self->owner_app_name) {
    g_clear_pointer (&self->owner_app_name, g_free);
    g_object_notify (G_OBJECT (self), "owner-application-name");
  }
}

static void
on_name_appeared (GDBusConnection *connection, const gchar *name,
    const gchar *owner, gpointer user_data)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);
  g_autoptr (WpTransition) t = g_weak_ref_get (&self->transition);

  if (!t || wp_transition_get_completed (t)) {
    self->state = WP_RESERVE_DEVICE_STATE_BUSY;
    wp_info_object (self, "%s busy (by %s)", name, owner);
    g_object_notify (G_OBJECT (self), "state");
    update_owner_app_name (self);
  }
}

static void
on_name_vanished (GDBusConnection *connection, const gchar *name,
    gpointer user_data)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);
  g_autoptr (WpTransition) t = g_weak_ref_get (&self->transition);

  if (!t || wp_transition_get_completed (t)) {
    self->state = WP_RESERVE_DEVICE_STATE_AVAILABLE;
    wp_info_object (self, "%s released", name);
    g_object_notify (G_OBJECT (self), "state");
    update_owner_app_name (self);
  }
}

static void
wp_reserve_device_constructed (GObject * object)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (object);
  g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
  g_autoptr (GDBusConnection) conn = wp_dbus_get_connection (plugin->dbus);

  self->service_name =
      g_strdup_printf (FDO_RESERVE_DEVICE1_SERVICE ".%s", self->name);
  self->object_path =
      g_strdup_printf (FDO_RESERVE_DEVICE1_PATH "/%s", self->name);

  /* Watch for the name */
  self->watcher_id = g_bus_watch_name_on_connection (conn,
      self->service_name, G_BUS_NAME_WATCHER_FLAGS_NONE,
      on_name_appeared, on_name_vanished, self, NULL);

  G_OBJECT_CLASS (wp_reserve_device_parent_class)->constructed (object);
}

static void
wp_reserve_device_finalize (GObject * object)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (object);

  if (self->watcher_id > 0)
    g_bus_unwatch_name (self->watcher_id);
  if (self->owner_id > 0)
    g_bus_unown_name (self->owner_id);

  g_weak_ref_clear (&self->plugin);
  g_weak_ref_clear (&self->transition);
  g_clear_pointer (&self->name, g_free);
  g_clear_pointer (&self->app_name, g_free);
  g_clear_pointer (&self->app_dev_name, g_free);
  g_clear_pointer (&self->service_name, g_free);
  g_clear_pointer (&self->object_path, g_free);

  G_OBJECT_CLASS (wp_reserve_device_parent_class)->finalize (object);
}

static void
on_acquire_transition_done (GObject *rd, GAsyncResult *res, gpointer data)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (data);
  g_autoptr (GError) error = NULL;

  gboolean acquired = wp_reserve_device_acquire_transition_finish (res, &error);
  if (error) {
    wp_message_object (self, "%s: Acquire error: %s", self->name,
        error->message);
  }

  self->state = acquired ?
      WP_RESERVE_DEVICE_STATE_ACQUIRED : WP_RESERVE_DEVICE_STATE_BUSY;
  g_object_notify (G_OBJECT (self), "state");
  update_owner_app_name (self);
}

static void
wp_reserve_device_acquire (WpReserveDevice * self)
{
  g_autoptr (WpTransition) t = g_weak_ref_get (&self->transition);
  if (self->state == WP_RESERVE_DEVICE_STATE_ACQUIRED ||
    (t && !wp_transition_get_completed (t))) {
    wp_debug_object (self, "%s: already acquired or operation in progress",
        self->name);
    return;
  }

  g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
  WpTransition *transition = wp_reserve_device_acquire_transition_new (self,
      NULL, on_acquire_transition_done, self);
  g_weak_ref_set (&self->transition, transition);
  wp_transition_advance (transition);
}

static void
wp_reserve_device_release (WpReserveDevice * self)
{
  if (self->state != WP_RESERVE_DEVICE_STATE_ACQUIRED) {
    wp_debug_object (self, "%s: not acquired", self->name);
    return;
  }

  /* set state to AVAILABLE to ensure that on_name_lost()
     does not emit SIGNAL_REQUEST_RELEASE */
  /* on_name_vanished() will emit the state change */
  self->state = WP_RESERVE_DEVICE_STATE_AVAILABLE;
  wp_reserve_device_unown_name (self);

  if (self->req_rel_invocation) {
    wp_org_freedesktop_reserve_device1_complete_request_release (NULL,
        self->req_rel_invocation, TRUE);
    self->req_rel_invocation = NULL;
  }
}

static void
wp_reserve_device_deny_release (WpReserveDevice * self, gboolean success)
{
  if (self->req_rel_invocation) {
    wp_org_freedesktop_reserve_device1_complete_request_release (NULL,
        self->req_rel_invocation, FALSE);
    self->req_rel_invocation = NULL;
  }
}

static gboolean
wp_reserve_device_handle_request_release (WpOrgFreedesktopReserveDevice1 *iface,
    GDBusMethodInvocation *invocation, gint priority, gpointer user_data)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);

  /* deny release if the priority is lower than ours */
  if (priority < self->priority) {
    wp_org_freedesktop_reserve_device1_complete_request_release (iface,
        g_object_ref (invocation), FALSE);
    return TRUE;
  }

  /* else, request the release of the device from the implementation;
     if signal handlers are connected, assume the functionality is implemented,
     otherwise return FALSE to let the iface return UnknownMethod */
  if (g_signal_has_handler_pending (self,
          signals[SIGNAL_RELEASE_REQUESTED], 0, FALSE)) {
    self->req_rel_invocation = g_object_ref (invocation);
    g_signal_emit (self, signals[SIGNAL_RELEASE_REQUESTED], 0, FALSE);
    return TRUE;
  }
  return FALSE;
}

static void
wp_reserve_device_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (object);

  switch (property_id) {
  case PROP_NAME:
    g_value_set_string (value, self->name);
    break;
  case PROP_APP_NAME:
    g_value_set_string (value, self->app_name);
    break;
  case PROP_APP_DEV_NAME:
    g_value_set_string (value, self->app_dev_name);
    break;
  case PROP_PRIORITY:
    g_value_set_int (value, self->priority);
    break;
  case PROP_STATE:
    g_value_set_enum (value, self->state);
    break;
  case PROP_OWNER_APP_NAME:
    switch (self->state) {
    case WP_RESERVE_DEVICE_STATE_ACQUIRED:
      g_value_set_string (value, self->app_name);
      break;
    default:
      g_value_set_string (value, self->owner_app_name);
      break;
    }
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_reserve_device_set_property (GObject * object,
    guint property_id, const GValue * value, GParamSpec * pspec)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (object);

  switch (property_id) {
  case PROP_PLUGIN:
    g_weak_ref_set (&self->plugin, g_value_get_object (value));
    break;
  case PROP_NAME:
    g_clear_pointer (&self->name, g_free);
    self->name = g_value_dup_string (value);
    break;
  case PROP_APP_NAME:
    g_clear_pointer (&self->app_name, g_free);
    self->app_name = g_value_dup_string (value);
    break;
  case PROP_APP_DEV_NAME:
    g_clear_pointer (&self->app_dev_name, g_free);
    self->app_dev_name = g_value_dup_string (value);
    break;
  case PROP_PRIORITY:
    self->priority = g_value_get_int(value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

/**
* wp_reserve_device_class_init
*
* @param klass: the reserve device class
*/
static void
wp_reserve_device_class_init (WpReserveDeviceClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;

  object_class->constructed = wp_reserve_device_constructed;
  object_class->finalize = wp_reserve_device_finalize;
  object_class->get_property = wp_reserve_device_get_property;
  object_class->set_property = wp_reserve_device_set_property;

  g_object_class_install_property (object_class, PROP_PLUGIN,
      g_param_spec_object ("plugin", "plugin",
          "The parent plugin instance", wp_reserve_device_plugin_get_type (),
          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_NAME,
      g_param_spec_string ("name", "name",
          "The reservation name", NULL,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_APP_NAME,
      g_param_spec_string ("application-name", "application-name",
          "The application name", NULL,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_APP_DEV_NAME,
      g_param_spec_string ("application-device-name", "application-device-name",
          "The application device name", NULL,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PRIORITY,
      g_param_spec_int ("priority", "priority",
          "The priority", G_MININT, G_MAXINT, 0,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_STATE,
      g_param_spec_enum ("state", "state", "The state",
          WP_TYPE_RESERVE_DEVICE_STATE, WP_RESERVE_DEVICE_STATE_UNKNOWN,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_OWNER_APP_NAME,
      g_param_spec_string ("owner-application-name", "owner-application-name",
          "The owner application name", NULL,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  /**
   * WpReserveDevice acquire:
   *
   * @section signal_acquire_section acquire
   */
  signals[ACTION_ACQUIRE] = g_signal_new_class_handler (
      "acquire", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_reserve_device_acquire,
      NULL, NULL, NULL, G_TYPE_NONE, 0);

  /**
   * WpReserveDevice release:
   *
   * @section signal_release_section release
   */
  signals[ACTION_RELEASE] = g_signal_new_class_handler (
      "release", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_reserve_device_release,
      NULL, NULL, NULL, G_TYPE_NONE, 0);

  /**
   * WpReserveDevice deny-release:
   *
   * @section signal_deny_release_section deny-release
   */
  signals[ACTION_DENY_RELEASE] = g_signal_new_class_handler (
      "deny-release", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      (GCallback) wp_reserve_device_deny_release,
      NULL, NULL, NULL, G_TYPE_NONE, 0);

  /**
   * WpReserveDevice release-requested:
   *
   * @section signal_release_requested_section release-requested
   *
   * @em forced: %TRUE if the name was forcibly taken from us,
   *    %FALSE if the `RequestRelease()` d-bus method was called
   *
   * @brief Signaled when the device needs to be released. If @em forced is %FALSE,
   * call [release](@ref signal_release_section) to release or
   * [deny-release](@ref signal_deny_release_section)
   * to refuse and return %FALSE from the `RequestRelease()` d-bus method.
   */
  signals[SIGNAL_RELEASE_REQUESTED] = g_signal_new (
      "release-requested", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
      G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
}

void
wp_reserve_device_export_object (WpReserveDevice *self)
{
  g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
  if (plugin) {
    g_autoptr (GDBusObjectSkeleton) skeleton =
        g_dbus_object_skeleton_new (self->object_path);
    g_autoptr (WpOrgFreedesktopReserveDevice1) iface =
        wp_org_freedesktop_reserve_device1_skeleton_new ();
    g_object_set (iface,
        "priority", self->priority,
        "application-name", self->app_name,
        "application-device-name", self->app_dev_name,
        NULL);
    g_signal_connect_object (iface, "handle-request-release",
        (GCallback) wp_reserve_device_handle_request_release, self, 0);
    g_dbus_object_skeleton_add_interface (skeleton,
        G_DBUS_INTERFACE_SKELETON (iface));
    g_dbus_object_manager_server_export (plugin->manager, skeleton);
  }
}

void
wp_reserve_device_unexport_object (WpReserveDevice *self)
{
  g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
  if (plugin) {
    wp_debug_object (self, "unexport %s", self->object_path);
    g_dbus_object_manager_server_unexport (plugin->manager, self->object_path);
  }
}

static void
on_name_acquired (GDBusConnection *connection, const gchar *name,
    gpointer user_data)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);
  g_autoptr (WpTransition) t = g_weak_ref_get (&self->transition);

  wp_debug_object (self, "%s acquired", name);

  if (t)
    wp_reserve_device_acquire_transition_name_acquired (t);
}

static void
on_name_lost (GDBusConnection *connection, const gchar *name,
    gpointer user_data)
{
  WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);
  g_autoptr (WpTransition) t = g_weak_ref_get (&self->transition);

  wp_debug_object (self, "%s lost", name);

  if (t) {
    wp_reserve_device_acquire_transition_name_lost (t);
    return;
  }

  if (self->state == WP_RESERVE_DEVICE_STATE_ACQUIRED) {
    /* Emit release signal with forced set to TRUE */
    g_signal_emit (self, signals[SIGNAL_RELEASE_REQUESTED], 0, TRUE);
    wp_reserve_device_unown_name (self);
  }

  wp_reserve_device_unexport_object (self);
}

void
wp_reserve_device_own_name (WpReserveDevice * self, gboolean force)
{
  g_return_if_fail (self->owner_id == 0);

  g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
  if (plugin) {
    g_autoptr (GDBusConnection) conn = wp_dbus_get_connection (plugin->dbus);
    GBusNameOwnerFlags flags = G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE;
    if (self->priority != G_MAXINT32)
      flags |= G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
    if (force)
      flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;

    wp_debug_object (self, "request ownership of %s", self->service_name);

    self->owner_id = g_bus_own_name_on_connection (conn,
        self->service_name, flags, on_name_acquired, on_name_lost, self, NULL);
  }
}

void
wp_reserve_device_unown_name (WpReserveDevice * self)
{
  if (self->owner_id) {
    wp_debug_object (self, "drop ownership of %s", self->service_name);
    g_bus_unown_name (self->owner_id);
    self->owner_id = 0;
  }
}
070701000000CD000081A4000000000000000000000001656CC35F000004A4000000000000000000000000000000000000004200000000wireplumber-0.4.17/modules/module-reserve-device/reserve-device.h /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_RESERVE_DEVICE_H__
#define __WIREPLUMBER_RESERVE_DEVICE_H__

#include <wp/wp.h>

G_BEGIN_DECLS

typedef enum {
  WP_RESERVE_DEVICE_STATE_UNKNOWN = 0,
  WP_RESERVE_DEVICE_STATE_BUSY,
  WP_RESERVE_DEVICE_STATE_AVAILABLE,
  WP_RESERVE_DEVICE_STATE_ACQUIRED,
} WpReserveDeviceState;

G_DECLARE_FINAL_TYPE (WpReserveDevice, wp_reserve_device,
    WP, RESERVE_DEVICE, GObject)

struct _WpReserveDevice
{
  GObject parent;

  GWeakRef plugin;
  gchar *name;
  gchar *app_name;
  gchar *app_dev_name;
  gint priority;
  gchar *owner_app_name;

  gchar *service_name;
  gchar *object_path;

  GWeakRef transition;
  GDBusMethodInvocation *req_rel_invocation;
  WpReserveDeviceState state;
  guint watcher_id;
  guint owner_id;
};

void wp_reserve_device_export_object (WpReserveDevice *self);
void wp_reserve_device_unexport_object (WpReserveDevice *self);

void wp_reserve_device_own_name (WpReserveDevice * self, gboolean force);
void wp_reserve_device_unown_name (WpReserveDevice * self);

G_END_DECLS

#endif
070701000000CE000081A4000000000000000000000001656CC35F00001C55000000000000000000000000000000000000003F00000000wireplumber-0.4.17/modules/module-reserve-device/transitions.c    /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "transitions.h"
#include "plugin.h"
#include "reserve-device.h"
#include "reserve-device-interface.h"

struct _WpReserveDeviceAcquireTransition
{
  WpTransition parent;
  gint owner_state;
  WpOrgFreedesktopReserveDevice1 *proxy;
};

enum {
  OWNER_STATE_NONE = 0,
  OWNER_STATE_ACQUIRED,
  OWNER_STATE_LOST,
};

enum {
  STEP_EXPORT_OBJECT = WP_TRANSITION_STEP_CUSTOM_START,
  STEP_ACQUIRE_NO_FORCE,
  STEP_GET_PROXY,
  STEP_REQUEST_RELEASE,
  STEP_ACQUIRE_WITH_FORCE,
  STEP_UNEXPORT_OBJECT,
};

G_DEFINE_TYPE (WpReserveDeviceAcquireTransition,
    wp_reserve_device_acquire_transition, WP_TYPE_TRANSITION)

static void
wp_reserve_device_acquire_transition_init (
    WpReserveDeviceAcquireTransition * self)
{
}

static void
wp_reserve_device_acquire_transition_finalize (GObject * object)
{
  WpReserveDeviceAcquireTransition *self =
      WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (object);

  g_clear_object (&self->proxy);

  G_OBJECT_CLASS (wp_reserve_device_acquire_transition_parent_class)->
      finalize (object);
}

static guint
wp_reserve_device_acquire_transition_get_next_step (
    WpTransition * transition, guint step)
{
  WpReserveDeviceAcquireTransition *self =
        WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (transition);

  switch (step) {
  case WP_TRANSITION_STEP_NONE:
    return STEP_EXPORT_OBJECT;

  case STEP_EXPORT_OBJECT:
    return STEP_ACQUIRE_NO_FORCE;

  case STEP_ACQUIRE_NO_FORCE:
    switch (self->owner_state) {
      case OWNER_STATE_ACQUIRED:
        return WP_TRANSITION_STEP_NONE;

      case OWNER_STATE_LOST:
        return STEP_GET_PROXY;

      default:
        return WP_TRANSITION_STEP_ERROR;
    }

  case STEP_GET_PROXY:
    if (self->proxy)
      return STEP_REQUEST_RELEASE;
    else
      return STEP_ACQUIRE_WITH_FORCE;

  case STEP_REQUEST_RELEASE:
    switch (self->owner_state) {
      case OWNER_STATE_ACQUIRED:
        return STEP_ACQUIRE_WITH_FORCE;

      case OWNER_STATE_LOST:
        return STEP_UNEXPORT_OBJECT;

      default:
        return WP_TRANSITION_STEP_ERROR;
    }

  case STEP_ACQUIRE_WITH_FORCE:
    return WP_TRANSITION_STEP_NONE;

  case STEP_UNEXPORT_OBJECT:
    return WP_TRANSITION_STEP_NONE;

  default:
    return WP_TRANSITION_STEP_ERROR;
  }
}

static void
on_got_proxy (GObject * src, GAsyncResult * res, WpTransition * transition)
{
  WpReserveDeviceAcquireTransition *self =
      WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (transition);
  g_autoptr (GError) error = NULL;

  self->proxy =
      wp_org_freedesktop_reserve_device1_proxy_new_finish (res, &error);
  if (!self->proxy) {
    WpReserveDevice *rd = wp_transition_get_source_object (transition);
    wp_info_object (rd, "%s: Could not get proxy of remote reservation: %s",
        rd->name, error->message);
  }

  wp_transition_advance (transition);
}

static void
on_request_release_done (GObject * src, GAsyncResult * res,
    WpTransition * transition)
{
  WpReserveDeviceAcquireTransition *self =
      WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (transition);
  g_autoptr (GError) error = NULL;
  gboolean released = FALSE;

  if (!wp_org_freedesktop_reserve_device1_call_request_release_finish (
          self->proxy, &released, res, &error)) {
    WpReserveDevice *rd = wp_transition_get_source_object (transition);
    wp_info_object (rd, "%s: Could not call RequestRelease: %s",
        rd->name, error->message);
  }

  self->owner_state = released ? OWNER_STATE_ACQUIRED : OWNER_STATE_LOST;
  wp_transition_advance (transition);
}

static void
wp_reserve_device_acquire_transition_execute_step (
    WpTransition * transition, guint step)
{
  WpReserveDeviceAcquireTransition *self =
      WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (transition);
  WpReserveDevice *rd = wp_transition_get_source_object (transition);
  g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&rd->plugin);

  if (G_UNLIKELY (!plugin && step != WP_TRANSITION_STEP_ERROR)) {
    wp_transition_return_error (transition, g_error_new (
            WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
            "plugin destroyed while Acquire was in progress"));
    return;
  }

  switch (step) {
  case STEP_EXPORT_OBJECT:
    wp_reserve_device_export_object (rd);
    wp_transition_advance (transition);
    break;

  case STEP_ACQUIRE_NO_FORCE:
    wp_reserve_device_own_name (rd, FALSE);
    break;

  case STEP_GET_PROXY: {
    g_autoptr (GDBusConnection) conn = wp_dbus_get_connection (plugin->dbus);
    wp_org_freedesktop_reserve_device1_proxy_new (conn,
        G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
        G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
        G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
        rd->service_name, rd->object_path, NULL,
        (GAsyncReadyCallback) on_got_proxy, self);
    break;
  }

  case STEP_REQUEST_RELEASE:
    self->owner_state = OWNER_STATE_NONE;
    wp_org_freedesktop_reserve_device1_call_request_release (
        self->proxy, rd->priority, NULL,
        (GAsyncReadyCallback) on_request_release_done, self);
    break;

  case STEP_ACQUIRE_WITH_FORCE:
    wp_reserve_device_unown_name (rd);
    self->owner_state = OWNER_STATE_NONE;
    wp_reserve_device_own_name (rd, TRUE);
    break;

  case STEP_UNEXPORT_OBJECT:
    wp_reserve_device_unown_name (rd);
    wp_reserve_device_unexport_object (rd);
    wp_transition_advance (transition);
    break;

  case WP_TRANSITION_STEP_ERROR:
    wp_reserve_device_unown_name (rd);
    break;

  default:
    g_return_if_reached ();
  }
}

static void
wp_reserve_device_acquire_transition_class_init (
    WpReserveDeviceAcquireTransitionClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpTransitionClass *transition_class = (WpTransitionClass *) klass;

  object_class->finalize =
      wp_reserve_device_acquire_transition_finalize;

  transition_class->get_next_step =
      wp_reserve_device_acquire_transition_get_next_step;
  transition_class->execute_step =
      wp_reserve_device_acquire_transition_execute_step;
}

WpTransition *
wp_reserve_device_acquire_transition_new (WpReserveDevice *rd,
    GCancellable * cancellable, GAsyncReadyCallback callback,
    gpointer callback_data)
{
  return wp_transition_new (wp_reserve_device_acquire_transition_get_type (),
      rd, cancellable, callback, callback_data);
}

void
wp_reserve_device_acquire_transition_name_acquired (WpTransition * tr)
{
  WpReserveDeviceAcquireTransition *self =
      WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (tr);
  self->owner_state = OWNER_STATE_ACQUIRED;
  wp_transition_advance (tr);
}

void
wp_reserve_device_acquire_transition_name_lost (WpTransition * tr)
{
  WpReserveDeviceAcquireTransition *self =
      WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (tr);
  self->owner_state = OWNER_STATE_LOST;
  wp_transition_advance (tr);
}

gboolean
wp_reserve_device_acquire_transition_finish (GAsyncResult * res,
    GError ** error)
{
  if (!wp_transition_finish (res, error))
    return FALSE;

  WpReserveDeviceAcquireTransition *self =
      WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (res);
  return (self->owner_state == OWNER_STATE_ACQUIRED) ? TRUE : FALSE;
}
   070701000000CF000081A4000000000000000000000001656CC35F0000038B000000000000000000000000000000000000003F00000000wireplumber-0.4.17/modules/module-reserve-device/transitions.h    /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef __WIREPLUMBER_RESERVE_DEVICE_TRANSITIONS_H__
#define __WIREPLUMBER_RESERVE_DEVICE_TRANSITIONS_H__

#include "reserve-device.h"

G_BEGIN_DECLS

G_DECLARE_FINAL_TYPE (WpReserveDeviceAcquireTransition,
    wp_reserve_device_acquire_transition,
    WP, RESERVE_DEVICE_ACQUIRE_TRANSITION, WpTransition)

WpTransition * wp_reserve_device_acquire_transition_new (WpReserveDevice *rd,
    GCancellable * cancellable, GAsyncReadyCallback callback,
    gpointer callback_data);

void wp_reserve_device_acquire_transition_name_acquired (WpTransition * tr);
void wp_reserve_device_acquire_transition_name_lost (WpTransition * tr);

gboolean wp_reserve_device_acquire_transition_finish (GAsyncResult * res,
    GError ** error);

G_END_DECLS

#endif
 070701000000D0000081A4000000000000000000000001656CC35F0000630A000000000000000000000000000000000000003500000000wireplumber-0.4.17/modules/module-si-audio-adapter.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <pipewire/keys.h>
#include <pipewire/properties.h>

#include <spa/param/format.h>
#include <spa/param/audio/raw.h>
#include <spa/param/audio/format-utils.h>
#include <spa/param/param.h>

#define SI_FACTORY_NAME "si-audio-adapter"

struct _WpSiAudioAdapter
{
  WpSessionItem parent;

  /* configuration */
  WpNode *node;
  WpPort *port;  /* only used for passthrough or convert mode */
  gboolean no_format;
  gboolean control_port;
  gboolean monitor;
  gboolean disable_dsp;
  WpDirection portconfig_direction;
  gboolean is_device;
  gboolean dont_remix;
  gboolean is_autoconnect;
  gboolean have_encoded;
  gboolean encoded_only;
  gboolean is_unpositioned;
  struct spa_audio_info_raw raw_format;

  gulong ports_changed_sigid;

  WpSpaPod *format;
  gchar mode[32];
  GTask *format_task;
  WpSiAdapterPortsState ports_state;
};

static void si_audio_adapter_linkable_init (WpSiLinkableInterface * iface);
static void si_audio_adapter_adapter_init (WpSiAdapterInterface * iface);

G_DECLARE_FINAL_TYPE(WpSiAudioAdapter, si_audio_adapter, WP, SI_AUDIO_ADAPTER,
    WpSessionItem)
G_DEFINE_TYPE_WITH_CODE (WpSiAudioAdapter, si_audio_adapter,
    WP_TYPE_SESSION_ITEM,
    G_IMPLEMENT_INTERFACE (WP_TYPE_SI_LINKABLE, si_audio_adapter_linkable_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ADAPTER, si_audio_adapter_adapter_init))

static void
si_audio_adapter_init (WpSiAudioAdapter * self)
{
}

static void
si_audio_adapter_set_ports_state (WpSiAudioAdapter *self, WpSiAdapterPortsState
    new_state)
{
  if (self->ports_state != new_state) {
    WpSiAdapterPortsState old_state = self->ports_state;
    self->ports_state = new_state;
    g_signal_emit_by_name (self, "adapter-ports-state-changed", old_state,
        new_state);
  }
}

static void
si_audio_adapter_reset (WpSessionItem * item)
{
  WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);

  /* deactivate first */
  wp_object_deactivate (WP_OBJECT (self), WP_SESSION_ITEM_FEATURE_ACTIVE);

  /* reset */
  g_clear_object (&self->node);
  g_clear_object (&self->port);
  self->no_format = FALSE;
  self->control_port = FALSE;
  self->monitor = FALSE;
  self->disable_dsp = FALSE;
  self->portconfig_direction = WP_DIRECTION_INPUT;
  self->is_device = FALSE;
  self->dont_remix = FALSE;
  self->is_autoconnect = FALSE;
  self->have_encoded = FALSE;
  self->encoded_only = FALSE;
  spa_memzero (&self->raw_format, sizeof(struct spa_audio_info_raw));
  if (self->format_task) {
    g_task_return_new_error (self->format_task, WP_DOMAIN_LIBRARY,
        WP_LIBRARY_ERROR_OPERATION_FAILED,
        "item deactivated before format set");
    g_clear_object (&self->format_task);
  }
  g_clear_pointer (&self->format, wp_spa_pod_unref);
  self->mode[0] = '\0';
  si_audio_adapter_set_ports_state (self, WP_SI_ADAPTER_PORTS_STATE_NONE);

  WP_SESSION_ITEM_CLASS (si_audio_adapter_parent_class)->reset (item);
}

static guint
si_audio_adapter_get_default_clock_rate (WpSiAudioAdapter * self)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
  g_autoptr (WpProperties) props = NULL;
  const gchar *rate_str = NULL;
  g_return_val_if_fail (core, 48000);
  props = wp_core_get_remote_properties (core);
  g_return_val_if_fail (props, 48000);
  rate_str = wp_properties_get (props, "default.clock.rate");
  return rate_str ? atoi (rate_str) : 48000;
}

static gboolean
is_unpositioned (struct spa_audio_info_raw *info)
{
  uint32_t i;
  if (SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED))
    return TRUE;
  for (i = 0; i < info->channels; i++)
    if (info->position[i] >= SPA_AUDIO_CHANNEL_START_Aux &&
        info->position[i] <= SPA_AUDIO_CHANNEL_LAST_Aux)
      return TRUE;
  return FALSE;
}

static gboolean
si_audio_adapter_find_format (WpSiAudioAdapter * self, WpNode * node)
{
  g_autoptr (WpIterator) formats = NULL;
  g_auto (GValue) value = G_VALUE_INIT;
  gboolean have_format = FALSE;

  formats = wp_pipewire_object_enum_params_sync (WP_PIPEWIRE_OBJECT (node),
      "EnumFormat", NULL);
  if (!formats)
    return FALSE;

  for (; wp_iterator_next (formats, &value); g_value_unset (&value)) {
    WpSpaPod *pod = g_value_get_boxed (&value);
    uint32_t mtype, msubtype;

    if (!wp_spa_pod_is_object (pod)) {
      wp_warning_object (self,
          "non-object POD appeared on formats list; this node is buggy");
      continue;
    }

    if (!wp_spa_pod_get_object (pod, NULL,
        "mediaType", "I", &mtype,
        "mediaSubtype", "I", &msubtype,
        NULL)) {
      wp_warning_object (self, "format does not have media type / subtype");
      continue;
    }

    if (mtype != SPA_MEDIA_TYPE_audio)
      continue;

    switch (msubtype) {
    case SPA_MEDIA_SUBTYPE_raw: {
      struct spa_audio_info_raw raw_format;
      struct spa_pod *position = NULL;
      wp_spa_pod_fixate (pod);

      spa_zero(raw_format);
      if (spa_pod_parse_object(wp_spa_pod_get_spa_pod (pod),
                               SPA_TYPE_OBJECT_Format, NULL,
                               SPA_FORMAT_AUDIO_format,   SPA_POD_OPT_Id(&raw_format.format),
                               SPA_FORMAT_AUDIO_rate,     SPA_POD_OPT_Int(&raw_format.rate),
                               SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&raw_format.channels),
                               SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)) < 0)
        continue;

      if (position == NULL ||
          !spa_pod_copy_array(position, SPA_TYPE_Id, raw_format.position, SPA_AUDIO_MAX_CHANNELS))
        SPA_FLAG_SET(raw_format.flags, SPA_AUDIO_FLAG_UNPOSITIONED);

      if (self->raw_format.channels < raw_format.channels) {
        self->raw_format = raw_format;
        if (is_unpositioned(&raw_format))
          self->is_unpositioned = TRUE;
      }
      have_format = TRUE;
      break;
    }
    case SPA_MEDIA_SUBTYPE_iec958:
    case SPA_MEDIA_SUBTYPE_dsd:
      wp_info_object (self, "passthrough IEC958/DSD node %d found",
          wp_proxy_get_bound_id (WP_PROXY (node)));
      self->have_encoded = TRUE;
      break;
    default: {
      enum spa_audio_format audio_format;
      if (spa_pod_parse_object(wp_spa_pod_get_spa_pod (pod),
                               SPA_TYPE_OBJECT_Format, NULL,
                               SPA_FORMAT_AUDIO_format,   SPA_POD_OPT_Id(&audio_format)) >= 0) {
        self->have_encoded = (audio_format == SPA_AUDIO_FORMAT_ENCODED);
      }
      break;
    }
    }
  }
  if (!have_format && self->have_encoded) {
    wp_info_object (self, ".. passthrough IEC958/DSD/encoded only");
    self->encoded_only = TRUE;
    have_format = TRUE;
  }

  return have_format;
}

static void
on_proxy_destroyed (WpNode * proxy, WpSiAudioAdapter * self)
{
  if (self->node == proxy) {
    wp_object_abort_activation (WP_OBJECT (self), "proxy destroyed");
    si_audio_adapter_reset (WP_SESSION_ITEM (self));
  }
}

static gboolean
si_audio_adapter_configure (WpSessionItem * item, WpProperties *p)
{
  WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
  g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
  WpNode *node = NULL;
  const gchar *str;

  /* reset previous config */
  si_audio_adapter_reset (item);

  str = wp_properties_get (si_props, "item.node");
  if (!str || sscanf(str, "%p", &node) != 1 || !WP_IS_NODE (node))
    return FALSE;

  str = wp_properties_get (si_props, PW_KEY_MEDIA_CLASS);
  if (!str)
    return FALSE;
  if ((strstr (str, "Source") || strstr (str, "Output"))
        && !strstr (str, "Virtual")) {
    self->portconfig_direction = WP_DIRECTION_OUTPUT;
  }

  str = wp_properties_get (si_props, "item.features.no-format");
  self->no_format = str && pw_properties_parse_bool (str);
  if (!self->no_format && !si_audio_adapter_find_format (self, node)) {
    wp_message_object (item, "no usable format found for node %d",
        wp_proxy_get_bound_id (WP_PROXY (node)));
    return FALSE;
  }

  str = wp_properties_get (si_props, "item.features.control-port");
  self->control_port = str && pw_properties_parse_bool (str);

  str = wp_properties_get (si_props, "item.features.monitor");
  self->monitor = str && pw_properties_parse_bool (str);

  str = wp_properties_get (si_props, "item.features.no-dsp");
  self->disable_dsp = str && pw_properties_parse_bool (str);

  str = wp_properties_get (si_props, "item.node.type");
  self->is_device = !g_strcmp0 (str, "device");

  str = wp_properties_get (si_props, PW_KEY_STREAM_DONT_REMIX);
  self->dont_remix = str && pw_properties_parse_bool (str);

  str = wp_properties_get (si_props, PW_KEY_NODE_AUTOCONNECT);
  self->is_autoconnect = str && pw_properties_parse_bool (str);

  self->node = g_object_ref (node);
  g_signal_connect_object (self->node, "pw-proxy-destroyed",
      G_CALLBACK (on_proxy_destroyed), self, 0);

  wp_properties_set (si_props, "item.node.supports-encoded-fmts",
      self->have_encoded ? "true" : "false");

  wp_properties_set (si_props, "item.node.encoded-only",
      self->encoded_only ? "true" : "false");

  wp_properties_set (si_props, "item.node.unpositioned",
      self->is_unpositioned ? "true" : "false");

  wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
  wp_session_item_set_properties (item, g_steal_pointer (&si_props));
  return TRUE;
}

static gpointer
si_audio_adapter_get_associated_proxy (WpSessionItem * item, GType proxy_type)
{
  WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);

  if (proxy_type == WP_TYPE_NODE)
    return self->node ? g_object_ref (self->node) : NULL;

  return NULL;
}

static WpSpaPod *
format_audio_raw_build (const struct spa_audio_info_raw *info)
{
  g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_object (
      "Spa:Pod:Object:Param:Format", "Format");
  wp_spa_pod_builder_add (builder,
      "mediaType",    "K", "audio",
      "mediaSubtype", "K", "raw",
      "format",       "I", info->format,
      "rate",         "i", info->rate,
      "channels",     "i", info->channels,
      NULL);

   if (!SPA_FLAG_IS_SET (info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) {
     /* Build the position array spa pod */
     g_autoptr (WpSpaPodBuilder) position_builder = wp_spa_pod_builder_new_array ();
     for (guint i = 0; i < info->channels; i++)
       wp_spa_pod_builder_add_id (position_builder, info->position[i]);

     /* Add the position property */
     wp_spa_pod_builder_add_property (builder, "position");
     g_autoptr (WpSpaPod) position = wp_spa_pod_builder_end (position_builder);
     wp_spa_pod_builder_add_pod (builder, position);
   }

   return wp_spa_pod_builder_end (builder);
}

static gboolean
parse_adapter_format (WpSpaPod *format, gint *channels,
   WpSpaPod **position)
{
  g_autoptr (WpSpaPodParser) parser = NULL;
  guint32 t = 0, s = 0, f = 0;
  gint r = 0, c = 0;
  g_autoptr (WpSpaPod) p = NULL;

  g_return_val_if_fail (format, FALSE);
  parser = wp_spa_pod_parser_new_object (format, NULL);
  g_return_val_if_fail (parser, FALSE);

  if (!wp_spa_pod_parser_get (parser, "mediaType", "I", &t, NULL) ||
      !wp_spa_pod_parser_get (parser, "mediaSubtype", "I", &s, NULL) ||
      !wp_spa_pod_parser_get (parser, "format", "I", &f, NULL) ||
      !wp_spa_pod_parser_get (parser, "rate", "i", &r, NULL) ||
      !wp_spa_pod_parser_get (parser, "channels", "i", &c, NULL))
    return FALSE;

  /* position is optional */
  wp_spa_pod_parser_get (parser, "position", "P", &p, NULL);

  if (channels && c != 0)
    *channels = c;
  if (position)
    *position = p ? wp_spa_pod_ref (p) : NULL;

  return TRUE;
}

static WpSpaPod *
build_adapter_format (WpSiAudioAdapter * self, guint32 format, gint channels,
    WpSpaPod *pos)
{
  g_autoptr (WpSpaPod) position = pos;
  g_autoptr (WpSpaPodBuilder) b = NULL;

  g_return_val_if_fail (channels > 0, NULL);

  /* build the position array if not given */
  if (!position) {
    switch (channels) {
    case 1: {
      g_autoptr (WpSpaPodBuilder) pos_b = wp_spa_pod_builder_new_array ();
      wp_spa_pod_builder_add_id (pos_b, SPA_AUDIO_CHANNEL_MONO);
      position = wp_spa_pod_builder_end (pos_b);
      break;
    }
    case 2: {
      g_autoptr (WpSpaPodBuilder) pos_b = wp_spa_pod_builder_new_array ();
      wp_spa_pod_builder_add_id (pos_b, SPA_AUDIO_CHANNEL_FL);
      wp_spa_pod_builder_add_id (pos_b, SPA_AUDIO_CHANNEL_FR);
      position = wp_spa_pod_builder_end (pos_b);
      break;
    }
    default:
      break;
    }
  }

  /* build the format */
  b = wp_spa_pod_builder_new_object ("Spa:Pod:Object:Param:Format", "Format");
  wp_spa_pod_builder_add_property (b, "mediaType");
  wp_spa_pod_builder_add_id (b, SPA_MEDIA_TYPE_audio);
  wp_spa_pod_builder_add_property (b, "mediaSubtype");
  wp_spa_pod_builder_add_id (b, SPA_MEDIA_SUBTYPE_raw);
  wp_spa_pod_builder_add_property (b, "format");
  wp_spa_pod_builder_add_id (b, format);
  wp_spa_pod_builder_add_property (b, "rate");
  wp_spa_pod_builder_add_int (b, si_audio_adapter_get_default_clock_rate (self));
  wp_spa_pod_builder_add_property (b, "channels");
  wp_spa_pod_builder_add_int (b, channels);
  if (position) {
    wp_spa_pod_builder_add_property (b, "position");
    wp_spa_pod_builder_add_pod (b, position);
  }
  return wp_spa_pod_builder_end (b);
}

static WpSpaPod *
build_adapter_dsp_format (WpSiAudioAdapter * self, WpSpaPod *dev_format)
{
  g_autoptr (WpSpaPod) position = NULL;
  gint channels = 2;

  /* parse device format */
  if (dev_format && !parse_adapter_format (dev_format, &channels, &position))
    return NULL;

  /* build F32P with same channels and position as device format */
  return build_adapter_format (self, SPA_AUDIO_FORMAT_F32P, channels,
      g_steal_pointer (&position));
}

static WpSpaPod *
build_adapter_default_format (WpSiAudioAdapter * self, const gchar *mode)
{
  guint32 format = SPA_AUDIO_FORMAT_F32;

  /* if dsp, use plannar format */
  if (!mode || g_strcmp0 (mode, "dsp") == 0)
    format = SPA_AUDIO_FORMAT_F32P;

  return build_adapter_format (self, format, 2, NULL);
}

static void
on_format_set (GObject *obj, GAsyncResult * res, gpointer p)
{
  g_autoptr(WpTransition) transition = p;
  WpSiAudioAdapter *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (wp_transition_get_completed (transition))
    return;

  wp_si_adapter_set_ports_format_finish (WP_SI_ADAPTER (self), res, &error);
  if (error) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  wp_object_update_features (WP_OBJECT (self),
      WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
}

static void
si_audio_adapter_configure_node (WpSiAudioAdapter *self,
    WpTransition * transition)
{
  g_autoptr (WpSpaPod) format = NULL;
  g_autoptr (WpSpaPod) ports_format = NULL;
  const gchar *mode = NULL;

  /* set the chosen format on the node */
  format = format_audio_raw_build (&self->raw_format);
  wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node), "Format", 0,
      wp_spa_pod_ref (format));

  /* build the ports format */
  if (self->disable_dsp) {
    mode = "passthrough";
    ports_format = g_steal_pointer (&format);
  } else {
    mode = "dsp";
    ports_format = build_adapter_dsp_format (self, format);
    if (!ports_format) {
        wp_transition_return_error (transition,
          g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
              "failed to build ports format"));
      return;
    }
  }

  /* set chosen format in the ports */
  wp_si_adapter_set_ports_format (WP_SI_ADAPTER (self),
      g_steal_pointer (&ports_format), mode, on_format_set, g_object_ref (transition));
}

static void
on_port_param_info (WpPipewireObject * port, GParamSpec * param,
    WpSiAudioAdapter *self)
{
  /* finish the task started by _set_ports_format() */
  if (self->format_task) {
    g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
    si_audio_adapter_set_ports_state (self,
        WP_SI_ADAPTER_PORTS_STATE_CONFIGURED);
    g_task_return_boolean (t, TRUE);
  }
}

static void
on_node_ports_changed (WpObject * node, WpSiAudioAdapter *self)
{
  /* clear port and handler */
  if (self->port) {
    g_signal_handlers_disconnect_by_func (self->port, on_port_param_info, self);
    g_clear_object (&self->port);
  }

  if (wp_node_get_n_ports (self->node) > 0) {
    /* if non DSP mode, listen for param-info on the single port in order to
     * be notified of format changed events */
    if (g_strcmp0 (self->mode, "dsp") != 0) {
      self->port = wp_node_lookup_port (self->node,
          WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.direction", "=s",
          self->portconfig_direction == WP_DIRECTION_INPUT ? "in" : "out",
          NULL);
      if (self->port)
        g_signal_connect_object (self->port, "notify::param-info",
            G_CALLBACK (on_port_param_info), self, 0);
    }

    /* finish the task started by _set_ports_format() */
    if (self->format_task) {
      g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
      si_audio_adapter_set_ports_state (self,
          WP_SI_ADAPTER_PORTS_STATE_CONFIGURED);
      g_task_return_boolean (t, TRUE);
    }
  }
}

static void
si_audio_adapter_enable_active (WpSessionItem *si, WpTransition *transition)
{
  WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (si);

  if (!wp_session_item_is_configured (si)) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
        "si-audio-adapter: item is not configured"));
    return;
  }

  if (!(wp_object_get_active_features (WP_OBJECT (self->node))
    & WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL)) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
        "si-audio-adapter: node minimal feature not enabled"));
    return;
  }

  self->ports_changed_sigid = g_signal_connect_object (self->node,
      "ports-changed", (GCallback) on_node_ports_changed, self, 0);

  /* If device node, enum available formats and set one of them */
  if (!self->no_format && (self->is_device || self->dont_remix ||
      !self->is_autoconnect || self->disable_dsp || self->is_unpositioned))
    si_audio_adapter_configure_node (self, transition);

  /* Otherwise just finish activating */
  else
    wp_object_update_features (WP_OBJECT (self),
          WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
}

static void
si_audio_adapter_disable_active (WpSessionItem *si)
{
  WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (si);

  if (self->ports_changed_sigid) {
    g_signal_handler_disconnect (self->node, self->ports_changed_sigid);
    self->ports_changed_sigid = 0;
  }

  wp_object_update_features (WP_OBJECT (self), 0,
      WP_SESSION_ITEM_FEATURE_ACTIVE);
}

static WpObjectFeatures
si_audio_adapter_get_supported_features (WpObject * self)
{
  return WP_SESSION_ITEM_FEATURE_ACTIVE;
}

static void
si_audio_adapter_class_init (WpSiAudioAdapterClass * klass)
{
  WpObjectClass * wpobject_class = (WpObjectClass *) klass;
  WpSessionItemClass *si_class = (WpSessionItemClass *) klass;

  wpobject_class->get_supported_features =
      si_audio_adapter_get_supported_features;

  si_class->reset = si_audio_adapter_reset;
  si_class->configure = si_audio_adapter_configure;
  si_class->get_associated_proxy = si_audio_adapter_get_associated_proxy;
  si_class->disable_active = si_audio_adapter_disable_active;
  si_class->enable_active = si_audio_adapter_enable_active;
}

static WpSiAdapterPortsState
si_audio_adapter_get_ports_state (WpSiAdapter * item)
{
  WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
  return self->ports_state;
}

static WpSpaPod *
si_audio_adapter_get_ports_format (WpSiAdapter * item, const gchar **mode)
{
  WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
  if (mode)
    *mode = self->mode;
  return self->format ? wp_spa_pod_ref (self->format) : NULL;
}

static void
si_audio_adapter_set_ports_format (WpSiAdapter * item, WpSpaPod *f,
    const gchar *mode, GAsyncReadyCallback callback, gpointer data)
{
  WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
  g_autoptr (WpSpaPod) format = f;
  g_autoptr (GTask) task = g_task_new (self, NULL, callback, data);
  guint32 active = 0;

  /* cancel previous task if any */
  if (self->format_task) {
    g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
    g_task_return_new_error (t, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
        "setting new format before previous done");
  }

  /* build default format if NULL was given */
  if (!format && !g_strcmp0 (mode, "dsp")) {
    format = build_adapter_default_format (self, mode);
    if (!format) {
      g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
          WP_LIBRARY_ERROR_OPERATION_FAILED,
          "failed to build default format, aborting set format operation");
      return;
    }
  }

  /* make sure the node has WP_NODE_FEATURE_PORTS */
  active = wp_object_get_active_features (WP_OBJECT (self->node));
  if (G_UNLIKELY (!(active & WP_NODE_FEATURE_PORTS))) {
    g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
        WP_LIBRARY_ERROR_OPERATION_FAILED,
        "node feature ports is not enabled, aborting set format operation");
    return;
  }

  /* skip reconfiguring if the same mode & format are requested */
  if (!g_strcmp0 (mode, self->mode) &&
      ((format == NULL && self->format == NULL) ||
        wp_spa_pod_equal (format, self->format))) {
    g_task_return_boolean (task, TRUE);
    return;
  }

  /* ensure the node is suspended */
  if (wp_node_get_state (self->node, NULL) >= WP_NODE_STATE_IDLE)
    wp_node_send_command (self->node, "Suspend");

  /* set task, format and mode */
  self->format_task = g_steal_pointer (&task);
  g_clear_pointer (&self->format, wp_spa_pod_unref);
  self->format = g_steal_pointer (&format);
  strncpy (self->mode, mode ? mode : "dsp", sizeof (self->mode) - 1);

  si_audio_adapter_set_ports_state (self,
      WP_SI_ADAPTER_PORTS_STATE_CONFIGURING);

  /* configure DSP with chosen format */
  wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node),
      "PortConfig", 0, wp_spa_pod_new_object (
          "Spa:Pod:Object:Param:PortConfig", "PortConfig",
          "direction",  "I", self->portconfig_direction,
          "mode",       "K", self->mode,
          "monitor",    "b", self->monitor,
          "control",    "b", self->control_port,
          "format",     "P", self->format,
          NULL));

  /* the task finishes with new ports being emitted -> on_node_ports_changed */
}

static gboolean
si_audio_adapter_set_ports_format_finish (WpSiAdapter * item,
    GAsyncResult * res, GError ** error)
{
  return g_task_propagate_boolean (G_TASK (res), error);
}

static void
si_audio_adapter_adapter_init (WpSiAdapterInterface * iface)
{
  iface->get_ports_state = si_audio_adapter_get_ports_state;
  iface->get_ports_format = si_audio_adapter_get_ports_format;
  iface->set_ports_format = si_audio_adapter_set_ports_format;
  iface->set_ports_format_finish = si_audio_adapter_set_ports_format_finish;
}

static GVariant *
si_audio_adapter_get_ports (WpSiLinkable * item, const gchar * context)
{
  WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
  g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
  g_autoptr (WpIterator) it = NULL;
  g_auto (GValue) val = G_VALUE_INIT;
  WpDirection direction;
  guint32 node_id;

  if (!g_strcmp0 (context, "output")) {
    direction = WP_DIRECTION_OUTPUT;
  }
  else if (!g_strcmp0 (context, "input")) {
    direction = WP_DIRECTION_INPUT;
  }
  else {
    /* on any other context, return an empty list of ports */
    return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
  }

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a(uuu)"));
  node_id = wp_proxy_get_bound_id (WP_PROXY (self->node));

  for (it = wp_node_new_ports_iterator (self->node);
       wp_iterator_next (it, &val);
       g_value_unset (&val))
  {
    WpPort *port = g_value_get_object (&val);
    g_autoptr (WpProperties) props = NULL;
    const gchar *channel;
    guint32 port_id, channel_id = 0;

    if (wp_port_get_direction (port) != direction)
      continue;

    port_id = wp_proxy_get_bound_id (WP_PROXY (port));
    props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (port));

    /* skip control ports for now */
    if (spa_atob (wp_properties_get (props, PW_KEY_PORT_CONTROL)))
      continue;

    /* try to find the audio channel; if channel is NULL, this will silently
       leave the channel_id to its default value, 0 */
    channel = wp_properties_get (props, PW_KEY_AUDIO_CHANNEL);
    if (channel) {
      WpSpaIdValue idval = wp_spa_id_value_from_short_name (
          "Spa:Enum:AudioChannel", channel);
      if (idval)
        channel_id = wp_spa_id_value_number (idval);
    }

    g_variant_builder_add (&b, "(uuu)", node_id, port_id, channel_id);
  }

  return g_variant_builder_end (&b);
}

static void
si_audio_adapter_linkable_init (WpSiLinkableInterface * iface)
{
  iface->get_ports = si_audio_adapter_get_ports;
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_si_factory_register (core, wp_si_factory_new_simple (SI_FACTORY_NAME,
      si_audio_adapter_get_type ()));
  return TRUE;
}
  070701000000D1000081A4000000000000000000000001656CC35F000035EA000000000000000000000000000000000000003600000000wireplumber-0.4.17/modules/module-si-audio-endpoint.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include <spa/param/format.h>
#include <spa/param/audio/raw.h>
#include <spa/param/param.h>

#define SI_FACTORY_NAME "si-audio-endpoint"

struct _WpSiAudioEndpoint
{
  WpSessionItem parent;

  /* configuration */
  gchar name[96];
  gchar media_class[32];
  WpDirection direction;
  gchar role[32];
  guint priority;
  gboolean disable_dsp;

  /* activation */
  WpNode *node;
  WpSiAdapter *adapter;

  /* export */
  WpImplEndpoint *impl_endpoint;
};

static void si_audio_endpoint_endpoint_init (WpSiEndpointInterface * iface);
static void si_audio_endpoint_linkable_init (WpSiLinkableInterface * iface);
static void si_audio_endpoint_adapter_init (WpSiAdapterInterface * iface);

G_DECLARE_FINAL_TYPE(WpSiAudioEndpoint, si_audio_endpoint, WP,
    SI_AUDIO_ENDPOINT, WpSessionItem)
G_DEFINE_TYPE_WITH_CODE (WpSiAudioEndpoint, si_audio_endpoint,
    WP_TYPE_SESSION_ITEM,
    G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ENDPOINT, si_audio_endpoint_endpoint_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_SI_LINKABLE,
        si_audio_endpoint_linkable_init)
    G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ADAPTER, si_audio_endpoint_adapter_init))

static void
si_audio_endpoint_init (WpSiAudioEndpoint * self)
{
}

static void
si_audio_endpoint_reset (WpSessionItem * item)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);

  /* deactivate first */
  wp_object_deactivate (WP_OBJECT (self),
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);

  /* reset */
  self->name[0] = '\0';
  self->media_class[0] = '\0';
  self->direction = WP_DIRECTION_INPUT;
  self->role[0] = '\0';
  self->priority = 0;
  self->disable_dsp = FALSE;

  WP_SESSION_ITEM_CLASS (si_audio_endpoint_parent_class)->reset (item);
}

static gboolean
si_audio_endpoint_configure (WpSessionItem * item, WpProperties *p)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
  g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
  const gchar *str;

  /* reset previous config */
  si_audio_endpoint_reset (item);

  str = wp_properties_get (si_props, "name");
  if (!str)
    return FALSE;
  strncpy (self->name, str, sizeof (self->name) - 1);

  str = wp_properties_get (si_props, "media.class");
  if (!str)
    return FALSE;
  strncpy (self->media_class, str, sizeof (self->media_class) - 1);

  if (strstr (self->media_class, "Source") ||
      strstr (self->media_class, "Output"))
    self->direction = WP_DIRECTION_OUTPUT;
  wp_properties_setf (si_props, "direction", "%u", self->direction);

  str = wp_properties_get (si_props, "role");
  if (str) {
    strncpy (self->role, str, sizeof (self->role) - 1);
  } else {
    strncpy (self->role, "Unknown", sizeof (self->role) - 1);
    wp_properties_set (si_props, "role", self->role);
  }

  str = wp_properties_get (si_props, "priority");
  if (str && sscanf(str, "%u", &self->priority) != 1)
    return FALSE;
  if (!str)
    wp_properties_setf (si_props, "priority", "%u", self->priority);

  str = wp_properties_get (si_props, "item.features.no-dsp");
  self->disable_dsp = str && pw_properties_parse_bool (str);

  wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
  wp_session_item_set_properties (WP_SESSION_ITEM (self),
      g_steal_pointer (&si_props));
  return TRUE;
}

static gpointer
si_audio_endpoint_get_associated_proxy (WpSessionItem * item, GType proxy_type)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);

  if (proxy_type == WP_TYPE_ENDPOINT)
    return self->impl_endpoint ? g_object_ref (self->impl_endpoint) : NULL;

  return wp_session_item_get_associated_proxy (
      WP_SESSION_ITEM (self->adapter), proxy_type);
}

static void
si_audio_endpoint_disable_active (WpSessionItem *si)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (si);

  g_clear_object (&self->adapter);
  g_clear_object (&self->node);
  wp_object_update_features (WP_OBJECT (self), 0,
      WP_SESSION_ITEM_FEATURE_ACTIVE);
}

static void
si_audio_endpoint_disable_exported (WpSessionItem *si)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (si);

  g_clear_object (&self->impl_endpoint);
  wp_object_update_features (WP_OBJECT (self), 0,
      WP_SESSION_ITEM_FEATURE_EXPORTED);
}

static void
on_adapter_activate_done (WpObject * adapter, GAsyncResult * res,
    WpTransition * transition)
{
  WpSiAudioEndpoint *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (adapter, res, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  wp_object_update_features (WP_OBJECT (self),
      WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
}

static void
on_adapter_port_state_changed (WpSiAdapter *item,
    WpSiAdapterPortsState old_state, WpSiAdapterPortsState new_state,
    WpSiAudioEndpoint *self)
{
  g_signal_emit_by_name (self, "adapter-ports-state-changed", old_state,
      new_state);
}

static void
on_node_activate_done (WpObject * node, GAsyncResult * res,
    WpTransition * transition)
{
  WpSiAudioEndpoint *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;
  g_autoptr (WpCore) core = NULL;
  g_autoptr (WpProperties) props = NULL;

  if (!wp_object_activate_finish (node, res, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  /* create adapter */
  core = wp_object_get_core (WP_OBJECT (self));
  self->adapter = WP_SI_ADAPTER (wp_session_item_make (core,
      "si-audio-adapter"));
  if (!self->adapter) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
            "si-audio-endpoint: could not create si-audio-adapter"));
  }

  /* Forward adapter-ports-state-changed signal */
  g_signal_connect_object (self->adapter, "adapter-ports-state-changed",
      G_CALLBACK (on_adapter_port_state_changed), self, 0);

  /* configure adapter */
  props = wp_properties_new_empty ();
  wp_properties_setf (props, "item.node", "%p", node);
  wp_properties_set (props, "name", self->name);
  wp_properties_set (props, "media.class", "Audio/Sink");
  wp_properties_set (props, "item.features.no-format", "true");
  wp_properties_set (props, "item.features.monitor", "true");
  if (self->disable_dsp)
    wp_properties_set (props, "item.features.no-dsp", "true");
  if (!wp_session_item_configure (WP_SESSION_ITEM (self->adapter),
      g_steal_pointer (&props))) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
            "si-audio-endpoint: could not configure si-audio-adapter"));
  }

  /* activate adapter */
  wp_object_activate (WP_OBJECT (self->adapter), WP_SESSION_ITEM_FEATURE_ACTIVE,
      NULL, (GAsyncReadyCallback) on_adapter_activate_done, transition);
}

static void
si_audio_endpoint_enable_active (WpSessionItem *si, WpTransition *transition)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (si);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
  g_autofree gchar *name = g_strdup_printf ("control.%s", self->name);
  g_autofree gchar *desc = g_strdup_printf ("%s %s Endpoint", self->role,
      (self->direction == WP_DIRECTION_OUTPUT) ? "Capture" : "Playback");
  g_autofree gchar *media = g_strdup_printf ("Audio/%s",
      (self->direction == WP_DIRECTION_OUTPUT) ? "Source" : "Sink");
  const gchar *passive =
      (self->direction == WP_DIRECTION_OUTPUT) ? "in" : "out";

  if (!wp_session_item_is_configured (si)) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
            "si-audio-endpoint: item is not configured"));
    return;
  }

  /* create the node */
  self->node = wp_node_new_from_factory (core, "adapter",
      wp_properties_new (
          PW_KEY_NODE_NAME, name,
          PW_KEY_MEDIA_CLASS, media,
          PW_KEY_FACTORY_NAME, "support.null-audio-sink",
          PW_KEY_NODE_DESCRIPTION, desc,
          PW_KEY_NODE_PASSIVE, passive,
          "monitor.channel-volumes", "true",
          "wireplumber.is-endpoint", "true",
          NULL));
  if (!self->node) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
            "si-audio-endpoint: could not create null-audio-sink node"));
    return;
  }

  /* activate node */
  wp_object_activate (WP_OBJECT (self->node),
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | WP_NODE_FEATURE_PORTS, NULL,
      (GAsyncReadyCallback) on_node_activate_done, transition);
}

static void
on_impl_endpoint_activated (WpObject * object, GAsyncResult * res,
    WpTransition * transition)
{
  WpSiAudioEndpoint *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (object, res, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  wp_object_update_features (WP_OBJECT (self),
      WP_SESSION_ITEM_FEATURE_EXPORTED, 0);
}

static void
si_audio_endpoint_enable_exported (WpSessionItem *si, WpTransition *transition)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (si);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));

  self->impl_endpoint = wp_impl_endpoint_new (core, WP_SI_ENDPOINT (self));

  g_signal_connect_object (self->impl_endpoint, "pw-proxy-destroyed",
      G_CALLBACK (wp_session_item_handle_proxy_destroyed), self, 0);

  wp_object_activate (WP_OBJECT (self->impl_endpoint),
      WP_OBJECT_FEATURES_ALL, NULL,
      (GAsyncReadyCallback) on_impl_endpoint_activated, transition);
}

static void
si_audio_endpoint_class_init (WpSiAudioEndpointClass * klass)
{
  WpSessionItemClass *si_class = (WpSessionItemClass *) klass;

  si_class->reset = si_audio_endpoint_reset;
  si_class->configure = si_audio_endpoint_configure;
  si_class->get_associated_proxy = si_audio_endpoint_get_associated_proxy;
  si_class->disable_active = si_audio_endpoint_disable_active;
  si_class->disable_exported = si_audio_endpoint_disable_exported;
  si_class->enable_active = si_audio_endpoint_enable_active;
  si_class->enable_exported = si_audio_endpoint_enable_exported;
}

static GVariant *
si_audio_endpoint_get_registration_info (WpSiEndpoint * item)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
  GVariantBuilder b;

  g_variant_builder_init (&b, G_VARIANT_TYPE ("(ssya{ss})"));
  g_variant_builder_add (&b, "s", self->name);
  g_variant_builder_add (&b, "s", self->media_class);
  g_variant_builder_add (&b, "y", self->direction);
  g_variant_builder_add (&b, "a{ss}", NULL);

  return g_variant_builder_end (&b);
}

static WpProperties *
si_audio_endpoint_get_properties (WpSiEndpoint * item)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
  g_autoptr (WpNode) node = wp_session_item_get_associated_proxy (
      WP_SESSION_ITEM (self->adapter), WP_TYPE_NODE);
  WpProperties *result = wp_properties_new_empty ();

  wp_properties_set (result, "endpoint.name", self->name);
  wp_properties_setf (result, "endpoint.priority", "%u", self->priority);
  wp_properties_setf (result, "endpoint.description", "%s: %s",
      (self->direction == WP_DIRECTION_OUTPUT) ? "Capture" : "Playback",
      self->role);
  wp_properties_set (result, "media.role", self->role);

  /* associate with the node */
  g_return_val_if_fail (node, NULL);
  wp_properties_setf (result, PW_KEY_NODE_ID, "%d",
      wp_proxy_get_bound_id (WP_PROXY (node)));

  return result;
}

static void
si_audio_endpoint_endpoint_init (WpSiEndpointInterface * iface)
{
  iface->get_registration_info = si_audio_endpoint_get_registration_info;
  iface->get_properties = si_audio_endpoint_get_properties;
}

static GVariant *
si_audio_endpoint_get_ports (WpSiLinkable * item, const gchar * context)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
  return wp_si_linkable_get_ports (WP_SI_LINKABLE (self->adapter), context);
}

static void
si_audio_endpoint_linkable_init (WpSiLinkableInterface * iface)
{
  iface->get_ports = si_audio_endpoint_get_ports;
}

static WpSiAdapterPortsState
si_audio_endpoint_get_ports_state (WpSiAdapter * item)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
  return wp_si_adapter_get_ports_state (self->adapter);
}

static WpSpaPod *
si_audio_endpoint_get_ports_format (WpSiAdapter * item, const gchar **mode)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
  return wp_si_adapter_get_ports_format (self->adapter, mode);
}

static void
si_audio_endpoint_set_ports_format (WpSiAdapter * item, WpSpaPod *f,
    const gchar *mode, GAsyncReadyCallback callback, gpointer data)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
  wp_si_adapter_set_ports_format (self->adapter, f, mode, callback, data);
}

static gboolean
si_audio_endpoint_set_ports_format_finish (WpSiAdapter * item,
    GAsyncResult * res, GError ** error)
{
  WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
  return wp_si_adapter_set_ports_format_finish (self->adapter, res, error);
}

static void
si_audio_endpoint_adapter_init (WpSiAdapterInterface * iface)
{
  iface->get_ports_state = si_audio_endpoint_get_ports_state;
  iface->get_ports_format = si_audio_endpoint_get_ports_format;
  iface->set_ports_format = si_audio_endpoint_set_ports_format;
  iface->set_ports_format_finish = si_audio_endpoint_set_ports_format_finish;
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_si_factory_register (core, wp_si_factory_new_simple (SI_FACTORY_NAME,
      si_audio_endpoint_get_type ()));
  return TRUE;
}
  070701000000D2000081A4000000000000000000000001656CC35F00001848000000000000000000000000000000000000002C00000000wireplumber-0.4.17/modules/module-si-node.c   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <pipewire/keys.h>
#include <pipewire/properties.h>
#include <pipewire/extensions/session-manager/keys.h>

#define SI_FACTORY_NAME "si-node"

struct _WpSiNode
{
  WpSessionItem parent;

  /* configuration */
  WpNode *node;
};

static void si_node_linkable_init (WpSiLinkableInterface * iface);

G_DECLARE_FINAL_TYPE(WpSiNode, si_node, WP, SI_NODE, WpSessionItem)
G_DEFINE_TYPE_WITH_CODE (WpSiNode, si_node, WP_TYPE_SESSION_ITEM,
    G_IMPLEMENT_INTERFACE (WP_TYPE_SI_LINKABLE, si_node_linkable_init))

static void
si_node_init (WpSiNode * self)
{
}

static void
si_node_reset (WpSessionItem * item)
{
  WpSiNode *self = WP_SI_NODE (item);

  /* deactivate first */
  wp_object_deactivate (WP_OBJECT (self), WP_SESSION_ITEM_FEATURE_ACTIVE);

  /* reset */
  g_clear_object (&self->node);

  WP_SESSION_ITEM_CLASS (si_node_parent_class)->reset (item);
}

static void
on_proxy_destroyed (WpNode * proxy, WpSiNode * self)
{
  if (self->node == proxy) {
    wp_object_abort_activation (WP_OBJECT (self), "proxy destroyed");
    si_node_reset (WP_SESSION_ITEM (self));
  }
}

static gboolean
si_node_configure (WpSessionItem * item, WpProperties *p)
{
  WpSiNode *self = WP_SI_NODE (item);
  g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
  WpNode *node = NULL;
  const gchar *str;

  /* reset previous config */
  si_node_reset (item);

  str = wp_properties_get (si_props, "item.node");
  if (!str || sscanf(str, "%p", &node) != 1 || !WP_IS_NODE (node))
    return FALSE;

  self->node = g_object_ref (node);
  g_signal_connect_object (self->node, "pw-proxy-destroyed",
      G_CALLBACK (on_proxy_destroyed), self, 0);

  wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
  wp_session_item_set_properties (WP_SESSION_ITEM (self),
      g_steal_pointer (&si_props));
  return TRUE;
}

static gpointer
si_node_get_associated_proxy (WpSessionItem * item, GType proxy_type)
{
  WpSiNode *self = WP_SI_NODE (item);

  if (proxy_type == WP_TYPE_NODE)
    return self->node ? g_object_ref (self->node) : NULL;

  return NULL;
}

static void
si_node_disable_active (WpSessionItem *si)
{
  WpSiNode *self = WP_SI_NODE (si);

  wp_object_update_features (WP_OBJECT (self), 0,
      WP_SESSION_ITEM_FEATURE_ACTIVE);
}

static void
on_node_activated (WpObject * node, GAsyncResult * res,
    WpTransition * transition)
{
  WpSiNode *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (node, res, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  wp_object_update_features (WP_OBJECT (self),
      WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
}

static void
si_node_enable_active (WpSessionItem *si, WpTransition *transition)
{
  WpSiNode *self = WP_SI_NODE (si);

  if (!wp_session_item_is_configured (si)) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
            "si-node: item is not configured"));
    return;
  }

  wp_object_activate (WP_OBJECT (self->node),
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | WP_NODE_FEATURE_PORTS,
      NULL, (GAsyncReadyCallback) on_node_activated, transition);
}

static WpObjectFeatures
si_node_get_supported_features (WpObject * self)
{
  return WP_SESSION_ITEM_FEATURE_ACTIVE;
}

static void
si_node_class_init (WpSiNodeClass * klass)
{
  WpObjectClass * wpobject_class = (WpObjectClass *) klass;
  WpSessionItemClass *si_class = (WpSessionItemClass *) klass;

  wpobject_class->get_supported_features = si_node_get_supported_features;

  si_class->reset = si_node_reset;
  si_class->configure = si_node_configure;
  si_class->get_associated_proxy = si_node_get_associated_proxy;
  si_class->disable_active = si_node_disable_active;
  si_class->enable_active = si_node_enable_active;
}

static GVariant *
si_node_get_ports (WpSiLinkable * item, const gchar * context)
{
  WpSiNode *self = WP_SI_NODE (item);
  g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
  g_autoptr (WpIterator) it = NULL;
  g_auto (GValue) val = G_VALUE_INIT;
  WpDirection direction;
  guint32 node_id;

  if (!g_strcmp0 (context, "output")) {
    direction = WP_DIRECTION_OUTPUT;
  }
  else if (!g_strcmp0 (context, "input")) {
    direction = WP_DIRECTION_INPUT;
  }
  else {
    /* on any other context, return an empty list of ports */
    return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
  }

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a(uuu)"));
  node_id = wp_proxy_get_bound_id (WP_PROXY (self->node));

  for (it = wp_node_new_ports_iterator (self->node);
       wp_iterator_next (it, &val);
       g_value_unset (&val))
  {
    WpPort *port = g_value_get_object (&val);
    g_autoptr (WpProperties) props = NULL;
    const gchar *channel;
    guint32 port_id, channel_id = 0;

    if (wp_port_get_direction (port) != direction)
      continue;

    port_id = wp_proxy_get_bound_id (WP_PROXY (port));
    props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (port));

    /* skip control ports for now */
    if (spa_atob (wp_properties_get (props, PW_KEY_PORT_CONTROL)))
      continue;

    /* try to find the audio channel; if channel is NULL, this will silently
       leave the channel_id to its default value, 0 */
    channel = wp_properties_get (props, PW_KEY_AUDIO_CHANNEL);
    if (channel) {
      WpSpaIdValue idval = wp_spa_id_value_from_short_name (
          "Spa:Enum:AudioChannel", channel);
      if (idval)
        channel_id = wp_spa_id_value_number (idval);
    }

    g_variant_builder_add (&b, "(uuu)", node_id, port_id, channel_id);
  }

  return g_variant_builder_end (&b);
}

static void
si_node_linkable_init (WpSiLinkableInterface * iface)
{
  iface->get_ports = si_node_get_ports;
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_si_factory_register (core, wp_si_factory_new_simple (SI_FACTORY_NAME,
      si_node_get_type ()));
  return TRUE;
}
070701000000D3000081A4000000000000000000000001656CC35F00006448000000000000000000000000000000000000003500000000wireplumber-0.4.17/modules/module-si-standard-link.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include <spa/debug/types.h>
#include <spa/param/audio/type-info.h>

#define SI_FACTORY_NAME "si-standard-link"

struct _WpSiStandardLink
{
  WpSessionItem parent;

  /* configuration */
  GWeakRef out_item;
  GWeakRef in_item;
  const gchar *out_item_port_context;
  const gchar *in_item_port_context;
  gboolean passthrough;

  /* activate */
  GPtrArray *node_links;
  guint n_active_links;
  guint n_failed_links;
  guint n_async_ops_wait;
};

enum {
  SIGNAL_LINK_ERROR,
  LAST_SIGNAL,
};

static guint signals[LAST_SIGNAL] = { 0 };

static void si_standard_link_link_init (WpSiLinkInterface * iface);

G_DECLARE_FINAL_TYPE (WpSiStandardLink, si_standard_link, WP, SI_STANDARD_LINK,
    WpSessionItem)
G_DEFINE_TYPE_WITH_CODE (WpSiStandardLink, si_standard_link,
    WP_TYPE_SESSION_ITEM,
    G_IMPLEMENT_INTERFACE (WP_TYPE_SI_LINK, si_standard_link_link_init))

static void
si_standard_link_init (WpSiStandardLink * self)
{
  g_weak_ref_init (&self->out_item, NULL);
  g_weak_ref_init (&self->in_item, NULL);
}

static void
si_standard_link_reset (WpSessionItem * item)
{
  WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);

  /* deactivate first */
  wp_object_deactivate (WP_OBJECT (self),
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);

  /* reset */
  g_weak_ref_set (&self->out_item, NULL);
  g_weak_ref_set (&self->in_item, NULL);
  self->out_item_port_context = NULL;
  self->in_item_port_context = NULL;
  self->passthrough = FALSE;

  WP_SESSION_ITEM_CLASS (si_standard_link_parent_class)->reset (item);
}

static WpSessionItem *
get_and_validate_item (WpProperties * props, const gchar *key)
{
  WpSessionItem *res = NULL;
  const gchar *str = NULL;

  str = wp_properties_get (props, key);
  if (!str || sscanf(str, "%p", &res) != 1 || !WP_IS_SI_LINKABLE (res) ||
      !(wp_object_get_active_features (WP_OBJECT (res)) &
          WP_SESSION_ITEM_FEATURE_ACTIVE))
    return NULL;

  return res;
}

static gboolean
si_standard_link_configure (WpSessionItem * item, WpProperties * p)
{
  WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
  g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
  WpSessionItem *out_item, *in_item;
  const gchar *str;

  /* reset previous config */
  si_standard_link_reset (item);

  out_item = get_and_validate_item (si_props, "out.item");
  if (!out_item)
    return FALSE;
  wp_properties_setf (si_props, "out.item.id", "%u",
      wp_session_item_get_id (out_item));

  in_item = get_and_validate_item (si_props, "in.item");
  if (!in_item)
    return FALSE;
  wp_properties_setf (si_props, "in.item.id", "%u",
      wp_session_item_get_id (in_item));

  self->out_item_port_context = wp_properties_get (si_props,
      "out.item.port.context");

  self->in_item_port_context = wp_properties_get (si_props,
      "in.item.port.context");

  str = wp_properties_get (si_props, "passthrough");
  self->passthrough = str && pw_properties_parse_bool (str);

  g_weak_ref_set(&self->out_item, out_item);
  g_weak_ref_set(&self->in_item, in_item);

  wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
  wp_session_item_set_properties (WP_SESSION_ITEM (self),
      g_steal_pointer (&si_props));
  return TRUE;
}

static gpointer
si_standard_link_get_associated_proxy (WpSessionItem * item, GType proxy_type)
{
  return NULL;
}

static void
request_destroy_link (gpointer data, gpointer user_data)
{
  WpLink *link = WP_LINK (data);

  wp_global_proxy_request_destroy (WP_GLOBAL_PROXY (link));
}

static void
clear_node_links (GPtrArray **node_links_p)
{
  /*
   * Something else (eg. object managers) may be keeping the WpLink
   * objects alive. Deactive the links now, to destroy the PW objects.
   */
  if (*node_links_p)
    g_ptr_array_foreach (*node_links_p, request_destroy_link, NULL);

  g_clear_pointer (node_links_p, g_ptr_array_unref);
}

static void
si_standard_link_disable_active (WpSessionItem *si)
{
  WpSiStandardLink *self = WP_SI_STANDARD_LINK (si);
  g_autoptr (WpSessionItem) si_out = g_weak_ref_get (&self->out_item);
  g_autoptr (WpSessionItem) si_in = g_weak_ref_get (&self->in_item);
  WpSiAcquisition *out_acquisition, *in_acquisition;

  if (si_out) {
    out_acquisition = wp_si_linkable_get_acquisition (
        WP_SI_LINKABLE (si_out));
    if (out_acquisition)
      wp_si_acquisition_release (out_acquisition, WP_SI_LINK (self),
          WP_SI_LINKABLE (si_out));
  }
  if (si_in) {
    in_acquisition = wp_si_linkable_get_acquisition (WP_SI_LINKABLE (si_in));
    if (in_acquisition)
      wp_si_acquisition_release (in_acquisition, WP_SI_LINK (self),
          WP_SI_LINKABLE (si_in));
  }

  clear_node_links (&self->node_links);

  self->n_active_links = 0;
  self->n_failed_links = 0;
  self->n_async_ops_wait = 0;

  wp_object_update_features (WP_OBJECT (self), 0,
      WP_SESSION_ITEM_FEATURE_ACTIVE);
}

static void
on_link_activated (WpObject * proxy, GAsyncResult * res,
    WpTransition * transition)
{
  WpSiStandardLink *self = wp_transition_get_source_object (transition);
  guint len = self->node_links ? self->node_links->len : 0;

  /* Count the number of failed and active links */
  if (wp_object_activate_finish (proxy, res, NULL))
    self->n_active_links++;
  else
    self->n_failed_links++;

  /* Wait for all links to finish activation */
  if (self->n_failed_links + self->n_active_links != len)
    return;

  /* We only active feature if all links activated successfully */
  if (self->n_failed_links > 0) {
    clear_node_links (&self->node_links);
    wp_transition_return_error (transition, g_error_new (
        WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
        "%d of %d PipeWire links failed to activate",
        self->n_failed_links, len));
  } else {
    wp_object_update_features (WP_OBJECT (self),
        WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
  }
}

static void
on_link_state_changed (WpLink *link, WpLinkState old_state,
  WpLinkState new_state, WpSiStandardLink * self)
{
  if (new_state == WP_LINK_STATE_ERROR) {
    const gchar *error_msg;
    wp_link_get_state (link, &error_msg);
    g_signal_emit_by_name (self, "link-error", error_msg);
  }
}

struct port
{
  guint32 node_id;
  guint32 port_id;
  guint32 channel;
  gboolean visited;
};

static inline bool
channel_is_aux(guint32 channel)
{
  return channel >= SPA_AUDIO_CHANNEL_START_Aux &&
    channel <= SPA_AUDIO_CHANNEL_LAST_Aux;
}

static inline int
score_ports(struct port *out, struct port *in)
{
  int score = 0;

  if (out->channel == in->channel)
    score += 100;
  else if ((out->channel == SPA_AUDIO_CHANNEL_SL && in->channel == SPA_AUDIO_CHANNEL_RL) ||
            (out->channel == SPA_AUDIO_CHANNEL_RL && in->channel == SPA_AUDIO_CHANNEL_SL) ||
            (out->channel == SPA_AUDIO_CHANNEL_SR && in->channel == SPA_AUDIO_CHANNEL_RR) ||
            (out->channel == SPA_AUDIO_CHANNEL_RR && in->channel == SPA_AUDIO_CHANNEL_SR))
    score += 60;
  else if ((out->channel == SPA_AUDIO_CHANNEL_FC && in->channel == SPA_AUDIO_CHANNEL_MONO) ||
            (out->channel == SPA_AUDIO_CHANNEL_MONO && in->channel == SPA_AUDIO_CHANNEL_FC))
    score += 50;
  else if (in->channel == SPA_AUDIO_CHANNEL_UNKNOWN ||
            in->channel == SPA_AUDIO_CHANNEL_MONO ||
            out->channel == SPA_AUDIO_CHANNEL_UNKNOWN ||
            out->channel == SPA_AUDIO_CHANNEL_MONO)
    score += 10;
  else if (channel_is_aux(in->channel) != channel_is_aux(out->channel))
    score += 7;
  if (score > 0 && !in->visited)
    score += 5;
  if (score <= 10)
    score = 0;
  return score;
}

static gboolean
create_links (WpSiStandardLink * self, WpTransition * transition,
    GVariant * out_ports, GVariant * in_ports)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
  g_autoptr (GArray) in_ports_arr = NULL;
  struct port out_port = {0};
  struct port *in_port;
  GVariantIter *iter = NULL;
  guint i;

  /* Clear old links if any */
  self->n_active_links = 0;
  self->n_failed_links = 0;
  clear_node_links (&self->node_links);

  /* tuple format:
      uint32 node_id;
      uint32 port_id;
      uint32 channel;  // enum spa_audio_channel
   */
  if (!g_variant_is_of_type (out_ports, G_VARIANT_TYPE("a(uuu)")))
    return FALSE;
  if (!g_variant_is_of_type (in_ports, G_VARIANT_TYPE("a(uuu)")))
    return FALSE;

  i = g_variant_n_children (in_ports);
  if (i == 0)
    return FALSE;

  self->node_links = g_ptr_array_new_with_free_func (g_object_unref);

  /* transfer the in ports to an array so that we can
     mark them when they are linked */
  in_ports_arr = g_array_sized_new (FALSE, TRUE, sizeof (struct port), i + 1);
  g_array_set_size (in_ports_arr, i + 1);
  g_variant_get (in_ports, "a(uuu)", &iter);
  i = 0;
  do {
    in_port = &g_array_index (in_ports_arr, struct port, i++);
  } while (g_variant_iter_loop (iter, "(uuu)", &in_port->node_id,
              &in_port->port_id, &in_port->channel));
  g_variant_iter_free (iter);

  /* now loop over the out ports and figure out where they should be linked */
  g_variant_get (out_ports, "a(uuu)", &iter);
  while (g_variant_iter_loop (iter, "(uuu)", &out_port.node_id,
              &out_port.port_id, &out_port.channel))
  {
    int best_score = 0;
    struct port *best_port = NULL;
    WpProperties *props = NULL;
    WpLink *link;

    for (i = 0; i < in_ports_arr->len - 1; i++) {
      in_port = &g_array_index (in_ports_arr, struct port, i);
      int score = score_ports (&out_port, in_port);
      if (score > best_score) {
        best_score = score;
        best_port = in_port;
      }
    }

    /* not all output ports have to be linked ... */
    if (!best_port || best_port->visited)
      continue;

    best_port->visited = TRUE;

    /* Create the properties */
    props = wp_properties_new_empty ();
    wp_properties_setf (props, PW_KEY_LINK_OUTPUT_NODE, "%u", out_port.node_id);
    wp_properties_setf (props, PW_KEY_LINK_OUTPUT_PORT, "%u", out_port.port_id);
    wp_properties_setf (props, PW_KEY_LINK_INPUT_NODE, "%u", best_port->node_id);
    wp_properties_setf (props, PW_KEY_LINK_INPUT_PORT, "%u", best_port->port_id);

    wp_debug_object (self, "create pw link: %u:%u (%s) -> %u:%u (%s)",
        out_port.node_id, out_port.port_id,
        spa_debug_type_find_name (spa_type_audio_channel, out_port.channel),
        best_port->node_id, best_port->port_id,
        spa_debug_type_find_name (spa_type_audio_channel, best_port->channel));

    /* create the link */
    link = wp_link_new_from_factory (core, "link-factory", props);
    g_ptr_array_add (self->node_links, link);

    /* activate to ensure it is created without errors */
    wp_object_activate_closure (WP_OBJECT (link),
        WP_OBJECT_FEATURES_ALL & ~WP_LINK_FEATURE_ESTABLISHED, NULL,
        g_cclosure_new_object (
            (GCallback) on_link_activated, G_OBJECT (transition)));

    g_signal_connect_object (link, "state-changed",
      G_CALLBACK (on_link_state_changed), self, 0);
  }
  g_variant_iter_free (iter);
  return self->node_links->len > 0;
}

static void
get_ports_and_create_links (WpSiStandardLink *self, WpTransition *transition)
{
  g_autoptr (WpSiLinkable) si_out = NULL;
  g_autoptr (WpSiLinkable) si_in = NULL;
  g_autoptr (GVariant) out_ports = NULL;
  g_autoptr (GVariant) in_ports = NULL;

  si_out = WP_SI_LINKABLE (g_weak_ref_get (&self->out_item));
  si_in = WP_SI_LINKABLE (g_weak_ref_get (&self->in_item));

  if (!si_out || !si_in ||
      !wp_session_item_is_configured (WP_SESSION_ITEM (si_out)) ||
      !wp_session_item_is_configured (WP_SESSION_ITEM (si_in))) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
            "si-standard-link: in/out items are not valid anymore"));
    return;
  }

  out_ports = wp_si_linkable_get_ports (si_out, self->out_item_port_context);
  in_ports = wp_si_linkable_get_ports (si_in, self->in_item_port_context);
  if (!out_ports || !in_ports) {
    wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY,
          WP_LIBRARY_ERROR_INVARIANT,
          "Failed to create links because one of the nodes has no ports"));
    return;
  }

  if (!create_links (self, transition, out_ports, in_ports))
      wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY,
          WP_LIBRARY_ERROR_INVARIANT,
          "Failed to create links because of wrong ports"));
}

static void
on_adapters_ready (GObject *obj, GAsyncResult * res, gpointer p)
{
  WpTransition *transition = p;
  WpSiStandardLink *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  wp_si_adapter_set_ports_format_finish (WP_SI_ADAPTER (obj), res, &error);
  if (error) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  /* create links */
  get_ports_and_create_links (self, transition);
}

struct adapter
{
  WpSiAdapter *si;
  gboolean is_device;
  gboolean dont_remix;
  gboolean unpositioned;
  gboolean no_dsp;
  WpSpaPod *fmt;
  const gchar *mode;
};

static void
adapter_free (struct adapter *a)
{
  g_clear_object (&a->si);
  g_clear_pointer (&a->fmt, wp_spa_pod_unref);
  g_slice_free (struct adapter, a);
}

static void
configure_adapter (WpSiStandardLink *self, WpTransition *transition,
    struct adapter *main, struct adapter *other)
{
  /* configure other to have the same format with main, if necessary */
  if (!main->no_dsp && !other->dont_remix && !other->unpositioned && !main->unpositioned) {
    /* if formats are the same, no need to reconfigure */
    if (other->fmt && !g_strcmp0 (main->mode, other->mode)
        && wp_spa_pod_equal (main->fmt, other->fmt))
      get_ports_and_create_links (self, transition);
    else
      wp_si_adapter_set_ports_format (other->si, wp_spa_pod_ref (main->fmt),
          "dsp", on_adapters_ready, transition);
  } else if (main->no_dsp) {
    /* if formats are the same, no need to reconfigure */
    if (other->fmt && !g_strcmp0 (other->mode, "convert")
        && wp_spa_pod_equal (main->fmt, other->fmt))
      get_ports_and_create_links (self, transition);
    else
      wp_si_adapter_set_ports_format (other->si, wp_spa_pod_ref (main->fmt),
          "convert", on_adapters_ready, transition);
  } else {
    /* dont_remix or unpositioned case */
    if (other->fmt)
      get_ports_and_create_links (self, transition);
    else
      wp_si_adapter_set_ports_format (other->si, NULL,
          "dsp", on_adapters_ready, transition);
  }
}

static void
on_main_adapter_ready (GObject *obj, GAsyncResult * res, gpointer p)
{
  WpTransition *transition = p;
  WpSiStandardLink *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;
  struct adapter *main, *other;

  wp_si_adapter_set_ports_format_finish (WP_SI_ADAPTER (obj), res, &error);
  if (error) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  main = g_object_get_data (G_OBJECT (transition), "adapter_main");
  other = g_object_get_data (G_OBJECT (transition), "adapter_other");

  if (!wp_session_item_is_configured (WP_SESSION_ITEM (main->si)) ||
      !wp_session_item_is_configured (WP_SESSION_ITEM (other->si))) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
            "si-standard-link: in/out items are not valid anymore"));
    return;
  }

  if (self->passthrough) {
    wp_si_adapter_set_ports_format (other->si, NULL, "passthrough",
        on_adapters_ready, transition);
  } else {
    /* get the up-to-date formats */
    g_clear_pointer (&main->fmt, wp_spa_pod_unref);
    g_clear_pointer (&other->fmt, wp_spa_pod_unref);
    main->fmt = wp_si_adapter_get_ports_format (main->si, &main->mode);
    other->fmt = wp_si_adapter_get_ports_format (other->si, &other->mode);

    /* now configure other based on main */
    configure_adapter (self, transition, main, other);
  }
}

static void
configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition)
{
  g_autoptr (WpSiAdapter) si_out =
      WP_SI_ADAPTER (g_weak_ref_get (&self->out_item));
  g_autoptr (WpSiAdapter) si_in =
      WP_SI_ADAPTER (g_weak_ref_get (&self->in_item));
  struct adapter *out, *in, *main, *other;
  const gchar *str = NULL;

  if (!si_out || !si_in ||
      !wp_session_item_is_configured (WP_SESSION_ITEM (si_out)) ||
      !wp_session_item_is_configured (WP_SESSION_ITEM (si_in))) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
            "si-standard-link: in/out items are not valid anymore"));
    return;
  }

  out = g_slice_new0 (struct adapter);
  in = g_slice_new0 (struct adapter);
  out->si = g_steal_pointer (&si_out);
  in->si = g_steal_pointer (&si_in);

  str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.node.type");
  out->is_device = !g_strcmp0 (str, "device");
  str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.node.type");
  in->is_device = !g_strcmp0 (str, "device");

  str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.factory.name");
  out->is_device = (str && !g_strcmp0 (str, "si-audio-endpoint") && !in->is_device)
      || out->is_device;
  str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.factory.name");
  in->is_device = (str && !g_strcmp0 (str, "si-audio-endpoint") && !out->is_device)
      || in->is_device;

  str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "stream.dont-remix");
  out->dont_remix = str && pw_properties_parse_bool (str);
  str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "stream.dont-remix");
  in->dont_remix = str && pw_properties_parse_bool (str);

  str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.node.unpositioned");
  out->unpositioned = str && pw_properties_parse_bool (str);
  str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.node.unpositioned");
  in->unpositioned = str && pw_properties_parse_bool (str);

  str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.features.no-dsp");
  out->no_dsp = str && pw_properties_parse_bool (str);
  str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.features.no-dsp");
  in->no_dsp = str && pw_properties_parse_bool (str);

  wp_debug_object (self, "out [device:%d, dont_remix %d, unpos %d], "
      "in: [device %d, dont_remix %d, unpos %d]",
      out->is_device, out->dont_remix, out->unpositioned,
      in->is_device, in->dont_remix, in->unpositioned);

  /* we always use out->si format, unless in->si is device */
  if (!out->is_device && in->is_device) {
    main = in;
    other = out;
  } else {
    main = out;
    other = in;
  }

  /* always configure both adapters in passthrough mode
     if this is a passthrough link */
  if (self->passthrough) {
    g_object_set_data_full (G_OBJECT (transition), "adapter_main", main,
        (GDestroyNotify) adapter_free);
    g_object_set_data_full (G_OBJECT (transition), "adapter_other", other,
        (GDestroyNotify) adapter_free);
    wp_si_adapter_set_ports_format (main->si, NULL, "passthrough",
        on_main_adapter_ready, transition);
    return;
  }

  main->fmt = wp_si_adapter_get_ports_format (main->si, &main->mode);
  other->fmt = wp_si_adapter_get_ports_format (other->si, &other->mode);

  if (main->fmt)
    /* ideally, configure other based on main */
    configure_adapter (self, transition, main, other);
  else if (other->fmt)
    /* if main is not configured but other is, do it the other way around */
    configure_adapter (self, transition, other, main);
  else {
    /* no adapter configured, let's configure main first */
    g_object_set_data_full (G_OBJECT (transition), "adapter_main", main,
        (GDestroyNotify) adapter_free);
    g_object_set_data_full (G_OBJECT (transition), "adapter_other", other,
        (GDestroyNotify) adapter_free);
    wp_si_adapter_set_ports_format (main->si, NULL,
        main->no_dsp ? "passthrough" : "dsp", on_main_adapter_ready, transition);
    return;
  }

  adapter_free (main);
  adapter_free (other);
}

static void
si_standard_link_do_link (WpSiStandardLink *self, WpTransition *transition)
{
  g_autoptr (WpSessionItem) si_out = g_weak_ref_get (&self->out_item);
  g_autoptr (WpSessionItem) si_in = g_weak_ref_get (&self->in_item);

  if (!si_out || !si_in ||
      !wp_session_item_is_configured (si_out) ||
      !wp_session_item_is_configured (si_in)) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
            "si-standard-link: in/out items are not valid anymore"));
    return;
  }

  if (WP_IS_SI_ADAPTER (si_out) && WP_IS_SI_ADAPTER (si_in))
    configure_and_link_adapters (self, transition);
  else if (!WP_IS_SI_ADAPTER (si_out) && !WP_IS_SI_ADAPTER (si_in))
    get_ports_and_create_links (self, transition);
  else
    wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY,
          WP_LIBRARY_ERROR_INVARIANT,
          "Adapters cannot be linked with non-adapters"));
}

static void
on_item_acquired (WpSiAcquisition * acq, GAsyncResult * res,
    WpTransition * transition)
{
  WpSiStandardLink *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (!wp_si_acquisition_acquire_finish (acq, res, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  self->n_async_ops_wait--;
  if (self->n_async_ops_wait == 0)
    si_standard_link_do_link (self, transition);
}

static void
si_standard_link_enable_active (WpSessionItem *si, WpTransition *transition)
{
  WpSiStandardLink *self = WP_SI_STANDARD_LINK (si);
  g_autoptr (WpSessionItem) si_out = NULL;
  g_autoptr (WpSessionItem) si_in = NULL;
  WpSiAcquisition *out_acquisition = NULL, *in_acquisition = NULL;

  if (!wp_session_item_is_configured (si)) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
            "si-standard-link: item is not configured"));
    return;
  }

  /* make sure in/out items are valid */
  si_out = g_weak_ref_get (&self->out_item);
  si_in = g_weak_ref_get (&self->in_item);
  if (!si_out || !si_in ||
      !wp_session_item_is_configured (si_out) ||
      !wp_session_item_is_configured (si_in)) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
            "si-standard-link: in/out items are not valid anymore"));
    return;
  }

  /* acquire */
  out_acquisition = wp_si_linkable_get_acquisition (WP_SI_LINKABLE (si_out));
  in_acquisition = wp_si_linkable_get_acquisition (WP_SI_LINKABLE (si_in));
  if (out_acquisition && in_acquisition)
    self->n_async_ops_wait = 2;
  else if (out_acquisition || in_acquisition)
    self->n_async_ops_wait = 1;
  else {
    self->n_async_ops_wait = 0;
    si_standard_link_do_link (self, transition);
    return;
  }

  if (out_acquisition) {
    wp_si_acquisition_acquire (out_acquisition, WP_SI_LINK (self),
        WP_SI_LINKABLE (si_out), (GAsyncReadyCallback) on_item_acquired,
        transition);
  }
  if (in_acquisition) {
    wp_si_acquisition_acquire (in_acquisition, WP_SI_LINK (self),
        WP_SI_LINKABLE (si_in), (GAsyncReadyCallback) on_item_acquired,
        transition);
  }
}

static void
si_standard_link_finalize (GObject * object)
{
  WpSiStandardLink *self = WP_SI_STANDARD_LINK (object);

  g_weak_ref_clear (&self->out_item);
  g_weak_ref_clear (&self->in_item);

  G_OBJECT_CLASS (si_standard_link_parent_class)->finalize (object);
}

static void
si_standard_link_class_init (WpSiStandardLinkClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpSessionItemClass *si_class = (WpSessionItemClass *) klass;

  object_class->finalize = si_standard_link_finalize;

  si_class->reset = si_standard_link_reset;
  si_class->configure = si_standard_link_configure;
  si_class->get_associated_proxy = si_standard_link_get_associated_proxy;
  si_class->disable_active = si_standard_link_disable_active;
  si_class->enable_active = si_standard_link_enable_active;

  signals[SIGNAL_LINK_ERROR] = g_signal_new (
      "link-error", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
      0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
}

static GVariant *
si_standard_link_get_registration_info (WpSiLink * item)
{
  GVariantBuilder b;
  g_variant_builder_init (&b, G_VARIANT_TYPE ("a{ss}"));
  return g_variant_builder_end (&b);
}

static WpSiLinkable *
si_standard_link_get_out_item (WpSiLink * item)
{
  WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
  return WP_SI_LINKABLE (g_weak_ref_get (&self->out_item));
}

static WpSiLinkable *
si_standard_link_get_in_item (WpSiLink * item)
{
  WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
  return WP_SI_LINKABLE (g_weak_ref_get (&self->in_item));
}

static void
si_standard_link_link_init (WpSiLinkInterface * iface)
{
  iface->get_registration_info = si_standard_link_get_registration_info;
  iface->get_out_item = si_standard_link_get_out_item;
  iface->get_in_item = si_standard_link_get_in_item;
}

WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
  wp_si_factory_register (core, wp_si_factory_new_simple (SI_FACTORY_NAME,
      si_standard_link_get_type ()));
  return TRUE;
}
070701000000D4000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001600000000wireplumber-0.4.17/po 070701000000D5000081A4000000000000000000000001656CC35F000000B7000000000000000000000000000000000000001E00000000wireplumber-0.4.17/po/LINGUAS af
as
be
bg
bn_IN
ca
cs
da
de
de_CH
el
eo
es
fa
fi
fr
gl
gu
he
hi
hr
hu
id
it
ja
ka
kk
kn
ko
lt
ml
mr
my
nl
nn
oc
or
pa
pl
pt
pt_BR
ro
ru
si
sk
sr
sr@latin
sv
ta
te
tr
uk
zh_CN
zh_TW
 070701000000D6000081A4000000000000000000000001656CC35F00000041000000000000000000000000000000000000002200000000wireplumber-0.4.17/po/POTFILES.in src/scripts/monitors/alsa.lua
src/scripts/monitors/libcamera.lua
   070701000000D7000081A4000000000000000000000001656CC35F000000A6000000000000000000000000000000000000002400000000wireplumber-0.4.17/po/POTFILES.skip   src/systemd/system/wireplumber.service.in
src/systemd/system/wireplumber@.service.in
src/systemd/user/wireplumber.service.in
src/systemd/user/wireplumber@.service.in
  070701000000D8000081A4000000000000000000000001656CC35F00000758000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/af.po   # Afrikaans translation of PipeWire.
# This file is distributed under the same license as the PipeWire package.
# F Wolff <friedel@translate.org.za>, 2019.
msgid ""
msgstr ""
"Project-Id-Version: master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2019-01-09 12:17+0200\n"
"Last-Translator: F Wolff <friedel@translate.org.za>\n"
"Language-Team: translate-discuss-af@lists.sourceforge.net\n"
"Language: af\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Virtaal 0.7.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Ingeboude oudio"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
070701000000D9000081A4000000000000000000000001656CC35F000007CD000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/as.po   # translation of pipewire.master-tx.as.po to Assamese
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Amitakhya Phukan <aphukan@fedoraproject.org>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx.as\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:52+0000\n"
"Last-Translator: Amitakhya Phukan <aphukan@fedoraproject.org>\n"
"Language-Team: Assamese <>\n"
"Language: as\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 0.2\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "আভ্যন্তৰীণ অ'ডিঅ'"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "মোডেম"
   070701000000DA000081A4000000000000000000000001656CC35F00000FB3000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/be.po   # Belarusian tanslation of PipeWire
# Copyright (C) 2016 PipeWire's COPYRIGHT HOLDER
# This file is distributed under the same license as the PipeWire package.
# Viktar Vaŭčkievič <victorenator@gmail.com>, 2016-2023.
#
msgid ""
msgstr ""
"Project-Id-Version: PipeWire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2023-03-11 14:28+0300\n"
"PO-Revision-Date: 2023-03-11 14:31+0300\n"
"Last-Translator: Viktar Vaŭčkievič <victorenator@gmail.com>\n"
"Language-Team: Belarusian <>\n"
"Language: be\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 42.0\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:236
msgid "Loopback"
msgstr "Пятля"

#: src/scripts/monitors/alsa.lua:238
msgid "Built-in Audio"
msgstr "Убудаванае аўдыя"

#: src/scripts/monitors/alsa.lua:240
msgid "Modem"
msgstr "Мадэм"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Убудаваная пярэдняя камера"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Убудаваная задняя камера"
 070701000000DB000081A4000000000000000000000001656CC35F0000070F000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/bg.po   # Valentin Laskov <laskov@festa.bg>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2020-10-15 21:30+0000\n"
"Last-Translator: Emanuil Novachev <em.novachev@gmail.com>\n"
"Language-Team: Bulgarian <https://translate.fedoraproject.org/projects/"
"pipewire/pipewire/bg/>\n"
"Language: bg\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.2.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr ""

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr ""
 070701000000DC000081A4000000000000000000000001656CC35F000007E7000000000000000000000000000000000000001F00000000wireplumber-0.4.17/po/bn_IN.po    # translation of pipewire.master-tx.bn_IN.po to Bengali INDIA
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Runa Bhattacharjee <runab@redhat.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx.bn_IN\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:52+0000\n"
"Last-Translator: Runa Bhattacharjee <runab@redhat.com>\n"
"Language-Team: Bengali INDIA <anubad@lists.ankur.org.in>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: KBabel 1.11.4\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "অভ্যন্তরীণ অডিও"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "মোডেম"
 070701000000DD000081A4000000000000000000000001656CC35F000012A1000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/ca.po   # Catalan translation of pipewire by Softcatalà
# Copyright (C) 2008 Free Software Foundation
# This file is distributed under the same license as the pipewire package.
#
# Xavier Conde Rueda <xavi.conde@gmail.com>, 2008.
# Agustí Grau <fletxa@gmail.com>, 2009.
# Judith Pintó Subirada <judithp@gmail.com>
#
# This file is translated according to the glossary and style guide of
# Softcatalà. If you plan to modify this file, please read first the page
# of the Catalan translation team for the Fedora project at:
# http://www.softcatala.org/projectes/fedora/
# and contact the previous translator.
#
# Aquest fitxer s'ha de traduir d'acord amb el recull de termes i la guia
# d'estil de Softcatalà. Si voleu modificar aquest fitxer, llegiu si
# us plau la pàgina de catalanització del projecte Fedora a:
# http://www.softcatala.org/projectes/fedora/
# i contacteu l'anterior traductor/a.
# Josep Torné Llavall <josep.torne@gmail.com>, 2009, 2012.
# Robert Antoni Buj Gelonch <rbuj@fedoraproject.org>, 2016. #zanata
# Wim Taymans <wim.taymans@gmail.com>, 2016. #zanata
# Robert Antoni Buj Gelonch <rbuj@fedoraproject.org>, 2017. #zanata
# Robert Antoni Buj Gelonch <rbuj@fedoraproject.org>, 2019. #zanata
# Jordi Mas i Hernàndez <jmas@softcatala.org>, 2022
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues\n"
"POT-Creation-Date: 2022-07-08 03:32+0000\n"
"PO-Revision-Date: 2022-09-12 09:52+0000\n"
"Last-Translator: Jordi Mas i Hernàndez <jmas@softcatala.org>\n"
"Language-Team: Catalan <fedora@softcatala.net>\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#. WirePlumber
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:228
msgid "Built-in Audio"
msgstr "Àudio intern"

#: src/scripts/monitors/alsa.lua:230
msgid "Modem"
msgstr "Mòdem"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device
#. name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Càmera frontal integrada"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Càmera posterior integrada"
   070701000000DE000081A4000000000000000000000001656CC35F00000F5B000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/cs.po   # Czech translation of pipewire.
# Copyright (C) 2008, 2009 the author(s) of pipewire.
# This file is distributed under the same license as the pipewire package.
# Petr Kovar <pknbe@volny.cz>, 2008, 2009, 2012.
# Daniel Rusek <mail@asciiwolf.com>, 2018.
# Marek Černocký <marek@manet.cz>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
"issues\n"
"POT-Creation-Date: 2022-07-08 03:32+0000\n"
"PO-Revision-Date: 2022-10-19 20:52+0200\n"
"Last-Translator: Daniel Rusek <mail@asciiwolf.com>\n"
"Language-Team: čeština <gnome-cs-list@gnome.org>\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Poedit 3.1.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:228
msgid "Built-in Audio"
msgstr "Vnitřní zvukový systém"

#: src/scripts/monitors/alsa.lua:230
msgid "Modem"
msgstr "Modem"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Vestavěná přední kamera"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Vestavěná zadní kamera"
 070701000000DF000081A4000000000000000000000001656CC35F00000727000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/da.po   # Danish translation for PipeWire.
# Copyright (C) 2019 PipeWire's COPYRIGHT HOLDER
# This file is distributed under the same license as the PipeWire package.
# scootergrisen, 2019, 2021.
msgid ""
msgstr ""
"Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-04-19 20:26+0200\n"
"Last-Translator: scootergrisen\n"
"Language-Team: Danish\n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Indbygget lyd"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
 070701000000E0000081A4000000000000000000000001656CC35F0000100C000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/de.po   # translation of WirePlumber
# German translation of WirePlumber
# Copyright (C) 2008 WirePlumber
# This file is distributed under the same license as the WirePlumber package.
#
#
# Fabian Affolter <fab@fedoraproject.org>, 2008-2009.
# Micha Pietsch <barney@fedoraproject.org>, 2008, 2009.
# Hedda Peters <hpeters@redhat.com>, 2009, 2012.
# Mario Blättermann <mario.blaettermann@gmail.com>, 2016.
# Jürgen Benvenuti <gastornis@posteo.org>, 2023.
#
msgid ""
msgstr ""
"Project-Id-Version: WirePlumber\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
"issues\n"
"POT-Creation-Date: 2023-01-30 15:31+0000\n"
"PO-Revision-Date: 2023-01-28 20:55+0100\n"
"Last-Translator: Jürgen Benvenuti <gastornis@posteo.org>\n"
"Language-Team: German <gnome-de@gnome.org>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Poedit 3.1.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:234
msgid "Loopback"
msgstr "Schleifenschaltung"

#: src/scripts/monitors/alsa.lua:236
msgid "Built-in Audio"
msgstr "Internes Audio"

#: src/scripts/monitors/alsa.lua:238
msgid "Modem"
msgstr "Modem"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Interne vordere Kamera"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Interne hintere Kamera"
070701000000E1000081A4000000000000000000000001656CC35F000007BC000000000000000000000000000000000000001F00000000wireplumber-0.4.17/po/de_CH.po    # German translation of pipewire
# Copyright (C) 2008 pipewire
# This file is distributed under the same license as the pipewire package.
#
# Micha Pietsch <barney@fedoraproject.org>, 2008, 2009.
# Fabian Affolter <fab@fedoraproject.org>, 2008-2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:53+0000\n"
"Last-Translator: Fabian Affolter <fab@fedoraproject.org>\n"
"Language-Team: German <fedora-trans-de@redhat.com>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-Language: German\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Internes Audio"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
070701000000E2000081A4000000000000000000000001656CC35F00000813000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/el.po   # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Dimitris Glezos <dimitris@glezos.com>, 2008.
# Thalia Papoutsaki <saliyath@gmail.com>, 2009, 2012.
# Dimitris Spingos (Δημήτρης Σπίγγος) <dmtrs32@gmail.com>, 2013, 2014.
msgid ""
msgstr ""
"Project-Id-Version: el\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2014-05-08 09:53+0300\n"
"Last-Translator: Dimitris Spingos (Δημήτρης Σπίγγος) <dmtrs32@gmail.com>\n"
"Language-Team: team@lists.gnome.gr\n"
"Language: el\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Virtaal 0.7.0\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Εσωτερικός ήχος"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Μόντεμ"
 070701000000E3000081A4000000000000000000000001656CC35F000007A9000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/eo.po   # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the pipewire package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-02-05 01:40+0000\n"
"Last-Translator: Carmen Bianca Bakker <carmen@carmenbianca.eu>\n"
"Language-Team: Esperanto <https://translate.fedoraproject.org/projects/"
"pipewire/pipewire/eo/>\n"
"Language: eo\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.4.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Integrita sono"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr ""
   070701000000E4000081A4000000000000000000000001656CC35F0000091B000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/es.po   # Fedora Spanish translation of PipeWire.
# This file is distributed under the same license as the PipeWire Package.
#
# Domingo Becker <domingobecker@gmail.com>, 2009.
# Héctor Daniel Cabrera <h.daniel.cabrera@gmail.com>, 2009.
# Fernando Gonzalez Blanco <fgonz@fedoraproject.org>, 2009, 2012.
# Alberto Castillo <acg@albertocg.com>, 2016. #zanata
# Gladys Guerrero Lozano <gguerrer@redhat.com>, 2016. #zanata
# Máximo Castañeda Riloba <mcrcctm@gmail.com>, 2016. #zanata
# Wim Taymans <wim.taymans@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2020-10-01 15:30+0000\n"
"Last-Translator: Emilio Herrera <ehespinosa57@gmail.com>\n"
"Language-Team: Spanish <https://translate.fedoraproject.org/projects/"
"pipewire/pipewire/es/>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.2.2\n"
"X-Poedit-Language: Spanish\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Audio Interno"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Módem"
 070701000000E5000081A4000000000000000000000001656CC35F00000F17000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/fa.po   # Persian translation for WirePlumber.
# Copyright (C) 2022 WirePlumber's COPYRIGHT HOLDER
# This file is distributed under the same license as the WirePlumber package.
# Danial Behzadi <dani.behzi@ubuntu.com>, 2022-2023.
#
msgid ""
msgstr ""
"Project-Id-Version: WirePlumber master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
"issues\n"
"POT-Creation-Date: 2023-10-06 03:31+0000\n"
"PO-Revision-Date: 2023-10-06 16:22+0330\n"
"Last-Translator: Danial Behzadi <dani.behzi@ubuntu.com>\n"
"Language-Team: Persian <fa@li.org>\n"
"Language: fa\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.3.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:236
msgid "Loopback"
msgstr "حلقهٔ معکوس"

#: src/scripts/monitors/alsa.lua:238
msgid "Built-in Audio"
msgstr "صدای توکار"

#: src/scripts/monitors/alsa.lua:240
msgid "Modem"
msgstr "مودم"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "دوربین جلوی توکار"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "دوربین عقب توکار"
 070701000000E6000081A4000000000000000000000001656CC35F000007EA000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/fi.po   # pipewire translation to Finnish (fi).
# Copyright (C) 2008 Timo Jyrinki
# This file is distributed under the same license as the pipewire package.
# Timo Jyrinki <timo.jyrinki@iki.fi>, 2008.
# Ville-Pekka Vainio <vpivaini@cs.helsinki.fi>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: git trunk\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-04-18 16:38+0300\n"
"Last-Translator: Ricky Tigg <ricky.tigg@gmail.com>\n"
"Language-Team: Finnish <https://translate.fedoraproject.org/projects/"
"pipewire/pipewire/fi/>\n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.5.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Sisäinen äänentoisto"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modeemi"
  070701000000E7000081A4000000000000000000000001656CC35F00000940000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/fr.po   # French translation of pipewire.
# Copyright (C) 2006-2008 Lennart Poettering
# This file is distributed under the same license as the pipewire package.
#
#
# Robert-André Mauchin <zebob.m@pengzone.org>, 2008.
# Michaël Ughetto <telimektar esraonline com>, 2008.
# Pablo Martin-Gomez <pablo.martin-gomez@laposte.net>, 2008.
# Corentin Perard <corentin.perard@gmail.com>, 2009.
# Thomas Canniot <mrtom@fedoraproject.org>, 2009, 2012.
# Sam Friedmann <sfriedma@redhat.com>, 2016. #zanata
# Wim Taymans <wim.taymans@gmail.com>, 2016. #zanata
# Edouard Duliege <edouard.duliege@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2020-12-13 17:35+0000\n"
"Last-Translator: Julien Humbert <julroy67@gmail.com>\n"
"Language-Team: French <https://translate.fedoraproject.org/projects/pipewire/"
"pipewire/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.3.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Audio interne"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
070701000000E8000081A4000000000000000000000001656CC35F000007FB000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/gl.po   # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Translators:
# bassball93 <bassball93@gmail.com>, 2011.
# mbouzada <mbouzada@gmail.com>, 2011.
# Fran Dieguez <frandieguez@gnome.org>, 2012, 2019.
# Marcos Lans <marcoslansgarza@gmail.com>, 2018.
msgid ""
msgstr ""
"Project-Id-Version: PipeWire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2019-02-20 01:36+0200\n"
"Last-Translator: Fran Dieguez <frandieguez@gnome.org>\n"
"Language-Team: Galician\n"
"Language: gl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Virtaal 0.7.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Audio interno"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Módem"
 070701000000E9000081A4000000000000000000000001656CC35F00000792000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/gu.po   # translation of PipeWire.po to Gujarati
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Sweta Kothari <swkothar@redhat.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: PipeWire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:53+0000\n"
"Last-Translator: Sweta Kothari <swkothar@redhat.com>\n"
"Language-Team: Gujarati\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: KBabel 1.11.4\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "આંતરિક ઓડિયો"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "મોડેમ"
  070701000000EA000081A4000000000000000000000001656CC35F0000074C000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/he.po   #
# Elad <el.il@doom.co.il>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-03-02 14:40+0000\n"
"Last-Translator: Yaron Shahrabani <sh.yaron@gmail.com>\n"
"Language-Team: Hebrew <https://translate.fedoraproject.org/projects/pipewire/"
"pipewire/he/>\n"
"Language: he\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Weblate 4.4.2\n"
"X-Poedit-Language: Hebrew\n"
"X-Poedit-Country: Israel\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "צליל פנימי"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "מודם"
070701000000EB000081A4000000000000000000000001656CC35F0000058D000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/hi.po   # translation of Wireplumber.master to Hindi
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Rajesh Ranjan <rajesh672@gmail.com>, 2009, 2012.
# Hemish <hemish04082005@gmail.com>, 2022

msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
"issues\n"
"POT-Creation-Date: 2022-07-08 03:32+0000\n"
"PO-Revision-Date: 2022-12-04 12:27+0530\n"
"Last-Translator: Hemish <hemish04082005@gmail.com>\n"
"Language-Team: Hindi <indlinux-hindi@lists.sourceforge.net> https://indlinux.org/hindi\n"
"Language: hi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT

#: src/scripts/monitors/alsa.lua:228
msgid "Built-in Audio"
msgstr "आंतरिक ऑडियो"

#: src/scripts/monitors/alsa.lua:230
msgid "Modem"
msgstr "मॉडेम"

#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "आंतरिक आगे का कैमरा"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "आंतरिक पीछे का कैमरा"
   070701000000EC000081A4000000000000000000000001656CC35F0000082E000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/hr.po   # Croatian translation for pipewire
# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
# This file is distributed under the same license as the pipewire package.
# gogo <trebelnik2@gmail.com>, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2022-04-03 12:57+0200\n"
"Last-Translator: gogo <trebelnik2@gmail.com>\n"
"Language-Team: Croatian <https://translate.fedoraproject.org/projects/"
"pipewire/pipewire/hr/>\n"
"Language: hr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Poedit 2.3\n"
"X-Launchpad-Export-Date: 2017-04-20 21:04+0000\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Ugrađeni zvuk"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
  070701000000ED000081A4000000000000000000000001656CC35F00000FE0000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/hu.po   # Hungarian translation for PipeWire.
# Copyright (C) 2012, 2016. Free Software Foundation, Inc.
# This file is distributed under the same license as the PipeWire package.
#
# KAMI <kami911 at gmail dot com>, 2012.
# Gabor Kelemen <kelemeng at ubuntu dot com>, 2016.
# Balázs Úr <ur.balazs at fsf dot hu>, 2016, 2022.
msgid ""
msgstr ""
"Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/is"
"sues\n"
"POT-Creation-Date: 2022-07-08 03:32+0000\n"
"PO-Revision-Date: 2022-09-21 07:31+0200\n"
"Last-Translator: Balázs Úr <ur.balazs at fsf dot hu>\n"
"Language-Team: Hungarian <https://translate.fedoraproject.org/projects/pipewir"
"e/pipewire/hu/>\n"
"Language: hu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 19.12.3\n"
"X-Poedit-Language: Hungarian\n"
"X-Poedit-Country: HUNGARY\n"
"X-Poedit-SourceCharset: utf-8\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:228
msgid "Built-in Audio"
msgstr "Belső hangforrás"

#: src/scripts/monitors/alsa.lua:230
msgid "Modem"
msgstr "Modem"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Beépített elülső kamera"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Beépített hátsó kamera"
070701000000EE000081A4000000000000000000000001656CC35F00000797000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/id.po   # Indonesian translation of pipewire
# Copyright (C) 2011 THE pipewire'S COPYRIGHT HOLDER
# This file is distributed under the same license as the pipewire package.
#
# Translators:
# Andika Triwidada <andika@gmail.com>, 2011, 2012, 2018.
msgid ""
msgstr ""
"Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-08-11 10:50+0700\n"
"Last-Translator: Andika Triwidada <andika@gmail.com>\n"
"Language-Team: Indonesia <id@li.org>\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n>1;\n"
"X-Generator: Poedit 2.2.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Audio Bawaan"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
 070701000000EF000081A4000000000000000000000001656CC35F00000861000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/it.po   # Italian translation for PipeWire.
# Copyright (C) 2008, 2009, 2012, 2015, 2019 The Free Software Foundation, Inc
# This file is distributed under the same license as the pipewire package.
#
# Luca Ferretti <elle.uca@libero.it>, 2008, 2009.
# mario_santagiuliana <mario at marionline.it>, 2009.
# Milo Casagrande <milo@ubuntu.com>, 2009, 2012, 2015, 2019.
# Albano Battistella <albano_battistella@hotmail.com>,2021
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-05-09 13:29+0100\n"
"Last-Translator: Albano Battistella <albano_battistella@hotmail.com>\n"
"Language-Team: Italian <>pipewire/pipewire/it/>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.2.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Audio interno"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
   070701000000F0000081A4000000000000000000000001656CC35F00000867000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/ja.po   # translation of ja.po to Japanese
# PipeWire
# Copyright (C) 2009.
# This file is distributed under the same license as the PACKAGE package.
#
# Hyu_gabaru Ryu_ichi <hyu_gabaru@yahoo.co.jp>, 2009.
# Kiyoto Hashida <khashida@redhat.com>, 2009, 2012.
# Kenzo Moriguchi <kmoriguc@redhat.com>, 2016. #zanata
# Ooyama Yosiyuki <qqke6wd9k@apricot.ocn.ne.jp>, 2016. #zanata
# Wim Taymans <wim.taymans@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2016-03-15 08:25+0000\n"
"Last-Translator: Kenzo Moriguchi <kmoriguc@redhat.com>\n"
"Language-Team: Japanese <jp@li.org>\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Zanata 4.6.2\n"
"Plural-Forms: Plural-Forms: nplurals=1; plural=0;\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "内部オーディオ"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "モデム"
 070701000000F1000081A4000000000000000000000001656CC35F00000F41000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/ka.po   # Georgian translation for pipewire.
# Copyright © 2008-2022 Free Software Foundation, Inc.
# This file is distributed under the same license as the pipewire package.
# Temuri Doghonadze <temuri.doghonadze@gmail.com>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
"issues\n"
"POT-Creation-Date: 2022-06-15 15:30+0000\n"
"PO-Revision-Date: 2022-07-25 13:53+0200\n"
"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n"
"Language-Team: Georgian <(nothing)>\n"
"Language: ka\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.1.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:220
msgid "Built-in Audio"
msgstr "ჩაშენებული აუდიო"

#: src/scripts/monitors/alsa.lua:222
msgid "Modem"
msgstr "მოდემი"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "ჩაშენებული წინა კამერა"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "ჩაშენებული უკანა კამერა"
   070701000000F2000081A4000000000000000000000001656CC35F00000782000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/kk.po   # Kazakh translation of pipewire.
# Copyright (C) 2020 The pipewire authors.
# This file is distributed under the same license as the pipewire package.
# Baurzhan Muftakhidinov <baurthefirst@gmail.com>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2020-06-30 08:04+0500\n"
"Last-Translator: Baurzhan Muftakhidinov <baurthefirst@gmail.com>\n"
"Language-Team: \n"
"Language: kk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.3.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Құрамындағы аудио"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Модем"
  070701000000F3000081A4000000000000000000000001656CC35F000007C9000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/kn.po   # translation of pipewire.master-tx.kn.po to Kannada
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Shankar Prasad <svenkate@redhat.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx.kn\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:54+0000\n"
"Last-Translator: Shankar Prasad <svenkate@redhat.com>\n"
"Language-Team: Kannada <kde-l10n-kn@kde.org>\n"
"Language: kn\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 1.0\n"
"Plural-Forms:  nplurals=2; plural=(n != 1);\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "ಆಂತರಿಕ ಆಡಿಯೊ"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "ಮಾಡೆಮ್"
   070701000000F4000081A4000000000000000000000001656CC35F00000722000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/ko.po   # eukim <eukim@redhat.com>, 2013. #zanata
# KimJeongYeon <jeongyeon.kim@samsung.com>, 2017.
# Sangchul Lee <sc11.lee@samsung.com>, 2018.
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2018-06-21 15:10+0900\n"
"Last-Translator: Sangchul Lee <sc11.lee@samsung.com>\n"
"Language-Team: Korean\n"
"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.8.7.1\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "내장 오디오 "

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "모뎀 "
  070701000000F5000081A4000000000000000000000001656CC35F0000075D000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/lt.po   # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Moo, 2017-2019
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2019-09-01 16:15+0300\n"
"Last-Translator: Moo\n"
"Language-Team: \n"
"Language: lt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.1\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"(n%100<10 || n%100>=20) ? 1 : 2);\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Įtaisytas garsas"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modemas"
   070701000000F6000081A4000000000000000000000001656CC35F00000190000000000000000000000000000000000000002200000000wireplumber-0.4.17/po/meson.build i18n = import('i18n')

i18n.gettext(
  meson.project_name(),
  preset: 'glib',
  # Page width is set to 90 characters in order to avoid bad wrapping of the
  # bug reporting address.
  args: ['--msgid-bugs-address=https://gitlab.freedesktop.org/pipewire/wireplumber/issues/new',
         '--width=90', '--keyword=I18n.gettext:1', '--keyword=I18n.ngettext:1,2']
)

po_dir = meson.current_source_dir()
070701000000F7000081A4000000000000000000000001656CC35F0000069D000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/ml.po   #
#  <>, YEAR, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx.ml\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:41+0000\n"
"Last-Translator: \n"
"Language-Team:  <en@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: KBabel 1.11.4\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "ഇന്റേര്‍ണല്‍ ഓഡിയോ"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "മോഡം"
   070701000000F8000081A4000000000000000000000001656CC35F000007C6000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/mr.po   # translation of pipewire.master-tx.po to Marathi
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Sandeep Shedmake <sshedmak@redhat.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:54+0000\n"
"Last-Translator: Sandeep Shedmake <sshedmak@redhat.com>\n"
"Language-Team: Marathi <fedora-trans-mr@redhat.com>\n"
"Language: mr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: KBabel 1.11.4\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "आंतरीक ऑडिओ"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "मोडेम"
  070701000000F9000081A4000000000000000000000001656CC35F000007CE000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/my.po   # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire-master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-08-26 21:52+0630\n"
"Last-Translator: zayar lwin <lw1nzayar@yandex.com>\n"
"Language-Team: lw1nzayar@yandex.com\n"
"Language: my\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.4.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "နဂိုတည်းကထည့်သွင်းထားသည်ံ့ အသံကရိယာ"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "ဆ.သ.ရ-စက်"
  070701000000FA000081A4000000000000000000000001656CC35F00000812000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/nl.po   # Dutch translation of pipewire.master-tx.
# Copyright (C) 2009 THE pipewire.master-tx'S COPYRIGHT HOLDER
# This file is distributed under the same license as the pipewire.master-tx package.
# Geert Warrink <geert.warrink@onsnet.nu>, 2009.
# Reinout van Schouwen <reinout@gmail.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2020-07-11 20:27+0000\n"
"Last-Translator: Geert Warrink <geert.warrink@onsnet.nu>\n"
"Language-Team: Dutch <https://translate.fedoraproject.org/projects/pipewire/"
"pipewire/nl/>\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.1.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Intern geluid"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
  070701000000FB000081A4000000000000000000000001656CC35F000007C2000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/nn.po   # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Karl Ove Hufthammer <karl@huftis.org>, 2017.
# Nicolai Syvertsen <saivert@saivert.com> 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-02-07 15:40+0000\n"
"Last-Translator: Karl Ove Hufthammer <karl@huftis.org>\n"
"Language-Team: Norwegian Nynorsk <https://translate.fedoraproject.org/"
"projects/pipewire/pipewire/nn/>\n"
"Language: nn\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.4.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Innebygd lyd"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
  070701000000FC000081A4000000000000000000000001656CC35F00000909000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/oc.po   # French translation of pipewire.
# Copyright (C) 2006-2008 Lennart Poettering
# This file is distributed under the same license as the pipewire package.
# Robert-André Mauchin <zebob.m@pengzone.org>, 2008.
# Michaël Ughetto <telimektar esraonline com>, 2008.
# Pablo Martin-Gomez <pablo.martin-gomez@laposte.net>, 2008.
# Corentin Perard <corentin.perard@gmail.com>, 2009.
# Thomas Canniot <mrtom@fedoraproject.org>, 2009, 2012.
# Cédric Valmary (Tot en Òc) <cvalmary@yahoo.fr>, 2015.
# Cédric Valmary (totenoc.eu) <cvalmary@yahoo.fr>, 2016.
msgid ""
msgstr ""
"Project-Id-Version: pipewire trunk\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2016-10-12 22:20+0200\n"
"Last-Translator: Cédric Valmary (totenoc.eu) <cvalmary@yahoo.fr>\n"
"Language-Team: Tot En Òc\n"
"Language: oc\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Virtaal 0.7.1\n"
"X-Launchpad-Export-Date: 2016-10-12 20:12+0000\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Àudio integrat"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modèm"
   070701000000FD000081A4000000000000000000000001656CC35F0000081E000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/or.po   # translation of pipewire.master-tx.or.po to Oriya
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Manoj Kumar Giri <mgiri@redhat.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx.or\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:55+0000\n"
"Last-Translator: Manoj Kumar Giri <mgiri@redhat.com>\n"
"Language-Team: Oriya <oriya-it@googlegroups.com>\n"
"Language: or\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: KBabel 1.11.4\n"
"Plural-Forms:  nplurals=2; plural=(n!=1);\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "ଆଭ୍ୟନ୍ତରୀଣ ଧ୍ୱନି"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "ମଡେମ"
  070701000000FE000081A4000000000000000000000001656CC35F00000822000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/pa.po   # translation of pipewire.master-tx.pa.po to Punjabi
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Amanpreet Singh Alam <aalam@users.sf.net>, 2008.
# A S Alam <aalam@users.sf.net>, 2009.
# Jaswinder Singh <jsingh@redhat.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx.pa\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:55+0000\n"
"Last-Translator: Jaswinder Singh <jsingh@redhat.com>\n"
"Language-Team: Punjabi/Panjabi <kde-i18n-doc@kde.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 1.0\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "ਅੰਦਰੂਨੀ ਆਡੀਓ"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "ਮਾਡਮ"
  070701000000FF000081A4000000000000000000000001656CC35F00000F52000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/pl.po   # Polish translation for wireplumber.
# Copyright © 2008-2023 the wireplumber authors.
# This file is distributed under the same license as the wireplumber package.
# Piotr Drąg <piotrdrag@gmail.com>, 2008, 2012-2023.
#
msgid ""
msgstr ""
"Project-Id-Version: wireplumber\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
"issues\n"
"POT-Creation-Date: 2023-03-04 13:34+0000\n"
"PO-Revision-Date: 2023-03-04 14:35+0100\n"
"Last-Translator: Piotr Drąg <piotrdrag@gmail.com>\n"
"Language-Team: Polish <community-poland@mozilla.org>\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:236
msgid "Loopback"
msgstr "Urządzenie zwrotne"

#: src/scripts/monitors/alsa.lua:238
msgid "Built-in Audio"
msgstr "Wbudowany dźwięk"

#: src/scripts/monitors/alsa.lua:240
msgid "Modem"
msgstr "Modem"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Wbudowana przednia kamera"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Wbudowana tylna kamera"
  07070100000100000081A4000000000000000000000001656CC35F00000875000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/pt.po   # Portuguese translation for pipewire.
# Copyright © 2008-2022 the pipewire authors.
# This file is distributed under the same license as the pipewire package.
# Rui Gouveia <rui.gouveia@globaltek.pt>, 2012.
# Rui Gouveia <rui.gouveia@globaltek.pt>, 2012, 2015.
# Pedro Albuquerque <palbuquerque73@openmailbox.com>, 2015.
# Juliano de Souza Camargo <julianosc@pm.me>, 2020.
# Hugo Carvalho <hugokarvalho@hotmail.com>, 2021 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2022-03-30 19:37+0100\n"
"Last-Translator: Hugo Carvalho <hugokarvalho@hotmail.com>\n"
"Language-Team: Portuguese <https://l10n.gnome.org/teams/pt/>\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Áudio interno"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
   07070100000101000081A4000000000000000000000001656CC35F00000825000000000000000000000000000000000000001F00000000wireplumber-0.4.17/po/pt_BR.po    # Brazilian Portuguese translation for pipewire
# Copyright (C) 2021 Rafael Fontenelle <rafaelff@gnome.org>
# This file is distributed under the same license as the pipewire package.
# Fabian Affolter <fab@fedoraproject.org>, 2008.
# Igor Pires Soares <igor@projetofedora.org>, 2009, 2012.
# Rafael Fontenelle <rafaelff@gnome.org>, 2013-2021.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-08-01 17:02-0300\n"
"Last-Translator: Rafael Fontenelle <rafaelff@gnome.org>\n"
"Language-Team: Brazilian Portuguese <gnome-pt_br-list@gnome.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
"X-Generator: Gtranslator 40.0\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Áudio interno"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
   07070100000102000081A4000000000000000000000001656CC35F00000773000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/ro.po   # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the pipewire package.
#
# Sergiu Bivol <sergiu@cip.md>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2022-02-05 12:06+0000\n"
"Last-Translator: Sergiu Bivol <sergiu@cip.md>\n"
"Language-Team: Romanian\n"
"Language: ro\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < "
"20)) ? 1 : 2;\n"
"X-Generator: Lokalize 21.12.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Audio încorporat"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
 07070100000103000081A4000000000000000000000001656CC35F00000FF3000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/ru.po   # Russian translation of pipewire.
# Copyright (C) 2010 pipewire
# This file is distributed under the same license as the pipewire package.
#
# Leonid Kurbatov <llenchikk@rambler.ru>, 2010, 2012.
# Alexander Potashev <aspotashev@gmail.com>, 2014, 2019.
# Victor Ryzhykh <victorr2007@yandex.ru>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
"issues\n"
"POT-Creation-Date: 2023-04-06 03:34+0000\n"
"PO-Revision-Date: 2023-04-06 11:08+0300\n"
"Last-Translator: Aleksandr Melman <Alexmelman88@gmail.com>\n"
"Language-Team: ru\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Poedit 3.2.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:236
msgid "Loopback"
msgstr "Петля"

#: src/scripts/monitors/alsa.lua:238
msgid "Built-in Audio"
msgstr "Встроенное аудио"

#: src/scripts/monitors/alsa.lua:240
msgid "Modem"
msgstr "Модем"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Встроенная фронтальная камера"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Встроенная задняя камера"
 07070100000104000081A4000000000000000000000001656CC35F00000718000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/si.po   # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the pipewire package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: si\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr ""

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr ""
07070100000105000081A4000000000000000000000001656CC35F00000F1C000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/sk.po   # Slovak translation for PipeWire.
# Copyright (C) 2014 PipeWire's COPYRIGHT HOLDER
# This file is distributed under the same license as the PipeWire package.
# Dušan Kazik <prescott66@gmail.com>, 2014-2022.
#
msgid ""
msgstr ""
"Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
"issues\n"
"POT-Creation-Date: 2022-07-08 03:32+0000\n"
"PO-Revision-Date: 2022-11-05 11:45+0100\n"
"Last-Translator: Dušan Kazik <prescott66@gmail.com>\n"
"Language-Team: Slovak <https://translate.fedoraproject.org/projects/pipewire/"
"pipewire/sk/>\n"
"Language: sk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n"
"X-Generator: Poedit 3.1.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:228
msgid "Built-in Audio"
msgstr "Vstavaný zvuk"

#: src/scripts/monitors/alsa.lua:230
msgid "Modem"
msgstr "Modem"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Vstavaná predná kamera"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Vstavaná zadná kamera"
07070100000106000081A4000000000000000000000001656CC35F0000081A000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/sr.po   # Serbian translations for pipewire
# Copyright (C) 2006 Lennart Poettering
# This file is distributed under the same license as the pipewire package.
# Igor Miletic (Игор Милетић) <grejigl-gnomeprevod@yahoo.ca>, 2009.
# Miloš Komarčević <kmilos@gmail.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:55+0000\n"
"Last-Translator: Miloš Komarčević <kmilos@gmail.com>\n"
"Language-Team: Serbian (sr) <fedora-trans-sr@redhat.com>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Унутрашњи звук"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Модем"
  07070100000107000081A4000000000000000000000001656CC35F00000807000000000000000000000000000000000000002200000000wireplumber-0.4.17/po/sr@latin.po # Serbian(Latin) translations for pipewire
# Copyright (C) 2006 Lennart Poettering
# This file is distributed under the same license as the pipewire package.
# Igor Miletic (Igor Miletić) <grejigl-gnomeprevod@yahoo.ca>, 2009.
# Miloš Komarčević <kmilos@gmail.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:55+0000\n"
"Last-Translator: Miloš Komarčević <kmilos@gmail.com>\n"
"Language-Team: Serbian (sr) <fedora-trans-sr@redhat.com>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Unutrašnji zvuk"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
 07070100000108000081A4000000000000000000000001656CC35F00001058000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/sv.po   # Swedish translation for pipewire.
# Copyright © 2008-2022 Free Software Foundation, Inc.
# This file is distributed under the same license as the pipewire package.
# Daniel Nylander <po@danielnylander.se>, 2008, 2012.
# Josef Andersson <josef.andersson@fripost.org>, 2014, 2017.
# Anders Jonsson <anders.jonsson@norsjovallen.se>, 2021, 2022.
#
# Termer:
# input/output: ingång/utgång (det handlar om ljud)
# latency: latens
# delay: fördröjning
# boost: öka
# gain: förstärkning
# channel map: kanalmappning
# passthrough: genomströmning
# och en hel del termer som inte översätts inom ljuddomänen, ex. surround.
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
"issues\n"
"POT-Creation-Date: 2022-06-15 15:30+0000\n"
"PO-Revision-Date: 2022-06-15 21:11+0200\n"
"Last-Translator: Anders Jonsson <anders.jonsson@norsjovallen.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"Language: sv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Poedit 3.0.1\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:220
msgid "Built-in Audio"
msgstr "Inbyggt ljud"

#: src/scripts/monitors/alsa.lua:222
msgid "Modem"
msgstr "Modem"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Inbyggd främre kamera"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Inbyggd bakre kamera"
07070100000109000081A4000000000000000000000001656CC35F0000082B000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/ta.po   # translation of pipewire.master-tx.ta.po to Tamil
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# I. Felix <ifelix@redhat.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx.ta\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:56+0000\n"
"Last-Translator: I. Felix <ifelix@redhat.com>\n"
"Language-Team: Tamil <fedora-trans-ta@redhat.com>\n"
"Language: ta\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: KBabel 1.11.4\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\\n\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "உட்புற ஆடியோ"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "மாதிரி"
 0707010000010A000081A4000000000000000000000001656CC35F000007E6000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/te.po   # translation of pipewire.master-tx.te.po to Telugu
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Krishna Babu K <kkrothap@redhat.com>, 2009, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx.te\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2012-01-30 09:56+0000\n"
"Last-Translator: Krishna Babu K <kkrothap@redhat.com>\n"
"Language-Team: Telugu <en@li.org>\n"
"Language: te\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: KBabel 1.11.4\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "అంతర్గత ఆడియో"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "మోడెమ్"
  0707010000010B000081A4000000000000000000000001656CC35F0000080B000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/tr.po   # Turkish translation for PipeWire.
# Copyright (C) 2014 PipeWire's COPYRIGHT HOLDER
# This file is distributed under the same license as the PipeWire package.
# Necdet Yücel <necdetyucel@gmail.com>, 2014.
# Kaan Özdinçer <kaanozdincer@gmail.com>, 2014.
# Muhammet Kara <muhammetk@gmail.com>, 2015, 2016, 2017.
# Oğuz Ersen <oguzersen@protonmail.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-12-06 21:31+0300\n"
"Last-Translator: Oğuz Ersen <oguzersen@protonmail.com>\n"
"Language-Team: Turkish <tr>\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 4.4.2\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "Dahili Ses"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "Modem"
 0707010000010C000081A4000000000000000000000001656CC35F00000F97000000000000000000000000000000000000001C00000000wireplumber-0.4.17/po/uk.po   # Copyright (C) 2009 Free Software Foundation, Inc.
# This file is distributed under the same license as the pipewire package.
#
# Yuri Chornoivan <yurchor@ukr.net>, 2009-2021, 2022, 2023.
msgid ""
msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/i"
"ssues\n"
"POT-Creation-Date: 2022-12-13 15:30+0000\n"
"PO-Revision-Date: 2023-02-15 22:44+0200\n"
"Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n"
"Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<"
"=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Lokalize 20.12.0\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. unique device/node name tables
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply VM overrides
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:234
msgid "Loopback"
msgstr "Петля"

#: src/scripts/monitors/alsa.lua:236
msgid "Built-in Audio"
msgstr "Вбудоване аудіо"

#: src/scripts/monitors/alsa.lua:238
msgid "Modem"
msgstr "Модем"

#. ensure the device has a nick
#. set the icon name
#. form factor -> icon
#. apply properties from config.rules
#. override the device factory to use ACP
#. use device reservation, if available
#. unlike pipewire-media-session, this logic here keeps the device
#. acquired at all times and destroys it if someone else acquires
#. create the device
#. attempt to acquire again
#. destroy the device
#. TODO enable the jack device
#. TODO disable the jack device
#. create the device
#. handle create-object to prepare device
#. handle object-removed to destroy device reservations and recycle device name
#. reset the name tables to make sure names are recycled
#. activate monitor
#. create the JACK device (for PipeWire to act as client to a JACK server)
#. enable device reservation if requested
#. if the reserve-device plugin is enabled, at the point of script execution
#. it is expected to be connected. if it is not, assume the d-bus connection
#. has failed and continue without it
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
#. case D-Bus service is restarted
#. create the monitor
#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. set the node name
#. sanitize name
#. deduplicate nodes with the same name
#. set the node description
#: src/scripts/monitors/libcamera.lua:88
msgid "Built-in Front Camera"
msgstr "Вбудована передня камера"

#: src/scripts/monitors/libcamera.lua:90
msgid "Built-in Back Camera"
msgstr "Вбудована задня камера"
 0707010000010D000081A4000000000000000000000001656CC35F00000880000000000000000000000000000000000000001F00000000wireplumber-0.4.17/po/zh_CN.po    # Simplified Chinese translation for PipeWire.
# Copyright (C) 2008 PULSEAUDIO COPYRIGHT HOLDER
# This file is distributed under the same license as the pipewire package.
# 闫丰刚 <sainry@gmail.com>, 2008, 2009.
# Leah Liu <lliu@redhat.com>, 2009, 2012.
# Cheng-Chia Tseng <pswo10680@gmail.com>, 2010, 2012.
# Frank Hill <hxf.prc@gmail.com>, 2015.
# Mingye Wang (Arthur2e5) <arthur200126@gmail.com>, 2015.
#
msgid ""
msgstr ""
"Project-Id-Version: pipewire.master-tx\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2021-04-18 10:56+0800\n"
"Last-Translator: Huang-Huang Bao <i@eh5.me>\n"
"Language-Team: Huang-Huang Bao <i@eh5.me>\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2016-03-22 13:23+0000\n"
"X-Generator: Poedit 2.4.1\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "内置音频"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "调制解调器"
0707010000010E000081A4000000000000000000000001656CC35F000007CC000000000000000000000000000000000000001F00000000wireplumber-0.4.17/po/zh_TW.po    # Chinese (Taiwan) translation for pipewire.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Cheng-Chia Tseng <pswo10680@gmail.com>, 2010, 2012.
# pan93412 <pan93412@gmail.com>, 2020.
msgid ""
msgstr ""
"Project-Id-Version: PipeWire Volume Control\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
"issues/new\n"
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
"PO-Revision-Date: 2020-01-11 13:49+0800\n"
"Last-Translator: pan93412 <pan93412@gmail.com>\n"
"Language-Team: Chinese <zh-l10n@lists.linux.org.tw>\n"
"Language: zh_TW\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 19.12.0\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. WirePlumber
#.
#. Copyright © 2021 Collabora Ltd.
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
#.
#. SPDX-License-Identifier: MIT
#. Receive script arguments from config.lua
#. ensure config.properties is not nil
#. preprocess rules and create Interest objects
#. applies properties from config.rules when asked to
#. set the device id and spa factory name; REQUIRED, do not change
#. set the default pause-on-idle setting
#. try to negotiate the max ammount of channels
#. set priority
#. ensure the node has a media class
#. ensure the node has a name
#. sanitize name
#. deduplicate nodes with the same name
#. and a nick
#. also sanitize nick, replace ':' with ' '
#. ensure the node has a description
#. also sanitize description, replace ':' with ' '
#. add api.alsa.card.* properties for rule matching purposes
#. apply properties from config.rules
#. create the node
#. ensure the device has an appropriate name
#. deduplicate devices with the same name
#. ensure the device has a description
#: src/scripts/monitors/alsa.lua:222
msgid "Built-in Audio"
msgstr "內部音效"

#: src/scripts/monitors/alsa.lua:224
msgid "Modem"
msgstr "數據機"
0707010000010F000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001700000000wireplumber-0.4.17/src    07070100000110000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001E00000000wireplumber-0.4.17/src/config 07070100000111000081A4000000000000000000000001656CC35F00000A53000000000000000000000000000000000000002D00000000wireplumber-0.4.17/src/config/bluetooth.conf  # WirePlumber daemon context configuration #

context.properties = {
  ## Properties to configure the PipeWire context and some modules

  application.name = "WirePlumber Bluetooth"
  log.level = 2
  wireplumber.script-engine = lua-scripting
  wireplumber.export-core = true

  #mem.mlock-all = false
  #support.dbus  = true
}

context.spa-libs = {
  #<factory-name regex> = <library-name>
  #
  # Used to find spa factory names. It maps an spa factory name
  # regular expression to a library name that should contain
  # that factory.
  #
  api.bluez5.*    = bluez5/libspa-bluez5
  audio.convert.* = audioconvert/libspa-audioconvert
  support.*       = support/libspa-support
}

context.modules = [
  #{   name = <module-name>
  #    [ args = { <key> = <value> ... } ]
  #    [ flags = [ [ ifexists ] [ nofail ] ]
  #}
  #
  # PipeWire modules to load.
  # If ifexists is given, the module is ignored when it is not found.
  # If nofail is given, module initialization failures are ignored.
  #

  # Uses RTKit to boost the data thread priority. Also allows clamping
  # of utilisation when using the Completely Fair Scheduler on Linux.
  { name = libpipewire-module-rt
      args = {
          nice.level   = -11
          #rt.prio      = 88
          #rt.time.soft = -1
          #rt.time.hard = -1
          #uclamp.min = 0
          #uclamp.max = 1024
      }
      flags = [ ifexists nofail ]
  }

  # The native communication protocol.
  { name = libpipewire-module-protocol-native }

  # Allows creating nodes that run in the context of the
  # client. Is used by all clients that want to provide
  # data to PipeWire.
  { name = libpipewire-module-client-node }

  # Allows creating devices that run in the context of the
  # client. Is used by the session manager.
  { name = libpipewire-module-client-device }

  # Makes a factory for wrapping nodes in an adapter with a
  # converter and resampler.
  { name = libpipewire-module-adapter }

  # Allows applications to create metadata objects. It creates
  # a factory for Metadata objects.
  { name = libpipewire-module-metadata }

  # Provides factories to make session manager objects.
  { name = libpipewire-module-session-manager }

  # Provides factories to make SPA node objects.
  { name = libpipewire-module-spa-node-factory }
]

wireplumber.components = [
  #{ name = <component-name>, type = <component-type> }
  #
  # WirePlumber components to load
  #

  # The lua scripting engine
  { name = libwireplumber-module-lua-scripting, type = module }

  # The lua configuration file
  # Other components are loaded from there
  { name = bluetooth.lua, type = config/lua }
]
 07070100000112000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002E00000000wireplumber-0.4.17/src/config/bluetooth.lua.d 070701000001130000A1FF000000000000000000000001657851B20000001A000000000000000000000000000000000000003F00000000wireplumber-0.4.17/src/config/bluetooth.lua.d/00-functions.lua    ../common/00-functions.lua  07070100000114000081A4000000000000000000000001656CC35F0000019E000000000000000000000000000000000000004800000000wireplumber-0.4.17/src/config/bluetooth.lua.d/30-bluez-midi-monitor.lua   bluez_midi_monitor = {}
bluez_midi_monitor.properties = {}
bluez_midi_monitor.rules = {}

function bluez_midi_monitor.enable()
  if bluez_midi_monitor.enabled == false then
    return
  end

  load_monitor("bluez-midi", {
    properties = bluez_midi_monitor.properties,
    rules = bluez_midi_monitor.rules,
  })

  if bluez_midi_monitor.properties["with-logind"] then
    load_optional_module("logind")
  end
end
  07070100000115000081A4000000000000000000000001656CC35F00000171000000000000000000000000000000000000004300000000wireplumber-0.4.17/src/config/bluetooth.lua.d/30-bluez-monitor.lua    bluez_monitor = {}
bluez_monitor.properties = {}
bluez_monitor.rules = {}

function bluez_monitor.enable()
  if bluez_monitor.enabled == false then
    return
  end

  load_monitor("bluez", {
    properties = bluez_monitor.properties,
    rules = bluez_monitor.rules,
  })

  if bluez_monitor.properties["with-logind"] then
    load_optional_module("logind")
  end
end
   07070100000116000081A4000000000000000000000001656CC35F00001698000000000000000000000000000000000000004200000000wireplumber-0.4.17/src/config/bluetooth.lua.d/50-bluez-config.lua bluez_monitor.enabled = true

bluez_monitor.properties = {
  -- Enabled roles (default: [ a2dp_sink a2dp_source bap_sink bap_source hfp_hf hfp_ag ])
  --
  -- Currently some headsets (Sony WH-1000XM3) are not working with
  -- both hsp_ag and hfp_ag enabled, so by default we enable only HFP.
  --
  -- Supported roles: hsp_hs (HSP Headset),
  --                  hsp_ag (HSP Audio Gateway),
  --                  hfp_hf (HFP Hands-Free),
  --                  hfp_ag (HFP Audio Gateway)
  --                  a2dp_sink (A2DP Audio Sink)
  --                  a2dp_source (A2DP Audio Source)
  --                  bap_sink (LE Audio Basic Audio Profile Sink)
  --                  bap_source (LE Audio Basic Audio Profile Source)
  --["bluez5.roles"] = "[ a2dp_sink a2dp_source bap_sink bap_source hsp_hs hsp_ag hfp_hf hfp_ag ]",

  -- Enabled A2DP codecs (default: all).
  --["bluez5.codecs"] = "[ sbc sbc_xq aac ldac aptx aptx_hd aptx_ll aptx_ll_duplex faststream faststream_duplex ]",

  -- HFP/HSP backend (default: native).
  -- Available values: any, none, hsphfpd, ofono, native
  --["bluez5.hfphsp-backend"] = "native",

  -- HFP/HSP native backend modem (default: none).
  -- Available values: none, any or the modem device string as found in
  --   'Device' property of org.freedesktop.ModemManager1.Modem interface
  --["bluez5.hfphsp-backend-native-modem"] = "none",

  -- HFP/HSP hardware offload SCO support (default: false).
  --["bluez5.hw-offload-sco"] = false,

  -- Properties for the A2DP codec configuration
  --["bluez5.default.rate"] = 48000,
  --["bluez5.default.channels"] = 2,

  -- Register dummy AVRCP player, required for AVRCP volume function.
  -- Disable if you are running mpris-proxy or equivalent.
  --["bluez5.dummy-avrcp-player"] = true,

  -- Opus Pro Audio mode settings
  --["bluez5.a2dp.opus.pro.channels"] = 3,  -- no. channels
  --["bluez5.a2dp.opus.pro.coupled-streams"] = 1,  -- no. joint stereo pairs, see RFC 7845 Sec. 5.1.1
  --["bluez5.a2dp.opus.pro.locations"] = "FL,FR,LFE",  -- audio locations
  --["bluez5.a2dp.opus.pro.max-bitrate"] = 600000,
  --["bluez5.a2dp.opus.pro.frame-dms"] = 50,  -- frame duration in 1/10 ms: 25, 50, 100, 200, 400
  --["bluez5.a2dp.opus.pro.bidi.channels"] = 1,  -- same settings for the return direction
  --["bluez5.a2dp.opus.pro.bidi.coupled-streams"] = 0,
  --["bluez5.a2dp.opus.pro.bidi.locations"] = "FC",
  --["bluez5.a2dp.opus.pro.bidi.max-bitrate"] = 160000,
  --["bluez5.a2dp.opus.pro.bidi.frame-dms"] = 400,

  -- Enable the logind module, which arbitrates which user will be allowed
  -- to have bluetooth audio enabled at any given time (particularly useful
  -- if you are using GDM as a display manager, as the gdm user also launches
  -- pipewire and wireplumber).
  -- This requires access to the D-Bus user session; disable if you are running
  -- a system-wide instance of wireplumber.
  ["with-logind"] = true,

  -- The settings below can be used to override feature enabled status. By default
  -- all of them are enabled. They may also be disabled via the hardware quirk
  -- database, see bluez-hardware.conf
  --["bluez5.enable-sbc-xq"] = true,
  --["bluez5.enable-msbc"] = true,
  --["bluez5.enable-hw-volume"] = true,
}

bluez_monitor.rules = {
  -- An array of matches/actions to evaluate.
  {
    -- Rules for matching a device or node. It is an array of
    -- properties that all need to match the regexp. If any of the
    -- matches work, the actions are executed for the object.
    matches = {
      {
        -- This matches all cards.
        { "device.name", "matches", "bluez_card.*" },
      },
    },
    -- Apply properties on the matched object.
    apply_properties = {
      -- Auto-connect device profiles on start up or when only partial
      -- profiles have connected. Disabled by default if the property
      -- is not specified.
      --["bluez5.auto-connect"] = "[ hfp_hf hsp_hs a2dp_sink hfp_ag hsp_ag a2dp_source ]",

      -- Hardware volume control (default: [ hfp_ag hsp_ag a2dp_source ])
      --["bluez5.hw-volume"] = "[ hfp_hf hsp_hs a2dp_sink hfp_ag hsp_ag a2dp_source ]",

      -- LDAC encoding quality
      -- Available values: auto (Adaptive Bitrate, default)
      --                   hq   (High Quality, 990/909kbps)
      --                   sq   (Standard Quality, 660/606kbps)
      --                   mq   (Mobile use Quality, 330/303kbps)
      --["bluez5.a2dp.ldac.quality"] = "auto",

      -- AAC variable bitrate mode
      -- Available values: 0 (cbr, default), 1-5 (quality level)
      --["bluez5.a2dp.aac.bitratemode"] = 0,

      -- Profile connected first
      -- Available values: a2dp-sink (default), headset-head-unit
      --["device.profile"] = "a2dp-sink",

      -- Opus Pro Audio encoding mode: audio, voip, lowdelay
      --["bluez5.a2dp.opus.pro.application"] = "audio",
      --["bluez5.a2dp.opus.pro.bidi.application"] = "audio",
    },
  },
  {
    matches = {
      {
        -- Matches all sources.
        { "node.name", "matches", "bluez_input.*" },
      },
      {
        -- Matches all sinks.
        { "node.name", "matches", "bluez_output.*" },
      },
    },
    apply_properties = {
      --["node.nick"] = "My Node",
      --["priority.driver"] = 100,
      --["priority.session"] = 100,
      --["node.pause-on-idle"] = false,
      --["resample.quality"] = 4,
      --["channelmix.normalize"] = false,
      --["channelmix.mix-lfe"] = false,
      --["session.suspend-timeout-seconds"] = 5,  -- 0 disables suspend
      --["monitor.channel-volumes"] = false,

      -- Media source role, "input" or "playback"
      -- Defaults to "playback", playing stream to speakers
      -- Set to "input" to use as an input for apps
      --["bluez5.media-source-role"] = "input",
    },
  },
}
07070100000117000081A4000000000000000000000001656CC35F0000069B000000000000000000000000000000000000004700000000wireplumber-0.4.17/src/config/bluetooth.lua.d/50-bluez-midi-config.lua    -- BLE MIDI is currently disabled by default, because it conflicts with
-- the SELinux policy on Fedora 37 and potentially other systems using
-- SELinux. For a workaround, see
-- https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/spa/plugins/bluez5/README-MIDI.md
bluez_midi_monitor.enabled = false

bluez_midi_monitor.properties = {
  -- Enable the logind module, which arbitrates which user will be allowed
  -- to have bluetooth audio enabled at any given time (particularly useful
  -- if you are using GDM as a display manager, as the gdm user also launches
  -- pipewire and wireplumber).
  -- This requires access to the D-Bus user session; disable if you are running
  -- a system-wide instance of wireplumber.
  ["with-logind"] = true,

  -- List of MIDI server node names. Each node name given will create a new instance
  -- of a BLE MIDI service. Typical BLE MIDI instruments have on service instance,
  -- so adding more than one here may confuse some clients. The node property matching
  -- rules below apply also to these servers.
  --["servers"] = { "bluez_midi.server" },
}

bluez_midi_monitor.rules = {
  -- An array of matches/actions to evaluate.
  {
    matches = {
      {
        -- Matches all nodes.
        { "node.name", "matches", "bluez_midi.*" },
      },
    },
    apply_properties = {
      --["node.nick"] = "My Node",
      --["priority.driver"] = 100,
      --["priority.session"] = 100,
      --["node.pause-on-idle"] = false,
      --["session.suspend-timeout-seconds"] = 5,  -- 0 disables suspend
      --["monitor.channel-volumes"] = false,
      --["node.latency-offset-msec"] = -10,  -- delay (<0) input to reduce jitter
    },
  },
}
 07070100000118000081A4000000000000000000000001656CC35F00000033000000000000000000000000000000000000004000000000wireplumber-0.4.17/src/config/bluetooth.lua.d/90-enable-all.lua   bluez_monitor.enable()
bluez_midi_monitor.enable()
 07070100000119000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002500000000wireplumber-0.4.17/src/config/common  0707010000011A000081A4000000000000000000000001656CC35F000003D0000000000000000000000000000000000000003600000000wireplumber-0.4.17/src/config/common/00-functions.lua components = {}

function load_module(m, a)
  assert(type(m) == "string", "module name is mandatory, bail out");
  if not components[m] then
    components[m] = { "libwireplumber-module-" .. m, type = "module", args = a }
  end
end

function load_optional_module(m, a)
  assert(type(m) == "string", "module name is mandatory, bail out");
  if not components[m] then
    components[m] = { "libwireplumber-module-" .. m, type = "module", args = a, optional = true }
  end
end

function load_pw_module(m, a)
  assert(type(m) == "string", "module name is mandatory, bail out");
  if not components[m] then
    components[m] = { "libpipewire-module-" .. m, type = "pw_module", args = a }
  end
end

function load_script(s, a)
  if not components[s] then
    components[s] = { s, type = "script/lua", args = a }
  end
end

function load_monitor(s, a)
  load_script("monitors/" .. s .. ".lua", a)
end

function load_access(s, a)
  load_script("access/access-" .. s .. ".lua", a)
end
0707010000011B000081A4000000000000000000000001656CC35F00000849000000000000000000000000000000000000002800000000wireplumber-0.4.17/src/config/main.conf   # WirePlumber daemon context configuration #

context.properties = {
  ## Properties to configure the PipeWire context and some modules

  #application.name = WirePlumber
  log.level = 2
  wireplumber.script-engine = lua-scripting

  #mem.mlock-all = false
  #support.dbus  = true
}

context.spa-libs = {
  #<factory-name regex> = <library-name>
  #
  # Used to find spa factory names. It maps an spa factory name
  # regular expression to a library name that should contain
  # that factory.
  #
  api.alsa.*      = alsa/libspa-alsa
  api.v4l2.*      = v4l2/libspa-v4l2
  audio.convert.* = audioconvert/libspa-audioconvert
  support.*       = support/libspa-support
}

context.modules = [
  #{   name = <module-name>
  #    [ args = { <key> = <value> ... } ]
  #    [ flags = [ [ ifexists ] [ nofail ] ]
  #}
  #
  # PipeWire modules to load.
  # If ifexists is given, the module is ignored when it is not found.
  # If nofail is given, module initialization failures are ignored.
  #

  # The native communication protocol.
  { name = libpipewire-module-protocol-native }

  # Allows creating nodes that run in the context of the
  # client. Is used by all clients that want to provide
  # data to PipeWire.
  { name = libpipewire-module-client-node }

  # Allows creating devices that run in the context of the
  # client. Is used by the session manager.
  { name = libpipewire-module-client-device }

  # Makes a factory for wrapping nodes in an adapter with a
  # converter and resampler.
  { name = libpipewire-module-adapter }

  # Allows applications to create metadata objects. It creates
  # a factory for Metadata objects.
  { name = libpipewire-module-metadata }

  # Provides factories to make session manager objects.
  { name = libpipewire-module-session-manager }
]

wireplumber.components = [
  #{ name = <component-name>, type = <component-type> }
  #
  # WirePlumber components to load
  #

  # The lua scripting engine
  { name = libwireplumber-module-lua-scripting, type = module }

  # The lua configuration file
  # Other components are loaded from there
  { name = main.lua, type = config/lua }
]
   0707010000011C000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002900000000wireplumber-0.4.17/src/config/main.lua.d  0707010000011D0000A1FF000000000000000000000001657851B20000001A000000000000000000000000000000000000003A00000000wireplumber-0.4.17/src/config/main.lua.d/00-functions.lua ../common/00-functions.lua  0707010000011E000081A4000000000000000000000001656CC35F000001CA000000000000000000000000000000000000003F00000000wireplumber-0.4.17/src/config/main.lua.d/20-default-access.lua    default_access = {}
default_access.properties = {}
default_access.rules = {}

function default_access.enable()
  if default_access.enabled == false then
    return
  end

  load_access("default", {
    rules = default_access.rules
  })

  if default_access.properties["enable-flatpak-portal"] then
    -- Enables portal permissions via org.freedesktop.impl.portal.PermissionStore
    load_module("portal-permissionstore")
    load_access("portal")
  end
end
  0707010000011F000081A4000000000000000000000001656CC35F000002FA000000000000000000000000000000000000003D00000000wireplumber-0.4.17/src/config/main.lua.d/30-alsa-monitor.lua  alsa_monitor = {}
alsa_monitor.properties = {}
alsa_monitor.rules = {}

function alsa_monitor.enable()
  if alsa_monitor.enabled == false then
    return
  end

  -- The "reserve-device" module needs to be loaded for reservation to work
  if alsa_monitor.properties["alsa.reserve"] then
    load_module("reserve-device")
  end

  load_monitor("alsa", {
    properties = alsa_monitor.properties,
    rules = alsa_monitor.rules,
  })

  if alsa_monitor.properties["alsa.midi"] then
    load_monitor("alsa-midi", {
      properties = alsa_monitor.properties,
    })
    -- The "file-monitor-api" module needs to be loaded for MIDI device monitoring
    if alsa_monitor.properties["alsa.midi.monitoring"] then
      load_module("file-monitor-api")
    end
  end
end
  07070100000120000081A4000000000000000000000001656CC35F00000135000000000000000000000000000000000000004200000000wireplumber-0.4.17/src/config/main.lua.d/30-libcamera-monitor.lua libcamera_monitor = {}
libcamera_monitor.properties = {}
libcamera_monitor.rules = {}

function libcamera_monitor.enable()
  if libcamera_monitor.enabled == false then
    return
  end

  load_monitor("libcamera", {
    properties = libcamera_monitor.properties,
    rules = libcamera_monitor.rules,
  })
end
   07070100000121000081A4000000000000000000000001656CC35F0000010D000000000000000000000000000000000000003D00000000wireplumber-0.4.17/src/config/main.lua.d/30-v4l2-monitor.lua  v4l2_monitor = {}
v4l2_monitor.properties = {}
v4l2_monitor.rules = {}

function v4l2_monitor.enable()
  if v4l2_monitor.enabled == false then
    return
  end

  load_monitor("v4l2", {
    properties = v4l2_monitor.properties,
    rules = v4l2_monitor.rules,
  })
end
   07070100000122000081A4000000000000000000000001656CC35F00000A02000000000000000000000000000000000000004000000000wireplumber-0.4.17/src/config/main.lua.d/40-device-defaults.lua   device_defaults = {}
device_defaults.enabled = true

device_defaults.properties = {
  -- store preferences to the file system and restore them at startup;
  -- when set to false, default nodes and routes are selected based on
  -- their priorities and any runtime changes do not persist after restart
  ["use-persistent-storage"] = true,

  -- the default volumes to apply to ACP device nodes, in the linear scale
  --["default-volume"] = 0.064,
  --["default-input-volume"] = 1.0,

  -- Whether to auto-switch to echo cancel sink and source nodes or not
  ["auto-echo-cancel"] = true,

  -- Sets the default echo-cancel-sink node name to automatically switch to
  ["echo-cancel-sink-name"] = "echo-cancel-sink",

  -- Sets the default echo-cancel-source node name to automatically switch to
  ["echo-cancel-source-name"] = "echo-cancel-source",
}

-- Sets persistent device profiles that should never change when wireplumber is
-- running, even if a new profile with higher priority becomes available
device_defaults.persistent_profiles = {
  {
    matches = {
      {
        -- Matches all devices
        { "device.name", "matches", "*" },
      },
    },
    profile_names = {
      "off",
      "pro-audio"
    }
  },
}

device_defaults.profile_priorities = {
  {
    matches = {
      {
        -- Matches all bluez devices
        { "device.name", "matches", "bluez_card.*" },
      },
    },
    -- lower the index higher the priority
    priorities = {
      -- "a2dp-sink-sbc",
      -- "a2dp-sink-aptx_ll",
      -- "a2dp-sink-aptx",
      -- "a2dp-sink-aptx_hd",
      -- "a2dp-sink-ldac",
      -- "a2dp-sink-aac",
      -- "a2dp-sink-sbc_xq",
    }
  },
}

function device_defaults.enable()
  if device_defaults.enabled == false then
    return
  end

  -- Selects appropriate default nodes and enables saving and restoring them
  load_module("default-nodes", device_defaults.properties)

  -- Selects appropriate profile for devices
  load_script("policy-device-profile.lua", {
    persistent = device_defaults.persistent_profiles,
    priorities = device_defaults.profile_priorities
  })

  -- Selects appropriate device routes ("ports" in pulseaudio terminology)
  -- and enables saving and restoring them together with
  -- their properties (per-route/port volume levels, channel maps, etc)
  load_script("policy-device-routes.lua", device_defaults.properties)

  if device_defaults.properties["use-persistent-storage"] then
    -- Enables functionality to save and restore default device profiles
    load_module("default-profile")
  end
end
  07070100000123000081A4000000000000000000000001656CC35F00000436000000000000000000000000000000000000004000000000wireplumber-0.4.17/src/config/main.lua.d/40-stream-defaults.lua   stream_defaults = {}
stream_defaults.enabled = true

stream_defaults.properties = {
  -- whether to restore the last stream properties or not
  ["restore-props"] = true,

  -- whether to restore the last stream target or not
  ["restore-target"] = true,

  -- the default channel volume for new streams whose props were never saved
  -- previously. This is only used if "restore-props" is set to true.
  ["default-channel-volume"] = 1.0,
}

stream_defaults.rules = {
  -- Rules to override settings per node
  -- {
  --   matches = {
  --     {
  --       { "application.name", "matches", "pw-play" },
  --     },
  --   },
  --   apply_properties = {
  --     ["state.restore-props"] = false,
  --     ["state.restore-target"] = false,
  --     ["state.default-channel-volume"] = 0.5,
  --   },
  -- },
}

function stream_defaults.enable()
  if stream_defaults.enabled == false then
    return
  end

  -- Save and restore stream-specific properties
  load_script("restore-stream.lua", {
    properties = stream_defaults.properties,
    rules = stream_defaults.rules,
  })
end
  07070100000124000081A4000000000000000000000001656CC35F00001665000000000000000000000000000000000000003C00000000wireplumber-0.4.17/src/config/main.lua.d/50-alsa-config.lua   alsa_monitor.enabled = true

alsa_monitor.properties = {
  -- Create a JACK device. This is not enabled by default because
  -- it requires that the PipeWire JACK replacement libraries are
  -- not used by the session manager, in order to be able to
  -- connect to the real JACK server.
  --["alsa.jack-device"] = false,

  -- Reserve devices via org.freedesktop.ReserveDevice1 on D-Bus
  -- Disable if you are running a system-wide instance, which
  -- doesn't have access to the D-Bus user session
  ["alsa.reserve"] = true,
  --["alsa.reserve.priority"] = -20,
  --["alsa.reserve.application-name"] = "WirePlumber",

  -- Enables MIDI functionality
  ["alsa.midi"] = true,

  -- Enables monitoring of alsa MIDI devices
  ["alsa.midi.monitoring"] = true,

  -- MIDI bridge node properties
  ["alsa.midi.node-properties"] = {
    -- Name set for the node with ALSA MIDI ports
    ["node.name"] = "Midi-Bridge",
    -- Removes longname/number from MIDI port names
    --["api.alsa.disable-longname"] = true,
    ["priority.session"] = 100,
    ["priority.driver"] = 1,
  },

  -- These properties override node defaults when running in a virtual machine.
  -- The rules below still override those.
  ["vm.node.defaults"] = {
    ["api.alsa.period-size"] = 1024,
    ["api.alsa.headroom"] = 8192,
  },
}

alsa_monitor.rules = {
  -- An array of matches/actions to evaluate.
  --
  -- If you want to disable some devices or nodes, you can apply properties per device as the following example.
  -- The name can be found by running pw-cli ls Device, or pw-cli dump Device
  --{
  --  matches = {
  --    {
  --      { "device.name", "matches", "name_of_some_disabled_card" },
  --    },
  --  },
  --  apply_properties = {
  --    ["device.disabled"] = true,
  --  },
  --}
  {
    -- Rules for matching a device or node. It is an array of
    -- properties that all need to match the regexp. If any of the
    -- matches work, the actions are executed for the object.
    matches = {
      {
        -- This matches all cards.
        { "device.name", "matches", "alsa_card.*" },
      },
    },
    -- Apply properties on the matched object.
    apply_properties = {
      -- Use ALSA-Card-Profile devices. They use UCM or the profile
      -- configuration to configure the device and mixer settings.
      ["api.alsa.use-acp"] = true,

      -- Use UCM instead of profile when available. Can be
      -- disabled to skip trying to use the UCM profile.
      --["api.alsa.use-ucm"] = true,

      -- Don't use the hardware mixer for volume control. It
      -- will only use software volume. The mixer is still used
      -- to mute unused paths based on the selected port.
      --["api.alsa.soft-mixer"] = false,

      -- Ignore decibel settings of the driver. Can be used to
      -- work around buggy drivers that report wrong values.
      --["api.alsa.ignore-dB"] = false,

      -- The profile set to use for the device. Usually this is
      -- "default.conf" but can be changed with a udev rule or here.
      --["device.profile-set"] = "profileset-name",

      -- The default active profile. Is by default set to "Off".
      --["device.profile"] = "default profile name",

      -- Automatically select the best profile. This is the
      -- highest priority available profile. This is disabled
      -- here and instead implemented in the session manager
      -- where it can save and load previous preferences.
      ["api.acp.auto-profile"] = false,

      -- Automatically switch to the highest priority available port.
      -- This is disabled here and implemented in the session manager instead.
      ["api.acp.auto-port"] = false,

      -- Other properties can be set here.
      --["device.nick"] = "My Device",
    },
  },
  {
    matches = {
      {
        -- Matches all sources.
        { "node.name", "matches", "alsa_input.*" },
      },
      {
        -- Matches all sinks.
        { "node.name", "matches", "alsa_output.*" },
      },
    },
    apply_properties = {
      --["node.nick"]              = "My Node",
      --["node.description"]       = "My Node Description",
      --["priority.driver"]        = 100,
      --["priority.session"]       = 100,
      --["node.pause-on-idle"]     = false,
      --["monitor.channel-volumes"] = false
      --["resample.quality"]       = 4,
      --["resample.disable"]       = false,
      --["channelmix.normalize"]   = false,
      --["channelmix.mix-lfe"]     = false,
      --["channelmix.upmix"]       = true,
      --["channelmix.upmix-method"] = "psd",  -- "none" or "simple"
      --["channelmix.lfe-cutoff"]  = 150,
      --["channelmix.fc-cutoff"]   = 12000,
      --["channelmix.rear-delay"]  = 12.0,
      --["channelmix.stereo-widen"] = 0.0,
      --["channelmix.hilbert-taps"] = 0,
      --["channelmix.disable"]     = false,
      --["dither.noise"]           = 0,
      --["dither.method"]          = "none",  -- "rectangular", "triangular" or "shaped5"
      --["audio.channels"]         = 2,
      --["audio.format"]           = "S16LE",
      --["audio.rate"]             = 44100,
      --["audio.allowed-rates"]    = "32000,96000",
      --["audio.position"]         = "FL,FR",
      --["api.alsa.period-size"]   = 1024,
      --["api.alsa.period-num"]    = 2,
      --["api.alsa.headroom"]      = 0,
      --["api.alsa.start-delay"]   = 0,
      --["api.alsa.disable-mmap"]  = false,
      --["api.alsa.disable-batch"] = false,
      --["api.alsa.use-chmap"]     = false,
      --["api.alsa.multirate"]     = true,
      --["latency.internal.rate"]  = 0
      --["latency.internal.ns"]    = 0
      --["clock.name"]             = "api.alsa.0"
      --["session.suspend-timeout-seconds"] = 5,  -- 0 disables suspend
    },
  },
}
   07070100000125000081A4000000000000000000000001656CC35F000002D9000000000000000000000000000000000000004600000000wireplumber-0.4.17/src/config/main.lua.d/50-default-access-config.lua default_access.enabled = true

default_access.properties = {
  -- Enable the use of the flatpak portal integration.
  -- Disable if you are running a system-wide instance, which
  -- doesn't have access to the D-Bus user session
  ["enable-flatpak-portal"] = true,
}

default_access.rules = {
  {
    matches = {
      {
        { "pipewire.access", "=", "flatpak" },
        { "media.category", "=", "Manager" },
      },
    },
    default_permissions = "all",
  },
  {
    matches = {
      {
        { "pipewire.access", "=", "flatpak" },
      },
    },
    default_permissions = "rx",
  },
  {
    matches = {
      {
        { "pipewire.access", "=", "restricted" },
      },
    },
    default_permissions = "rx",
  },
}
   07070100000126000081A4000000000000000000000001656CC35F000003D9000000000000000000000000000000000000004100000000wireplumber-0.4.17/src/config/main.lua.d/50-libcamera-config.lua  libcamera_monitor.enabled = true

libcamera_monitor.rules = {
  -- An array of matches/actions to evaluate.
  {
    -- Rules for matching a device or node. It is an array of
    -- properties that all need to match the regexp. If any of the
    -- matches work, the actions are executed for the object.
    matches = {
      {
        -- This matches all cards.
        { "device.name", "matches", "libcamera_device.*" },
      },
    },
    -- Apply properties on the matched object.
    apply_properties = {
      -- ["device.nick"] = "My Device",
    },
  },
  {
    matches = {
      {
        -- Matches all sources.
        { "node.name", "matches", "libcamera_input.*" },
      },
      {
        -- Matches all sinks.
        { "node.name", "matches", "libcamera_output.*" },
      },
    },
    apply_properties = {
      --["node.nick"] = "My Node",
      --["priority.driver"] = 100,
      --["priority.session"] = 100,
      --["node.pause-on-idle"] = false,
    },
  },
}
   07070100000127000081A4000000000000000000000001656CC35F000003C0000000000000000000000000000000000000003C00000000wireplumber-0.4.17/src/config/main.lua.d/50-v4l2-config.lua   v4l2_monitor.enabled = true

v4l2_monitor.rules = {
  -- An array of matches/actions to evaluate.
  {
    -- Rules for matching a device or node. It is an array of
    -- properties that all need to match the regexp. If any of the
    -- matches work, the actions are executed for the object.
    matches = {
      {
        -- This matches all cards.
        { "device.name", "matches", "v4l2_device.*" },
      },
    },
    -- Apply properties on the matched object.
    apply_properties = {
      -- ["device.nick"] = "My Device",
    },
  },
  {
    matches = {
      {
        -- Matches all sources.
        { "node.name", "matches", "v4l2_input.*" },
      },
      {
        -- Matches all sinks.
        { "node.name", "matches", "v4l2_output.*" },
      },
    },
    apply_properties = {
      --["node.nick"] = "My Node",
      --["priority.driver"] = 100,
      --["priority.session"] = 100,
      --["node.pause-on-idle"] = false,
    },
  },
}
07070100000128000081A4000000000000000000000001656CC35F000002A4000000000000000000000000000000000000003B00000000wireplumber-0.4.17/src/config/main.lua.d/90-enable-all.lua    -- Provide the "default" pw_metadata, which stores
-- dynamic properties of pipewire objects in RAM
load_module("metadata")

-- Default client access policy
default_access.enable()

-- Load devices
alsa_monitor.enable()
v4l2_monitor.enable()
libcamera_monitor.enable()

-- Track/store/restore user choices about devices
device_defaults.enable()

-- Track/store/restore user choices about streams
stream_defaults.enable()

-- Link nodes by stream role and device intended role
load_script("intended-roles.lua")

-- Automatically suspends idle nodes after 3 seconds
load_script("suspend-node.lua")

-- Allows loading objects on demand via metadata
load_script("sm-objects.lua")
07070100000129000081A4000000000000000000000001656CC35F0000082B000000000000000000000000000000000000002A00000000wireplumber-0.4.17/src/config/policy.conf # WirePlumber daemon context configuration #

context.properties = {
  ## Properties to configure the PipeWire context and some modules

  application.name = "WirePlumber Policy"
  log.level = 2
  wireplumber.script-engine = lua-scripting
  wireplumber.export-core = false

  #mem.mlock-all = false
  #support.dbus  = true
}

context.spa-libs = {
  #<factory-name regex> = <library-name>
  #
  # Used to find spa factory names. It maps an spa factory name
  # regular expression to a library name that should contain
  # that factory.
  #
  audio.convert.* = audioconvert/libspa-audioconvert
  support.*       = support/libspa-support
}

context.modules = [
  #{   name = <module-name>
  #    [ args = { <key> = <value> ... } ]
  #    [ flags = [ [ ifexists ] [ nofail ] ]
  #}
  #
  # PipeWire modules to load.
  # If ifexists is given, the module is ignored when it is not found.
  # If nofail is given, module initialization failures are ignored.
  #

  # The native communication protocol.
  { name = libpipewire-module-protocol-native }

  # Allows creating nodes that run in the context of the
  # client. Is used by all clients that want to provide
  # data to PipeWire.
  { name = libpipewire-module-client-node }

  # Allows creating devices that run in the context of the
  # client. Is used by the session manager.
  { name = libpipewire-module-client-device }

  # Makes a factory for wrapping nodes in an adapter with a
  # converter and resampler.
  { name = libpipewire-module-adapter }

  # Allows applications to create metadata objects. It creates
  # a factory for Metadata objects.
  { name = libpipewire-module-metadata }

  # Provides factories to make session manager objects.
  { name = libpipewire-module-session-manager }
]

wireplumber.components = [
  #{ name = <component-name>, type = <component-type> }
  #
  # WirePlumber components to load
  #

  # The lua scripting engine
  { name = libwireplumber-module-lua-scripting, type = module }

  # The lua configuration file
  # Other components are loaded from there
  { name = policy.lua, type = config/lua }
]
 0707010000012A000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002B00000000wireplumber-0.4.17/src/config/policy.lua.d    0707010000012B0000A1FF000000000000000000000001657851B20000001A000000000000000000000000000000000000003C00000000wireplumber-0.4.17/src/config/policy.lua.d/00-functions.lua   ../common/00-functions.lua  0707010000012C000081A4000000000000000000000001656CC35F00000E88000000000000000000000000000000000000004100000000wireplumber-0.4.17/src/config/policy.lua.d/10-default-policy.lua  default_policy = {}
default_policy.enabled = true
default_policy.properties = {}
default_policy.endpoints = {}

default_policy.policy = {
  ["move"] = true,   -- moves session items when metadata target.node changes
  ["follow"] = true, -- moves session items to the default device when it has changed

  -- Whether to forward the ports format of filter stream nodes to their
  -- associated filter device nodes. This is needed for application to stream
  -- surround audio if echo-cancel is enabled.
  ["filter.forward-format"] = false,

  -- Set to 'true' to disable channel splitting & merging on nodes and enable
  -- passthrough of audio in the same format as the format of the device.
  -- Note that this breaks JACK support; it is generally not recommended
  ["audio.no-dsp"] = false,

  -- how much to lower the volume of lower priority streams when ducking
  -- note that this is a linear volume modifier (not cubic as in pulseaudio)
  ["duck.level"] = 0.3,
}

bluetooth_policy = {}

bluetooth_policy.policy = {
  -- Whether to store state on the filesystem.
  ["use-persistent-storage"] = true,

  -- Whether to use headset profile in the presence of an input stream.
  ["media-role.use-headset-profile"] = true,

  -- Application names correspond to application.name in stream properties.
  -- Applications which do not set media.role but which should be considered
  -- for role based profile switching can be specified here.
  ["media-role.applications"] = {
    "Firefox", "Chromium input", "Google Chrome input", "Brave input",
    "Microsoft Edge input", "Vivaldi input", "ZOOM VoiceEngine",
    "Telegram Desktop", "telegram-desktop", "linphone", "Mumble",
    "WEBRTC VoiceEngine", "Skype", "Firefox Developer Edition",
  },
}

dsp_policy = {}

dsp_policy.policy = {}

dsp_policy.policy.properties = {}

-- An array of matches/filters to apply.
-- `matches` are rules for matching a sink node. It is an array of
-- properties that all need to match the regexp. If any of the
-- matches in an array work, the filters are executed for the sink.
-- `filter_chain` is a JSON string of parameters to filter-chain module
-- `properties` table only has `pro_audio` boolean, which enables Pro Audio mode on the sink when applying DSP
dsp_policy.policy.rules = {}

function default_policy.enable()
  if default_policy.enabled == false then
    return
  end

  -- Session item factories, building blocks for the session management graph
  -- Do not disable these unless you really know what you are doing
  load_module("si-node")
  load_module("si-audio-adapter")
  load_module("si-standard-link")
  load_module("si-audio-endpoint")

  -- API to access default nodes from scripts
  load_module("default-nodes-api")

  -- API to access mixer controls, needed for volume ducking
  load_module("mixer-api")

  -- Create endpoints statically at startup
  load_script("static-endpoints.lua", default_policy.endpoints)

  -- Create items for nodes that appear in the graph
  load_script("create-item.lua", default_policy.policy)

  -- Link nodes to each other to make media flow in the graph
  load_script("policy-node.lua", default_policy.policy)

  -- Link client nodes with endpoints to make media flow in the graph
  load_script("policy-endpoint-client.lua", default_policy.policy)
  load_script("policy-endpoint-client-links.lua", default_policy.policy)

  -- Link endpoints with device nodes to make media flow in the graph
  load_script("policy-endpoint-device.lua", default_policy.policy)

  -- Switch bluetooth profile based on media.role
  load_script("policy-bluetooth.lua", bluetooth_policy.policy)

  -- Load filter chains for hardware requiring DSP
  load_script("policy-dsp.lua", dsp_policy.policy)
end
0707010000012D000081A4000000000000000000000001656CC35F000008D9000000000000000000000000000000000000004300000000wireplumber-0.4.17/src/config/policy.lua.d/50-endpoints-config.lua    -- uncomment to enable role-based endpoints
-- this is not yet ready for desktop use
--
--[[

default_policy.policy.roles = {
  ["Capture"] = {
    ["alias"] = { "Multimedia", "Music", "Voice", "Capture" },
    ["priority"] = 25,
    ["action.default"] = "cork",
    ["action.capture"] = "mix",
    ["media.class"] = "Audio/Source",
  },
  ["Multimedia"] = {
    ["alias"] = { "Movie", "Music", "Game" },
    ["priority"] = 25,
    ["action.default"] = "cork",
  },
  ["Speech-Low"] = {
    ["priority"] = 30,
    ["action.default"] = "cork",
    ["action.Speech-Low"] = "mix",
  },
  ["Custom-Low"] = {
    ["priority"] = 35,
    ["action.default"] = "cork",
    ["action.Custom-Low"] = "mix",
  },
  ["Navigation"] = {
    ["priority"] = 50,
    ["action.default"] = "duck",
    ["action.Navigation"] = "mix",
  },
  ["Speech-High"] = {
    ["priority"] = 60,
    ["action.default"] = "cork",
    ["action.Speech-High"] = "mix",
  },
  ["Custom-High"] = {
    ["priority"] = 65,
    ["action.default"] = "cork",
    ["action.Custom-High"] = "mix",
  },
  ["Communication"] = {
    ["priority"] = 75,
    ["action.default"] = "cork",
    ["action.Communication"] = "mix",
  },
  ["Emergency"] = {
    ["alias"] = { "Alert" },
    ["priority"] = 99,
    ["action.default"] = "cork",
    ["action.Emergency"] = "mix",
  },
}

default_policy.endpoints = {
  ["endpoint.capture"] = {
    ["media.class"] = "Audio/Source",
    ["role"] = "Capture",
  },
  ["endpoint.multimedia"] = {
    ["media.class"] = "Audio/Sink",
    ["role"] = "Multimedia",
  },
  ["endpoint.speech_low"] = {
    ["media.class"] = "Audio/Sink",
    ["role"] = "Speech-Low",
  },
  ["endpoint.custom_low"] = {
    ["media.class"] = "Audio/Sink",
    ["role"] = "Custom-Low",
  },
  ["endpoint.navigation"] = {
    ["media.class"] = "Audio/Sink",
    ["role"] = "Navigation",
  },
  ["endpoint.speech_high"] = {
    ["media.class"] = "Audio/Sink",
    ["role"] = "Speech-High",
  },
  ["endpoint.custom_high"] = {
    ["media.class"] = "Audio/Sink",
    ["role"] = "Custom-High",
  },
  ["endpoint.communication"] = {
    ["media.class"] = "Audio/Sink",
    ["role"] = "Communication",
  },
  ["endpoint.emergency"] = {
    ["media.class"] = "Audio/Sink",
    ["role"] = "Emergency",
  },
}
]]--
   0707010000012E000081A4000000000000000000000001656CC35F00000018000000000000000000000000000000000000003D00000000wireplumber-0.4.17/src/config/policy.lua.d/90-enable-all.lua  default_policy.enable()
0707010000012F000081A4000000000000000000000001656CC35F00000B19000000000000000000000000000000000000002F00000000wireplumber-0.4.17/src/config/wireplumber.conf    # WirePlumber daemon context configuration #

context.properties = {
  ## Properties to configure the PipeWire context and some modules

  #application.name = WirePlumber
  log.level = 2
  wireplumber.script-engine = lua-scripting
  #wireplumber.export-core = true

  #mem.mlock-all = false
  #support.dbus  = true
}

context.spa-libs = {
  #<factory-name regex> = <library-name>
  #
  # Used to find spa factory names. It maps an spa factory name
  # regular expression to a library name that should contain
  # that factory.
  #
  api.alsa.*      = alsa/libspa-alsa
  api.bluez5.*    = bluez5/libspa-bluez5
  api.v4l2.*      = v4l2/libspa-v4l2
  api.libcamera.* = libcamera/libspa-libcamera
  audio.convert.* = audioconvert/libspa-audioconvert
  support.*       = support/libspa-support
}

context.modules = [
  #{   name = <module-name>
  #    [ args = { <key> = <value> ... } ]
  #    [ flags = [ [ ifexists ] [ nofail ] ]
  #}
  #
  # PipeWire modules to load.
  # If ifexists is given, the module is ignored when it is not found.
  # If nofail is given, module initialization failures are ignored.
  #

  # Uses RTKit to boost the data thread priority. Also allows clamping
  # of utilisation when using the Completely Fair Scheduler on Linux.
  { name = libpipewire-module-rt
      args = {
          nice.level   = -11
          #rt.prio      = 88
          #rt.time.soft = -1
          #rt.time.hard = -1
          #uclamp.min = 0
          #uclamp.max = 1024
      }
      flags = [ ifexists nofail ]
  }

  # The native communication protocol.
  { name = libpipewire-module-protocol-native }

  # Allows creating nodes that run in the context of the
  # client. Is used by all clients that want to provide
  # data to PipeWire.
  { name = libpipewire-module-client-node }

  # Allows creating devices that run in the context of the
  # client. Is used by the session manager.
  { name = libpipewire-module-client-device }

  # Makes a factory for wrapping nodes in an adapter with a
  # converter and resampler.
  { name = libpipewire-module-adapter }

  # Allows applications to create metadata objects. It creates
  # a factory for Metadata objects.
  { name = libpipewire-module-metadata }

  # Provides factories to make session manager objects.
  { name = libpipewire-module-session-manager }

  # Provides factories to make SPA node objects.
  { name = libpipewire-module-spa-node-factory }
]

wireplumber.components = [
  #{ name = <component-name>, type = <component-type> }
  #
  # WirePlumber components to load
  #

  # The lua scripting engine
  { name = libwireplumber-module-lua-scripting, type = module }

  # The lua configuration file(s)
  # Other components are loaded from there
  { name = main.lua, type = config/lua }
  { name = policy.lua, type = config/lua }
  { name = bluetooth.lua, type = config/lua }
]
   07070100000130000081A4000000000000000000000001656CC35F000035A4000000000000000000000000000000000000001E00000000wireplumber-0.4.17/src/main.c /* WirePlumber
 *
 * Copyright © 2019-2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <glib-unix.h>
#include <pipewire/pipewire.h>
#include <locale.h>

#define WP_DOMAIN_DAEMON (wp_domain_daemon_quark ())
static G_DEFINE_QUARK (wireplumber-daemon, wp_domain_daemon);

enum WpExitCode
{
  /* based on sysexits.h */
  WP_EXIT_OK = 0,
  WP_EXIT_USAGE = 64,       /* command line usage error */
  WP_EXIT_UNAVAILABLE = 69, /* service unavailable */
  WP_EXIT_SOFTWARE = 70,    /* internal software error */
  WP_EXIT_CONFIG = 78,      /* configuration error */
};

static gboolean show_version = FALSE;
static gchar * config_file = NULL;

static GOptionEntry entries[] =
{
  { "version", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &show_version,
    "Show version", NULL },
  { "config-file", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &config_file,
    "The context configuration file", NULL },
  { NULL }
};

/*** WpInitTransition ***/

struct _WpInitTransition
{
  WpTransition parent;
  WpObjectManager *om;
  guint pending_plugins;
};

enum {
  STEP_LOAD_COMPONENTS = WP_TRANSITION_STEP_CUSTOM_START,
  STEP_CONNECT,
  STEP_CHECK_MEDIA_SESSION,
  STEP_ACTIVATE_PLUGINS,
  STEP_ACTIVATE_SCRIPTS,
  STEP_CLEANUP,
};

G_DECLARE_FINAL_TYPE (WpInitTransition, wp_init_transition,
                      WP, INIT_TRANSITION, WpTransition)
G_DEFINE_TYPE (WpInitTransition, wp_init_transition, WP_TYPE_TRANSITION)

static void
wp_init_transition_init (WpInitTransition * self)
{
}

static guint
wp_init_transition_get_next_step (WpTransition * transition, guint step)
{
  switch (step) {
  case WP_TRANSITION_STEP_NONE: return STEP_LOAD_COMPONENTS;
  case STEP_LOAD_COMPONENTS:    return STEP_CONNECT;
  case STEP_CONNECT:            return STEP_CHECK_MEDIA_SESSION;
  case STEP_CHECK_MEDIA_SESSION:return STEP_ACTIVATE_PLUGINS;
  case STEP_CLEANUP:            return WP_TRANSITION_STEP_NONE;

  case STEP_ACTIVATE_PLUGINS: {
    WpInitTransition *self = WP_INIT_TRANSITION (transition);
    if (self->pending_plugins == 0)
      return STEP_ACTIVATE_SCRIPTS;
    else
      return STEP_ACTIVATE_PLUGINS;
  }

  case STEP_ACTIVATE_SCRIPTS: {
    WpInitTransition *self = WP_INIT_TRANSITION (transition);
    if (self->pending_plugins == 0)
      return STEP_CLEANUP;
    else
      return STEP_ACTIVATE_SCRIPTS;
  }

  default:
    g_return_val_if_reached (WP_TRANSITION_STEP_ERROR);
  }
}

static void
on_plugin_activated (WpObject * p, GAsyncResult * res, WpInitTransition *self)
{
  GError *error = NULL;

  if (!wp_object_activate_finish (p, res, &error)) {
    wp_transition_return_error (WP_TRANSITION (self), error);
    return;
  }

  --self->pending_plugins;
  wp_transition_advance (WP_TRANSITION (self));
}

static void
on_plugin_added (WpObjectManager * om, WpObject * p, WpInitTransition *self)
{
  self->pending_plugins++;
  wp_object_activate_closure (p, WP_PLUGIN_FEATURE_ENABLED, NULL,
      g_cclosure_new_object (G_CALLBACK (on_plugin_activated),
      G_OBJECT (self)));
}

static void
check_media_session (WpObjectManager * om, WpInitTransition *self)
{
  if (wp_object_manager_get_n_objects (om) > 0) {
    wp_transition_return_error (WP_TRANSITION (self), g_error_new (
        WP_DOMAIN_DAEMON, WP_EXIT_SOFTWARE,
        "pipewire-media-session appears to be running; "
        "please stop it before starting wireplumber"));
    return;
  }
  wp_transition_advance (WP_TRANSITION (self));
}

struct data {
  WpTransition *transition;
  int count;
};

static int
do_load_components(void *data, const char *location, const char *section,
		const char *str, size_t len)
{
  struct data *d = data;
  WpTransition *transition = d->transition;
  WpCore *core = wp_transition_get_source_object (transition);
  g_autoptr (WpSpaJson) json = NULL;
  g_autoptr (WpIterator) it = NULL;
  g_auto (GValue) item = G_VALUE_INIT;
  GError *error = NULL;

  json = wp_spa_json_new_from_stringn (str, len);

  if (!wp_spa_json_is_array (json)) {
    wp_transition_return_error (transition, g_error_new (
        WP_DOMAIN_DAEMON, WP_EXIT_CONFIG,
        "wireplumber.components is not a JSON array"));
    return -EINVAL;
  }

  it = wp_spa_json_new_iterator (json);
  for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
    WpSpaJson *o = g_value_get_boxed (&item);
    g_autofree gchar *name = NULL;
    g_autofree gchar *type = NULL;

    if (!wp_spa_json_is_object (o) ||
        !wp_spa_json_object_get (o,
            "name", "s", &name,
            "type", "s", &type,
            NULL)) {
      wp_transition_return_error (transition, g_error_new (
          WP_DOMAIN_DAEMON, WP_EXIT_CONFIG,
          "component must have both a 'name' and a 'type'"));
      return -EINVAL;
    }
    if (!wp_core_load_component (core, name, type, NULL, &error)) {
      wp_transition_return_error (transition, error);
      return -EINVAL;
    }
    d->count++;
  }
  return 0;
}

static void
wp_init_transition_execute_step (WpTransition * transition, guint step)
{
  WpInitTransition *self = WP_INIT_TRANSITION (transition);
  WpCore *core = wp_transition_get_source_object (transition);
  struct pw_context *pw_ctx = wp_core_get_pw_context (core);
  const struct pw_properties *props = pw_context_get_properties (pw_ctx);

  switch (step) {
  case STEP_LOAD_COMPONENTS: {
    struct data data = { .transition = transition };

    if (pw_context_conf_section_for_each(pw_ctx, "wireplumber.components",
		    do_load_components, &data) < 0)
	    return;
    if (data.count == 0) {
      wp_transition_return_error (transition, g_error_new (
          WP_DOMAIN_DAEMON, WP_EXIT_CONFIG,
          "No components configured in the context conf file; nothing to do"));
      return;
    }
    wp_transition_advance (transition);
    break;
  }

  case STEP_CONNECT: {
    g_signal_connect_object (core, "connected",
        G_CALLBACK (wp_transition_advance), transition, G_CONNECT_SWAPPED);

    if (!wp_core_connect (core)) {
      wp_transition_return_error (transition, g_error_new (WP_DOMAIN_DAEMON,
          WP_EXIT_UNAVAILABLE, "Failed to connect to PipeWire"));
      return;
    }

    /* initialize secondary connection to pipewire */
    const char *str = pw_properties_get (props, "wireplumber.export-core");
    if (str && pw_properties_parse_bool (str)) {
      g_autofree gchar *export_core_name = NULL;
      g_autoptr (WpCore) export_core = NULL;

      str = pw_properties_get (props, PW_KEY_APP_NAME);
      export_core_name = g_strdup_printf ("%s [export]", str);

      export_core = wp_core_clone (core);
      wp_core_update_properties (export_core, wp_properties_new (
            PW_KEY_APP_NAME, export_core_name,
            NULL));

      if (!wp_core_connect (export_core)) {
        wp_transition_return_error (transition, g_error_new (
            WP_DOMAIN_DAEMON, WP_EXIT_UNAVAILABLE,
            "Failed to connect export core to PipeWire"));
        return;
      }

      g_object_set_data_full (G_OBJECT (core), "wireplumber.export-core",
          g_steal_pointer (&export_core), g_object_unref);
    }
    break;
  }

  case STEP_CHECK_MEDIA_SESSION: {
    wp_info_object (self, "Checking for session manager conflicts...");

    self->om = wp_object_manager_new ();
    wp_object_manager_add_interest (self->om, WP_TYPE_CLIENT,
        WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY,
        "application.name", "=s", "pipewire-media-session", NULL);
    g_signal_connect_object (self->om, "installed",
        G_CALLBACK (check_media_session), self, 0);
    wp_core_install_object_manager (core, self->om);
    break;
  }

  case STEP_ACTIVATE_PLUGINS: {
    const char *engine = pw_properties_get (props, "wireplumber.script-engine");

    g_clear_object (&self->om);
    wp_info_object (self, "Activating plugins...");

    self->om = wp_object_manager_new ();
    if (engine) {
      wp_object_manager_add_interest (self->om, WP_TYPE_PLUGIN,
          WP_CONSTRAINT_TYPE_G_PROPERTY, "name", "!s", engine,
          NULL);
    } else {
      wp_object_manager_add_interest (self->om, WP_TYPE_PLUGIN, NULL);
    }
    g_signal_connect_object (self->om, "object-added",
        G_CALLBACK (on_plugin_added), self, 0);
    g_signal_connect_object (self->om, "installed",
        G_CALLBACK (wp_transition_advance), transition, G_CONNECT_SWAPPED);
    wp_core_install_object_manager (core, self->om);
    break;
  }

  case STEP_ACTIVATE_SCRIPTS: {
    const char *engine = pw_properties_get (props, "wireplumber.script-engine");

    g_clear_object (&self->om);

    if (engine) {
      wp_info_object (self, "Executing scripts...");

      g_autoptr (WpPlugin) plugin = wp_plugin_find (core, engine);
      if (!plugin) {
        wp_transition_return_error (transition, g_error_new (
            WP_DOMAIN_DAEMON, WP_EXIT_CONFIG,
            "script engine '%s' is not loaded", engine));
        return;
      }

      self->pending_plugins = 1;

      self->om = wp_object_manager_new ();
      wp_object_manager_add_interest (self->om, WP_TYPE_PLUGIN,
          WP_CONSTRAINT_TYPE_G_PROPERTY, "name", "#s", "script:*",
          NULL);
      g_signal_connect_object (self->om, "object-added",
          G_CALLBACK (on_plugin_added), self, 0);
      wp_core_install_object_manager (core, self->om);

      wp_object_activate (WP_OBJECT (plugin), WP_PLUGIN_FEATURE_ENABLED, NULL,
          (GAsyncReadyCallback) on_plugin_activated, self);
    } else {
      wp_transition_advance (transition);
    }
    break;
  }

  case STEP_CLEANUP:
  case WP_TRANSITION_STEP_ERROR:
    g_clear_object (&self->om);
    break;

  default:
    g_assert_not_reached ();
  }
}

static void
wp_init_transition_class_init (WpInitTransitionClass * klass)
{
  WpTransitionClass * transition_class = (WpTransitionClass *) klass;

  transition_class->get_next_step = wp_init_transition_get_next_step;
  transition_class->execute_step = wp_init_transition_execute_step;
}

/*** WpDaemon ***/

typedef struct
{
  WpCore *core;
  GMainLoop *loop;
  gint exit_code;
} WpDaemon;

static void
daemon_clear (WpDaemon * self)
{
  g_clear_pointer (&self->loop, g_main_loop_unref);
  g_clear_object (&self->core);
}

G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (WpDaemon, daemon_clear)

static void
daemon_exit (WpDaemon * d, gint code)
{
  /* replace OK with an error, but do not replace error with OK */
  if (d->exit_code == WP_EXIT_OK)
    d->exit_code = code;
  g_main_loop_quit (d->loop);
}

static void
on_disconnected (WpCore *core, WpDaemon * d)
{
  wp_message ("disconnected from pipewire");
  daemon_exit (d, WP_EXIT_OK);
}

static gboolean
signal_handler (int signal, gpointer data)
{
  WpDaemon *d = data;
  wp_message ("stopped by signal: %s", strsignal (signal));
  daemon_exit (d, WP_EXIT_OK);
  return G_SOURCE_CONTINUE;
}

static gboolean
signal_handler_int (gpointer data)
{
  return signal_handler (SIGINT, data);
}

static gboolean
signal_handler_hup (gpointer data)
{
  return signal_handler (SIGHUP, data);
}

static gboolean
signal_handler_term (gpointer data)
{
  return signal_handler (SIGTERM, data);
}


static gboolean
init_start (WpTransition * transition)
{
  wp_transition_advance (transition);
  return G_SOURCE_REMOVE;
}

static void
init_done (WpCore * core, GAsyncResult * res, WpDaemon * d)
{
  g_autoptr (GError) error = NULL;
  if (!wp_transition_finish (res, &error)) {
    fprintf (stderr, "%s\n", error->message);
    daemon_exit (d, (error->domain == WP_DOMAIN_DAEMON) ?
        error->code : WP_EXIT_SOFTWARE);
  }
}

gint
main (gint argc, gchar **argv)
{
  g_auto (WpDaemon) d = {0};
  g_autoptr (GOptionContext) context = NULL;
  g_autoptr (GError) error = NULL;
  g_autoptr (WpProperties) properties = NULL;
  g_autofree gchar *config_file_path = NULL;

  setlocale (LC_ALL, "");
  setlocale (LC_NUMERIC, "C");
  wp_init (WP_INIT_ALL);

  context = g_option_context_new ("- PipeWire Session/Policy Manager");
  g_option_context_add_main_entries (context, entries, NULL);
  if (!g_option_context_parse (context, &argc, &argv, &error)) {
    fprintf (stderr, "%s\n", error->message);
    return WP_EXIT_USAGE;
  }

  if (show_version) {
    g_print ("%s\n"
        "Compiled with libwireplumber %s\n"
        "Linked with libwireplumber %s\n",
        argv[0],
        WIREPLUMBER_VERSION,
        wp_get_library_version());
    return WP_EXIT_OK;
  }

  if (!config_file)
    config_file = "wireplumber.conf";

  config_file_path = wp_find_file (
      WP_LOOKUP_DIR_ENV_CONFIG |
      WP_LOOKUP_DIR_XDG_CONFIG_HOME |
      WP_LOOKUP_DIR_ETC |
      WP_LOOKUP_DIR_PREFIX_SHARE,
      config_file, NULL);
  if (config_file_path == NULL) {
    fprintf (stderr, "Unable to find the required configuration file %s\n",
             config_file);
    return WP_EXIT_CONFIG;
  }

  properties = wp_properties_new (
      PW_KEY_CONFIG_NAME, config_file_path,
      PW_KEY_APP_NAME, "WirePlumber",
      "wireplumber.daemon", "true",
      "wireplumber.export-core", "true",
      NULL);

  /* init wireplumber daemon */
  d.loop = g_main_loop_new (NULL, FALSE);
  d.core = wp_core_new (NULL, g_steal_pointer (&properties));
  g_signal_connect (d.core, "disconnected", G_CALLBACK (on_disconnected), &d);

  /* watch for exit signals */
  g_unix_signal_add (SIGINT, signal_handler_int, &d);
  g_unix_signal_add (SIGTERM, signal_handler_term, &d);
  g_unix_signal_add (SIGHUP, signal_handler_hup, &d);

  /* initialization transition */
  g_idle_add ((GSourceFunc) init_start,
      wp_transition_new (wp_init_transition_get_type (), d.core,
          NULL, (GAsyncReadyCallback) init_done, &d));

  /* run */
  g_main_loop_run (d.loop);
  wp_core_disconnect (d.core);
  return d.exit_code;
}
07070100000131000081A4000000000000000000000001656CC35F00000241000000000000000000000000000000000000002300000000wireplumber-0.4.17/src/meson.build    if build_tools
  subdir('tools')
endif

if build_daemon
  subdir('systemd')

  install_subdir('config',
    install_dir: wireplumber_data_dir,
    strip_directory : true
  )
  install_subdir('scripts',
    install_dir: wireplumber_data_dir,
    strip_directory : false
  )

  wp_sources = [
    'main.c',
  ]

  wireplumber = executable('wireplumber',
    wp_sources,
    c_args : [
      '-D_GNU_SOURCE',
      '-DG_LOG_USE_STRUCTURED',
      '-DG_LOG_DOMAIN="wireplumber"',
    ],
    install: true,
    dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep],
  )
endif
   07070100000132000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001F00000000wireplumber-0.4.17/src/scripts    07070100000133000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002600000000wireplumber-0.4.17/src/scripts/access 07070100000134000081A4000000000000000000000001656CC35F0000052F000000000000000000000000000000000000003900000000wireplumber-0.4.17/src/scripts/access/access-default.lua  -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT

local config = ... or {}

-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
  r.interests = {}
  for _, i in ipairs(r.matches) do
    local interest_desc = { type = "properties" }
    for _, c in ipairs(i) do
      c.type = "pw"
      table.insert(interest_desc, Constraint(c))
    end
    local interest = Interest(interest_desc)
    table.insert(r.interests, interest)
  end
  r.matches = nil
end

function rulesGetDefaultPermissions(properties)
  for _, r in ipairs(config.rules or {}) do
    if r.default_permissions then
      for _, interest in ipairs(r.interests) do
        if interest:matches(properties) then
          return r.default_permissions
        end
      end
    end
  end
end

clients_om = ObjectManager {
  Interest { type = "client" }
}

clients_om:connect("object-added", function (om, client)
  local id = client["bound-id"]
  local properties = client["properties"]

  local perms = rulesGetDefaultPermissions(properties)

  if perms then
    Log.info(client, "Granting permissions to client " .. id .. ": " .. perms)
    client:update_permissions { ["any"] = perms }
  end
end)

clients_om:activate()
 07070100000135000081A4000000000000000000000001656CC35F00001027000000000000000000000000000000000000003800000000wireplumber-0.4.17/src/scripts/access/access-portal.lua   MEDIA_ROLE_NONE = 0
MEDIA_ROLE_CAMERA = 1 << 0

function hasPermission (permissions, app_id, lookup)
  if permissions then
    for key, values in pairs(permissions) do
      if key == app_id then
        for _, v in pairs(values) do
          if v == lookup then
            return true
          end
        end
      end
    end
  end
  return false
end

function parseMediaRoles (media_roles_str)
  local media_roles = MEDIA_ROLE_NONE
  for role in media_roles_str:gmatch('[^,%s]+') do
    if role == "Camera" then
      media_roles = media_roles | MEDIA_ROLE_CAMERA
    end
  end
  return media_roles
end

function setPermissions (client, allow_client, allow_nodes)
  local client_id = client["bound-id"]
  Log.info(client, "Granting ALL access to client " .. client_id)

  -- Update permissions on client
  client:update_permissions { [client_id] = allow_client and "all" or "-" }

  -- Update permissions on camera source nodes
  for node in nodes_om:iterate() do
    local node_id = node["bound-id"]
    client:update_permissions { [node_id] = allow_nodes and "all" or "-" }
  end
end

function updateClientPermissions (client, permissions)
  local client_id = client["bound-id"]
  local str_prop = nil
  local app_id = nil
  local media_roles = nil
  local allowed = false

  -- Make sure the client is not the portal itself
  str_prop = client.properties["pipewire.access.portal.is_portal"]
  if str_prop == "yes" then
    Log.info (client, "client is the portal itself")
    return
  end

  -- Make sure the client has a portal app Id
  str_prop = client.properties["pipewire.access.portal.app_id"]
  if str_prop == nil then
    Log.info (client, "Portal managed client did not set app_id")
    return
  end
  if str_prop == "" then
    Log.info (client, "Ignoring portal check for non-sandboxed client")
    setPermissions (client, true, true)
    return
  end
  app_id = str_prop

  -- Make sure the client has portal media roles
  str_prop = client.properties["pipewire.access.portal.media_roles"]
  if str_prop == nil then
  Log.info (client, "Portal managed client did not set media_roles")
    return
  end
  media_roles = parseMediaRoles (str_prop)
  if (media_roles & MEDIA_ROLE_CAMERA) == 0 then
    Log.info (client, "Ignoring portal check for clients without camera role")
    return
  end

  -- Update permissions
  allowed = hasPermission (permissions, app_id, "yes")

  Log.info (client, "setting permissions: " .. tostring(allowed))
  setPermissions (client, allowed, allowed)
end

-- Create portal clients object manager
clients_om = ObjectManager {
  Interest {
    type = "client",
    Constraint { "pipewire.access", "=", "portal" },
  }
}

-- Set permissions to portal clients from the permission store if loaded
pps_plugin = Plugin.find("portal-permissionstore")
if pps_plugin then
  nodes_om = ObjectManager {
    Interest {
      type = "node",
      Constraint { "media.role", "=", "Camera" },
      Constraint { "media.class", "=", "Video/Source" },
    }
  }
  nodes_om:activate()

  clients_om:connect("object-added", function (om, client)
    local new_perms = pps_plugin:call("lookup", "devices", "camera");
    updateClientPermissions (client, new_perms)
  end)

  nodes_om:connect("object-added", function (om, node)
    local new_perms = pps_plugin:call("lookup", "devices", "camera");
    for client in clients_om:iterate() do
      updateClientPermissions (client, new_perms)
    end
  end)

  pps_plugin:connect("changed", function (p, table, id, deleted, permissions)
    if table == "devices" or id == "camera" then
      for app_id, _ in pairs(permissions) do
        for client in clients_om:iterate {
            Constraint { "pipewire.access.portal.app_id", "=", app_id }
        } do
          updateClientPermissions (client, permissions)
        end
      end
    end
  end)
else
  -- Otherwise, just set all permissions to all portal clients
  clients_om:connect("object-added", function (om, client)
    local id = client["bound-id"]
    Log.info(client, "Granting ALL access to client " .. id)
    client:update_permissions { ["any"] = "all" }
  end)
end

clients_om:activate()
 07070100000136000081A4000000000000000000000001656CC35F00000D1C000000000000000000000000000000000000002F00000000wireplumber-0.4.17/src/scripts/create-item.lua    -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT

-- Receive script arguments from config.lua
local config = ... or {}

items = {}

function configProperties(node)
  local np = node.properties
  local properties = {
    ["item.node"] = node,
    ["item.plugged.usec"] = GLib.get_monotonic_time(),
    ["item.features.no-dsp"] = config["audio.no-dsp"],
    ["item.features.monitor"] = true,
    ["item.features.control-port"] = false,
    ["node.id"] = node["bound-id"],
    ["client.id"] = np["client.id"],
    ["object.path"] = np["object.path"],
    ["object.serial"] = np["object.serial"],
    ["target.object"] = np["target.object"],
    ["priority.session"] = np["priority.session"],
    ["device.id"] = np["device.id"],
    ["card.profile.device"] = np["card.profile.device"],
    ["target.endpoint"] = np["target.endpoint"],
  }

  for k, v in pairs(np) do
    if k:find("^node") or k:find("^stream") or k:find("^media") then
      properties[k] = v
    end
  end

  local media_class = properties["media.class"] or ""

  if not properties["media.type"] then
    for _, i in ipairs({ "Audio", "Video", "Midi" }) do
      if media_class:find(i) then
        properties["media.type"] = i
        break
      end
    end
  end

  properties["item.node.type"] =
      media_class:find("^Stream/") and "stream" or "device"

  if media_class:find("Sink") or
      media_class:find("Input") or
      media_class:find("Duplex") then
    properties["item.node.direction"] = "input"
  elseif media_class:find("Source") or media_class:find("Output") then
    properties["item.node.direction"] = "output"
  end
  return properties
end

function addItem (node, item_type)
  local id = node["bound-id"]
  local item

  -- create item
  item = SessionItem ( item_type )
  items[id] = item

  -- configure item
  if not item:configure(configProperties(node)) then
    Log.warning(item, "failed to configure item for node " .. tostring(id))
    return
  end

  item:register ()

  -- activate item
  items[id]:activate (Features.ALL, function (item, e)
    if e then
      Log.message(item, "failed to activate item: " .. tostring(e));
      if item then
        item:remove ()
      end
    else
      Log.info(item, "activated item for node " .. tostring(id))

      -- Trigger object managers to update status
      item:remove ()
      if item["active-features"] ~= 0 then
        item:register ()
      end
    end
  end)
end

nodes_om = ObjectManager {
  Interest {
    type = "node",
    Constraint { "media.class", "#", "Stream/*", type = "pw-global" },
  },
  Interest {
    type = "node",
    Constraint { "media.class", "#", "Video/*", type = "pw-global" },
  },
  Interest {
    type = "node",
    Constraint { "media.class", "#", "Audio/*", type = "pw-global" },
    Constraint { "wireplumber.is-endpoint", "-", type = "pw" },
  },
}

nodes_om:connect("object-added", function (om, node)
  local media_class = node.properties['media.class']
  if string.find (media_class, "Audio") then
    addItem (node, "si-audio-adapter")
  else
    addItem (node, "si-node")
  end
end)

nodes_om:connect("object-removed", function (om, node)
  local id = node["bound-id"]
  if items[id] then
    items[id]:remove ()
    items[id] = nil
  end
end)

nodes_om:activate()
07070100000137000081A4000000000000000000000001656CC35F0000094C000000000000000000000000000000000000003100000000wireplumber-0.4.17/src/scripts/fallback-sink.lua  -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author Frédéric Danis <frederic.danis@collabora.com>
--
-- SPDX-License-Identifier: MIT

local sink_ids = {}
local fallback_node = nil

node_om = ObjectManager {
  Interest {
    type = "node",
    Constraint { "media.class", "matches", "Audio/Sink", type = "pw-global" },
    -- Do not consider endpoints created by WirePlumber
    Constraint { "wireplumber.is-endpoint", "!", true, type = "pw" },
    -- or the fallback sink itself
    Constraint { "wireplumber.is-fallback", "!", true, type = "pw" },
  }
}

function createFallbackSink()
  if fallback_node then
    return
  end

  Log.info("Create fallback sink")

  local properties = {}

  properties["node.name"] = "auto_null"
  properties["node.description"] = "Dummy Output"

  properties["audio.rate"] = 48000
  properties["audio.channels"] = 2
  properties["audio.position"] = "FL,FR"

  properties["media.class"] = "Audio/Sink"
  properties["factory.name"] = "support.null-audio-sink"
  properties["node.virtual"] = "true"
  properties["monitor.channel-volumes"] = "true"

  properties["wireplumber.is-fallback"] = "true"
  properties["priority.session"] = 500

  fallback_node = LocalNode("adapter", properties)
  fallback_node:activate(Feature.Proxy.BOUND)
end

function checkSinks()
  local sink_ids_items = 0
  for _ in pairs(sink_ids) do sink_ids_items = sink_ids_items + 1 end

  if sink_ids_items > 0 then
    if fallback_node then
      Log.info("Remove fallback sink")
      fallback_node = nil
    end
  elseif not fallback_node then
    createFallbackSink()
  end
end

function checkSinksAfterTimeout()
  if timeout_source then
    timeout_source:destroy()
  end
  timeout_source = Core.timeout_add(1000, function ()
    checkSinks()
    timeout_source = nil
  end)
end

node_om:connect("object-added", function (_, node)
  Log.debug("object added: " .. node.properties["object.id"] .. " " ..
      tostring(node.properties["node.name"]))

  sink_ids[node.properties["object.id"]] = node.properties["node.name"]

  checkSinksAfterTimeout()
end)

node_om:connect("object-removed", function (_, node)
  Log.debug("object removed: " .. node.properties["object.id"] .. " " ..
      tostring(node.properties["node.name"]))

  sink_ids[node.properties["object.id"]] = nil
  checkSinksAfterTimeout()
end)

node_om:activate()

checkSinksAfterTimeout()
07070100000138000081A4000000000000000000000001656CC35F0000081F000000000000000000000000000000000000003200000000wireplumber-0.4.17/src/scripts/intended-roles.lua -- WirePlumber
--
-- Copyright © 2021 Asymptotic
--    @author Arun Raghavan <arun@asymptotic.io>
--
-- SPDX-License-Identifier: MIT
--
-- Route streams of a given role (media.role property) to devices that are
-- intended for that role (device.intended-roles property)

metadata_om = ObjectManager {
  Interest {
    type = "metadata",
    Constraint { "metadata.name", "=", "default" },
  }
}

devices_om = ObjectManager {
  Interest {
    type = "node",
    Constraint { "media.class", "matches", "Audio/*", type = "pw-global" },
    Constraint { "device.intended-roles", "is-present", type = "pw" },
  }
}

streams_om = ObjectManager {
  Interest {
    type = "node",
    Constraint { "media.class", "matches", "Stream/*/Audio", type = "pw-global" },
    Constraint { "media.role", "is-present", type = "pw-global" }
  }
}

local function routeUsingIntendedRole(stream, dev)
  local stream_role = stream.properties["media.role"]
  local is_input = stream.properties["media.class"]:find("Input") ~= nil

  local is_source = dev.properties["media.class"]:find("Source") ~= nil
  local dev_roles = dev.properties["device.intended-roles"]

  -- Make sure the stream and device direction match
  if is_input ~= is_source then
    return
  end

  for role in dev_roles:gmatch("(%a+)") do
    if role == stream_role then
      Log.info(stream,
        string.format("Routing stream '%s' (%d) with role '%s' to '%s' (%d)",
          stream.properties["node.name"], stream["bound-id"], stream_role,
          dev.properties["node.name"], dev["bound-id"])
      )

      local metadata = metadata_om:lookup()
      metadata:set(stream["bound-id"], "target.node", "Spa:Id", dev["bound-id"])
    end
  end
end

streams_om:connect("object-added", function (streams_om, stream)
  for dev in devices_om:iterate() do
    routeUsingIntendedRole(stream, dev)
  end
end)

devices_om:connect("object-added", function (devices_om, dev)
  for stream in streams_om:iterate() do
    routeUsingIntendedRole(stream, dev)
  end
end)

metadata_om:activate()
devices_om:activate()
streams_om:activate()
 07070100000139000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002800000000wireplumber-0.4.17/src/scripts/monitors   0707010000013A000081A4000000000000000000000001656CC35F0000072E000000000000000000000000000000000000003600000000wireplumber-0.4.17/src/scripts/monitors/alsa-midi.lua -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT

-- Receive script arguments from config.lua
local config = ... or {}

-- ensure config.properties is not nil
config.properties = config.properties or {}

SND_PATH = "/dev/snd"
SEQ_NAME = "seq"
SND_SEQ_PATH = SND_PATH .. "/" .. SEQ_NAME

midi_node = nil
fm_plugin = nil

function CreateMidiNode ()
  -- Midi properties
  local props = {}
  if type(config.properties["alsa.midi.node-properties"]) == "table" then
     props = config.properties["alsa.midi.node-properties"]
  end
  props["factory.name"] = "api.alsa.seq.bridge"
  props["node.name"] = props["node.name"] or "Midi-Bridge"

  -- create the midi node
  local node = Node("spa-node-factory", props)
  node:activate(Feature.Proxy.BOUND, function (n)
    Log.info ("activated Midi bridge")
  end)

  return node;
end

if GLib.access (SND_SEQ_PATH, "rw") then
  midi_node = CreateMidiNode ()
elseif config.properties["alsa.midi.monitoring"] then
  fm_plugin = Plugin.find("file-monitor-api")
end

-- Only monitor the MIDI device if file does not exist and plugin API is loaded
if midi_node == nil and fm_plugin ~= nil then
  -- listen for changed events
  fm_plugin:connect ("changed", function (o, file, old, evtype)
    -- files attributes changed
    if evtype == "attribute-changed" then
      if file ~= SND_SEQ_PATH then
        return
      end
      if midi_node == nil and GLib.access (SND_SEQ_PATH, "rw") then
        midi_node = CreateMidiNode ()
        fm_plugin:call ("remove-watch", SND_PATH)
      end
    end

    -- directory is going to be unmounted
    if evtype == "pre-unmount" then
      fm_plugin:call ("remove-watch", SND_PATH)
    end
  end)

  -- add watch
  fm_plugin:call ("add-watch", SND_PATH, "m")
end
  0707010000013B000081A4000000000000000000000001656CC35F000032D8000000000000000000000000000000000000003100000000wireplumber-0.4.17/src/scripts/monitors/alsa.lua  -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT

-- Receive script arguments from config.lua
local config = ... or {}

-- ensure config.properties is not nil
config.properties = config.properties or {}

-- unique device/node name tables
device_names_table = nil
node_names_table = nil

-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
  r.interests = {}
  for _, i in ipairs(r.matches) do
    local interest_desc = { type = "properties" }
    for _, c in ipairs(i) do
      c.type = "pw"
      table.insert(interest_desc, Constraint(c))
    end
    local interest = Interest(interest_desc)
    table.insert(r.interests, interest)
  end
  r.matches = nil
end

-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
  for _, r in ipairs(config.rules or {}) do
    if r.apply_properties then
      for _, interest in ipairs(r.interests) do
        if interest:matches(properties) then
          for k, v in pairs(r.apply_properties) do
            properties[k] = v
          end
        end
      end
    end
  end
end

function nonempty(str)
  return str ~= "" and str or nil
end

function createNode(parent, id, obj_type, factory, properties)
  local dev_props = parent.properties

  -- set the device id and spa factory name; REQUIRED, do not change
  properties["device.id"] = parent["bound-id"]
  properties["factory.name"] = factory

  -- set the default pause-on-idle setting
  properties["node.pause-on-idle"] = false

  -- try to negotiate the max ammount of channels
  if dev_props["api.alsa.use-acp"] ~= "true" then
    properties["audio.channels"] = properties["audio.channels"] or "64"
  end

  local dev = properties["api.alsa.pcm.device"]
              or properties["alsa.device"] or "0"
  local subdev = properties["api.alsa.pcm.subdevice"]
                 or properties["alsa.subdevice"] or "0"
  local stream = properties["api.alsa.pcm.stream"] or "unknown"
  local profile = properties["device.profile.name"]
                  or (stream .. "." .. dev .. "." .. subdev)
  local profile_desc = properties["device.profile.description"]

  -- set priority
  if not properties["priority.driver"] then
    local priority = (dev == "0") and 1000 or 744
    if stream == "capture" then
      priority = priority + 1000
    end

    priority = priority - (tonumber(dev) * 16) - tonumber(subdev)

    if profile:find("^pro%-") then
      priority = priority + 500
    elseif profile:find("^analog%-") then
      priority = priority + 9
    elseif profile:find("^iec958%-") then
      priority = priority + 8
    end

    properties["priority.driver"] = priority
    properties["priority.session"] = priority
  end

  -- ensure the node has a media class
  if not properties["media.class"] then
    if stream == "capture" then
      properties["media.class"] = "Audio/Source"
    else
      properties["media.class"] = "Audio/Sink"
    end
  end

  -- ensure the node has a name
  if not properties["node.name"] then
    local name =
        (stream == "capture" and "alsa_input" or "alsa_output")
        .. "." ..
        (dev_props["device.name"]:gsub("^alsa_card%.(.+)", "%1") or
         dev_props["device.name"] or
         "unnamed-device")
         .. "." ..
         profile

    -- sanitize name
    name = name:gsub("([^%w_%-%.])", "_")

    properties["node.name"] = name

    -- deduplicate nodes with the same name
    for counter = 2, 99, 1 do
      if node_names_table[properties["node.name"]] ~= true then
        node_names_table[properties["node.name"]] = true
        break
      end
      properties["node.name"] = name .. "." .. counter
    end
  end

  -- and a nick
  local nick = nonempty(properties["node.nick"])
      or nonempty(properties["api.alsa.pcm.name"])
      or nonempty(properties["alsa.name"])
      or nonempty(profile_desc)
      or dev_props["device.nick"]
  if nick == "USB Audio" then
    nick = dev_props["device.nick"]
  end
  -- also sanitize nick, replace ':' with ' '
  properties["node.nick"] = nick:gsub("(:)", " ")

  -- ensure the node has a description
  if not properties["node.description"] then
    local desc = nonempty(dev_props["device.description"]) or "unknown"
    local name = nonempty(properties["api.alsa.pcm.name"]) or
                 nonempty(properties["api.alsa.pcm.id"]) or dev

    if profile_desc then
      desc = desc .. " " .. profile_desc
    elseif subdev ~= "0" then
      desc = desc .. " (" .. name .. " " .. subdev .. ")"
    elseif dev ~= "0" then
      desc = desc .. " (" .. name .. ")"
    end

    -- also sanitize description, replace ':' with ' '
    properties["node.description"] = desc:gsub("(:)", " ")
  end

  -- add api.alsa.card.* properties for rule matching purposes
  for k, v in pairs(dev_props) do
    if k:find("^api%.alsa%.card%..*") then
      properties[k] = v
    end
  end

  -- apply VM overrides
  local vm_overrides = config.properties["vm.node.defaults"]
  if nonempty(Core.get_vm_type()) and type(vm_overrides) == "table" then
    for k, v in pairs(vm_overrides) do
      properties[k] = v
    end
  end

  -- apply properties from config.rules
  rulesApplyProperties(properties)
  if properties["node.disabled"] then
    node_names_table [properties ["node.name"]] = nil
    return
  end

  -- create the node
  local node = Node("adapter", properties)
  node:activate(Feature.Proxy.BOUND)
  parent:store_managed_object(id, node)
end

function createDevice(parent, id, factory, properties)
  local device = SpaDevice(factory, properties)
  if device then
    device:connect("create-object", createNode)
    device:connect("object-removed", function (parent, id)
      local node = parent:get_managed_object(id)
      if not node then
        return
      end

      node_names_table[node.properties["node.name"]] = nil
    end)
    device:activate(Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND)
    parent:store_managed_object(id, device)
  else
    Log.warning ("Failed to create '" .. factory .. "' device")
  end
end

function prepareDevice(parent, id, obj_type, factory, properties)
  -- ensure the device has an appropriate name
  local name = "alsa_card." ..
    (properties["device.name"] or
     properties["device.bus-id"] or
     properties["device.bus-path"] or
     tostring(id)):gsub("([^%w_%-%.])", "_")

  properties["device.name"] = name

  -- deduplicate devices with the same name
  for counter = 2, 99, 1 do
    if device_names_table[properties["device.name"]] ~= true then
      device_names_table[properties["device.name"]] = true
      break
    end
    properties["device.name"] = name .. "." .. counter
  end

  -- ensure the device has a description
  if not properties["device.description"] then
    local d = nil
    local f = properties["device.form-factor"]
    local c = properties["device.class"]
    local n = properties["api.alsa.card.name"]

    if n == "Loopback" then
      d = I18n.gettext("Loopback")
    elseif f == "internal" then
      d = I18n.gettext("Built-in Audio")
    elseif c == "modem" then
      d = I18n.gettext("Modem")
    end

    d = d or properties["device.product.name"]
          or properties["api.alsa.card.name"]
          or properties["alsa.card_name"]
          or "Unknown device"
    properties["device.description"] = d
  end

  -- ensure the device has a nick
  properties["device.nick"] =
      properties["device.nick"] or
      properties["api.alsa.card.name"] or
      properties["alsa.card_name"]

  -- set the icon name
  if not properties["device.icon-name"] then
    local icon = nil
    local icon_map = {
      -- form factor -> icon
      ["microphone"] = "audio-input-microphone",
      ["webcam"] = "camera-web",
      ["handset"] = "phone",
      ["portable"] = "multimedia-player",
      ["tv"] = "video-display",
      ["headset"] = "audio-headset",
      ["headphone"] = "audio-headphones",
      ["speaker"] = "audio-speakers",
      ["hands-free"] = "audio-handsfree",
    }
    local f = properties["device.form-factor"]
    local c = properties["device.class"]
    local b = properties["device.bus"]

    icon = icon_map[f] or ((c == "modem") and "modem") or "audio-card"
    properties["device.icon-name"] = icon .. "-analog" .. (b and ("-" .. b) or "")
  end

  -- apply properties from config.rules
  rulesApplyProperties(properties)
  if properties["device.disabled"] then
    device_names_table [properties ["device.name"]] = nil
    return
  end

  -- override the device factory to use ACP
  if properties["api.alsa.use-acp"] then
    Log.info("Enabling the use of ACP on " .. properties["device.name"])
    factory = "api.alsa.acp.device"
  end

  -- use device reservation, if available
  if rd_plugin and properties["api.alsa.card"] then
    local rd_name = "Audio" .. properties["api.alsa.card"]
    local rd = rd_plugin:call("create-reservation",
        rd_name,
        config.properties["alsa.reserve.application-name"] or "WirePlumber",
        properties["device.name"],
        config.properties["alsa.reserve.priority"] or -20);

    properties["api.dbus.ReserveDevice1"] = rd_name

    -- unlike pipewire-media-session, this logic here keeps the device
    -- acquired at all times and destroys it if someone else acquires
    rd:connect("notify::state", function (rd, pspec)
      local state = rd["state"]

      if state == "acquired" then
        -- create the device
        createDevice(parent, id, factory, properties)

      elseif state == "available" then
        -- attempt to acquire again
        rd:call("acquire")

      elseif state == "busy" then
        -- destroy the device
        parent:store_managed_object(id, nil)
      end
    end)

    rd:connect("release-requested", function (rd)
        Log.info("release requested")
        parent:store_managed_object(id, nil)
        rd:call("release")
    end)

    if jack_device then
      rd:connect("notify::owner-name-changed", function (rd, pspec)
        if rd["state"] == "busy" and
           rd["owner-application-name"] == "Jack audio server" then
            -- TODO enable the jack device
        else
            -- TODO disable the jack device
        end
      end)
    end

    rd:call("acquire")
  else
    -- create the device
    createDevice(parent, id, factory, properties)
  end
end

function createMonitor ()
  local m = SpaDevice("api.alsa.enum.udev", config.properties)
  if m == nil then
    Log.message("PipeWire's SPA ALSA udev plugin(\"api.alsa.enum.udev\")"
      .. "missing or broken. Sound Cards cannot be enumerated")
    return nil
  end

  -- handle create-object to prepare device
  m:connect("create-object", prepareDevice)

  -- handle object-removed to destroy device reservations and recycle device name
  m:connect("object-removed", function (parent, id)
    local device = parent:get_managed_object(id)
    if not device then
      return
    end

    if rd_plugin then
      local rd_name = device.properties["api.dbus.ReserveDevice1"]
      if rd_name then
        rd_plugin:call("destroy-reservation", rd_name)
      end
    end
    device_names_table[device.properties["device.name"]] = nil
    for managed_node in device:iterate_managed_objects() do
      node_names_table[managed_node.properties["node.name"]] = nil
    end
  end)

  -- reset the name tables to make sure names are recycled
  device_names_table = {}
  node_names_table = {}

  -- activate monitor
  Log.info("Activating ALSA monitor")
  m:activate(Feature.SpaDevice.ENABLED)
  return m
end

-- create the JACK device (for PipeWire to act as client to a JACK server)
if config.properties["alsa.jack-device"] then
  jack_device = Device("spa-device-factory", {
    ["factory.name"] = "api.jack.device",
    ["node.name"] = "JACK-Device",
  })
  jack_device:activate(Feature.Proxy.BOUND)
end

-- enable device reservation if requested
if config.properties["alsa.reserve"] then
  rd_plugin = Plugin.find("reserve-device")
end

-- if the reserve-device plugin is enabled, at the point of script execution
-- it is expected to be connected. if it is not, assume the d-bus connection
-- has failed and continue without it
if rd_plugin and rd_plugin:call("get-dbus")["state"] ~= "connected" then
  Log.message("reserve-device plugin is not connected to D-Bus, "
              .. "disabling device reservation")
  rd_plugin = nil
end

-- handle rd_plugin state changes to destroy and re-create the ALSA monitor in
-- case D-Bus service is restarted
if rd_plugin then
  local dbus = rd_plugin:call("get-dbus")
  dbus:connect("notify::state", function (b, pspec)
    local state = b["state"]
    Log.info ("rd-plugin state changed to " .. state)
    if state == "connected" then
      Log.info ("Creating ALSA monitor")
      monitor = createMonitor()
    elseif state == "closed" then
      Log.info ("Destroying ALSA monitor")
      monitor = nil
    end
  end)
end

-- create the monitor
monitor = createMonitor()
0707010000013C000081A4000000000000000000000001656CC35F00001427000000000000000000000000000000000000003700000000wireplumber-0.4.17/src/scripts/monitors/bluez-midi.lua    -- WirePlumber
--
-- Copyright © 2022 Pauli Virtanen
--    @author Pauli Virtanen
--
-- SPDX-License-Identifier: MIT

local config = ... or {}

-- unique device/node name tables
node_names_table = nil
id_to_name_table = nil

-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
  r.interests = {}
  for _, i in ipairs(r.matches) do
    local interest_desc = { type = "properties" }
    for _, c in ipairs(i) do
      c.type = "pw"
      table.insert(interest_desc, Constraint(c))
    end
    local interest = Interest(interest_desc)
    table.insert(r.interests, interest)
  end
  r.matches = nil
end

-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
  for _, r in ipairs(config.rules or {}) do
    if r.apply_properties then
      for _, interest in ipairs(r.interests) do
        if interest:matches(properties) then
          for k, v in pairs(r.apply_properties) do
            properties[k] = v
          end
        end
      end
    end
  end
end

function setLatencyOffset(node, offset_msec)
  if not offset_msec then
    return
  end

  local props = { "Spa:Pod:Object:Param:Props", "Props" }
  props.latencyOffsetNsec = tonumber(offset_msec) * 1000000

  local param = Pod.Object(props)
  Log.debug(param, "setting latency offset on " .. tostring(node))
  node:set_param("Props", param)
end

function createNode(parent, id, type, factory, properties)
  properties["factory.name"] = factory

  -- set the node description
  local desc = properties["node.description"]
  -- sanitize description, replace ':' with ' '
  properties["node.description"] = desc:gsub("(:)", " ")

  -- set the node name
  local name =
      "bluez_midi." .. properties["api.bluez5.address"]
  -- sanitize name
  name = name:gsub("([^%w_%-%.])", "_")
  -- deduplicate nodes with the same name
  properties["node.name"] = name
  for counter = 2, 99, 1 do
    if node_names_table[properties["node.name"]] ~= true then
      node_names_table[properties["node.name"]] = true
      break
    end
    properties["node.name"] = name .. "." .. counter
  end

  properties["api.glib.mainloop"] = "true"

  -- apply properties from config.rules
  rulesApplyProperties(properties)

  local latency_offset = properties["node.latency-offset-msec"]
  properties["node.latency-offset-msec"] = nil

  -- create the node
  -- it doesn't necessarily need to be a local node,
  -- the other Bluetooth parts run in the local process,
  -- so it's consistent to have also this here
  local node = LocalNode("spa-node-factory", properties)
  node:activate(Feature.Proxy.BOUND)
  parent:store_managed_object(id, node)
  id_to_name_table[id] = properties["node.name"]
  setLatencyOffset(node, latency_offset)
end

function createMonitor()
  local monitor_props = {}
  for k, v in pairs(config.properties or {}) do
    monitor_props[k] = v
  end
  monitor_props["server"] = nil

  monitor_props["api.glib.mainloop"] = "true"

  local monitor = SpaDevice("api.bluez5.midi.enum", monitor_props)
  if monitor then
    monitor:connect("create-object", createNode)
    monitor:connect("object-removed", function (parent, id)
        node_names_table[id_to_name_table[id]] = nil
        id_to_name_table[id] = nil
    end)
  else
    Log.message("PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported.")
    return nil
  end

  -- reset the name tables to make sure names are recycled
  node_names_table = {}
  id_to_name_table = {}

  monitor:activate(Feature.SpaDevice.ENABLED)
  return monitor
end

function createServers()
  local props = config.properties or {}

  if not props["servers"] then
    return nil
  end

  local servers = {}
  local i = 1

  for k, v in pairs(props["servers"]) do
    local node_props = {
      ["node.name"] = v,
      ["node.description"] = string.format(I18n.gettext("BLE MIDI %d"), i),
      ["api.bluez5.role"] = "server",
      ["factory.name"] = "api.bluez5.midi.node",
      ["api.glib.mainloop"] = "true",
    }
    rulesApplyProperties(node_props)

    local latency_offset = node_props["node.latency-offset-msec"]
    node_props["node.latency-offset-msec"] = nil

    local node = LocalNode("spa-node-factory", node_props)
    if node then
      node:activate(Feature.Proxy.BOUND)
      table.insert(servers, node)
      setLatencyOffset(node, latency_offset)
    else
      Log.message("Failed to create BLE MIDI server.")
    end
    i = i + 1
  end

  return servers
end

logind_plugin = Plugin.find("logind")
if logind_plugin then
  -- if logind support is enabled, activate
  -- the monitor only when the seat is active
  function startStopMonitor(seat_state)
    Log.info(logind_plugin, "Seat state changed: " .. seat_state)

    if seat_state == "active" then
      monitor = createMonitor()
      servers = createServers()
    elseif monitor then
      monitor:deactivate(Feature.SpaDevice.ENABLED)
      monitor = nil
      servers = nil
    end
  end

  logind_plugin:connect("state-changed", function(p, s) startStopMonitor(s) end)
  startStopMonitor(logind_plugin:call("get-state"))
else
  monitor = createMonitor()
  servers = createServers()
end
 0707010000013D000081A4000000000000000000000001656CC35F000032C7000000000000000000000000000000000000003200000000wireplumber-0.4.17/src/scripts/monitors/bluez.lua -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT

local config = ... or {}
local COMBINE_OFFSET = 64

devices_om = ObjectManager {
  Interest {
    type = "device",
  }
}

nodes_om = ObjectManager {
  Interest {
    type = "node",
    Constraint { "node.name", "#", "*.bluez_*put*"},
    Constraint { "device.id", "+" },
  }
}

-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
  r.interests = {}
  for _, i in ipairs(r.matches) do
    local interest_desc = { type = "properties" }
    for _, c in ipairs(i) do
      c.type = "pw"
      table.insert(interest_desc, Constraint(c))
    end
    local interest = Interest(interest_desc)
    table.insert(r.interests, interest)
  end
  r.matches = nil
end

-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
  for _, r in ipairs(config.rules or {}) do
    if r.apply_properties then
      for _, interest in ipairs(r.interests) do
        if interest:matches(properties) then
          for k, v in pairs(r.apply_properties) do
            properties[k] = v
          end
        end
      end
    end
  end
end

function setOffloadActive(device, value)
  local pod = Pod.Object {
    "Spa:Pod:Object:Param:Props", "Props", bluetoothOffloadActive = value
  }
  device:set_params("Props", pod)
end

nodes_om:connect("object-added", function(_, node)
  node:connect("state-changed", function(node, old_state, cur_state)
    local interest = Interest {
      type = "device",
      Constraint { "object.id", "=", node.properties["device.id"]}
    }
    for d in devices_om:iterate (interest) do
      if cur_state == "running" then
        setOffloadActive(d, true)
      else
        setOffloadActive(d, false)
      end
    end
  end)
end)

function createOffloadScoNode(parent, id, type, factory, properties)
  local dev_props = parent.properties

  local args = {
    ["audio.channels"] = 1,
    ["audio.position"] = "[MONO]",
  }

  local desc =
      dev_props["device.description"]
      or dev_props["device.name"]
      or dev_props["device.nick"]
      or dev_props["device.alias"]
      or "bluetooth-device"
  -- sanitize description, replace ':' with ' '
  args["node.description"] = desc:gsub("(:)", " ")

  if factory:find("sink") then
    local capture_args = {
      ["device.id"] = parent["bound-id"],
      ["media.class"] = "Audio/Sink",
      ["node.pause-on-idle"] = false,
    }
    for k, v in pairs(properties) do
      capture_args[k] = v
    end

    local name = "bluez_output" .. "." .. (properties["api.bluez5.address"] or dev_props["device.name"]) .. "." .. tostring(id)
    args["node.name"] = name:gsub("([^%w_%-%.])", "_")
    args["capture.props"] = Json.Object(capture_args)
    args["playback.props"] = Json.Object {
      ["node.passive"] = true,
      ["node.pause-on-idle"] = false,
    }
  elseif factory:find("source") then
    local playback_args = {
      ["device.id"] = parent["bound-id"],
      ["media.class"] = "Audio/Source",
      ["node.pause-on-idle"] = false,
    }
    for k, v in pairs(properties) do
      playback_args[k] = v
    end

    local name = "bluez_input" .. "." .. (properties["api.bluez5.address"] or dev_props["device.name"]) .. "." .. tostring(id)
    args["node.name"] = name:gsub("([^%w_%-%.])", "_")
    args["capture.props"] = Json.Object {
      ["node.passive"] = true,
      ["node.pause-on-idle"] = false,
    }
    args["playback.props"] = Json.Object(playback_args)
  else
    Log.warning(parent, "Unsupported factory: " .. factory)
    return
  end

  -- Transform 'args' to a json object here
  local args_json = Json.Object(args)

  -- and get the final JSON as a string from the json object
  local args_string = args_json:get_data()

  local loopback_properties = {}

  local loopback = LocalModule("libpipewire-module-loopback", args_string, loopback_properties)
  parent:store_managed_object(id, loopback)
end

device_set_nodes_om = ObjectManager {
  Interest {
    type = "node",
    Constraint { "api.bluez5.set.leader", "+", type = "pw" },
  }
}

device_set_nodes_om:connect ("object-added", function(_, node)
    -- Connect ObjectConfig events to the right node
    if not monitor then
      return
    end

    local interest = Interest {
      type = "device",
      Constraint { "object.id", "=", node.properties["device.id"] }
    }
    Log.info("Device set node found: " .. tostring (node["bound-id"]))
    for device in devices_om:iterate (interest) do
      local device_id = device.properties["api.bluez5.id"]
      if not device_id then
        goto next_device
      end

      local spa_device = monitor:get_managed_object (tonumber (device_id))
      if not spa_device then
        goto next_device
      end

      local id = node.properties["card.profile.device"]
      if id ~= nil then
        Log.info(".. assign to device: " .. tostring (device["bound-id"]) .. " node " .. tostring (id))
        spa_device:store_managed_object (id, node)

        -- set routes again to update volumes etc.
        for route in device:iterate_params ("Route") do
          device:set_param ("Route", route)
        end
      end

      ::next_device::
    end
end)

function createSetNode(parent, id, type, factory, properties)
  local args = {}
  local name
  local target_class
  local stream_class
  local rules = {}
  local members_json = Json.Raw (properties["api.bluez5.set.members"])
  local channels_json = Json.Raw (properties["api.bluez5.set.channels"])
  local members = members_json:parse ()
  local channels = channels_json:parse ()

  if properties["media.class"] == "Audio/Sink" then
    name = "bluez_output"
    args["combine.mode"] = "sink"
    target_class = "Audio/Sink/Internal"
    stream_class = "Stream/Output/Audio/Internal"
  else
    name = "bluez_input"
    args["combine.mode"] = "source"
    target_class = "Audio/Source/Internal"
    stream_class = "Stream/Input/Audio/Internal"
  end

  Log.info("Device set: " .. properties["node.name"])

  for _, member in pairs(members) do
    Log.info("Device set member:" .. member["object.path"])
    table.insert(rules,
      Json.Object {
        ["matches"] = Json.Array {
          Json.Object {
            ["object.path"] = member["object.path"],
            ["media.class"] = target_class,
          },
        },
        ["actions"] = Json.Object {
          ["create-stream"] = Json.Object {
            ["media.class"] = stream_class,
            ["audio.position"] = Json.Array (member["channels"]),
          }
        },
      }
    )
  end

  properties["node.virtual"] = false
  properties["device.api"] = "bluez5"
  properties["api.bluez5.set.members"] = nil
  properties["api.bluez5.set.channels"] = nil
  properties["api.bluez5.set.leader"] = true
  properties["audio.position"] = Json.Array (channels)
  args["combine.props"] = Json.Object (properties)
  args["stream.props"] = Json.Object {}
  args["stream.rules"] = Json.Array (rules)

  local args_json = Json.Object(args)
  local args_string = args_json:get_data()
  local combine_properties = {}
  Log.info("Device set node: " .. args_string)
  return LocalModule("libpipewire-module-combine-stream", args_string, combine_properties)
end

function createNode(parent, id, type, factory, properties)
  local dev_props = parent.properties

  if config.properties["bluez5.hw-offload-sco"] and factory:find("sco") then
    createOffloadScoNode(parent, id, type, factory, properties)
    return
  end

  -- set the device id and spa factory name; REQUIRED, do not change
  properties["device.id"] = parent["bound-id"]
  properties["factory.name"] = factory

  -- set the default pause-on-idle setting
  properties["node.pause-on-idle"] = false

  -- set the node description
  local desc =
      dev_props["device.description"]
      or dev_props["device.name"]
      or dev_props["device.nick"]
      or dev_props["device.alias"]
      or "bluetooth-device"
  -- sanitize description, replace ':' with ' '
  properties["node.description"] = desc:gsub("(:)", " ")

  -- set the node name
  local name =
      ((factory:find("sink") and "bluez_output") or
       (factory:find("source") and "bluez_input" or factory)) .. "." ..
      (properties["api.bluez5.address"] or dev_props["device.name"]) .. "." ..
      tostring(id)
  -- sanitize name
  properties["node.name"] = name:gsub("([^%w_%-%.])", "_")

  -- set priority
  if not properties["priority.driver"] then
    local priority = factory:find("source") and 2010 or 1010
    properties["priority.driver"] = priority
    properties["priority.session"] = priority
  end

  -- autoconnect if it's a stream
  if properties["api.bluez5.profile"] == "headset-audio-gateway" or
     properties["api.bluez5.profile"] == "bap-sink" or
     factory:find("a2dp.source") or factory:find("media.source") then
    properties["node.autoconnect"] = true
  end

  -- apply properties from config.rules
  rulesApplyProperties(properties)

  -- create the node; bluez requires "local" nodes, i.e. ones that run in
  -- the same process as the spa device, for several reasons

  if properties["api.bluez5.set.leader"] then
    local combine = createSetNode(parent, id, type, factory, properties)
    parent:store_managed_object(id + COMBINE_OFFSET, combine)
  else
    local node = LocalNode("adapter", properties)
    node:activate(Feature.Proxy.BOUND)
    parent:store_managed_object(id, node)
  end
end

function removeNode(parent, id)
  -- Clear also the device set module, if any
  parent:store_managed_object(id + COMBINE_OFFSET, nil)
end

function createDevice(parent, id, type, factory, properties)
  local device = parent:get_managed_object(id)
  if not device then
    -- ensure a proper device name
    local name =
        (properties["device.name"] or
         properties["api.bluez5.address"] or
         properties["device.description"] or
         tostring(id)):gsub("([^%w_%-%.])", "_")

    if not name:find("^bluez_card%.", 1) then
      name = "bluez_card." .. name
    end
    properties["device.name"] = name

    -- set the icon name
    if not properties["device.icon-name"] then
      local icon = nil
      local icon_map = {
        -- form factor -> icon
        ["microphone"] = "audio-input-microphone",
        ["webcam"] = "camera-web",
        ["handset"] = "phone",
        ["portable"] = "multimedia-player",
        ["tv"] = "video-display",
        ["headset"] = "audio-headset",
        ["headphone"] = "audio-headphones",
        ["speaker"] = "audio-speakers",
        ["hands-free"] = "audio-handsfree",
      }
      local f = properties["device.form-factor"]
      local b = properties["device.bus"]

      icon = icon_map[f] or "audio-card"
      properties["device.icon-name"] = icon .. (b and ("-" .. b) or "")
    end

    -- initial profile is to be set by policy-device-profile.lua, not spa-bluez5
    properties["bluez5.profile"] = "off"
    properties["api.bluez5.id"] = id

    -- apply properties from config.rules
    rulesApplyProperties(properties)

    -- create the device
    device = SpaDevice(factory, properties)
    if device then
      device:connect("create-object", createNode)
      device:connect("object-removed", removeNode)
      parent:store_managed_object(id, device)
    else
      Log.warning ("Failed to create '" .. factory .. "' device")
      return
    end
  end

  Log.info(parent, string.format("%d, %s (%s): %s",
        id, properties["device.description"],
        properties["api.bluez5.address"], properties["api.bluez5.connection"]))

  -- activate the device after the bluez profiles are connected
  if properties["api.bluez5.connection"] == "connected" then
    device:activate(Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND)
  else
    device:deactivate(Features.ALL)
  end
end

function createMonitor()
  local monitor_props = config.properties or {}
  monitor_props["api.bluez5.connection-info"] = true

  local monitor = SpaDevice("api.bluez5.enum.dbus", monitor_props)
  if monitor then
    monitor:connect("create-object", createDevice)
  else
    Log.message("PipeWire's BlueZ SPA missing or broken. Bluetooth not supported.")
    return nil
  end
  monitor:activate(Feature.SpaDevice.ENABLED)

  return monitor
end

logind_plugin = Plugin.find("logind")
if logind_plugin then
  -- if logind support is enabled, activate
  -- the monitor only when the seat is active
  function startStopMonitor(seat_state)
    Log.info(logind_plugin, "Seat state changed: " .. seat_state)

    if seat_state == "active" then
      monitor = createMonitor()
    elseif monitor then
      monitor:deactivate(Feature.SpaDevice.ENABLED)
      monitor = nil
    end
  end

  logind_plugin:connect("state-changed", function(p, s) startStopMonitor(s) end)
  startStopMonitor(logind_plugin:call("get-state"))
else
  monitor = createMonitor()
end

nodes_om:activate()
devices_om:activate()
device_set_nodes_om:activate()
 0707010000013E000081A4000000000000000000000001656CC35F00001401000000000000000000000000000000000000003600000000wireplumber-0.4.17/src/scripts/monitors/libcamera.lua -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT

local config = ... or {}

-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
  r.interests = {}
  for _, i in ipairs(r.matches) do
    local interest_desc = { type = "properties" }
    for _, c in ipairs(i) do
      c.type = "pw"
      table.insert(interest_desc, Constraint(c))
    end
    local interest = Interest(interest_desc)
    table.insert(r.interests, interest)
  end
  r.matches = nil
end

-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
  for _, r in ipairs(config.rules or {}) do
    if r.apply_properties then
      for _, interest in ipairs(r.interests) do
        if interest:matches(properties) then
          for k, v in pairs(r.apply_properties) do
            properties[k] = v
          end
        end
      end
    end
  end
end

function findDuplicate(parent, id, property, value)
  for i = 0, id - 1, 1 do
    local obj = parent:get_managed_object(i)
    if obj and obj.properties[property] == value then
      return true
    end
  end
  return false
end

function createNode(parent, id, type, factory, properties)
  local dev_props = parent.properties
  local location = properties["api.libcamera.location"]

  -- set the device id and spa factory name; REQUIRED, do not change
  properties["device.id"] = parent["bound-id"]
  properties["factory.name"] = factory

  -- set the default pause-on-idle setting
  properties["node.pause-on-idle"] = false

  -- set the node name
  local name =
      (factory:find("sink") and "libcamera_output") or
       (factory:find("source") and "libcamera_input" or factory)
      .. "." ..
      (dev_props["device.name"]:gsub("^libcamera_device%.(.+)", "%1") or
       dev_props["device.name"] or
       dev_props["device.nick"] or
       dev_props["device.alias"] or
       "libcamera-device")
  -- sanitize name
  name = name:gsub("([^%w_%-%.])", "_")

  properties["node.name"] = name

  -- deduplicate nodes with the same name
  for counter = 2, 99, 1 do
    if findDuplicate(parent, id, "node.name", properties["node.name"]) then
      properties["node.name"] = name .. "." .. counter
    else
      break
    end
  end

  -- set the node description
  local desc = dev_props["device.description"] or "libcamera-device"
  if location == "front" then
    desc = I18n.gettext("Built-in Front Camera")
  elseif location == "back" then
    desc = I18n.gettext("Built-in Back Camera")
  end
  -- sanitize description, replace ':' with ' '
  properties["node.description"] = desc:gsub("(:)", " ")

  -- set the node nick
  local nick = properties["node.nick"] or
               dev_props["device.product.name"] or
               dev_props["device.description"] or
               dev_props["device.nick"]
  properties["node.nick"] = nick:gsub("(:)", " ")

  -- set priority
  if not properties["priority.session"] then
    local priority = 700
    if location == "external" then
      priority = priority + 150
    elseif location == "front" then
      priority = priority + 100
    elseif location == "back" then
      priority = priority + 50
    end
    properties["priority.session"] = priority
  end

  -- apply properties from config.rules
  rulesApplyProperties(properties)
  if properties ["node.disabled"] then
    return
  end

  -- create the node
  local node = Node("spa-node-factory", properties)
  node:activate(Feature.Proxy.BOUND)
  parent:store_managed_object(id, node)
end

function createDevice(parent, id, type, factory, properties)
  -- ensure the device has an appropriate name
  local name = "libcamera_device." ..
      (properties["device.name"] or
       properties["device.bus-id"] or
       properties["device.bus-path"] or
       tostring(id)):gsub("([^%w_%-%.])", "_")

  properties["device.name"] = name

  -- deduplicate devices with the same name
  for counter = 2, 99, 1 do
    if findDuplicate(parent, id, "device.name", properties["device.name"]) then
      properties["device.name"] = name .. "." .. counter
    else
      break
    end
  end

  -- ensure the device has a description
  properties["device.description"] =
      properties["device.description"]
      or properties["device.product.name"]
      or "Unknown device"

  -- apply properties from config.rules
  rulesApplyProperties(properties)
  if properties ["device.disabled"] then
    return
  end
  -- create the device
  local device = SpaDevice(factory, properties)
  if device then
    device:connect("create-object", createNode)
    device:activate(Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND)
    parent:store_managed_object(id, device)
  else
    Log.warning ("Failed to create '" .. factory .. "' device")
  end
end

monitor = SpaDevice("api.libcamera.enum.manager", config.properties or {})
if monitor then
  monitor:connect("create-object", createDevice)
  monitor:activate(Feature.SpaDevice.ENABLED)
else
  Log.message("PipeWire's libcamera SPA missing or broken. libcamera not supported.")
end
   0707010000013F000081A4000000000000000000000001656CC35F000012E0000000000000000000000000000000000000003100000000wireplumber-0.4.17/src/scripts/monitors/v4l2.lua  -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT

local config = ... or {}

-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
  r.interests = {}
  for _, i in ipairs(r.matches) do
    local interest_desc = { type = "properties" }
    for _, c in ipairs(i) do
      c.type = "pw"
      table.insert(interest_desc, Constraint(c))
    end
    local interest = Interest(interest_desc)
    table.insert(r.interests, interest)
  end
  r.matches = nil
end

-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
  for _, r in ipairs(config.rules or {}) do
    if r.apply_properties then
      for _, interest in ipairs(r.interests) do
        if interest:matches(properties) then
          for k, v in pairs(r.apply_properties) do
            properties[k] = v
          end
        end
      end
    end
  end
end

function findDuplicate(parent, id, property, value)
  for i = 0, id - 1, 1 do
    local obj = parent:get_managed_object(i)
    if obj and obj.properties[property] == value then
      return true
    end
  end
  return false
end

function createNode(parent, id, type, factory, properties)
  local dev_props = parent.properties

  -- set the device id and spa factory name; REQUIRED, do not change
  properties["device.id"] = parent["bound-id"]
  properties["factory.name"] = factory

  -- set the default pause-on-idle setting
  properties["node.pause-on-idle"] = false

  -- set the node name
  local name =
      (factory:find("sink") and "v4l2_output") or
       (factory:find("source") and "v4l2_input" or factory)
      .. "." ..
      (dev_props["device.name"]:gsub("^v4l2_device%.(.+)", "%1") or
       dev_props["device.name"] or
       dev_props["device.nick"] or
       dev_props["device.alias"] or
       "v4l2-device")
  -- sanitize name
  name = name:gsub("([^%w_%-%.])", "_")

  properties["node.name"] = name

  -- deduplicate nodes with the same name
  for counter = 2, 99, 1 do
    if findDuplicate(parent, id, "node.name", properties["node.name"]) then
      properties["node.name"] = name .. "." .. counter
    else
      break
    end
  end

  -- set the node description
  local desc = dev_props["device.description"] or "v4l2-device"
  desc = desc .. " (V4L2)"
  -- sanitize description, replace ':' with ' '
  properties["node.description"] = desc:gsub("(:)", " ")

  -- set the node nick
  local nick = properties["node.nick"] or
               dev_props["device.product.name"] or
               dev_props["api.v4l2.cap.card"] or
               dev_props["device.description"] or
               dev_props["device.nick"]
  properties["node.nick"] = nick:gsub("(:)", " ")

  -- set priority
  if not properties["priority.session"] then
    local path = properties["api.v4l2.path"] or "/dev/video100"
    local dev = path:gsub("/dev/video(%d+)", "%1")
    properties["priority.session"] = 1000 - (tonumber(dev) * 10)
  end

  -- apply properties from config.rules
  rulesApplyProperties(properties)
  if properties["node.disabled"] then
    return
  end

  -- create the node
  local node = Node("spa-node-factory", properties)
  node:activate(Feature.Proxy.BOUND)
  parent:store_managed_object(id, node)
end

function createDevice(parent, id, type, factory, properties)
  -- ensure the device has an appropriate name
  local name = "v4l2_device." ..
      (properties["device.name"] or
       properties["device.bus-id"] or
       properties["device.bus-path"] or
       tostring(id)):gsub("([^%w_%-%.])", "_")

  properties["device.name"] = name

  -- deduplicate devices with the same name
  for counter = 2, 99, 1 do
    if findDuplicate(parent, id, "device.name", properties["device.name"]) then
      properties["device.name"] = name .. "." .. counter
    else
      break
    end
  end

  -- ensure the device has a description
  properties["device.description"] =
      properties["device.description"]
      or properties["device.product.name"]
      or "Unknown device"

  -- apply properties from config.rules
  rulesApplyProperties(properties)
  if properties["device.disabled"] then
    return
  end

  -- create the device
  local device = SpaDevice(factory, properties)
  if device then
    device:connect("create-object", createNode)
    device:activate(Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND)
    parent:store_managed_object(id, device)
  else
    Log.warning ("Failed to create '" .. factory .. "' device")
  end
end

monitor = SpaDevice("api.v4l2.enum.udev", config.properties or {})
if monitor then
  monitor:connect("create-object", createDevice)
  monitor:activate(Feature.SpaDevice.ENABLED)
else
  Log.message("PipeWire's V4L SPA missing or broken. Video4Linux not supported.")
end
07070100000140000081A4000000000000000000000001656CC35F00002B88000000000000000000000000000000000000003400000000wireplumber-0.4.17/src/scripts/policy-bluetooth.lua   -- WirePlumber
--
-- Copyright © 2021 Asymptotic Inc.
--    @author Sanchayan Maity <sanchayan@asymptotic.io>
--
-- Based on bt-profile-switch.lua in tests/examples
-- Copyright © 2021 George Kiagiadakis
--
-- Based on bluez-autoswitch in media-session
-- Copyright © 2021 Pauli Virtanen
--
-- SPDX-License-Identifier: MIT
--
-- Checks for the existence of media.role and if present switches the bluetooth
-- profile accordingly. Also see bluez-autoswitch in media-session.
-- The intended logic of the script is as follows.
--
-- When a stream comes in, if it has a Communication or phone role in PulseAudio
-- speak in props, we switch to the highest priority profile that has an Input
-- route available. The reason for this is that we may have microphone enabled
-- non-HFP codecs eg. Faststream.
-- We track the incoming streams with Communication role or the applications
-- specified which do not set the media.role correctly perhaps.
-- When a stream goes away if the list with which we track the streams above
-- is empty, then we revert back to the old profile.

local config = ...
local use_persistent_storage = config["use-persistent-storage"] or false
local applications = {}
local use_headset_profile = config["media-role.use-headset-profile"] or false
local profile_restore_timeout_msec = 2000

local INVALID = -1
local timeout_source = nil
local restore_timeout_source = nil

local state = use_persistent_storage and State("policy-bluetooth") or nil
local headset_profiles = state and state:load() or {}
local last_profiles = {}

local active_streams = {}
local previous_streams = {}

for _, value in ipairs(config["media-role.applications"] or {}) do
  applications[value] = true
end

metadata_om = ObjectManager {
  Interest {
    type = "metadata",
    Constraint { "metadata.name", "=", "default" },
  }
}

devices_om = ObjectManager {
  Interest {
    type = "device",
    Constraint { "device.api", "=", "bluez5" },
  }
}

streams_om = ObjectManager {
  Interest {
    type = "node",
    Constraint { "media.class", "matches", "Stream/Input/Audio", type = "pw-global" },
    -- Do not consider monitor streams
    Constraint { "stream.monitor", "!", "true" }
  }
}

local function parseParam(param_to_parse, id)
  local param = param_to_parse:parse()
  if param.pod_type == "Object" and param.object_id == id then
    return param.properties
  else
    return nil
  end
end

local function storeAfterTimeout()
  if not use_persistent_storage then
    return
  end

  if timeout_source then
    timeout_source:destroy()
  end
  timeout_source = Core.timeout_add(1000, function ()
    local saved, err = state:save(headset_profiles)
    if not saved then
      Log.warning(err)
    end
    timeout_source = nil
  end)
end

local function saveHeadsetProfile(device, profile_name)
  local key = "saved-headset-profile:" .. device.properties["device.name"]
  headset_profiles[key] = profile_name
  storeAfterTimeout()
end

local function getSavedHeadsetProfile(device)
  local key = "saved-headset-profile:" .. device.properties["device.name"]
  return headset_profiles[key]
end

local function saveLastProfile(device, profile_name)
  last_profiles[device.properties["device.name"]] = profile_name
end

local function getSavedLastProfile(device)
  return last_profiles[device.properties["device.name"]]
end

local function isSwitched(device)
  return getSavedLastProfile(device) ~= nil
end

local function isBluez5AudioSink(sink_name)
  if sink_name and string.find(sink_name, "bluez_output.") ~= nil then
    return true
  end
  return false
end

local function isBluez5DefaultAudioSink()
  local metadata = metadata_om:lookup()
  local default_audio_sink = metadata:find(0, "default.audio.sink")
  return isBluez5AudioSink(default_audio_sink)
end

local function findProfile(device, index, name)
  for p in device:iterate_params("EnumProfile") do
    local profile = parseParam(p, "EnumProfile")
    if not profile then
      goto skip_enum_profile
    end

    Log.debug("Profile name: " .. profile.name .. ", priority: "
              .. tostring(profile.priority) .. ", index: " .. tostring(profile.index))
    if (index ~= nil and profile.index == index) or
        (name ~= nil and profile.name == name) then
      return profile.priority, profile.index, profile.name
    end

    ::skip_enum_profile::
  end

  return INVALID, INVALID, nil
end

local function getCurrentProfile(device)
  for p in device:iterate_params("Profile") do
    local profile = parseParam(p, "Profile")
    if profile then
      return profile.name
    end
  end

  return nil
end

local function highestPrioProfileWithInputRoute(device)
  local profile_priority = INVALID
  local profile_index = INVALID
  local profile_name = nil

  for p in device:iterate_params("EnumRoute") do
    local route = parseParam(p, "EnumRoute")
    -- Parse pod
    if not route then
      goto skip_enum_route
    end

    if route.direction ~= "Input" then
      goto skip_enum_route
    end

    Log.debug("Route with index: " .. tostring(route.index) .. ", direction: "
          .. route.direction .. ", name: " .. route.name .. ", description: "
          .. route.description .. ", priority: " .. route.priority)
    if route.profiles then
      for _, v in pairs(route.profiles) do
        local priority, index, name = findProfile(device, v)
        if priority ~= INVALID then
          if profile_priority < priority then
            profile_priority = priority
            profile_index = index
            profile_name = name
          end
        end
      end
    end

    ::skip_enum_route::
  end

  return profile_priority, profile_index, profile_name
end

local function hasProfileInputRoute(device, profile_index)
  for p in device:iterate_params("EnumRoute") do
    local route = parseParam(p, "EnumRoute")
    if route and route.direction == "Input" and route.profiles then
      for _, v in pairs(route.profiles) do
        if v == profile_index then
          return true
        end
      end
    end
  end
  return false
end

local function switchProfile()
  local index
  local name

  if restore_timeout_source then
    restore_timeout_source:destroy()
    restore_timeout_source = nil
  end

  for device in devices_om:iterate() do
    if isSwitched(device) then
      goto skip_device
    end

    local cur_profile_name = getCurrentProfile(device)
    saveLastProfile(device, cur_profile_name)

    _, index, name = findProfile(device, nil, cur_profile_name)
    if hasProfileInputRoute(device, index) then
      Log.info("Current profile has input route, not switching")
      goto skip_device
    end

    local saved_headset_profile = getSavedHeadsetProfile(device)
    index = INVALID
    if saved_headset_profile then
      _, index, name = findProfile(device, nil, saved_headset_profile)
    end
    if index == INVALID then
      _, index, name = highestPrioProfileWithInputRoute(device)
    end

    if index ~= INVALID then
      local pod = Pod.Object {
        "Spa:Pod:Object:Param:Profile", "Profile",
        index = index
      }

      Log.info("Setting profile of '"
            .. device.properties["device.description"]
            .. "' from: " .. cur_profile_name
            .. " to: " .. name)
      device:set_params("Profile", pod)
    else
      Log.warning("Got invalid index when switching profile")
    end

    ::skip_device::
  end
end

local function restoreProfile()
  for device in devices_om:iterate() do
    if isSwitched(device) then
      local profile_name = getSavedLastProfile(device)
      local cur_profile_name = getCurrentProfile(device)

      saveLastProfile(device, nil)

      if cur_profile_name then
        Log.info("Setting saved headset profile to: " .. cur_profile_name)
        saveHeadsetProfile(device, cur_profile_name)
      end

      if profile_name then
        local _, index, name = findProfile(device, nil, profile_name)

        if index ~= INVALID then
          local pod = Pod.Object {
            "Spa:Pod:Object:Param:Profile", "Profile",
            index = index
          }

          Log.info("Restoring profile of '"
                .. device.properties["device.description"]
                .. "' from: " .. cur_profile_name
                .. " to: " .. name)
          device:set_params("Profile", pod)
        else
          Log.warning("Failed to restore profile")
        end
      end
    end
  end
end

local function triggerRestoreProfile()
  if restore_timeout_source then
    return
  end
  if next(active_streams) ~= nil then
    return
  end
  restore_timeout_source = Core.timeout_add(profile_restore_timeout_msec, function ()
    restore_timeout_source = nil
    restoreProfile()
  end)
end

-- We consider a Stream of interest to have role Communication if it has
-- media.role set to Communication in props or it is in our list of
-- applications as these applications do not set media.role correctly or at
-- all.
local function checkStreamStatus(stream)
  local app_name = stream.properties["application.name"]
  local stream_role = stream.properties["media.role"]

  if not (stream_role == "Communication" or applications[app_name]) then
    return false
  end
  if not isBluez5DefaultAudioSink() then
    return false
  end

  -- If a stream we previously saw stops running, we consider it
  -- inactive, because some applications (Teams) just cork input
  -- streams, but don't close them.
  if previous_streams[stream["bound-id"]] and stream.state ~= "running" then
    return false
  end

  return true
end

local function handleStream(stream)
  if not use_headset_profile then
    return
  end

  if checkStreamStatus(stream) then
    active_streams[stream["bound-id"]] = true
    previous_streams[stream["bound-id"]] = true
    switchProfile()
  else
    active_streams[stream["bound-id"]] = nil
    triggerRestoreProfile()
  end
end

local function handleAllStreams()
  for stream in streams_om:iterate {
    Constraint { "media.class", "matches", "Stream/Input/Audio", type = "pw-global" },
    Constraint { "stream.monitor", "!", "true" }
  } do
    handleStream(stream)
  end
end

streams_om:connect("object-added", function (_, stream)
  stream:connect("state-changed", function (stream, old_state, cur_state)
    handleStream(stream)
  end)
  stream:connect("params-changed", handleStream)
  handleStream(stream)
end)

streams_om:connect("object-removed", function (_, stream)
  active_streams[stream["bound-id"]] = nil
  previous_streams[stream["bound-id"]] = nil
  triggerRestoreProfile()
end)

devices_om:connect("object-added", function (_, device)
  -- Devices are unswitched initially
  if isSwitched(device) then
    saveLastProfile(device, nil)
  end
  handleAllStreams()
end)

metadata_om:connect("object-added", function (_, metadata)
  metadata:connect("changed", function (m, subject, key, t, value)
    if (use_headset_profile and subject == 0 and key == "default.audio.sink"
        and isBluez5AudioSink(value)) then
      -- If bluez sink is set as default, rescan for active input streams
      handleAllStreams()
    end
  end)
end)

metadata_om:activate()
devices_om:activate()
streams_om:activate()
07070100000141000081A4000000000000000000000001656CC35F00001B21000000000000000000000000000000000000003900000000wireplumber-0.4.17/src/scripts/policy-device-profile.lua  -- WirePlumber
--
-- Copyright © 2022 Collabora Ltd.
--    @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT

local self = {}
self.config = ... or {}
self.config.persistent = self.config.persistent or {}
self.config.priorities = self.config.priorities or {}
self.active_profiles = {}
self.default_profile_plugin = Plugin.find("default-profile")

function createIntrestObjects(t)
  for _, p in ipairs(t or {}) do
    p.interests = {}
    for _, i in ipairs(p.matches) do
      local interest_desc = { type = "properties" }
      for _, c in ipairs(i) do
        c.type = "pw"
        table.insert(interest_desc, Constraint(c))
      end
      local interest = Interest(interest_desc)
      table.insert(p.interests, interest)
    end
    p.matches = nil
  end
end

-- Preprocess persistent profiles and create Interest objects
createIntrestObjects(self.config.persistent)
-- Preprocess profile priorities and create Interest objects
createIntrestObjects(self.config.priorities)

-- Checks whether a device profile is persistent or not
function isProfilePersistent(device_props, profile_name)
  for _, p in ipairs(self.config.persistent or {}) do
    if p.profile_names then
      for _, interest in ipairs(p.interests) do
        if interest:matches(device_props) then
          for _, pn in ipairs(p.profile_names) do
            if pn == profile_name then
              return true
            end
          end
        end
      end
    end
  end
  return false
end

function parseParam(param, id)
  local parsed = param:parse()
  if parsed.pod_type == "Object" and parsed.object_id == id then
    return parsed.properties
  else
    return nil
  end
end

function setDeviceProfile (device, dev_id, dev_name, profile)
  if self.active_profiles[dev_id] and
      self.active_profiles[dev_id].index == profile.index then
    Log.info ("Profile " .. profile.name .. " is already set in " .. dev_name)
    return
  end

  local param = Pod.Object {
    "Spa:Pod:Object:Param:Profile", "Profile",
    index = profile.index,
  }
  Log.info ("Setting profile " .. profile.name .. " on " .. dev_name)
  device:set_param("Profile", param)
end

function findDefaultProfile (device)
  local def_name = nil

  if self.default_profile_plugin ~= nil then
    def_name = self.default_profile_plugin:call ("get-profile", device)
  end
  if def_name == nil then
    return nil
  end

  for p in device:iterate_params("EnumProfile") do
    local profile = parseParam(p, "EnumProfile")
    if profile.name == def_name then
      return profile
    end
  end

  return nil
end

-- returns the priorities, if defined
function getDevicePriorities(device_props, profile_name)
  for _, p in ipairs(self.config.priorities or {}) do
    for _, interest in ipairs(p.interests) do
      if interest:matches(device_props) then
        return p.priorities
      end
    end
  end

  return nil
end

-- find profiles based on user preferences.
function findPreferredProfile(device)
  local priority_table = getDevicePriorities(device.properties)

  if not priority_table or #priority_table == 0 then
    return nil
  else
    Log.info("priority table found for device " ..
      device.properties["device.name"])
  end

  for _, priority_profile in ipairs(priority_table) do
    for p in device:iterate_params("EnumProfile") do
      device_profile = parseParam(p, "EnumProfile")
      if device_profile.name == priority_profile then
        Log.info("Selected user preferred profile " ..
          device_profile.name .. " for " .. device.properties["device.name"])
        return device_profile
      end
    end
  end

  return nil
end

-- find profiles based on inbuilt priorities.
function findBestProfile(device)
  -- Takes absolute priority if available or unknown
  local profile_prop = device.properties["device.profile"]
  local off_profile = nil
  local best_profile = nil
  local unk_profile = nil
  local profile = nil

  for p in device:iterate_params("EnumProfile") do
    profile = parseParam(p, "EnumProfile")
    if profile and profile.name == profile_prop and profile.available ~= "no" then
      return profile
    elseif profile and profile.name ~= "pro-audio" then
      if profile.name == "off" then
        off_profile = profile
      elseif profile.available == "yes" then
        if best_profile == nil or profile.priority > best_profile.priority then
          best_profile = profile
        end
      elseif profile.available ~= "no" then
        if unk_profile == nil or profile.priority > unk_profile.priority then
          unk_profile = profile
        end
      end
    end
  end

  if best_profile ~= nil then
    profile = best_profile
  elseif unk_profile ~= nil then
    profile = unk_profile
  elseif off_profile ~= nil then
    profile = off_profile
  end

  if profile ~= nil then
    Log.info("Found best profile " .. profile.name .. " for " .. device.properties["device.name"])
    return profile
  else
    return nil
  end
end

function handleProfiles (device, new_device)
  local dev_id = device["bound-id"]
  local dev_name = device.properties["device.name"]

  local def_profile = findDefaultProfile (device)

  -- Do not do anything if active profile is both persistent and default
  if not new_device and
      self.active_profiles[dev_id] ~= nil and
      isProfilePersistent (device.properties, self.active_profiles[dev_id].name) and
      def_profile ~= nil and
      self.active_profiles[dev_id].name == def_profile.name
      then
    local active_profile = self.active_profiles[dev_id].name
    Log.info ("Device profile " .. active_profile .. " is persistent for " .. dev_name)
    return
  end

  if def_profile ~= nil then
    if def_profile.available == "no" then
      Log.info ("Default profile " .. def_profile.name .. " unavailable for " .. dev_name)
    else
      Log.info ("Found default profile " .. def_profile.name .. " for " .. dev_name)
      setDeviceProfile (device, dev_id, dev_name, def_profile)
      return
    end
  else
    Log.info ("Default profile not found for " .. dev_name)
  end

  local best_profile = findPreferredProfile(device)

  if not best_profile then
    best_profile = findBestProfile(device)
  end

  if best_profile ~= nil then
    setDeviceProfile (device, dev_id, dev_name, best_profile)
  else
    Log.info ("Best profile not found on " .. dev_name)
  end
end

function onDeviceParamsChanged (device, param_name)
  if param_name == "EnumProfile" then
    handleProfiles (device, false)
  end
end

self.om = ObjectManager {
  Interest {
    type = "device",
    Constraint { "device.name", "is-present", type = "pw-global" },
  }
}

self.om:connect("object-added", function (_, device)
  device:connect ("params-changed", onDeviceParamsChanged)
  handleProfiles (device, true)
end)

self.om:connect("object-removed", function (_, device)
  local dev_id = device["bound-id"]
  self.active_profiles[dev_id] = nil
end)

self.om:activate()
   07070100000142000081A4000000000000000000000001656CC35F000036FA000000000000000000000000000000000000003800000000wireplumber-0.4.17/src/scripts/policy-device-routes.lua   -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- Based on default-routes.c from pipewire-media-session
-- Copyright © 2020 Wim Taymans
--
-- SPDX-License-Identifier: MIT

local config = ... or {}

-- whether to store state on the file system
use_persistent_storage = config["use-persistent-storage"] or false

-- the default volume to apply
default_volume = tonumber(config["default-volume"] or 0.4^3)
default_input_volume = tonumber(config["default-input-volume"] or 1.0)

-- table of device info
dev_infos = {}

-- the state storage
state = use_persistent_storage and State("default-routes") or nil
state_table = state and state:load() or {}

-- simple serializer {"foo", "bar"} -> "foo;bar;"
function serializeArray(a)
  local str = ""
  for _, v in ipairs(a) do
    str = str .. tostring(v):gsub(";", "\\;") .. ";"
  end
  return str
end

-- simple deserializer "foo;bar;" -> {"foo", "bar"}
function parseArray(str, convert_value)
  local array = {}
  local val = ""
  local escaped = false
  for i = 1, #str do
    local c = str:sub(i,i)
    if c == '\\' then
      escaped = true
    elseif c == ';' and not escaped then
      val = convert_value and convert_value(val) or val
      table.insert(array, val)
      val = ""
    else
      val = val .. tostring(c)
      escaped = false
    end
  end
  return array
end

function arrayContains(a, value)
  for _, v in ipairs(a) do
    if v == value then
      return true
    end
  end
  return false
end

function parseParam(param, id)
  local route = param:parse()
  if route.pod_type == "Object" and route.object_id == id then
    return route.properties
  else
    return nil
  end
end

function storeAfterTimeout()
  if timeout_source then
    timeout_source:destroy()
  end
  timeout_source = Core.timeout_add(1000, function ()
    local saved, err = state:save(state_table)
    if not saved then
      Log.warning(err)
    end
    timeout_source = nil
  end)
end

function saveProfile(dev_info, profile_name)
  if not use_persistent_storage then
    return
  end

  local routes = {}
  for idx, ri in pairs(dev_info.route_infos) do
    if ri.save then
      table.insert(routes, ri.name)
    end
  end

  if #routes > 0 then
    local key = dev_info.name .. ":profile:" .. profile_name
    state_table[key] = serializeArray(routes)
    storeAfterTimeout()
  end
end

function saveRouteProps(dev_info, route)
  if not use_persistent_storage or not route.props then
    return
  end

  local props = route.props.properties
  local key_base = dev_info.name .. ":" ..
                   route.direction:lower() .. ":" ..
                   route.name .. ":"

  state_table[key_base .. "volume"] =
    props.volume and tostring(props.volume) or nil
  state_table[key_base .. "mute"] =
    props.mute and tostring(props.mute) or nil
  state_table[key_base .. "channelVolumes"] =
    props.channelVolumes and serializeArray(props.channelVolumes) or nil
  state_table[key_base .. "channelMap"] =
    props.channelMap and serializeArray(props.channelMap) or nil
  state_table[key_base .. "latencyOffsetNsec"] =
    props.latencyOffsetNsec and tostring(props.latencyOffsetNsec) or nil
  state_table[key_base .. "iec958Codecs"] =
    props.iec958Codecs and serializeArray(props.iec958Codecs) or nil

  storeAfterTimeout()
end

function restoreRoute(device, dev_info, device_id, route)
  -- default props
  local props = {
    "Spa:Pod:Object:Param:Props", "Route",
    mute = false,
  }

  if route.direction == "Input" then
    props.channelVolumes = { default_input_volume }
  else
    props.channelVolumes = { default_volume }
  end

  -- restore props from persistent storage
  if use_persistent_storage then
    local key_base = dev_info.name .. ":" ..
                     route.direction:lower() .. ":" ..
                     route.name .. ":"

    local str = state_table[key_base .. "volume"]
    props.volume = str and tonumber(str) or props.volume

    local str = state_table[key_base .. "mute"]
    props.mute = str and (str == "true") or false

    local str = state_table[key_base .. "channelVolumes"]
    props.channelVolumes = str and parseArray(str, tonumber) or props.channelVolumes

    local str = state_table[key_base .. "channelMap"]
    props.channelMap = str and parseArray(str) or props.channelMap

    local str = state_table[key_base .. "latencyOffsetNsec"]
    props.latencyOffsetNsec = str and math.tointeger(str) or props.latencyOffsetNsec

    local str = state_table[key_base .. "iec958Codecs"]
    props.iec958Codecs = str and parseArray(str) or props.iec958Codecs
  end

  -- convert arrays to Spa Pod
  if props.channelVolumes then
    table.insert(props.channelVolumes, 1, "Spa:Float")
    props.channelVolumes = Pod.Array(props.channelVolumes)
  end
  if props.channelMap then
    table.insert(props.channelMap, 1, "Spa:Enum:AudioChannel")
    props.channelMap = Pod.Array(props.channelMap)
  end
  if props.iec958Codecs then
    table.insert(props.iec958Codecs, 1, "Spa:Enum:AudioIEC958Codec")
    props.iec958Codecs = Pod.Array(props.iec958Codecs)
  end

  -- construct Route param
  local param = Pod.Object {
    "Spa:Pod:Object:Param:Route", "Route",
    index = route.index,
    device = device_id,
    props = Pod.Object(props),
    save = route.save,
  }

  Log.debug(param, "setting route on " .. tostring(device))
  device:set_param("Route", param)

  route.prev_active = true
  route.active = true
end

function findActiveDeviceIDs(profile)
  -- parses the classes from the profile and returns the device IDs
  ----- sample structure, should return { 0, 8 } -----
  -- classes:
  --  1: 2
  --  2:
  --    1: Audio/Source
  --    2: 1
  --    3: card.profile.devices
  --    4:
  --      1: 0
  --      pod_type: Array
  --      value_type: Spa:Int
  --    pod_type: Struct
  --  3:
  --    1: Audio/Sink
  --    2: 1
  --    3: card.profile.devices
  --    4:
  --      1: 8
  --      pod_type: Array
  --      value_type: Spa:Int
  --    pod_type: Struct
  --  pod_type: Struct
  local active_ids = {}
  if type(profile.classes) == "table" and profile.classes.pod_type == "Struct" then
    for _, p in ipairs(profile.classes) do
      if type(p) == "table" and p.pod_type == "Struct" then
        local i = 1
        while true do
          local k, v = p[i], p[i+1]
          i = i + 2
          if not k or not v then
            break
          end
          if k == "card.profile.devices" and
              type(v) == "table" and v.pod_type == "Array" then
            for _, dev_id in ipairs(v) do
              table.insert(active_ids, dev_id)
            end
          end
        end
      end
    end
  end
  return active_ids
end

-- returns an array of the route names that were previously selected
-- for the given device and profile
function getStoredProfileRoutes(dev_name, profile_name)
  local key = dev_name .. ":profile:" .. profile_name
  local str = state_table[key]
  return str and parseArray(str) or {}
end

-- find a route that was previously stored for a device_id
-- spr needs to be the array returned from getStoredProfileRoutes()
function findSavedRoute(dev_info, device_id, spr)
  for idx, ri in pairs(dev_info.route_infos) do
    if arrayContains(ri.devices, device_id) and
        (ri.profiles == nil or arrayContains(ri.profiles, dev_info.active_profile)) and
        arrayContains(spr, ri.name) then
      return ri
    end
  end
  return nil
end

-- find the best route for a given device_id, based on availability and priority
function findBestRoute(dev_info, device_id)
  local best_avail = nil
  local best_unk = nil
  for idx, ri in pairs(dev_info.route_infos) do
    if arrayContains(ri.devices, device_id) and
          (ri.profiles == nil or arrayContains(ri.profiles, dev_info.active_profile)) then
      if ri.available == "yes" or ri.available == "unknown" then
        if ri.direction == "Output" and ri.available ~= ri.prev_available then
          best_avail = ri
          ri.save = true
          break
        elseif ri.available == "yes" then
          if (best_avail == nil or ri.priority > best_avail.priority) then
            best_avail = ri
          end
        elseif best_unk == nil or ri.priority > best_unk.priority then
            best_unk = ri
        end
      end
    end
  end
  return best_avail or best_unk
end

function restoreProfileRoutes(device, dev_info, profile, profile_changed)
  Log.info(device, "restore routes for profile " .. profile.name)

  local active_ids = findActiveDeviceIDs(profile)
  local spr = getStoredProfileRoutes(dev_info.name, profile.name)

  for _, device_id in ipairs(active_ids) do
    Log.info(device, "restoring device " .. device_id);

    local route = nil

    -- restore routes selection for the newly selected profile
    -- don't bother if spr is empty, there is no point
    if profile_changed and #spr > 0 then
      route = findSavedRoute(dev_info, device_id, spr)
      if route then
        -- we found a saved route
        if route.available == "no" then
          Log.info(device, "saved route '" .. route.name .. "' not available")
          -- not available, try to find next best
          route = nil
        else
          Log.info(device, "found saved route: " .. route.name)
          -- make sure we save it again
          route.save = true
        end
      end
    end

    -- we could not find a saved route, try to find a new best
    if not route then
      route = findBestRoute(dev_info, device_id)
      if not route then
        Log.info(device, "can't find best route")
      else
        Log.info(device, "found best route: " .. route.name)
      end
    end

    -- restore route
    if route then
      restoreRoute(device, dev_info, device_id, route)
    end
  end
end

function findRouteInfo(dev_info, route, return_new)
  local ri = dev_info.route_infos[route.index]
  if not ri and return_new then
    ri = {
      index = route.index,
      name = route.name,
      direction = route.direction,
      devices = route.devices or {},
      profiles = route.profiles,
      priority = route.priority or 0,
      available = route.available or "unknown",
      prev_available = route.available or "unknown",
      active = false,
      prev_active = false,
      save = false,
    }
  end
  return ri
end

function handleDevice(device)
  local dev_info = dev_infos[device["bound-id"]]
  local new_route_infos = {}
  local avail_routes_changed = false
  local profile = nil

  -- get current profile
  for p in device:iterate_params("Profile") do
    profile = parseParam(p, "Profile")
  end

  -- look at all the routes and update/reset cached information
  for p in device:iterate_params("EnumRoute") do
    -- parse pod
    local route = parseParam(p, "EnumRoute")
    if not route then
      goto skip_enum_route
    end

    -- find cached route information
    local route_info = findRouteInfo(dev_info, route, true)

    -- update properties
    route_info.prev_available = route_info.available
    if route_info.available ~= route.available then
      Log.info(device, "route " .. route.name .. " available changed " ..
                       route_info.available .. " -> " .. route.available)
      route_info.available = route.available
      if profile and arrayContains(route.profiles, profile.index) then
        avail_routes_changed = true
      end
    end
    route_info.prev_active = route_info.active
    route_info.active = false
    route_info.save = false

    -- store
    new_route_infos[route.index] = route_info

    ::skip_enum_route::
  end

  -- replace old route_infos to lose old routes
  -- that no longer exist on the device
  dev_info.route_infos = new_route_infos
  new_route_infos = nil

  -- check for changes in the active routes
  for p in device:iterate_params("Route") do
    local route = parseParam(p, "Route")
    if not route then
      goto skip_route
    end

    -- get cached route info and at the same time
    -- ensure that the route is also in EnumRoute
    local route_info = findRouteInfo(dev_info, route, false)
    if not route_info then
      goto skip_route
    end

    -- update state
    route_info.active = true
    route_info.save = route.save

    if not route_info.prev_active then
      -- a new route is now active, restore the volume and
      -- make sure we save this as a preferred route
      Log.info(device, "new active route found " .. route.name)
      restoreRoute(device, dev_info, route.device, route_info)
    elseif route.save then
      -- just save route properties
      Log.info(device, "storing route props for " .. route.name)
      saveRouteProps(dev_info, route)
    end

    ::skip_route::
  end

  -- restore routes for profile
  if profile then
    local profile_changed = (dev_info.active_profile ~= profile.index)

    -- if the profile changed, restore routes for that profile
    -- if any of the routes of the current profile changed in availability,
    -- then try to select a new "best" route for each device and ignore
    -- what was stored
    if profile_changed or avail_routes_changed then
      dev_info.active_profile = profile.index
      restoreProfileRoutes(device, dev_info, profile, profile_changed)
    end

    saveProfile(dev_info, profile.name)
  end
end

om = ObjectManager {
  Interest {
    type = "device",
    Constraint { "device.name", "is-present", type = "pw-global" },
  }
}

om:connect("objects-changed", function (om)
  local new_dev_infos = {}
  for device in om:iterate() do
    local dev_info = dev_infos[device["bound-id"]]
    -- new device appeared
    if not dev_info then
      dev_info = {
        name = device.properties["device.name"],
        active_profile = -1,
        route_infos = {},
      }
      dev_infos[device["bound-id"]] = dev_info

      device:connect("params-changed", handleDevice)
      handleDevice(device)
    end

    new_dev_infos[device["bound-id"]] = dev_info
  end
  -- replace list to get rid of dev_info for devices that no longer exist
  dev_infos = new_dev_infos
end)

om:activate()
  07070100000143000081A4000000000000000000000001656CC35F00000966000000000000000000000000000000000000002E00000000wireplumber-0.4.17/src/scripts/policy-dsp.lua -- WirePlumber
--
-- Copyright © 2022-2023 The WirePlumber project contributors
--    @author Dmitry Sharshakov <d3dx12.xx@gmail.com>
--
-- SPDX-License-Identifier: MIT

local config = ... or {}
config.rules = config.rules or {}

for _, r in ipairs(config.rules) do
  r.interests = {}
  for _, i in ipairs(r.matches) do
    local interest_desc = { type = "properties" }

    for _, c in ipairs(i) do
      c.type = "pw"
      table.insert(interest_desc, Constraint(c))
    end

    local interest = Interest(interest_desc)
    table.insert(r.interests, interest)
  end
end

-- TODO: only check for hotplug of nodes with known DSP rules
nodes_om = ObjectManager {
  Interest { type = "node" },
}

clients_om = ObjectManager {
  Interest { type = "client" }
}

filter_chains = {}
hidden_nodes = {}

nodes_om:connect("object-added", function (om, node)
  for _, r in ipairs(config.rules or {}) do
    for _, interest in ipairs(r.interests) do
      if interest:matches(node["global-properties"]) then
        local id = node["global-properties"]["object.id"]

        if r.filter_chain then
          if filter_chains[id] then
            Log.warning("Sink " .. id .. " has been plugged now, but has a filter chain loaded. Skipping")
          else
            filter_chains[id] = LocalModule("libpipewire-module-filter-chain", r.filter_chain, {}, true)
          end
        end

        if r.hide_parent then
          Log.debug("Hiding node " .. node["bound-id"] .. " from clients")
          for client in clients_om:iterate { type = "client" } do
            if not client["properties"]["wireplumber.daemon"] then
              client:update_permissions { [node["bound-id"]] = "-" }
            end
          end
          hidden_nodes[node["bound-id"]] = id
        end

      end
    end
  end
end)

nodes_om:connect("object-removed", function (om, node)
  local id = node["global-properties"]["object.id"]
  if filter_chains[id] then
    Log.debug("Unloading filter chain associated with sink " .. id)
    filter_chains[id] = nil
  else
    Log.debug("Disconnected sink " .. id .. " does not have any filters to be removed")
  end
end)

clients_om:connect("object-added", function (om, client)
  for id, _ in pairs(hidden_nodes) do
    if not client["properties"]["wireplumber.daemon"] then
      client:update_permissions { [id] = "-" }
    end
  end
end)

nodes_om:activate()
clients_om:activate()
  07070100000144000081A4000000000000000000000001656CC35F00001609000000000000000000000000000000000000004000000000wireplumber-0.4.17/src/scripts/policy-endpoint-client-links.lua   -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT

local config = ... or {}
config.roles = config.roles or {}
config["duck.level"] = config["duck.level"] or 0.3

function findRole(role)
  if role and not config.roles[role] then
    for r, p in pairs(config.roles) do
      if type(p.alias) == "table" then
        for i = 1, #(p.alias), 1 do
          if role == p.alias[i] then
            return r
          end
        end
      end
    end
  end
  return role
end

function priorityForRole(role)
  local r = role and config.roles[role] or nil
  return r and r.priority or 0
end

function getAction(dominant_role, other_role)
  -- default to "mix" if the role is not configured
  if not dominant_role or not config.roles[dominant_role] then
    return "mix"
  end

  local role_config = config.roles[dominant_role]
  return role_config["action." .. other_role]
      or role_config["action.default"]
      or "mix"
end

function restoreVolume(role, media_class)
  if not mixer_api then return end

  local ep = endpoints_om:lookup {
    Constraint { "media.role", "=", role, type = "pw" },
    Constraint { "media.class", "=", media_class, type = "pw" },
  }

  if ep and ep.properties["node.id"] then
    Log.debug(ep, "restore role " .. role)
    mixer_api:call("set-volume", ep.properties["node.id"], {
      monitorVolume = 1.0,
    })
  end
end

function duck(role, media_class)
  if not mixer_api then return end

  local ep = endpoints_om:lookup {
    Constraint { "media.role", "=", role, type = "pw" },
    Constraint { "media.class", "=", media_class, type = "pw" },
  }

  if ep and ep.properties["node.id"] then
    Log.debug(ep, "duck role " .. role)
    mixer_api:call("set-volume", ep.properties["node.id"], {
      monitorVolume = config["duck.level"],
    })
  end
end

function getSuspendPlaybackMetadata ()
  local suspend = false
  local metadata = metadata_om:lookup()
  if metadata then
    local value = metadata:find(0, "suspend.playback")
    if value then
      suspend = value == "1" and true or false
    end
  end
  return suspend
end

function rescan()
  local links = {
    ["Audio/Source"] = {},
    ["Audio/Sink"] = {},
    ["Video/Source"] = {},
  }

  Log.info("Rescan endpoint links")

  -- deactivate all links if suspend playback metadata is present
  local suspend = getSuspendPlaybackMetadata()
  for silink in silinks_om:iterate() do
    if suspend then
      silink:deactivate(Feature.SessionItem.ACTIVE)
    end
  end

  -- gather info about links
  for silink in silinks_om:iterate() do
    local props = silink.properties
    local role = props["media.role"]
    local target_class = props["target.media.class"]
    local plugged = props["item.plugged.usec"]
    local active =
      ((silink:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0)
    if links[target_class] then
      table.insert(links[target_class], {
        silink = silink,
        role = findRole(role),
        active = active,
        priority = priorityForRole(role),
        plugged = plugged and tonumber(plugged) or 0
      })
    end
  end

  local function compareLinks(l1, l2)
    return (l1.priority > l2.priority) or
        ((l1.priority == l2.priority) and (l1.plugged > l2.plugged))
  end

  for media_class, v in pairs(links) do
    -- sort on priority and stream creation time
    table.sort(v, compareLinks)

    -- apply actions
    local first_link = v[1]
    if first_link then
      for i = 2, #v, 1 do
        local action = getAction(first_link.role, v[i].role)
        if action == "cork" then
          if v[i].active then
            v[i].silink:deactivate(Feature.SessionItem.ACTIVE)
          end
        elseif action == "mix" then
          if not v[i].active and not suspend then
            v[i].silink:activate(Feature.SessionItem.ACTIVE, pendingOperation())
          end
          restoreVolume(v[i].role, media_class)
        elseif action == "duck" then
          if not v[i].active and not suspend then
            v[i].silink:activate(Feature.SessionItem.ACTIVE, pendingOperation())
          end
          duck(v[i].role, media_class)
        else
          Log.warning("Unknown action: " .. action)
        end
      end

      if not first_link.active and not suspend then
        first_link.silink:activate(Feature.SessionItem.ACTIVE, pendingOperation())
      end
      restoreVolume(first_link.role, media_class)
    end
  end
end

pending_ops = 0
pending_rescan = false

function pendingOperation()
  pending_ops = pending_ops + 1
  return function()
    pending_ops = pending_ops - 1
    if pending_ops == 0 and pending_rescan then
      pending_rescan = false
      rescan()
    end
  end
end

function maybeRescan()
  if pending_ops == 0 then
    rescan()
  else
    pending_rescan = true
  end
end

silinks_om = ObjectManager {
  Interest {
    type = "SiLink",
    Constraint { "is.policy.endpoint.client.link", "=", true },
  },
}
silinks_om:connect("objects-changed", maybeRescan)
silinks_om:activate()

-- enable ducking if mixer-api is loaded
mixer_api = Plugin.find("mixer-api")
if mixer_api then
  endpoints_om = ObjectManager {
    Interest { type = "endpoint" },
  }
  endpoints_om:activate()
end

metadata_om = ObjectManager {
  Interest {
    type = "metadata",
    Constraint { "metadata.name", "=", "default" },
  }
}
metadata_om:connect("object-added", function (om, metadata)
  metadata:connect("changed", function (m, subject, key, t, value)
    if key == "suspend.playback" then
      maybeRescan()
    end
  end)
end)
metadata_om:activate()
   07070100000145000081A4000000000000000000000001656CC35F00001BFC000000000000000000000000000000000000003A00000000wireplumber-0.4.17/src/scripts/policy-endpoint-client.lua -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT

-- Receive script arguments from config.lua
local config = ... or {}
config.roles = config.roles or {}

local self = {}
self.scanning = false
self.pending_rescan = false

function rescan ()
  for si in linkables_om:iterate() do
    handleLinkable (si)
  end
end

function scheduleRescan ()
  if self.scanning then
    self.pending_rescan = true
    return
  end

  self.scanning = true
  rescan ()
  self.scanning = false

  if self.pending_rescan then
    self.pending_rescan = false
    Core.sync(function ()
      scheduleRescan ()
    end)
  end
end

function findRole(role, tmc)
  if role and not config.roles[role] then
    -- find the role with matching alias
    for r, p in pairs(config.roles) do
      -- default media class can be overridden in the role config data
      mc = p["media.class"] or "Audio/Sink"
      if (type(p.alias) == "table" and tmc == mc) then
        for i = 1, #(p.alias), 1 do
          if role == p.alias[i] then
            return r
          end
        end
      end
    end

    -- otherwise get the lowest priority role
    local lowest_priority_p = nil
    local lowest_priority_r = nil
    for r, p in pairs(config.roles) do
      mc = p["media.class"] or "Audio/Sink"
      if tmc == mc and (lowest_priority_p == nil or
          p.priority < lowest_priority_p.priority) then
        lowest_priority_p = p
        lowest_priority_r = r
      end
    end
    return lowest_priority_r
  end
  return role
end

function findTargetEndpoint (node, media_class, role)
  local target_class_assoc = {
    ["Stream/Input/Audio"] = "Audio/Source",
    ["Stream/Output/Audio"] = "Audio/Sink",
    ["Stream/Input/Video"] = "Video/Source",
  }
  local media_role = nil
  local highest_priority = -1
  local target = nil

  -- get target media class
  local target_media_class = target_class_assoc[media_class]
  if not target_media_class then
    return nil
  end

  -- find highest priority endpoint by role
  media_role = findRole(role, target_media_class)
  for si_target_ep in endpoints_om:iterate {
    Constraint { "role", "=", media_role, type = "pw-global" },
    Constraint { "media.class", "=", target_media_class, type = "pw-global" },
  } do
    local priority = tonumber(si_target_ep.properties["priority"])
    if priority > highest_priority then
      highest_priority = priority
      target = si_target_ep
    end
  end

  return target
end

function createLink (si, si_target_ep)
  local out_item = nil
  local in_item = nil
  local si_props = si.properties
  local target_ep_props = si_target_ep.properties

  if si_props["item.node.direction"] == "output" then
    -- playback
    out_item = si
    in_item = si_target_ep
  else
    -- capture
    out_item = si_target_ep
    in_item = si
  end

  Log.info (string.format("link %s <-> %s",
      tostring(si_props["node.name"]),
      tostring(target_ep_props["name"])))

  -- create and configure link
  local si_link = SessionItem ( "si-standard-link" )
  if not si_link:configure {
    ["out.item"] = out_item,
    ["in.item"] = in_item,
    ["out.item.port.context"] = "output",
    ["in.item.port.context"] = "input",
    ["is.policy.endpoint.client.link"] = true,
    ["media.role"] = target_ep_props["role"],
    ["target.media.class"] = target_ep_props["media.class"],
    ["item.plugged.usec"] = si_props["item.plugged.usec"],
  } then
    Log.warning (si_link, "failed to configure si-standard-link")
    return
  end

  -- register
  si_link:register()
end

function checkLinkable (si)
  -- only handle session items that has a node associated proxy
  local node = si:get_associated_proxy ("node")
  if not node or not node.properties then
    return false
  end

  -- only handle stream session items
  local media_class = node.properties["media.class"]
  if not media_class or not string.find (media_class, "Stream") then
    return false
  end

  -- Determine if we can handle item by this policy
  if endpoints_om:get_n_objects () == 0 then
    Log.debug (si, "item won't be handled by this policy")
    return false
  end

  return true
end

function handleLinkable (si)
  if not checkLinkable (si) then
    return
  end

  local node = si:get_associated_proxy ("node")
  local media_class = node.properties["media.class"] or ""
  local media_role = node.properties["media.role"] or "Default"
  Log.info (si, "handling item " .. tostring(node.properties["node.name"]) ..
      " with role " .. media_role)

  -- find proper target endpoint
  local si_target_ep = findTargetEndpoint (node, media_class, media_role)
  if not si_target_ep then
    Log.info (si, "... target endpoint not found")
    return
  end

  -- Check if item is linked to proper target, otherwise re-link
  for link in links_om:iterate() do
    local out_id = tonumber(link.properties["out.item.id"])
    local in_id = tonumber(link.properties["in.item.id"])
    if out_id == si.id or in_id == si.id then
      local is_out = out_id == si.id and true or false
      for peer_ep in endpoints_om:iterate() do
        if peer_ep.id == (is_out and in_id or out_id) then

          if peer_ep.id == si_target_ep.id then
            Log.info (si, "... already linked to proper target endpoint")
            return
          end

          -- remove old link if active, otherwise schedule rescan
          if ((link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0) then
            link:remove ()
            Log.info (si, "... moving to new target")
          else
            scheduleRescan ()
            Log.info (si, "... scheduled rescan")
            return
          end

        end
      end
    end
  end

  -- create new link
  createLink (si, si_target_ep)
end

function unhandleLinkable (si)
  if not checkLinkable (si) then
    return
  end

  local node = si:get_associated_proxy ("node")
  Log.info (si, "unhandling item " .. tostring(node.properties["node.name"]))

  -- remove any links associated with this item
  for silink in links_om:iterate() do
    local out_id = tonumber (silink.properties["out.item.id"])
    local in_id = tonumber (silink.properties["in.item.id"])
    if out_id == si.id or in_id == si.id then
      silink:remove ()
      Log.info (silink, "... link removed")
    end
  end
end

endpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
linkables_om = ObjectManager { Interest { type = "SiLinkable",
  -- only handle si-audio-adapter and si-node
  Constraint {
    "item.factory.name", "=", "si-audio-adapter", type = "pw-global" },
  Constraint {
    "active-features", "!", 0, type = "gobject" },
  Constraint {
    "node.link-group", "-" },
  }
}
links_om = ObjectManager { Interest { type = "SiLink",
  -- only handle links created by this policy
  Constraint { "is.policy.endpoint.client.link", "=", true, type = "pw-global" },
} }

linkables_om:connect("objects-changed", function (om)
  scheduleRescan ()
end)

linkables_om:connect("object-removed", function (om, si)
  unhandleLinkable (si)
end)

endpoints_om:activate()
linkables_om:activate()
links_om:activate()
07070100000146000081A4000000000000000000000001656CC35F00001E54000000000000000000000000000000000000003A00000000wireplumber-0.4.17/src/scripts/policy-endpoint-device.lua -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT

-- Receive script arguments from config.lua
local config = ... or {}

-- ensure config.move and config.follow are not nil
config.move = config.move or false
config.follow = config.follow or false

local self = {}
self.scanning = false
self.pending_rescan = false

function rescan ()
  -- check endpoints and register new links
  for si_ep in endpoints_om:iterate() do
    handleLinkable(si_ep)
  end

  -- handle filters only if we have endpoints
  if endpoints_om:get_n_objects () > 0 then
    for filter in streams_om:iterate {
      Constraint { "node.link-group", "+" },
    } do
      handleFilter(filter)
    end
  end
end

function scheduleRescan ()
  if self.scanning then
    self.pending_rescan = true
    return
  end

  self.scanning = true
  rescan ()
  self.scanning = false

  if self.pending_rescan then
    self.pending_rescan = false
    Core.sync(function ()
      scheduleRescan ()
    end)
  end
end

function findFilterTarget (props)
  for si_target in linkables_om:iterate {
    -- exclude filter targets
    Constraint { "node.link-group", "+" },
  } do
    local si_props = si_target.properties
    if si_props["target.endpoint"] and si_props["target.endpoint"] == props["name"] then
      return si_target
    end
  end
end

function findTargetByDefaultNode (target_media_class)
  local def_id = default_nodes:call("get-default-node", target_media_class)
  if def_id ~= Id.INVALID then
    for si_target in linkables_om:iterate {
      -- exclude filter targets
      Constraint { "node.link-group", "-" },
    } do
      local target_node = si_target:get_associated_proxy ("node")
      if target_node["bound-id"] == def_id then
        return si_target
      end
    end
  end
  return nil
end

function findTargetByFirstAvailable (target_media_class)
  for si_target in linkables_om:iterate {
    -- exclude filter targets
    Constraint { "node.link-group", "-" },
  } do
    local target_node = si_target:get_associated_proxy ("node")
    if target_node.properties["media.class"] == target_media_class then
      return si_target
    end
  end
  return nil
end

function findUndefinedTarget (si_ep)
  local media_class = si_ep.properties["media.class"]
  local target_class_assoc = {
    ["Audio/Source"] = "Audio/Source",
    ["Stream/Output/Audio"] = "Audio/Sink",
    ["Audio/Sink"] = "Audio/Sink",
    ["Video/Source"] = "Video/Source",
  }
  local target_media_class = target_class_assoc[media_class]
  if not target_media_class then
    return nil
  end

  local si_target = findFilterTarget (si_ep.properties)
  if not si_target then
    si_target = findTargetByDefaultNode (target_media_class)
  end
  if not si_target then
    si_target = findTargetByFirstAvailable (target_media_class)
  end
  return si_target
end

function createLink (si_ep, si_target, is_filter)
  local out_item = nil
  local in_item = nil
  local ep_props = si_ep.properties
  local target_props = si_target.properties

  if target_props["item.node.direction"] == "input" then
    -- playback
    out_item = si_ep
    in_item = si_target
  else
    -- capture
    in_item = si_ep
    out_item = si_target
  end

  local link_string = string.format("link %s <-> %s ",
    (is_filter and ep_props["node.name"] or ep_props["name"]),
    target_props["node.name"])

  -- create and configure link
  local si_link = SessionItem ( "si-standard-link" )

  Log.info(si_link, link_string)

  if not si_link:configure {
    ["out.item"] = out_item,
    ["in.item"] = in_item,
    ["out.item.port.context"] = "output",
    ["in.item.port.context"] = "input",
    ["is.policy.endpoint.device.link"] = true,
  } then
    Log.warning (si_link, "failed to configure si-standard-link")
    return
  end

  -- register
  si_link:register ()

  Log.info (si_link, " activating link " .. link_string)

  -- activate
  si_link:activate (Feature.SessionItem.ACTIVE, function (l, e)
    if e then
      Log.warning (l, "failed to activate link: " .. link_string .. tostring(e))
      l:remove ()
    else
      Log.info (l, "activated link " .. link_string)
    end
  end)
end

function handleFilter(filter)
  handleLinkable(filter)
end

function handleLinkable (si)
  local si_props = si.properties
  local is_filter = (si_props["node.link-group"] ~= nil)
  if is_filter then
    Log.info (si, "handling filter " .. si_props["node.name"])
  else
    Log.info (si, "handling endpoint " .. si_props["name"])
  end

  -- find proper target item
  local si_target = findUndefinedTarget (si)
  if not si_target then
    Log.info (si, "... target item not found")
    return
  end

  -- Check if item is linked to proper target, otherwise re-link
  for link in links_om:iterate() do
    local out_id = tonumber(link.properties["out.item.id"])
    local in_id = tonumber(link.properties["in.item.id"])
    if out_id == si.id or in_id == si.id then
      local is_out = out_id == si.id and true or false
      for peer in linkables_om:iterate() do
        if peer.id == (is_out and in_id or out_id) then

          if peer.id == si_target.id then
            Log.info (si, "... already linked to proper target")
            return
          end

          -- remove old link if active, otherwise schedule rescan
          if ((link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0) then
            link:remove ()
            Log.info (si, "... moving to new target")
          else
            scheduleRescan ()
            Log.info (si, "... scheduled rescan")
            return
          end

        end
      end
    end
  end

  -- create new link
  createLink (si, si_target, is_filter)
end

function unhandleLinkable (si)
  si_props = si.properties

  Log.info (si, string.format("unhandling item: %s (%s)",
      tostring(si_props["node.name"]), tostring(si_props["node.id"])))

  -- remove any links associated with this item
  for silink in links_om:iterate() do
    local out_id = tonumber (silink.properties["out.item.id"])
    local in_id = tonumber (silink.properties["in.item.id"])
    if out_id == si.id or in_id == si.id then
      silink:remove ()
      Log.info (silink, "... link removed")
    end
  end
end

default_nodes = Plugin.find("default-nodes-api")
endpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
linkables_om = ObjectManager {
  Interest {
    type = "SiLinkable",
    -- only handle device si-audio-adapter items
    Constraint { "item.factory.name", "=", "si-audio-adapter", type = "pw-global" },
    Constraint { "item.node.type", "=", "device", type = "pw-global" },
    Constraint { "active-features", "!", 0, type = "gobject" },
  }
}
streams_om = ObjectManager {
  Interest {
    type = "SiLinkable",
    -- only handle stream si-audio-adapter items
    Constraint { "item.factory.name", "=", "si-audio-adapter", type = "pw-global" },
    Constraint { "active-features", "!", 0, type = "gobject" },
    Constraint { "media.class", "=", "Stream/Output/Audio" },
  }
}
links_om = ObjectManager {
  Interest {
    type = "SiLink",
    -- only handle links created by this policy
    Constraint { "is.policy.endpoint.device.link", "=", true, type = "pw-global" },
  }
}

-- listen for default node changes if config.follow is enabled
if config.follow then
  default_nodes:connect("changed", function (p)
    scheduleRescan ()
  end)
end

linkables_om:connect("objects-changed", function (om)
  scheduleRescan ()
end)

endpoints_om:connect("object-added", function (om)
  scheduleRescan ()
end)

linkables_om:connect("object-removed", function (om, si)
  unhandleLinkable (si)
end)

endpoints_om:activate()
linkables_om:activate()
links_om:activate()
streams_om:activate()
07070100000147000081A4000000000000000000000001656CC35F0000764F000000000000000000000000000000000000002F00000000wireplumber-0.4.17/src/scripts/policy-node.lua    -- WirePlumber
--
-- Copyright © 2020 Collabora Ltd.
--    @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT

-- Receive script arguments from config.lua
local config = ... or {}

-- ensure config.move and config.follow are not nil
config.move = config.move or false
config.follow = config.follow or false
config.filter_forward_format = config["filter.forward-format"] or false

local self = {}
self.scanning = false
self.pending_rescan = false
self.events_skipped = false
self.pending_error_timer = nil

function rescan()
  for si in linkables_om:iterate() do
    handleLinkable (si)
  end
end

function scheduleRescan ()
  if self.scanning then
    self.pending_rescan = true
    return
  end

  self.scanning = true
  rescan ()
  self.scanning = false

  if self.pending_rescan then
    self.pending_rescan = false
    Core.sync(function ()
      scheduleRescan ()
    end)
  end
end

function parseBool(var)
  return var and (var:lower() == "true" or var == "1")
end

function createLink (si, si_target, passthrough, exclusive)
  local out_item = nil
  local in_item = nil
  local si_props = si.properties
  local target_props = si_target.properties
  local si_id = si.id

  -- break rescan if tried more than 5 times with same target
  if si_flags[si_id].failed_peer_id ~= nil and
      si_flags[si_id].failed_peer_id == si_target.id and
      si_flags[si_id].failed_count ~= nil and
      si_flags[si_id].failed_count > 5 then
    Log.warning (si, "tried to link on last rescan, not retrying")
    return
  end

  if si_props["item.node.direction"] == "output" then
    -- playback
    out_item = si
    in_item = si_target
  else
    -- capture
    in_item = si
    out_item = si_target
  end

  Log.info (
    string.format("link %s <-> %s passthrough:%s, exclusive:%s",
      tostring(si_props["node.name"]),
      tostring(target_props["node.name"]),
      tostring(passthrough), tostring(exclusive)))

  -- create and configure link
  local si_link = SessionItem ( "si-standard-link" )
  if not si_link:configure {
    ["out.item"] = out_item,
    ["in.item"] = in_item,
    ["passthrough"] = passthrough,
    ["exclusive"] = exclusive,
    ["out.item.port.context"] = "output",
    ["in.item.port.context"] = "input",
    ["is.policy.item.link"] = true,
  } then
    Log.warning (si_link, "failed to configure si-standard-link")
    return
  end

  si_link:connect("link-error", function (_, error_msg)
    local ids = {si_id}
    if si_flags[si_id] ~= nil then
      table.insert (ids, si_flags[si_id].peer_id)
    end

    for _, id in ipairs (ids) do
      local si = linkables_om:lookup {
        Constraint { "id", "=", id, type = "gobject" },
      }
      if si then
        local node = si:get_associated_proxy ("node")
        local client_id = node.properties["client.id"]
        if client_id then
          local client = clients_om:lookup {
            Constraint { "bound-id", "=", client_id, type = "gobject" }
          }
          if client then
            Log.info (node, "sending client error: " .. error_msg)
            client:send_error (node["bound-id"], -32, error_msg)
          end
        end
      end
    end
  end)

  -- register
  si_flags[si_id].peer_id = si_target.id
  si_flags[si_id].failed_peer_id = si_target.id
  if si_flags[si_id].failed_count ~= nil then
    si_flags[si_id].failed_count = si_flags[si_id].failed_count + 1
  else
    si_flags[si_id].failed_count = 1
  end
  si_link:register ()

  -- activate
  si_link:activate (Feature.SessionItem.ACTIVE, function (l, e)
    if e then
      Log.info (l, "failed to activate si-standard-link: " .. tostring(e))
      if si_flags[si_id] ~= nil then
        si_flags[si_id].peer_id = nil
      end
      l:remove ()
    else
      if si_flags[si_id] ~= nil then
        si_flags[si_id].failed_peer_id = nil
        si_flags[si_id].failed_count = 0
      end
      Log.info (l, "activated si-standard-link")
    end
    scheduleRescan()
  end)
end

function isLinked(si_target)
  local target_id = si_target.id
  local linked = false
  local exclusive = false

  for l in links_om:iterate() do
    local p = l.properties
    local out_id = tonumber(p["out.item.id"])
    local in_id = tonumber(p["in.item.id"])
    linked = (out_id == target_id) or (in_id == target_id)
    if linked then
      exclusive = parseBool(p["exclusive"]) or parseBool(p["passthrough"])
      break
    end
  end
  return linked, exclusive
end

function canPassthrough (si, si_target)
  -- both nodes must support encoded formats
  if not parseBool(si.properties["item.node.supports-encoded-fmts"])
      or not parseBool(si_target.properties["item.node.supports-encoded-fmts"]) then
    return false
  end

  -- make sure that the nodes have at least one common non-raw format
  local n1 = si:get_associated_proxy ("node")
  local n2 = si_target:get_associated_proxy ("node")
  for p1 in n1:iterate_params("EnumFormat") do
    local p1p = p1:parse()
    if p1p.properties.mediaSubtype ~= "raw" then
      for p2 in n2:iterate_params("EnumFormat") do
        if p1:filter(p2) then
          return true
        end
      end
    end
  end
  return false
end

function canLink (properties, si_target)
  local target_properties = si_target.properties

  -- nodes must have the same media type
  if properties["media.type"] ~= target_properties["media.type"] then
    return false
  end

  -- nodes must have opposite direction, or otherwise they must be both input
  -- and the target must have a monitor (so the target will be used as a source)
  local function isMonitor(properties)
    return properties["item.node.direction"] == "input" and
          parseBool(properties["item.features.monitor"]) and
          not parseBool(properties["item.features.no-dsp"]) and
          properties["item.factory.name"] == "si-audio-adapter"
  end

  if properties["item.node.direction"] == target_properties["item.node.direction"]
      and not isMonitor(target_properties) then
    return false
  end

  -- check link group
  local function canLinkGroupCheck (link_group, si_target, hops)
    local target_props = si_target.properties
    local target_link_group = target_props["node.link-group"]

    if hops == 8 then
      return false
    end

    -- allow linking if target has no link-group property
    if not target_link_group then
      return true
    end

    -- do not allow linking if target has the same link-group
    if link_group == target_link_group then
      return false
    end

    -- make sure target is not linked with another node with same link group
    -- start by locating other nodes in the target's link-group, in opposite direction
    for n in linkables_om:iterate {
      Constraint { "id", "!", si_target.id, type = "gobject" },
      Constraint { "item.node.direction", "!", target_props["item.node.direction"] },
      Constraint { "node.link-group", "=", target_link_group },
    } do
      -- iterate their peers and return false if one of them cannot link
      for silink in links_om:iterate() do
        local out_id = tonumber(silink.properties["out.item.id"])
        local in_id = tonumber(silink.properties["in.item.id"])
        if out_id == n.id or in_id == n.id then
          local peer_id = (out_id == n.id) and in_id or out_id
          local peer = linkables_om:lookup {
            Constraint { "id", "=", peer_id, type = "gobject" },
          }
          if peer and not canLinkGroupCheck (link_group, peer, hops + 1) then
            return false
          end
        end
      end
    end
    return true
  end

  local link_group = properties["node.link-group"]
  if link_group then
    return canLinkGroupCheck (link_group, si_target, 0)
  end
  return true
end

function getTargetDirection(properties)
  local target_direction = nil
  if properties["item.node.direction"] == "output" or
     (properties["item.node.direction"] == "input" and
        parseBool(properties["stream.capture.sink"])) then
    target_direction = "input"
  else
    target_direction = "output"
  end
  return target_direction
end

function getDefaultNode(properties, target_direction)
  local target_media_class =
        properties["media.type"] ..
        (target_direction == "input" and "/Sink" or "/Source")
  return default_nodes:call("get-default-node", target_media_class)
end

-- Try to locate a valid target node that was explicitly requsted by the
-- client(node.target) or by the user(target.node)
-- Use the target.node metadata, if config.move is enabled,
-- then use the node.target property that was set on the node
-- `properties` must be the properties dictionary of the session item
-- that is currently being handled
function findDefinedTarget (properties)
  local metadata = config.move and metadata_om:lookup()
  local target_direction = getTargetDirection(properties)
  local target_key
  local target_value
  local node_defined = false

  if properties["target.object"] ~= nil then
    target_value = properties["target.object"]
    target_key = "object.serial"
    node_defined = true
  elseif properties["node.target"] ~= nil then
    target_value = properties["node.target"]
    target_key = "node.id"
    node_defined = true
  end

  if metadata then
    local id = metadata:find(properties["node.id"], "target.object")
    if id ~= nil then
      target_value = id
      target_key = "object.serial"
      node_defined = false
    else
      id = metadata:find(properties["node.id"], "target.node")
      if id ~= nil then
        target_value = id
        target_key = "node.id"
        node_defined = false
      end
    end
  end

  if target_value == "-1" then
    return nil, false, node_defined
  end

  if target_value and tonumber(target_value) then
    local si_target = linkables_om:lookup {
      Constraint { target_key, "=", target_value },
    }
    if si_target and canLink (properties, si_target) then
      return si_target, true, node_defined
    end
  end

  if target_value then
    for si_target in linkables_om:iterate() do
      local target_props = si_target.properties
      if (target_props["node.name"] == target_value or
          target_props["object.path"] == target_value) and
          target_props["item.node.direction"] == target_direction and
          canLink (properties, si_target) then
        return si_target, true, node_defined
      end
    end
  end
  return nil, (target_value ~= nil), node_defined
end

function parseParam(param, id)
  local route = param:parse()
  if route.pod_type == "Object" and route.object_id == id then
    return route.properties
  else
    return nil
  end
end

function arrayContains(a, value)
  for _, v in ipairs(a) do
    if v == value then
      return true
    end
  end
  return false
end


-- Does the target device have any active/available paths/routes to
-- the physical device(spkr/mic/cam)?
function haveAvailableRoutes (si_props)
  local card_profile_device = si_props["card.profile.device"]
  local device_id = si_props["device.id"]
  local device = device_id and devices_om:lookup {
    Constraint { "bound-id", "=", device_id, type = "gobject"},
  }

  if not card_profile_device or not device then
    return true
  end

  local found = 0
  local avail = 0

  -- First check "SPA_PARAM_Route" if there are any active devices
  -- in an active profile.
  for p in device:iterate_params("Route") do
    local route = parseParam(p, "Route")
    if not route then
      goto skip_route
    end

    if (route.device ~= tonumber(card_profile_device)) then
      goto skip_route
    end

    if (route.available == "no") then
      return false
    end

    do return true end

    ::skip_route::
  end

  -- Second check "SPA_PARAM_EnumRoute" if there is any route that
  -- is available if not active.
  for p in device:iterate_params("EnumRoute") do
    local route = parseParam(p, "EnumRoute")
    if not route then
      goto skip_enum_route
    end

    if not arrayContains(route.devices, tonumber(card_profile_device)) then
      goto skip_enum_route
    end
    found = found + 1;
    if (route.available ~= "no") then
      avail = avail +1
    end
    ::skip_enum_route::
  end

  if found == 0 then
    return true
  end
  if avail > 0 then
    return true
  end

  return false

end

function findDefaultLinkable (si)
  local si_props = si.properties
  local target_direction = getTargetDirection(si_props)
  local def_node_id = getDefaultNode(si_props, target_direction)
  return linkables_om:lookup {
      Constraint { "node.id", "=", tostring(def_node_id) }
  }
end

function checkPassthroughCompatibility (si, si_target)
  local si_must_passthrough = parseBool(si.properties["item.node.encoded-only"])
  local si_target_must_passthrough = parseBool(si_target.properties["item.node.encoded-only"])
  local can_passthrough = canPassthrough(si, si_target)
  if (si_must_passthrough or si_target_must_passthrough)
      and not can_passthrough then
    return false, can_passthrough
  end
  return true, can_passthrough
end

function findBestLinkable (si)
  local si_props = si.properties
  local target_direction = getTargetDirection(si_props)
  local target_picked = nil
  local target_can_passthrough = false
  local target_priority = 0
  local target_plugged = 0

  for si_target in linkables_om:iterate {
    Constraint { "item.node.type", "=", "device" },
    Constraint { "item.node.direction", "=", target_direction },
    Constraint { "media.type", "=", si_props["media.type"] },
  } do
    local si_target_props = si_target.properties
    local si_target_node_id = si_target_props["node.id"]
    local priority = tonumber(si_target_props["priority.session"]) or 0

    Log.debug(string.format("Looking at: %s (%s)",
        tostring(si_target_props["node.name"]),
        tostring(si_target_node_id)))

    if not canLink (si_props, si_target) then
      Log.debug("... cannot link, skip linkable")
      goto skip_linkable
    end

    if not haveAvailableRoutes(si_target_props) then
      Log.debug("... does not have routes, skip linkable")
      goto skip_linkable
    end

    local passthrough_compatible, can_passthrough =
        checkPassthroughCompatibility (si, si_target)
    if not passthrough_compatible then
      Log.debug("... passthrough is not compatible, skip linkable")
      goto skip_linkable
    end

    local plugged = tonumber(si_target_props["item.plugged.usec"]) or 0

    Log.debug("... priority:"..tostring(priority)..", plugged:"..tostring(plugged))

    -- (target_picked == NULL) --> make sure atleast one target is picked.
    -- (priority > target_priority) --> pick the highest priority linkable(node)
    -- target.
    -- (priority == target_priority and plugged > target_plugged) --> pick the
    -- latest connected/plugged(in time) linkable(node) target.
    if (target_picked == nil or
        priority > target_priority or
        (priority == target_priority and plugged > target_plugged)) then
          Log.debug("... picked")
          target_picked = si_target
          target_can_passthrough = can_passthrough
          target_priority = priority
          target_plugged = plugged
    end
    ::skip_linkable::
  end

  if target_picked then
    Log.info(string.format("... best target picked: %s (%s), can_passthrough:%s",
      tostring(target_picked.properties["node.name"]),
      tostring(target_picked.properties["node.id"]),
      tostring(target_can_passthrough)))
    return target_picked, target_can_passthrough
  else
    return nil, nil
  end

end

function findUndefinedTarget (si)
  -- Just find the best linkable if default nodes module is not loaded
  if default_nodes == nil then
    return findBestLinkable (si)
  end

  -- Otherwise find the default linkable. If the default linkable is not
  -- compatible, we find the best one instead. We return nil if the default
  -- linkable does not exist.
  local si_target = findDefaultLinkable (si)
  if si_target then
    local passthrough_compatible, can_passthrough =
        checkPassthroughCompatibility (si, si_target)
    if canLink (si.properties, si_target) and passthrough_compatible then
      Log.info(string.format("... default target picked: %s (%s), can_passthrough:%s",
        tostring(si_target.properties["node.name"]),
        tostring(si_target.properties["node.id"]),
        tostring(can_passthrough)))
      return si_target, can_passthrough
    else
      return findBestLinkable (si)
    end
  end
  return nil, nil
end

function lookupLink (si_id, si_target_id)
  local link = links_om:lookup {
    Constraint { "out.item.id", "=", si_id },
    Constraint { "in.item.id", "=", si_target_id }
  }
  if not link then
    link = links_om:lookup {
      Constraint { "in.item.id", "=", si_id },
      Constraint { "out.item.id", "=", si_target_id }
    }
  end
  return link
end

function checkLinkable(si, handle_nonstreams)
  -- only handle stream session items
  local si_props = si.properties
  if not si_props or (si_props["item.node.type"] ~= "stream"
        and not handle_nonstreams)  then
    return false
  end

  -- Determine if we can handle item by this policy
  if endpoints_om:get_n_objects () > 0 and
      si_props["item.factory.name"] == "si-audio-adapter" then
    return false
  end

  return true, si_props
end

si_flags = {}

function checkPending ()
  local pending_linkables = pending_linkables_om:get_n_objects ()

  -- We cannot process linkables if some of them are pending activation,
  -- because linkables do not appear in the same order as nodes,
  -- and we cannot resolve target node references until all linkables
  -- have appeared.

  if self.pending_error_timer then
    self.pending_error_timer:destroy ()
    self.pending_error_timer = nil
  end

  if pending_linkables ~= 0 then
    -- Wait for linkables to get it sync
    Log.debug(string.format("pending %d linkable not ready",
        pending_linkables))
    self.events_skipped = true

    -- To make bugs in activation easier to debug, emit an error message
    -- if they occur. policy-node should never be suspended for 20sec.
    self.pending_error_timer = Core.timeout_add(20000, function()
        self.pending_error_timer = nil
        if pending_linkables ~= 0 then
          Log.message(string.format("%d pending linkable(s) not activated in 20sec. "
              .. "This should never happen.", pending_linkables))
        end
    end)

    return true
  elseif self.events_skipped then
    Log.debug("pending linkables ready")
    self.events_skipped = false
    scheduleRescan ()
    return true
  end

  return false
end

function checkFollowDefault (si, si_target, has_node_defined_target)
  -- If it got linked to the default target that is defined by node
  -- props but not metadata, start ignoring the node prop from now on.
  -- This is what Pulseaudio does.
  --
  -- Pulseaudio skips here filter streams (i->origin_sink and
  -- o->destination_source set in PA). Pipewire does not have a flag
  -- explicitly for this, but we can use presence of node.link-group.
  if not has_node_defined_target then
    return
  end

  local si_props = si.properties
  local target_props = si_target.properties
  local reconnect = not parseBool(si_props["node.dont-reconnect"])
  local is_filter = (si_props["node.link-group"] ~= nil)

  if config.follow and default_nodes ~= nil and reconnect and not is_filter then
    local def_id = getDefaultNode(si_props, getTargetDirection(si_props))

    if target_props["node.id"] == tostring(def_id) then
      local metadata = metadata_om:lookup()
      -- Set target.node, for backward compatibility
      metadata:set(tonumber(si_props["node.id"]), "target.node", "Spa:Id", "-1")
      Log.info (si, "... set metadata to follow default")
    end
  end
end

function handleLinkable (si)
  if checkPending () then
    return
  end

  local valid, si_props = checkLinkable(si)
  if not valid then
    return
  end

  -- check if we need to link this node at all
  local autoconnect = parseBool(si_props["node.autoconnect"])
  if not autoconnect then
    Log.debug (si, tostring(si_props["node.name"]) .. " does not need to be autoconnected")
    return
  end

  Log.info (si, string.format("handling item: %s (%s)",
      tostring(si_props["node.name"]), tostring(si_props["node.id"])))

  ensureSiFlags(si)

  -- get other important node properties
  local reconnect = not parseBool(si_props["node.dont-reconnect"])
  local exclusive = parseBool(si_props["node.exclusive"])
  local si_must_passthrough = parseBool(si_props["item.node.encoded-only"])

  -- find defined target
  local si_target, has_defined_target, has_node_defined_target
      = findDefinedTarget(si_props)
  local can_passthrough = si_target and canPassthrough(si, si_target)

  if si_target and si_must_passthrough and not can_passthrough then
    si_target = nil
  end

  -- if the client has seen a target that we haven't yet prepared, schedule
  -- a rescan one more time and hope for the best
  local si_id = si.id
  if has_defined_target
      and not si_target
      and not si_flags[si_id].was_handled
      and not si_flags[si_id].done_waiting then
    Log.info (si, "... waiting for target")
    si_flags[si_id].done_waiting = true
    scheduleRescan()
    return
  end

  -- find fallback target
  if not si_target and (reconnect or not has_defined_target) then
    si_target, can_passthrough = findUndefinedTarget(si)
  end

  -- Check if item is linked to proper target, otherwise re-link
  if si_flags[si_id].peer_id then
    if si_target and si_flags[si_id].peer_id == si_target.id then
      Log.debug (si, "... already linked to proper target")
      -- Check this also here, in case in default targets changed
      checkFollowDefault (si, si_target, has_node_defined_target)
      return
    end
    local link = lookupLink (si_id, si_flags[si_id].peer_id)
    if reconnect then
      if link ~= nil then
        -- remove old link
        if ((link:get_active_features() & Feature.SessionItem.ACTIVE) == 0) then
          -- Link not yet activated. We don't want to remove it now, as that
          -- may cause problems. Instead, give up for now. A rescan is scheduled
          -- once the link activates.
          Log.info (link, "Link to be moved was not activated, will wait for it.")
          return
        end
        si_flags[si_id].peer_id = nil
        link:remove ()
        Log.info (si, "... moving to new target")
      end
    else
      if link ~= nil then
        Log.info (si, "... dont-reconnect, not moving")
        return
      end
    end
  end

  -- if the stream has dont-reconnect and was already linked before,
  -- don't link it to a new target
  if not reconnect and si_flags[si.id].was_handled then
    si_target = nil
  end

  -- check target's availability
  if si_target then
    local target_is_linked, target_is_exclusive = isLinked(si_target)
    if target_is_exclusive then
      Log.info(si, "... target is linked exclusively")
      si_target = nil
    end

    if target_is_linked then
      if exclusive or si_must_passthrough then
        Log.info(si, "... target is already linked, cannot link exclusively")
        si_target = nil
      else
        -- disable passthrough, we can live without it
        can_passthrough = false
      end
    end
  end

  if not si_target then
    Log.info (si, "... target not found, reconnect:" .. tostring(reconnect))

    local node = si:get_associated_proxy ("node")
    if reconnect and si_flags[si.id].was_handled then
      Log.info (si, "... waiting reconnect")
      return
    end

    local client_id = node.properties["client.id"]
    if client_id then
      local client = clients_om:lookup {
        Constraint { "bound-id", "=", client_id, type = "gobject" }
      }
      local message
      if reconnect then
        message = "no target node available"
      else
        message = "target not found"
      end
      if client then
        client:send_error(node["bound-id"], -2, message)
      end
    end

    if not reconnect then
      Log.info (si, "... destroy node")
      node:request_destroy()
    end
  else
    createLink (si, si_target, can_passthrough, exclusive)
    si_flags[si.id].was_handled = true

    checkFollowDefault (si, si_target, has_node_defined_target)
  end
end

function unhandleLinkable (si)
  local valid, si_props = checkLinkable(si, true)
  if not valid then
    return
  end

  Log.info (si, string.format("unhandling item: %s (%s)",
      tostring(si_props["node.name"]), tostring(si_props["node.id"])))

  -- remove any links associated with this item
  for silink in links_om:iterate() do
    local out_id = tonumber (silink.properties["out.item.id"])
    local in_id = tonumber (silink.properties["in.item.id"])
    if out_id == si.id or in_id == si.id then
      if out_id == si.id and
          si_flags[in_id] and si_flags[in_id].peer_id == out_id then
        si_flags[in_id].peer_id = nil
      elseif in_id == si.id and
          si_flags[out_id] and si_flags[out_id].peer_id == in_id then
        si_flags[out_id].peer_id = nil
      end
      silink:remove ()
      Log.info (silink, "... link removed")
    end
  end

  si_flags[si.id] = nil
end

default_nodes = Plugin.find("default-nodes-api")

metadata_om = ObjectManager {
  Interest {
    type = "metadata",
    Constraint { "metadata.name", "=", "default" },
  }
}

endpoints_om = ObjectManager { Interest { type = "SiEndpoint" } }

clients_om = ObjectManager { Interest { type = "client" } }

devices_om = ObjectManager { Interest { type = "device" } }

linkables_om = ObjectManager {
  Interest {
    type = "SiLinkable",
    -- only handle si-audio-adapter and si-node
    Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
    Constraint { "active-features", "!", 0, type = "gobject" },
  }
}

pending_linkables_om = ObjectManager {
  Interest {
    type = "SiLinkable",
    -- only handle si-audio-adapter and si-node
    Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
    Constraint { "active-features", "=", 0, type = "gobject" },
  }
}

links_om = ObjectManager {
  Interest {
    type = "SiLink",
    -- only handle links created by this policy
    Constraint { "is.policy.item.link", "=", true },
  }
}

-- listen for default node changes if config.follow is enabled
if config.follow and default_nodes ~= nil then
  default_nodes:connect("changed", function ()
    scheduleRescan ()
  end)
end

-- listen for target.node metadata changes if config.move is enabled
if config.move then
  metadata_om:connect("object-added", function (om, metadata)
    metadata:connect("changed", function (m, subject, key, t, value)
      if key == "target.node" or key == "target.object" then
        scheduleRescan ()
      end
    end)
  end)
end

function findAssociatedLinkGroupNode (si)
  local si_props = si.properties
  local node = si:get_associated_proxy ("node")
  local link_group = node.properties["node.link-group"]
  if link_group == nil then
    return nil
  end

  -- get the associated media class
  local assoc_direction = getTargetDirection(si_props)
  local assoc_media_class =
        si_props["media.type"] ..
        (assoc_direction == "input" and "/Sink" or "/Source")

  -- find the linkable with same link group and matching assoc media class
  for assoc_si in linkables_om:iterate() do
    local assoc_node = assoc_si:get_associated_proxy ("node")
    local assoc_link_group = assoc_node.properties["node.link-group"]
    if assoc_link_group == link_group and
        assoc_media_class == assoc_node.properties["media.class"] then
      return assoc_si
    end
  end

  return nil
end

function onLinkGroupPortsStateChanged (si, old_state, new_state)
  local new_str = tostring(new_state)
  local si_props = si.properties

  -- only handle items with configured ports state
  if new_str ~= "configured" then
    return
  end

  Log.info (si, "ports format changed on " .. si_props["node.name"])

  -- find associated device
  local si_device = findAssociatedLinkGroupNode (si)
  if si_device ~= nil then
    local device_node_name = si_device.properties["node.name"]

    -- get the stream format
    local f, m = si:get_ports_format()

    -- unregister the device
    Log.info (si_device, "unregistering " .. device_node_name)
    si_device:remove()

    -- set new format in the device
    Log.info (si_device, "setting new format in " .. device_node_name)
    si_device:set_ports_format(f, m, function (item, e)
      if e ~= nil then
        Log.warning (item, "failed to configure ports in " ..
            device_node_name .. ": " .. e)
      end

      -- register back the device
      Log.info (item, "registering " .. device_node_name)
      item:register()
    end)
  end
end

function ensureSiFlags (si)
  -- prepare flags table
  if not si_flags[si.id] then
    si_flags[si.id] = {}
  end
end

function checkFiltersPortsState (si)
  local si_props = si.properties
  local node = si:get_associated_proxy ("node")
  local link_group = node.properties["node.link-group"]

  ensureSiFlags(si)

  -- only listen for ports state changed on audio filter streams
  if si_flags[si.id].ports_state_signal ~= true and
      si_props["item.factory.name"] == "si-audio-adapter" and
      si_props["item.node.type"] == "stream" and
      link_group ~= nil then
    si:connect("adapter-ports-state-changed", onLinkGroupPortsStateChanged)
    si_flags[si.id].ports_state_signal = true
    Log.info (si, "listening ports state changed on " .. si_props["node.name"])
  end
end

linkables_om:connect("object-added", function (om, si)
  local si_props = si.properties

  -- Forward filters ports format to associated virtual devices if enabled
  if config.filter_forward_format then
    checkFiltersPortsState (si)
  end

  if si_props["item.node.type"] ~= "stream" then
    scheduleRescan ()
  else
    handleLinkable (si)
  end
end)

linkables_om:connect("object-removed", function (om, si)
  unhandleLinkable (si)
  scheduleRescan ()
end)

devices_om:connect("object-added", function (om, device)
  device:connect("params-changed", function (d, param_name)
    scheduleRescan ()
  end)
end)

metadata_om:activate()
endpoints_om:activate()
clients_om:activate()
linkables_om:activate()
pending_linkables_om:activate()
links_om:activate()
devices_om:activate()
 07070100000148000081A4000000000000000000000001656CC35F000032A6000000000000000000000000000000000000003200000000wireplumber-0.4.17/src/scripts/restore-stream.lua -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- Based on restore-stream.c from pipewire-media-session
-- Copyright © 2020 Wim Taymans
--
-- SPDX-License-Identifier: MIT

-- Receive script arguments from config.lua
local config = ... or {}
config.properties = config.properties or {}
config_restore_props = config.properties["restore-props"] or false
config_restore_target = config.properties["restore-target"] or false
config_default_channel_volume = config.properties["default-channel-volume"] or 1.0

-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
  r.interests = {}
  for _, i in ipairs(r.matches) do
    local interest_desc = { type = "properties" }
    for _, c in ipairs(i) do
      c.type = "pw"
      table.insert(interest_desc, Constraint(c))
    end
    local interest = Interest(interest_desc)
    table.insert(r.interests, interest)
  end
  r.matches = nil
end

-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
  for _, r in ipairs(config.rules or {}) do
    if r.apply_properties then
      for _, interest in ipairs(r.interests) do
        if interest:matches(properties) then
          for k, v in pairs(r.apply_properties) do
            properties[k] = v
          end
        end
      end
    end
  end
end

-- the state storage
state = State("restore-stream")
state_table = state:load()

-- simple serializer {"foo", "bar"} -> "foo;bar;"
function serializeArray(a)
  local str = ""
  for _, v in ipairs(a) do
    str = str .. tostring(v):gsub(";", "\\;") .. ";"
  end
  return str
end

-- simple deserializer "foo;bar;" -> {"foo", "bar"}
function parseArray(str, convert_value, with_type)
  local array = {}
  local val = ""
  local escaped = false
  for i = 1, #str do
    local c = str:sub(i,i)
    if c == '\\' then
      escaped = true
    elseif c == ';' and not escaped then
      val = convert_value and convert_value(val) or val
      table.insert(array, val)
      val = ""
    else
      val = val .. tostring(c)
      escaped = false
    end
  end
  if with_type then
    array["pod_type"] = "Array"
  end
  return array
end

function parseParam(param, id)
  local route = param:parse()
  if route.pod_type == "Object" and route.object_id == id then
    return route.properties
  else
    return nil
  end
end

function storeAfterTimeout()
  if timeout_source then
    timeout_source:destroy()
  end
  timeout_source = Core.timeout_add(1000, function ()
    local saved, err = state:save(state_table)
    if not saved then
      Log.warning(err)
    end
    timeout_source = nil
  end)
end

function findSuitableKey(properties)
  local keys = {
    "media.role",
    "application.id",
    "application.name",
    "media.name",
    "node.name",
  }
  local key = nil

  for _, k in ipairs(keys) do
    local p = properties[k]
    if p then
      key = string.format("%s:%s:%s",
          properties["media.class"]:gsub("^Stream/", ""), k, p)
      break
    end
  end
  return key
end

function saveTarget(subject, target_key, type, value)
  if target_key ~= "target.node" and target_key ~= "target.object" then
    return
  end

  local node = streams_om:lookup {
    Constraint { "bound-id", "=", subject, type = "gobject" }
  }
  if not node then
    return
  end

  local stream_props = node.properties
  rulesApplyProperties(stream_props)

  if stream_props["state.restore-target"] == false then
    return
  end

  local key_base = findSuitableKey(stream_props)
  if not key_base then
    return
  end

  local target_value = value
  local target_name = nil

  if not target_value then
    local metadata = metadata_om:lookup()
    if metadata then
      target_value = metadata:find(node["bound-id"], target_key)
    end
  end
  if target_value and target_value ~= "-1" then
    local target_node
    if target_key == "target.object" then
      target_node = allnodes_om:lookup {
        Constraint { "object.serial", "=", target_value, type = "pw-global" }
      }
    else
      target_node = allnodes_om:lookup {
        Constraint { "bound-id", "=", target_value, type = "gobject" }
      }
    end
    if target_node then
      target_name = target_node.properties["node.name"]
    end
  end
  state_table[key_base .. ":target"] = target_name

  Log.info(node, "saving stream target for " ..
    tostring(stream_props["node.name"]) ..
    " -> " .. tostring(target_name))

  storeAfterTimeout()
end

function restoreTarget(node, target_name)

  local stream_props = node.properties
  local target_in_props = nil

  if stream_props ["target.object"] ~= nil or
      stream_props ["node.target"] ~= nil then
    target_in_props = stream_props ["target.object"] or
        stream_props ["node.target"]

    Log.debug (string.format ("%s%s%s%s",
      "Not restoring the target for ",
      stream_props ["node.name"],
      " because it is already set to ",
      target_in_props))

    return
  end

  local target_node = allnodes_om:lookup {
    Constraint { "node.name", "=", target_name, type = "pw" }
  }

  if target_node then
    local metadata = metadata_om:lookup()
    if metadata then
      metadata:set(node["bound-id"], "target.node", "Spa:Id",
          target_node["bound-id"])
    end
  end
end

function jsonTable(val, name)
  local tmp = ""
  local count = 0

  if name then tmp = tmp .. string.format("%q", name) .. ": " end

  if type(val) == "table" then
    if val["pod_type"] == "Array" then
      tmp = tmp .. "["
      for _, v in ipairs(val) do
	if count > 0 then tmp = tmp .. "," end
        tmp = tmp .. jsonTable(v)
	count = count + 1
      end
      tmp = tmp .. "]"
    else
      tmp = tmp .. "{"
      for k, v in pairs(val) do
	if count > 0 then tmp = tmp .. "," end
        tmp = tmp .. jsonTable(v, k)
	count = count + 1
      end
      tmp = tmp .. "}"
    end
  elseif type(val) == "number" then
    tmp = tmp .. tostring(val)
  elseif type(val) == "string" then
    tmp = tmp .. string.format("%q", val)
  elseif type(val) == "boolean" then
    tmp = tmp .. (val and "true" or "false")
  else
    tmp = tmp .. "\"[type:" .. type(val) .. "]\""
  end
  return tmp
end

function moveToMetadata(key_base, metadata)
  local route_table = { }
  local count = 0

  key = "restore.stream." .. key_base
  key = string.gsub(key, ":", ".", 1);

  local str = state_table[key_base .. ":volume"]
  if str then
    route_table["volume"] = tonumber(str)
    count = count + 1;
  end
  local str = state_table[key_base .. ":mute"]
  if str then
    route_table["mute"] = str == "true"
    count = count + 1;
  end
  local str = state_table[key_base .. ":channelVolumes"]
  if str then
    route_table["volumes"] = parseArray(str, tonumber, true)
    count = count + 1;
  end
  local str = state_table[key_base .. ":channelMap"]
  if str then
    route_table["channels"] = parseArray(str, nil, true)
    count = count + 1;
  end

  if count > 0 then
    metadata:set(0, key, "Spa:String:JSON", jsonTable(route_table));
  end
end


function saveStream(node)
  local stream_props = node.properties
  rulesApplyProperties(stream_props)

  if config_restore_props and stream_props["state.restore-props"] ~= false then
    local key_base = findSuitableKey(stream_props)
    if not key_base then
      return
    end

    Log.info(node, "saving stream props for " ..
        tostring(stream_props["node.name"]))

    for p in node:iterate_params("Props") do
      local props = parseParam(p, "Props")
      if not props then
        goto skip_prop
      end

      if props.volume then
        state_table[key_base .. ":volume"] = tostring(props.volume)
      end
      if props.mute ~= nil then
        state_table[key_base .. ":mute"] = tostring(props.mute)
      end
      if props.channelVolumes then
        state_table[key_base .. ":channelVolumes"] = serializeArray(props.channelVolumes)
      end
      if props.channelMap then
        state_table[key_base .. ":channelMap"] = serializeArray(props.channelMap)
      end

      ::skip_prop::
    end

    storeAfterTimeout()
  end
end

function build_default_channel_volumes (node)
  local def_vol = config_default_channel_volume
  local channels = 2
  local res = {}

  local str = node.properties["state.default-channel-volume"]
  if str ~= nil then
    def_vol = tonumber (str)
  end

  for pod in node:iterate_params("Format") do
    local pod_parsed = pod:parse()
    if pod_parsed ~= nil then
      channels = pod_parsed.properties.channels
      break
    end
  end

  while (#res < channels) do
    table.insert(res, def_vol)
  end

  return res;
end

function restoreStream(node)
  local stream_props = node.properties
  rulesApplyProperties(stream_props)

  local key_base = findSuitableKey(stream_props)
  if not key_base then
    return
  end

  if config_restore_props and stream_props["state.restore-props"] ~= false then
    local props = { "Spa:Pod:Object:Param:Props", "Props" }

    local str = state_table[key_base .. ":volume"]
    props.volume = str and tonumber(str) or nil

    local str = state_table[key_base .. ":mute"]
    props.mute = str and (str == "true") or nil

    local str = state_table[key_base .. ":channelVolumes"]
    props.channelVolumes = str and parseArray(str, tonumber) or
        build_default_channel_volumes (node)

    local str = state_table[key_base .. ":channelMap"]
    props.channelMap = str and parseArray(str) or nil

    -- convert arrays to Spa Pod
    if props.channelVolumes then
      table.insert(props.channelVolumes, 1, "Spa:Float")
      props.channelVolumes = Pod.Array(props.channelVolumes)
    end
    if props.channelMap then
      table.insert(props.channelMap, 1, "Spa:Enum:AudioChannel")
      props.channelMap = Pod.Array(props.channelMap)
    end

    Log.info(node, "restore values from " .. key_base)
    local param = Pod.Object(props)
    Log.debug(param, "setting props on " .. tostring(node))
    node:set_param("Props", param)
  end

  if config_restore_target and stream_props["state.restore-target"] ~= false then
    local str = state_table[key_base .. ":target"]
    if str then
      restoreTarget(node, str)
    end
  end
end

if config_restore_target then
  metadata_om = ObjectManager {
    Interest {
      type = "metadata",
      Constraint { "metadata.name", "=", "default" },
    }
  }

  metadata_om:connect("object-added", function (om, metadata)
    -- process existing metadata
    for s, k, t, v in metadata:iterate(Id.ANY) do
      saveTarget(s, k, t, v)
    end
    -- and watch for changes
    metadata:connect("changed", function (m, subject, key, type, value)
      saveTarget(subject, key, type, value)
    end)
  end)
  metadata_om:activate()
end

function handleRouteSettings(subject, key, type, value)
  if type ~= "Spa:String:JSON" then
    return
  end
  if string.find(key, "^restore.stream.") == nil then
    return
  end
  if value == nil then
    return
  end
  local json = Json.Raw (value);
  if json == nil or not json:is_object () then
    return
  end

  local vparsed = json:parse()
  local key_base = string.sub(key, string.len("restore.stream.") + 1)
  local str;

  key_base = string.gsub(key_base, "%.", ":", 1);

  if vparsed.volume ~= nil then
    state_table[key_base .. ":volume"] = tostring (vparsed.volume)
  end
  if vparsed.mute ~= nil then
    state_table[key_base .. ":mute"] = tostring (vparsed.mute)
  end
  if vparsed.channels ~= nil then
    state_table[key_base .. ":channelMap"] = serializeArray (vparsed.channels)
  end
  if vparsed.volumes ~= nil then
    state_table[key_base .. ":channelVolumes"] = serializeArray (vparsed.volumes)
  end

  storeAfterTimeout()
end


rs_metadata = ImplMetadata("route-settings")
rs_metadata:activate(Features.ALL, function (m, e)
  if e then
    Log.warning("failed to activate route-settings metadata: " .. tostring(e))
    return
  end

  -- copy state into the metadata
  moveToMetadata("Output/Audio:media.role:Notification", m)
  -- watch for changes
  m:connect("changed", function (m, subject, key, type, value)
    handleRouteSettings(subject, key, type, value)
  end)
end)

allnodes_om = ObjectManager { Interest { type = "node" } }
allnodes_om:activate()

streams_om = ObjectManager {
  -- match stream nodes
  Interest {
    type = "node",
    Constraint { "media.class", "matches", "Stream/*", type = "pw-global" },
  },
  -- and device nodes that are not associated with any routes
  Interest {
    type = "node",
    Constraint { "media.class", "matches", "Audio/*", type = "pw-global" },
    Constraint { "device.routes", "is-absent", type = "pw" },
  },
  Interest {
    type = "node",
    Constraint { "media.class", "matches", "Audio/*", type = "pw-global" },
    Constraint { "device.routes", "equals", "0", type = "pw" },
  },
}
streams_om:connect("object-added", function (streams_om, node)
  node:connect("params-changed", saveStream)
  restoreStream(node)
end)
streams_om:activate()
  07070100000149000081A4000000000000000000000001656CC35F00000D58000000000000000000000000000000000000002E00000000wireplumber-0.4.17/src/scripts/sm-objects.lua -- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT
--
-- The script exposes a metadata object named "sm-objects" that clients can
-- use to load objects into the WirePlumber daemon process. The objects are
-- loaded as soon as the metadata is set and are destroyed when the metadata
-- is cleared.
--
-- To load an object, a client needs to set a metadata entry with:
--
--   * subject:
--  The ID of the owner of the object; you can use 0 here, but the
--  idea is to be able to restrict which clients can change and/or
--  delete these objects by using IDs of other objects appropriately
--
--   * key: "<UNIQUE-OBJECT-NAME>"
--  This is the name that will be used to identify the object.
--  If an object with the same name already exists, it will be destroyed.
--  Note that the keys are unique per subject, so you can have multiple
--  objects with the same name as long as they are owned by different subjects.
--
--   * type: "Spa:String:JSON"
--
--   * value: "{ type = <object-type>,
--               name = <object-name>,
--               args = { ...object arguments... } }"
--  The object type can be one of the following:
--   - "pw-module": loads a pipewire module: `name` and `args` are interpreted
--                  just like a module entry in pipewire.conf
--   - "metadata": loads a metadata object with `metadata.name` = `name`
--                 and any additional properties provided in `args`
--

on_demand_objects = {}

object_constructors = {
  ["pw-module"] = LocalModule,
  ["metadata"] = function (name, args)
    local m = ImplMetadata (name, args)
    m:activate (Features.ALL, function (m, e)
      if e then
        Log.warning ("failed to activate on-demand metadata `" .. name .. "`: " .. tostring (e))
      end
    end)
    return m
  end
}

function handle_metadata_changed (m, subject, key, type, value)
  -- destroy all objects when metadata is cleared
  if not key then
    on_demand_objects = {}
    return
  end

  local object_id = key .. "@" .. tostring(subject)

  -- destroy existing object instance, if needed
  if on_demand_objects[object_id] then
    Log.debug("destroy on-demand object: " .. object_id)
    on_demand_objects[object_id] = nil
  end

  if value then
    local json = Json.Raw(value)
    if not json:is_object() then
      Log.warning("loading '".. object_id .. "' failed: expected JSON object, got: '" .. value .. "'")
      return
    end

    local obj = json:parse(1)
    if not obj.type then
      Log.warning("loading '".. object_id .. "' failed: no object type specified")
      return
    end
    if not obj.name then
      Log.warning("loading '".. object_id .. "' failed: no object name specified")
      return
    end

    local constructor = object_constructors[obj.type]
    if not constructor then
      Log.warning("loading '".. object_id .. "' failed: unknown object type: " .. obj.type)
      return
    end

    Log.info("load on-demand object: " .. object_id .. " -> " .. obj.name)
    on_demand_objects[object_id] = constructor(obj.name, obj.args)
  end
end

objects_metadata = ImplMetadata ("sm-objects")
objects_metadata:activate (Features.ALL, function (m, e)
  if e then
    Log.warning ("failed to activate the sm-objects metadata: " .. tostring (e))
  else
    m:connect("changed", handle_metadata_changed)
  end
end)
0707010000014A000081A4000000000000000000000001656CC35F0000038E000000000000000000000000000000000000003400000000wireplumber-0.4.17/src/scripts/static-endpoints.lua   -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT

-- Receive script arguments from config.lua
local endpoints_config = ...

function createEndpoint (factory_name, properties)
  -- create endpoint
  local ep = SessionItem ( factory_name )
  if not ep then
    Log.warning (ep, "could not create endpoint of type " .. factory_name)
    return
  end

  -- configure endpoint
  if not ep:configure(properties) then
    Log.warning(ep, "failed to configure endpoint " .. properties.name)
    return
  end

  -- activate and register endpoint
  ep:activate (Features.ALL, function (item)
    item:register ()
    Log.info(item, "registered endpoint " .. properties.name)
  end)
end


for name, properties in pairs(endpoints_config) do
  properties["name"] = name
  createEndpoint ("si-audio-endpoint", properties)
end
  0707010000014B000081A4000000000000000000000001656CC35F00000667000000000000000000000000000000000000003000000000wireplumber-0.4.17/src/scripts/suspend-node.lua   -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT

om = ObjectManager {
  Interest { type = "node",
    Constraint { "media.class", "matches", "Audio/*" }
  },
  Interest { type = "node",
    Constraint { "media.class", "matches", "Video/*" }
  },
}

sources = {}

om:connect("object-added", function (om, node)
  node:connect("state-changed", function (node, old_state, cur_state)
    -- Always clear the current source if any
    local id = node["bound-id"]
    if sources[id] then
      sources[id]:destroy()
      sources[id] = nil
    end

    -- Add a timeout source if idle for at least 5 seconds
    if cur_state == "idle" or cur_state == "error" then
      -- honor "session.suspend-timeout-seconds" if specified
      local timeout =
          tonumber(node.properties["session.suspend-timeout-seconds"]) or 5

      if timeout == 0 then
        return
      end

      -- add idle timeout; multiply by 1000, timeout_add() expects ms
      sources[id] = Core.timeout_add(timeout * 1000, function()
        -- Suspend the node
        -- but check first if the node still exists
        if (node:get_active_features() & Feature.Proxy.BOUND) ~= 0 then
          Log.info(node, "was idle for a while; suspending ...")
          node:send_command("Suspend")
        end

        -- Unref the source
        sources[id] = nil

        -- false (== G_SOURCE_REMOVE) destroys the source so that this
        -- function does not get fired again after 5 seconds
        return false
      end)
    end

  end)
end)

om:activate()
 0707010000014C000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001F00000000wireplumber-0.4.17/src/systemd    0707010000014D000081A4000000000000000000000001656CC35F0000036E000000000000000000000000000000000000002B00000000wireplumber-0.4.17/src/systemd/meson.build    if systemd.found()
  systemd_config = configuration_data()
  systemd_config.set('WP_BINARY', wireplumber_bin_dir / 'wireplumber')

  # system service
  if get_option('systemd-system-service')
    systemd_system_unit_dir = systemd.get_variable(
        pkgconfig: 'systemdsystemunitdir',
        pkgconfig_define: ['prefix', get_option('prefix')])

    if get_option('systemd-system-unit-dir') != ''
      systemd_system_unit_dir = get_option('systemd-system-unit-dir')
    endif

    subdir('system')
  endif

  # user service
  if get_option('systemd-user-service')
    systemd_user_unit_dir = systemd.get_variable(
        pkgconfig: 'systemduserunitdir',
        pkgconfig_define: ['prefix', get_option('prefix')])

    if get_option('systemd-user-unit-dir') != ''
      systemd_user_unit_dir = get_option('systemd-user-unit-dir')
    endif

    subdir('user')
  endif
endif
  0707010000014E000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002600000000wireplumber-0.4.17/src/systemd/system 0707010000014F000081A4000000000000000000000001656CC35F0000018C000000000000000000000000000000000000003200000000wireplumber-0.4.17/src/systemd/system/meson.build configure_file(input : 'wireplumber.service.in',
               output : 'wireplumber.service',
               configuration : systemd_config,
               install_dir : systemd_system_unit_dir)
configure_file(input : 'wireplumber@.service.in',
               output : 'wireplumber@.service',
               configuration : systemd_config,
               install_dir : systemd_system_unit_dir)
07070100000150000081A4000000000000000000000001656CC35F00000218000000000000000000000000000000000000003D00000000wireplumber-0.4.17/src/systemd/system/wireplumber.service.in  [Unit]
Description=Multimedia Service Session Manager
After=pipewire.service
BindsTo=pipewire.service
Conflicts=pipewire-media-session.service

[Service]
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
Type=simple
AmbientCapabilities=CAP_SYS_NICE
ExecStart=@WP_BINARY@
Restart=on-failure
User=pipewire
Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire
Environment=GIO_USE_VFS=local

[Install]
WantedBy=pipewire.service
Alias=pipewire-session-manager.service
07070100000151000081A4000000000000000000000001656CC35F0000031E000000000000000000000000000000000000003E00000000wireplumber-0.4.17/src/systemd/system/wireplumber@.service.in # Service file to run WirePlumber in split mode, i.e. run multiple
# WirePlumber instances with different module sets.
# The template argument is used to load the configuration file, e.g.
# wireplumber@main.service loads main.conf,
# wireplumber@policy.service loads policy.conf, etc.
[Unit]
Description=Multimedia Service Session Manager (%i)
After=pipewire.service
BindsTo=pipewire.service
Conflicts=pipewire-media-session.service

[Service]
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
Type=simple
AmbientCapabilities=CAP_SYS_NICE
ExecStart=@WP_BINARY@ -c %i.conf
Restart=on-failure
User=pipewire
Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire
Environment=GIO_USE_VFS=local

[Install]
WantedBy=pipewire.service
  07070100000152000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002400000000wireplumber-0.4.17/src/systemd/user   07070100000153000081A4000000000000000000000001656CC35F00000188000000000000000000000000000000000000003000000000wireplumber-0.4.17/src/systemd/user/meson.build   configure_file(input : 'wireplumber.service.in',
               output : 'wireplumber.service',
               configuration : systemd_config,
               install_dir : systemd_user_unit_dir)
configure_file(input : 'wireplumber@.service.in',
               output : 'wireplumber@.service',
               configuration : systemd_config,
               install_dir : systemd_user_unit_dir)
07070100000154000081A4000000000000000000000001656CC35F000001D0000000000000000000000000000000000000003B00000000wireplumber-0.4.17/src/systemd/user/wireplumber.service.in    [Unit]
Description=Multimedia Service Session Manager
After=pipewire.service
BindsTo=pipewire.service
Conflicts=pipewire-media-session.service

[Service]
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
Type=simple
ExecStart=@WP_BINARY@
Restart=on-failure
Slice=session.slice
Environment=GIO_USE_VFS=local

[Install]
WantedBy=pipewire.service
Alias=pipewire-session-manager.service
07070100000155000081A4000000000000000000000001656CC35F000002D6000000000000000000000000000000000000003C00000000wireplumber-0.4.17/src/systemd/user/wireplumber@.service.in   # Service file to run WirePlumber in split mode, i.e. run multiple
# WirePlumber instances with different module sets.
# The template argument is used to load the configuration file, e.g.
# wireplumber@main.service loads main.conf,
# wireplumber@policy.service loads policy.conf, etc.
[Unit]
Description=Multimedia Service Session Manager (%i)
After=pipewire.service
BindsTo=pipewire.service
Conflicts=pipewire-media-session.service

[Service]
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
Type=simple
ExecStart=@WP_BINARY@ -c %i.conf
Restart=on-failure
Slice=session.slice
Environment=GIO_USE_VFS=local

[Install]
WantedBy=pipewire.service
  07070100000156000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001D00000000wireplumber-0.4.17/src/tools  07070100000157000081A4000000000000000000000001656CC35F0000022E000000000000000000000000000000000000002900000000wireplumber-0.4.17/src/tools/meson.build  executable('wpctl',
  'wpctl.c',
  c_args : [
    '-D_GNU_SOURCE',
    '-DG_LOG_USE_STRUCTURED',
    '-DG_LOG_DOMAIN="wpctl"',
  ],
  install: true,
  dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep],
)

install_data('shell-completion/wpctl.zsh',
  install_dir: get_option('datadir') / 'zsh/site-functions',
  rename: '_wpctl'
)

executable('wpexec',
  'wpexec.c',
  c_args : [
    '-D_GNU_SOURCE',
    '-DG_LOG_USE_STRUCTURED',
    '-DG_LOG_DOMAIN="wpexec"',
  ],
  install: true,
  dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep],
)
  07070100000158000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002E00000000wireplumber-0.4.17/src/tools/shell-completion 07070100000159000081A4000000000000000000000001656CC35F0000064A000000000000000000000000000000000000003800000000wireplumber-0.4.17/src/tools/shell-completion/wpctl.zsh   #compdef wpctl

(( $+functions[_wpctl_pw_nodes] )) ||
_wpctl_pw_nodes() {
  local -a pw_objects
  if (( $+commands[pw-dump] )) && (( $+commands[jq] )); then
    local -a pw_objects=(${(@f)"$(2>/dev/null {
    command pw-dump |
      command jq -r '.[] | select(
        .type == "PipeWire:Interface:Node"
      ) |
      {id, type, name: (
        .info.name //
        (.info.props | (
          ."application.name" //
          ."node.name")
        ) //
        .type)
      } |
        "\(.id):\(.name | gsub(":"; "\\:"))"'
      })"})
  fi
  _wpctl_describe_nodes() {_describe "node id" pw_objects "$@"}
  _alternative \
    'pw-defaults:defaults:(@DEFAULT_SINK@ @DEFAULT_SOURCE@)' \
    'pw-node-id:node id:_wpctl_describe_nodes'
}

local -a node_id=(/$'[^\0]#\0'/ ':pw-node-id:node id:_wpctl_pw_nodes')
local -a volume=(/$'[0-9]##(%|)([+-]|)\0'/ ':volume:volume:( )')
local -a toggle=(/$'[^\0]#\0'/ ':(0 1 toggle)')
local -a set_volume=( "$node_id[@]" "$volume[@]" )
local -a set_mute=( "$node_id[@]" "$toggle[@]" )

_regex_words options 'wpctl options' \
  {-h,--help}':show help message and exit'
local -a options=( "$reply[@]" )

_regex_words wpctl-commands 'wpctl commands' \
  'status:show wireplumber status' \
  'get-volume:get object volume:$node_id' \
  'set-default:set a default sink:$node_id' \
  'set-volume:set object volume:$set_volume' \
  'set-mute:set object mute:$set_mute' \
  'set-profile:set object profile:$node_id' \
  'clear-default:unset default sink:$node_id'
local -a wpctlcmd=( /$'[^\0]#\0'/ "$options[@]" "#" "$reply[@]")
_regex_arguments _wpctl "$wpctlcmd[@]"
_wpctl "$@"
  0707010000015A000081A4000000000000000000000001656CC35F0000BA08000000000000000000000000000000000000002500000000wireplumber-0.4.17/src/tools/wpctl.c  /* WirePlumber
 *
 * Copyright © 2019-2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <stdio.h>
#include <locale.h>
#include <spa/utils/defs.h>
#include <pipewire/keys.h>
#include <pipewire/extensions/session-manager/keys.h>

static const gchar *DEFAULT_NODE_MEDIA_CLASSES[] = {
  "Audio/Sink",
  "Audio/Source",
  "Video/Source",
};

typedef struct _WpCtl WpCtl;
struct _WpCtl
{
  GOptionContext *context;
  GMainLoop *loop;
  WpCore *core;
  GPtrArray *apis;
  WpObjectManager *om;
  guint pending_plugins;
  gint exit_code;
};

static struct {
  union {
    struct {
      gboolean display_nicknames;
      gboolean display_names;
    } status;
    struct {
      guint64 id;
      gboolean show_referenced;
      gboolean show_associated;
    } inspect;
    struct {
      guint64 id;
    } set_default;
    struct {
      guint64 id;
      gfloat volume;
      gdouble limit;
      gboolean is_pid;
      gchar type;
    } set_volume;

    struct {
      guint64 id;
    } get_volume;

    struct {
      guint64 id;
      guint mute;
      gboolean is_pid;
    } set_mute;

    struct {
      guint64 id;
      gint index;
    } set_profile;

    struct {
      guint64 id;
    } clear_default;
  };
} cmdline;

G_DEFINE_QUARK (wpctl-error, wpctl_error_domain)

static void
wp_ctl_clear (WpCtl * self)
{
  g_clear_pointer (&self->apis, g_ptr_array_unref);
  g_clear_object (&self->om);
  g_clear_object (&self->core);
  g_clear_pointer (&self->loop, g_main_loop_unref);
  g_clear_pointer (&self->context, g_option_context_free);
}

static void
async_quit (WpCore *core, GAsyncResult *res, WpCtl * self)
{
  g_main_loop_quit (self->loop);
}

#define DEFAULT_AUDIO_SINK_ID ((guint64)1 << 32)
#define DEFAULT_AUDIO_SOURCE_ID ((guint64)1 << 33)
#define DEFAULT_VIDEO_SOURCE_ID ((guint64)1 << 34)

static gboolean
parse_id (gboolean allow_def_audio, gboolean allow_def_video, gchar *arg, guint64 *result_id, GError **error)
{
  if (allow_def_audio && (g_strcmp0(arg, "@DEFAULT_SINK@") == 0 ||
      g_strcmp0(arg, "@DEFAULT_AUDIO_SINK@") == 0)) {
    *result_id = DEFAULT_AUDIO_SINK_ID;
  } else if (allow_def_audio && (g_strcmp0(arg, "@DEFAULT_SOURCE@") == 0 ||
      g_strcmp0(arg, "@DEFAULT_AUDIO_SOURCE@") == 0)) {
    *result_id = DEFAULT_AUDIO_SOURCE_ID;
  } else if (allow_def_video && g_strcmp0(arg, "@DEFAULT_VIDEO_SOURCE@") == 0) {
    *result_id = DEFAULT_VIDEO_SOURCE_ID;
  } else {
    long id = strtol (arg, NULL, 10);
    if (id <= 0 || id >= G_MAXUINT32) {
      g_set_error (error, wpctl_error_domain_quark(), 0,
          "'%s' is not a valid number", arg);
      return FALSE;
    }
    *result_id = id;
  }

  return TRUE;
}

static gboolean
translate_id (WpPlugin *def_nodes_api, guint64 id, guint32 *res, GError **error)
{
  gchar *media_class = NULL;

  if (id == DEFAULT_AUDIO_SINK_ID) {
    media_class = "Audio/Sink";
  } else if (id == DEFAULT_AUDIO_SOURCE_ID) {
    media_class = "Audio/Source";
  } else if (id == DEFAULT_VIDEO_SOURCE_ID) {
    media_class = "Video/Source";
  } else {
    /* SPA_ID_INVALID is a special case used by some parse_positional() to
       indicate that no specific ID was given. It needs to be checked because
       currently SPA_ID_INVALID == G_MAXUINT32. */
    if ((id <= 0 || id >= G_MAXUINT32) && id != SPA_ID_INVALID) {
      g_set_error (error, wpctl_error_domain_quark(), 0,
          "'%ld' is not a valid ID", id);
      return FALSE;
    }

    *res = (guint32)id;
    return TRUE;
  }

  if (!def_nodes_api) {
    g_set_error (error, wpctl_error_domain_quark(), 0,
        "Default nodes API is not loaded\n");
    return FALSE;
  }

  g_signal_emit_by_name (def_nodes_api, "get-default-node", media_class, res);

  if (*res <= 0 || *res >= G_MAXUINT32) {
    g_set_error (error, wpctl_error_domain_quark(), 0,
        "'%d' is not a valid ID (returned by default-nodes-api)", *res);
    return FALSE;
  }

  return TRUE;
}

static gboolean
run_nodes_by_pid (WpObjectManager *om, guint32 pid,
    WpIteratorFoldFunc func, gpointer data)
{
  gboolean res = TRUE;
  g_autoptr (WpIterator) client_it = NULL;
  g_auto (GValue) client_val = G_VALUE_INIT;
  client_it = wp_object_manager_new_filtered_iterator (om,
      WP_TYPE_CLIENT, WP_CONSTRAINT_TYPE_PW_PROPERTY,
      PW_KEY_APP_PROCESS_ID, "=u", pid, NULL);
  for (; wp_iterator_next (client_it, &client_val); g_value_unset (&client_val)) {
    WpPipewireObject *client = g_value_get_object (&client_val);
    guint32 client_id = wp_proxy_get_bound_id (WP_PROXY (client));
    g_autoptr (WpIterator) node_it = NULL;
    node_it = wp_object_manager_new_filtered_iterator (om,
        WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
        PW_KEY_CLIENT_ID, "=u", client_id, NULL);
    if (!wp_iterator_fold (node_it, func, NULL, data))
      res = FALSE;
  }

  return res;
}

/* status */

static gboolean
status_prepare (WpCtl * self, GError ** error)
{
  wp_object_manager_add_interest (self->om, WP_TYPE_CLIENT, NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_DEVICE, NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_ENDPOINT, NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_NODE, NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_PORT, NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_LINK, NULL);
  wp_object_manager_request_object_features (self->om, WP_TYPE_GLOBAL_PROXY,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
  return TRUE;
}

#define TREE_INDENT_LINE " │  "
#define TREE_INDENT_NODE " ├─ "
#define TREE_INDENT_END  " └─ "
#define TREE_INDENT_EMPTY "    "

struct print_context
{
  WpCtl *self;
  guint32 default_node;
  WpPlugin *mixer_api;
};

static void
print_controls (guint32 id, struct print_context *context)
{
  g_autoptr (GVariant) dict = NULL;

  if (context->mixer_api)
    g_signal_emit_by_name (context->mixer_api, "get-volume", id, &dict);

  if (dict) {
    gboolean mute = FALSE;
    gdouble volume = 1.0;
    if (g_variant_lookup (dict, "mute", "b", &mute) &&
        g_variant_lookup (dict, "volume", "d", &volume))
      printf (" [vol: %.2f%s", volume, mute ? " MUTED]" : "]");
  }
  printf ("\n");
}

static void
print_device (const GValue *item, gpointer data)
{
  WpPipewireObject *obj = g_value_get_object (item);
  guint32 id = wp_proxy_get_bound_id (WP_PROXY (obj));
  const gchar *api = wp_pipewire_object_get_property (obj, PW_KEY_DEVICE_API);
  const gchar *name = NULL;

  if (cmdline.status.display_nicknames)
    name = wp_pipewire_object_get_property (obj, PW_KEY_DEVICE_NICK);
  else if (cmdline.status.display_names)
    name = wp_pipewire_object_get_property (obj, PW_KEY_DEVICE_NAME);

  if (!name)
    name = wp_pipewire_object_get_property (obj, PW_KEY_DEVICE_DESCRIPTION);

  printf (TREE_INDENT_LINE "  %4u. %-35s [%s]\n", id, name, api);
}

static void
print_dev_node (const GValue *item, gpointer data)
{
  WpPipewireObject *obj = g_value_get_object (item);
  struct print_context *context = data;
  guint32 id = wp_proxy_get_bound_id (WP_PROXY (obj));
  gboolean is_default = (context->default_node == id);
  const gchar *name = NULL;

  if (cmdline.status.display_nicknames)
    name = wp_pipewire_object_get_property (obj, PW_KEY_NODE_NICK);
  else if (cmdline.status.display_names)
    name = wp_pipewire_object_get_property (obj, PW_KEY_NODE_NAME);

  if (!name)
    name = wp_pipewire_object_get_property (obj, PW_KEY_NODE_DESCRIPTION);

  printf (TREE_INDENT_LINE "%c %4u. %-35s", is_default ? '*' : ' ', id, name);
  print_controls (id, context);
}

static void
print_endpoint (const GValue *item, gpointer data)
{
  WpPipewireObject *obj = g_value_get_object (item);
  struct print_context *context = data;
  guint32 id = wp_proxy_get_bound_id (WP_PROXY (obj));
  guint32 node_id = -1;
  gboolean is_default = (context->default_node == id);
  const gchar *str, *name;

  if ((str = wp_pipewire_object_get_property (obj, "node.id")))
    node_id = atoi (str);

  name = wp_pipewire_object_get_property (obj, "endpoint.description");
  if (!name)
    name = wp_pipewire_object_get_property (obj, "endpoint.name");

  printf (TREE_INDENT_LINE "%c %4u. %-35s", is_default ? '*' : ' ', id, name);
  print_controls (node_id, context);
}

static void
print_stream_node (const GValue *item, gpointer data)
{
  WpCtl * self = data;
  WpPipewireObject *obj = g_value_get_object (item);
  guint32 id = wp_proxy_get_bound_id (WP_PROXY (obj));
  const gchar *name = wp_pipewire_object_get_property (obj, PW_KEY_APP_NAME);
  if (!name)
    name = wp_pipewire_object_get_property (obj, PW_KEY_NODE_NAME);

  printf (TREE_INDENT_EMPTY "  %4u. %-60s\n", id, name);

  g_autoptr (WpIterator) it = wp_object_manager_new_filtered_iterator (self->om,
      WP_TYPE_PORT, WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_NODE_ID, "=u", id,
      NULL);
  g_auto (GValue) val = G_VALUE_INIT;

  for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
    WpPort *port = g_value_get_object (&val);
    obj = WP_PIPEWIRE_OBJECT (port);
    id = wp_proxy_get_bound_id (WP_PROXY (obj));
    name = wp_pipewire_object_get_property (obj, PW_KEY_PORT_NAME);
    WpDirection dir = wp_port_get_direction (port);

    printf (TREE_INDENT_EMPTY "       %4u. %-15s", id, name);

    g_autoptr (WpLink) link = wp_object_manager_lookup (self->om, WP_TYPE_LINK,
        WP_CONSTRAINT_TYPE_PW_PROPERTY, (dir == WP_DIRECTION_OUTPUT) ?
            PW_KEY_LINK_OUTPUT_PORT : PW_KEY_LINK_INPUT_PORT, "=u", id,
        NULL);
    if (link) {
      guint32 peer_id = -1;
      wp_link_get_linked_object_ids(link,
          NULL, (dir == WP_DIRECTION_INPUT) ? &peer_id : NULL,
          NULL, (dir == WP_DIRECTION_OUTPUT) ? &peer_id : NULL);
      g_autoptr (WpPipewireObject) peer = wp_object_manager_lookup (
          self->om, WP_TYPE_PORT,
          WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", peer_id,
          NULL);
      name = wp_pipewire_object_get_property (peer, PW_KEY_PORT_ALIAS);

      g_autoptr (GEnumClass) klass = g_type_class_ref (WP_TYPE_LINK_STATE);
      GEnumValue *state = g_enum_get_value (klass, wp_link_get_state (link, NULL));

      printf (" %c %s\t[%s]\n", (dir == WP_DIRECTION_OUTPUT) ? '>' : '<', name,
          state->value_nick);
    } else {
      printf ("\n");
    }
  }
}

static void
status_run (WpCtl * self)
{
  g_autoptr (WpIterator) it = NULL;
  g_auto (GValue) val = G_VALUE_INIT;
  g_autoptr (WpPlugin) def_nodes_api = NULL;
  struct print_context context = { .self = self };

  def_nodes_api = wp_plugin_find (self->core, "default-nodes-api");
  context.mixer_api = wp_plugin_find (self->core, "mixer-api");

  /* server + clients */
  printf ("PipeWire '%s' [%s, %s@%s, cookie:%u]\n",
      wp_core_get_remote_name (self->core),
      wp_core_get_remote_version (self->core),
      wp_core_get_remote_user_name (self->core),
      wp_core_get_remote_host_name (self->core),
      wp_core_get_remote_cookie (self->core));

  printf (TREE_INDENT_END "Clients:\n");
  it = wp_object_manager_new_filtered_iterator (self->om, WP_TYPE_CLIENT, NULL);
  for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
    WpProxy *client = g_value_get_object (&val);
    g_autoptr (WpProperties) properties =
        wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (client));

    printf (TREE_INDENT_EMPTY "  %4u. %-35s [%s, %s@%s, pid:%s]\n",
        wp_proxy_get_bound_id (client),
        wp_properties_get (properties, PW_KEY_APP_NAME),
        wp_properties_get (properties, PW_KEY_CORE_VERSION),
        wp_properties_get (properties, PW_KEY_APP_PROCESS_USER),
        wp_properties_get (properties, PW_KEY_APP_PROCESS_HOST),
        wp_properties_get (properties, PW_KEY_APP_PROCESS_ID));
  }
  g_clear_pointer (&it, wp_iterator_unref);
  printf ("\n");

  /* sessions */
  const gchar *MEDIA_TYPES[] = { "Audio", "Video" };

  for (guint i = 0; i < G_N_ELEMENTS (MEDIA_TYPES); i++) {
    const gchar *media_type = MEDIA_TYPES[i];
    g_autoptr (WpIterator) child_it = NULL;

    printf ("%s\n", media_type);

    if (media_type && *media_type != '\0') {
      gchar media_type_glob[16];
      gchar media_class[24];

      g_snprintf (media_type_glob, sizeof(media_type_glob), "*%s*", media_type);

      printf (TREE_INDENT_NODE "Devices:\n");
      child_it = wp_object_manager_new_filtered_iterator (self->om,
          WP_TYPE_DEVICE,
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", media_type_glob,
          NULL);
      wp_iterator_foreach (child_it, print_device, self);
      g_clear_pointer (&child_it, wp_iterator_unref);

      printf (TREE_INDENT_LINE "\n");

      printf (TREE_INDENT_NODE "Sinks:\n");
      g_snprintf (media_class, sizeof(media_class), "%s/Sink", media_type);
      context.default_node = -1;
      if (def_nodes_api)
        g_signal_emit_by_name (def_nodes_api, "get-default-node", media_class,
            &context.default_node);
      child_it = wp_object_manager_new_filtered_iterator (self->om,
          WP_TYPE_NODE,
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", "*/Sink*",
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", media_type_glob,
          NULL);
      wp_iterator_foreach (child_it, print_dev_node, (gpointer) &context);
      g_clear_pointer (&child_it, wp_iterator_unref);

      printf (TREE_INDENT_LINE "\n");

      printf (TREE_INDENT_NODE "Sink endpoints:\n");
      child_it = wp_object_manager_new_filtered_iterator (self->om,
          WP_TYPE_ENDPOINT,
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", "*/Sink*",
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", media_type_glob,
          NULL);
      wp_iterator_foreach (child_it, print_endpoint, (gpointer) &context);
      g_clear_pointer (&child_it, wp_iterator_unref);

      printf (TREE_INDENT_LINE "\n");

      printf (TREE_INDENT_NODE "Sources:\n");
      g_snprintf (media_class, sizeof(media_class), "%s/Source", media_type);
      context.default_node = -1;
      if (def_nodes_api)
        g_signal_emit_by_name (def_nodes_api, "get-default-node", media_class,
            &context.default_node);
      child_it = wp_object_manager_new_filtered_iterator (self->om,
          WP_TYPE_NODE,
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", "*/Source*",
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", media_type_glob,
          NULL);
      wp_iterator_foreach (child_it, print_dev_node, (gpointer) &context);
      g_clear_pointer (&child_it, wp_iterator_unref);

      printf (TREE_INDENT_LINE "\n");

      printf (TREE_INDENT_NODE "Source endpoints:\n");
      child_it = wp_object_manager_new_filtered_iterator (self->om,
          WP_TYPE_ENDPOINT,
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", "*/Source*",
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", media_type_glob,
          NULL);
      wp_iterator_foreach (child_it, print_endpoint, (gpointer) &context);
      g_clear_pointer (&child_it, wp_iterator_unref);

      printf (TREE_INDENT_LINE "\n");

      printf (TREE_INDENT_END "Streams:\n");
      child_it = wp_object_manager_new_filtered_iterator (self->om,
          WP_TYPE_NODE,
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", "Stream/*",
          WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", media_type_glob,
          NULL);
      wp_iterator_foreach (child_it, print_stream_node, self);
      g_clear_pointer (&child_it, wp_iterator_unref);
    }

    printf ("\n");
  }

  /* Settings */
  printf ("Settings\n");

  if (def_nodes_api) {
    printf (TREE_INDENT_END "Default Configured Node Names:\n");
    for (guint i = 0; i < G_N_ELEMENTS (DEFAULT_NODE_MEDIA_CLASSES); i++) {
      const gchar *name = NULL;
      g_signal_emit_by_name (def_nodes_api, "get-default-configured-node-name",
          DEFAULT_NODE_MEDIA_CLASSES[i], &name);
      if (name)
        printf (TREE_INDENT_EMPTY "  %4u. %-12s  %s\n", i,
            DEFAULT_NODE_MEDIA_CLASSES[i], name);
    }
  }

  g_clear_object (&context.mixer_api);
  g_main_loop_quit (self->loop);
}

/* get-volume  */

static gboolean
get_volume_parse_positional (gint argc, gchar ** argv, GError **error)
{
  if (argc < 3) {
    g_set_error (error, wpctl_error_domain_quark(), 0,
        "ID is required");
    return FALSE;
  } else {
    return parse_id (true, false, argv[2], &cmdline.get_volume.id, error);
  }
}

static gboolean
get_volume_prepare (WpCtl * self, GError ** error)
{
  wp_object_manager_add_interest (self->om, WP_TYPE_NODE, NULL);
  wp_object_manager_request_object_features (self->om, WP_TYPE_GLOBAL_PROXY,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
  return TRUE;
}

static void
do_print_volume (WpCtl * self, WpPipewireObject *proxy)
{
  g_autoptr (WpPlugin) mixer_api = wp_plugin_find (self->core, "mixer-api");
  GVariant *variant = NULL;
  gboolean mute = FALSE;
  gdouble volume = 1.0;
  guint32 id = wp_proxy_get_bound_id (WP_PROXY (proxy));

  g_signal_emit_by_name (mixer_api, "get-volume", id, &variant);
  if (!variant) {
    fprintf (stderr, "Node %d does not support volume\n", id);
    return;
  }
  g_variant_lookup (variant, "volume", "d", &volume);
  g_variant_lookup (variant, "mute", "b", &mute);
  g_clear_pointer (&variant, g_variant_unref);

  printf ("Volume: %.2f%s", volume, mute ? " [MUTED]\n" : "\n");
}

static void
get_volume_run (WpCtl * self)
{
  g_autoptr (WpPlugin) def_nodes_api = NULL;
  g_autoptr (GError) error = NULL;
  g_autoptr (WpPipewireObject) proxy = NULL;
  guint32 id;

  def_nodes_api = wp_plugin_find (self->core, "default-nodes-api");

  if (!translate_id (def_nodes_api, cmdline.get_volume.id, &id, &error)) {
    fprintf(stderr, "Translate ID error: %s\n\n", error->message);
    goto out;
  }

  proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY,
    WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL);
  if (!proxy) {
    fprintf (stderr, "Node '%d' not found\n", id);
    goto out;
  }

  do_print_volume (self, proxy);

out:
  g_main_loop_quit (self->loop);
}

/* inspect */

static gboolean
inspect_parse_positional (gint argc, gchar ** argv, GError **error)
{
  if (argc < 3) {
    g_set_error (error, wpctl_error_domain_quark(), 0, "ID is required");
    return FALSE;
  }

  return parse_id (true, true, argv[2], &cmdline.inspect.id, error);
}

static gboolean
inspect_prepare (WpCtl * self, GError ** error)
{
  /* collect all objects */
  wp_object_manager_add_interest (self->om, WP_TYPE_GLOBAL_PROXY, NULL);
  wp_object_manager_request_object_features (self->om, WP_TYPE_GLOBAL_PROXY,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
  return TRUE;
}

static inline void
inspect_prefix_line (guint nest_level, gboolean node)
{
  for (guint i = 1; i < nest_level; i++)
    printf (TREE_INDENT_EMPTY TREE_INDENT_LINE);
  if (nest_level > 0)
    printf (TREE_INDENT_EMPTY "%s", node ? TREE_INDENT_NODE : TREE_INDENT_LINE);
}

struct {
  const gchar *key;
  const gchar *type;
} assoc_keys[] = {
  { PW_KEY_CLIENT_ID, "Client" },
  { PW_KEY_DEVICE_ID, "Device" },
  { PW_KEY_ENDPOINT_CLIENT_ID, NULL },
  { "endpoint-link.id", "EndpointLink" },
  { PW_KEY_ENDPOINT_STREAM_ID, "EndpointStream" },
  { PW_KEY_ENDPOINT_LINK_OUTPUT_ENDPOINT, NULL },
  { PW_KEY_ENDPOINT_LINK_OUTPUT_STREAM, NULL },
  { PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT, NULL },
  { PW_KEY_ENDPOINT_LINK_INPUT_STREAM, NULL },
  { PW_KEY_ENDPOINT_ID, "Endpoint" },
  { PW_KEY_LINK_INPUT_NODE, NULL },
  { PW_KEY_LINK_INPUT_PORT, NULL },
  { PW_KEY_LINK_OUTPUT_NODE, NULL },
  { PW_KEY_LINK_OUTPUT_PORT, NULL },
  { PW_KEY_LINK_ID, "Link" },
  { PW_KEY_NODE_ID, "Node" },
  { PW_KEY_PORT_ID, "Port" },
  { PW_KEY_SESSION_ID, "Session" },
};

static inline gboolean
key_is_object_reference (const gchar *key)
{
  for (guint i = 0; i < G_N_ELEMENTS (assoc_keys); i++)
    if (!g_strcmp0 (key, assoc_keys[i].key))
      return TRUE;
  return FALSE;
}

static inline const gchar *
get_association_key (WpProxy * proxy)
{
  for (guint i = 0; i < G_N_ELEMENTS (assoc_keys); i++) {
    if (assoc_keys[i].type &&
        strstr (WP_PROXY_GET_CLASS (proxy)->pw_iface_type, assoc_keys[i].type))
      return assoc_keys[i].key;
  }
  return NULL;
}

struct property_item {
  const gchar *key;
  const gchar *value;
};

static gint
property_item_compare (gconstpointer a, gconstpointer b)
{
  return g_strcmp0 (
      ((struct property_item *) a)->key,
      ((struct property_item *) b)->key);
}

static void
inspect_print_object (WpCtl * self, WpProxy * proxy, guint nest_level)
{
  g_autoptr (WpProperties) properties =
      WP_IS_PIPEWIRE_OBJECT (proxy) ?
      wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (proxy)) :
      wp_properties_new_empty ();
  g_autoptr (WpProperties) global_p =
      wp_global_proxy_get_global_properties (WP_GLOBAL_PROXY (proxy));
  g_autoptr (GArray) array =
      g_array_new (FALSE, FALSE, sizeof (struct property_item));

  /* print basic object info */
  inspect_prefix_line (nest_level, TRUE);
  printf ("id %u, type %s\n",
      wp_proxy_get_bound_id (proxy),
      WP_PROXY_GET_CLASS (proxy)->pw_iface_type);

  /* merge the two property sets */
  properties = wp_properties_ensure_unique_owner (properties);
  wp_properties_add (properties, global_p);
  wp_properties_set (properties, "object.id", NULL);

  /* copy key/value pointers to an array for sorting */
  {
    g_autoptr (WpIterator) it = NULL;
    g_auto (GValue) item = G_VALUE_INIT;

    for (it = wp_properties_new_iterator (properties);
          wp_iterator_next (it, &item);
          g_value_unset (&item)) {
      WpPropertiesItem *pi = g_value_get_boxed (&item);
      struct property_item prop_item = {
        .key = wp_properties_item_get_key (pi),
        .value = wp_properties_item_get_value (pi),
      };
      g_array_append_val (array, prop_item);
    }
  }

  /* sort */
  g_array_sort (array, property_item_compare);

  /* print */
  for (guint i = 0; i < array->len; i++) {
    struct property_item *prop_item =
        &g_array_index (array, struct property_item, i);
    gboolean is_global =
        (wp_properties_get (global_p, prop_item->key) != NULL);

    inspect_prefix_line (nest_level, FALSE);
    printf ("  %c %s = \"%s\"\n", is_global ? '*' : ' ',
        prop_item->key, prop_item->value);

    /* if the property is referencing an object, print the object */
    if (cmdline.inspect.show_referenced && nest_level == 0 &&
        key_is_object_reference (prop_item->key))
    {
      guint id = (guint) strtol (prop_item->value, NULL, 10);
      g_autoptr (WpProxy) refer_proxy =
          wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY,
              WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL);

      if (refer_proxy)
        inspect_print_object (self, refer_proxy, nest_level + 1);
    }
  }

  /* print associated objects */
  if (cmdline.inspect.show_associated && nest_level == 0) {
    const gchar *lookup_key = get_association_key (proxy);
    if (lookup_key) {
      g_autoptr (WpIterator) it =
          wp_object_manager_new_filtered_iterator (self->om,
              WP_TYPE_PIPEWIRE_OBJECT, WP_CONSTRAINT_TYPE_PW_PROPERTY,
              lookup_key, "=u", wp_proxy_get_bound_id (proxy), NULL);
      g_auto (GValue) item = G_VALUE_INIT;

      inspect_prefix_line (nest_level, TRUE);
      printf ("associated objects:\n");

      for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
        WpProxy *assoc_proxy = g_value_get_object (&item);
        inspect_print_object (self, assoc_proxy, nest_level + 1);
      }
    }
  }
}

static void
inspect_run (WpCtl * self)
{
  g_autoptr (WpProxy) proxy = NULL;
  g_autoptr (WpPlugin) def_nodes_api = wp_plugin_find (self->core, "default-nodes-api");
  g_autoptr (GError) error = NULL;
  guint32 id;


  if (!translate_id (def_nodes_api, cmdline.inspect.id, &id, &error)) {
    fprintf(stderr, "Translate ID error: %s\n\n", error->message);
    goto out_err;
  }

  proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL);
  if (!proxy) {
    fprintf (stderr, "Object '%d' not found\n", id);
    goto out_err;
  }

  inspect_print_object (self, proxy, 0);

out:
  g_main_loop_quit (self->loop);
  return;

out_err:
  self->exit_code = 3;
  goto out;
}

/* set-default */
static gboolean
set_default_parse_positional (gint argc, gchar ** argv, GError **error)
{
  if (argc < 3) {
    g_set_error (error, wpctl_error_domain_quark(), 0, "ID is required");
    return FALSE;
  }

  return parse_id (false, false, argv[2], &cmdline.set_default.id, error);
}

static gboolean
set_default_prepare (WpCtl * self, GError ** error)
{
  wp_object_manager_add_interest (self->om, WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY,
      "object.id", "=u", cmdline.set_default.id,
      NULL);
  wp_object_manager_request_object_features (self->om, WP_TYPE_METADATA,
      WP_OBJECT_FEATURES_ALL);
  wp_object_manager_request_object_features (self->om, WP_TYPE_NODE,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
  return TRUE;
}

static void
set_default_run (WpCtl * self)
{
  g_autoptr (WpPlugin) def_nodes_api = NULL;
  g_autoptr (WpProxy) proxy = NULL;
  guint32 id;
  g_autoptr (GError) error = NULL;
  const gchar *media_class;

  def_nodes_api = wp_plugin_find (self->core, "default-nodes-api");
  if (!def_nodes_api) {
    fprintf (stderr, "Default nodes API not loaded\n");
    goto out;
  }

  if (!translate_id (def_nodes_api, cmdline.set_default.id, &id, &error)) {
    fprintf(stderr, "Translate ID error: %s\n\n", error->message);
    goto out;
  }

  proxy = wp_object_manager_lookup (self->om, WP_TYPE_NODE, NULL);
  if (!proxy) {
    fprintf (stderr, "Node '%d' not found\n", id);
    goto out;
  }

  media_class = wp_pipewire_object_get_property (WP_PIPEWIRE_OBJECT (proxy),
      PW_KEY_MEDIA_CLASS);
  for (guint i = 0; i < G_N_ELEMENTS (DEFAULT_NODE_MEDIA_CLASSES); i++) {
    if (!g_strcmp0 (media_class, DEFAULT_NODE_MEDIA_CLASSES[i])) {
      gboolean res = FALSE;
      const gchar *name = wp_pipewire_object_get_property (
          WP_PIPEWIRE_OBJECT (proxy), PW_KEY_NODE_NAME);
      if (!name) {
        fprintf (stderr, "node %u does not have a valid node.name\n", id);
        goto out;
      }

      g_signal_emit_by_name (def_nodes_api, "set-default-configured-node-name",
          DEFAULT_NODE_MEDIA_CLASSES[i], name, &res);
      if (!res) {
        fprintf (stderr, "failed to set default node %u (media.class = %s)\n", id,
            media_class);
        goto out;
      }

      wp_core_sync (self->core, NULL, (GAsyncReadyCallback) async_quit, self);
      return;
    }
  }

  fprintf (stderr, "%u is not a device node (media.class = %s)\n", id, media_class);

out:
  self->exit_code = 3;
  g_main_loop_quit (self->loop);
}

/* set-volume */

static gboolean
set_volume_parse_positional (gint argc, gchar ** argv, GError **error)
{
  g_autoptr (GRegex) regex = NULL;
  g_autoptr (GMatchInfo) info = NULL;

  if (argc < 4) {
    g_set_error_literal (error, wpctl_error_domain_quark(), 0,
        "ID and VOL[%][-/+] are required");
    return FALSE;
  }

  regex = g_regex_new ("^(\\d*\\.?\\d*)(%?)([-+]?)$", 0, 0, NULL);

  if (g_regex_match(regex, argv[3], 0, &info)) {
    gchar *str = g_match_info_fetch (info, 1);
    cmdline.set_volume.volume = strtof (str, NULL);
    cmdline.set_volume.type = 'a';
    g_free (str);

    str = g_match_info_fetch (info, 2);
    if (g_strcmp0 (str, "%") == 0)
      cmdline.set_volume.volume /= 100;
    g_free (str);

    str = g_match_info_fetch (info, 3);
    if (g_strcmp0 (str, "-") == 0) {
      cmdline.set_volume.volume = -(cmdline.set_volume.volume);
      cmdline.set_volume.type = 's';
    } else if (g_strcmp0 (str, "+") == 0) {
      cmdline.set_volume.type = 's';
    }
    g_free (str);
  } else {
    g_set_error (error, wpctl_error_domain_quark(), 0,
        "Invalid volume argument. See wpctl set-volume --help");
    return FALSE;
  }

  return parse_id (!cmdline.set_volume.is_pid, false, argv[2],
      &cmdline.set_volume.id, error);
}

static gboolean
set_volume_prepare (WpCtl * self, GError ** error)
{
  wp_object_manager_add_interest (self->om, WP_TYPE_ENDPOINT, NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_NODE, NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_CLIENT, NULL);
  wp_object_manager_request_object_features (self->om, WP_TYPE_GLOBAL_PROXY,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
  return TRUE;
}

static gboolean
do_set_volume (WpCtl * self, WpPipewireObject *proxy)
{
  g_autoptr (WpPlugin) mixer_api = wp_plugin_find (self->core, "mixer-api");
  g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  GVariant *variant = NULL;
  gboolean res = FALSE;
  gdouble curr_volume = 1.0;
  guint32 id = wp_proxy_get_bound_id (WP_PROXY (proxy));

  if (WP_IS_ENDPOINT (proxy)) {
    const gchar *str = wp_pipewire_object_get_property (proxy, "node.id");
    if (!str) {
      fprintf (stderr, "Endpoint '%d' does not have an associated node\n", id);
      return FALSE;
    }
    id = atoi (str);
  }

  if (cmdline.set_volume.type == 's') {
    g_signal_emit_by_name (mixer_api, "get-volume", id, &variant);
      if (!variant) {
      fprintf (stderr, "Node %d does not support volume\n", id);
      g_clear_pointer (&variant, g_variant_unref);
      return FALSE;
    }
    g_variant_lookup (variant, "volume", "d", &curr_volume);
    g_clear_pointer (&variant, g_variant_unref);

    cmdline.set_volume.volume = (cmdline.set_volume.volume + curr_volume);
  }
  if (cmdline.set_volume.volume < 0) {
    cmdline.set_volume.volume = 0.0;
  }
  if (cmdline.set_volume.limit > 0) {
    if (cmdline.set_volume.volume > cmdline.set_volume.limit) {
      cmdline.set_volume.volume = cmdline.set_volume.limit;
    }
  }

  g_variant_builder_add (&b, "{sv}", "volume",
      g_variant_new_double (cmdline.set_volume.volume));
  variant = g_variant_builder_end (&b);

  g_signal_emit_by_name (mixer_api, "set-volume", id, variant, &res);
  if (!res) {
    fprintf (stderr, "Node %d does not support volume\n", id);
    return FALSE;
  }

  return TRUE;
}

static gboolean
do_set_volume_cb (const GValue *node_val, GValue *ret, gpointer data)
{
  WpCtl * self = data;
  WpPipewireObject *node = g_value_get_object (node_val);
  return do_set_volume (self, node);
}

static void
set_volume_run (WpCtl * self)
{
  g_autoptr (WpPipewireObject) proxy = NULL;
  g_autoptr (WpPlugin) def_nodes_api = wp_plugin_find (self->core, "default-nodes-api");
  g_autoptr (GError) error = NULL;
  guint32 id;

  if (!translate_id (def_nodes_api, cmdline.set_volume.id, &id, &error)) {
    fprintf(stderr, "Translate ID error: %s\n\n", error->message);
    goto out;
  }

  if (cmdline.set_volume.is_pid) {
    if (!run_nodes_by_pid (self->om, id, do_set_volume_cb, self)) {
      fprintf (stderr, "Could not set volume in all nodes with PID '%d'\n", id);
      goto out;
    }
  } else {
    proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY,
        WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "=u", id, NULL);
    if (!proxy) {
      fprintf (stderr, "Object '%d' not found\n", id);
      goto out;
    }
    if (!do_set_volume (self, proxy))
      goto out;
  }

  wp_core_sync (self->core, NULL, (GAsyncReadyCallback) async_quit, self);
  return;

out:
  self->exit_code = 3;
  g_main_loop_quit (self->loop);
}

/* set-mute */

static gboolean
set_mute_parse_positional (gint argc, gchar ** argv, GError **error)
{
  if (argc < 4) {
    g_set_error (error, wpctl_error_domain_quark(), 0,
        "ID and one of '1', '0' or 'toggle' are required");
    return FALSE;
  }

  if (!g_strcmp0 (argv[3], "1"))
    cmdline.set_mute.mute = 1;
  else if (!g_strcmp0 (argv[3], "0"))
    cmdline.set_mute.mute = 0;
  else if (!g_strcmp0 (argv[3], "toggle"))
    cmdline.set_mute.mute = 2;
  else {
    g_set_error (error, wpctl_error_domain_quark(), 0,
        "'%s' is not a valid mute option", argv[3]);
    return FALSE;
  }

  return parse_id (!cmdline.set_mute.is_pid, false, argv[2],
      &cmdline.set_mute.id, error);
}

static gboolean
set_mute_prepare (WpCtl * self, GError ** error)
{
  wp_object_manager_add_interest (self->om, WP_TYPE_ENDPOINT, NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_NODE, NULL);
  wp_object_manager_add_interest (self->om, WP_TYPE_CLIENT, NULL);
  wp_object_manager_request_object_features (self->om, WP_TYPE_GLOBAL_PROXY,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
  return TRUE;
}

static gboolean
do_set_mute (WpCtl * self, WpPipewireObject *proxy)
{
  g_autoptr (WpPlugin) mixer_api = wp_plugin_find (self->core, "mixer-api");
  g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  GVariant *variant = NULL;
  gboolean res = FALSE;
  gboolean mute = FALSE;
  guint32 id = wp_proxy_get_bound_id (WP_PROXY (proxy));

  if (WP_IS_ENDPOINT (proxy)) {
    const gchar *str = wp_pipewire_object_get_property (proxy, "node.id");
    if (!str) {
      fprintf (stderr, "Endpoint '%d' does not have an associated node\n", id);
      return FALSE;
    }
    id = atoi (str);
  }

  g_signal_emit_by_name (mixer_api, "get-volume", id, &variant);
  if (!variant) {
    fprintf (stderr, "Node %d does not support mute\n", id);
    return FALSE;
  }

  g_variant_lookup (variant, "mute", "b", &mute);
  g_clear_pointer (&variant, g_variant_unref);

  if (cmdline.set_mute.mute == 2)
    mute = !mute;
  else
    mute = !!cmdline.set_mute.mute;

  g_variant_builder_add (&b, "{sv}", "mute", g_variant_new_boolean (mute));
  variant = g_variant_builder_end (&b);

  g_signal_emit_by_name (mixer_api, "set-volume", id, variant, &res);

  return TRUE;
}

static gboolean
do_set_mute_cb (const GValue *node_val, GValue *ret, gpointer data)
{
  WpCtl * self = data;
  WpPipewireObject *node = g_value_get_object (node_val);
  return do_set_mute (self, node);
}

static void
set_mute_run (WpCtl * self)
{
  g_autoptr (WpPipewireObject) proxy = NULL;
  g_autoptr (WpPlugin) def_nodes_api = wp_plugin_find (self->core, "default-nodes-api");
  g_autoptr (GError) error = NULL;
  guint32 id;

  if (!translate_id (def_nodes_api, cmdline.set_mute.id, &id, &error)) {
    fprintf(stderr, "Translate ID error: %s\n\n", error->message);
    goto out;
  }

  if (cmdline.set_mute.is_pid) {
    if (!run_nodes_by_pid (self->om, id, do_set_mute_cb, self)) {
      fprintf (stderr, "Could not set mute in all nodes with PID '%d'\n", id);
      goto out;
    }
  } else {
    proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY,
        WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "=u", id, NULL);
    if (!proxy) {
      fprintf (stderr, "Object '%d' not found\n", id);
      goto out;
    }
    if (!do_set_mute (self, proxy))
        goto out;
  }

  wp_core_sync (self->core, NULL, (GAsyncReadyCallback) async_quit, self);
  return;

out:
  self->exit_code = 3;
  g_main_loop_quit (self->loop);
}

/* set-profile */

static gboolean
set_profile_parse_positional (gint argc, gchar ** argv, GError **error)
{
  if (argc < 4) {
    g_set_error (error, wpctl_error_domain_quark(), 0,
        "ID and INDEX are required");
    return FALSE;
  }

  cmdline.set_profile.index = atoi (argv[3]);
  return parse_id (true, true, argv[2], &cmdline.set_profile.id, error);
}

static gboolean
set_profile_prepare (WpCtl * self, GError ** error)
{
  wp_object_manager_add_interest (self->om, WP_TYPE_GLOBAL_PROXY, NULL);
  wp_object_manager_request_object_features (self->om, WP_TYPE_GLOBAL_PROXY,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
  return TRUE;
}

static void
set_profile_run (WpCtl * self)
{
  g_autoptr (WpPlugin) def_nodes_api = wp_plugin_find (self->core, "default-nodes-api");
  g_autoptr (WpPipewireObject) proxy = NULL;
  g_autoptr (GError) error = NULL;
  guint32 node_id;

  if (!translate_id (def_nodes_api, cmdline.set_profile.id, &node_id, &error)) {
    fprintf(stderr, "Translate ID error: %s\n\n", error->message);
    goto out;
  }

  proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "=u", node_id, NULL);
  if (!proxy) {
    fprintf (stderr, "Object '%d' not found\n", node_id);
    goto out;
  }
  wp_pipewire_object_set_param (proxy, "Profile", 0,
      wp_spa_pod_new_object (
        "Spa:Pod:Object:Param:Profile", "Profile",
        "index", "i", cmdline.set_profile.index,
        NULL));
  wp_core_sync (self->core, NULL, (GAsyncReadyCallback) async_quit, self);
  return;

out:
  self->exit_code = 3;
  g_main_loop_quit (self->loop);
}

/* clear-default */

static gboolean
clear_default_parse_positional (gint argc, gchar ** argv, GError **error)
{
  if (argc >= 3) {
    long id = strtol (argv[2], NULL, 10);
    if (id < 0 || id >= (long)G_N_ELEMENTS (DEFAULT_NODE_MEDIA_CLASSES)) {
      g_set_error (error, wpctl_error_domain_quark(), 0,
          "The setting ID value must be between 0 and %ld inclusive",
              G_N_ELEMENTS (DEFAULT_NODE_MEDIA_CLASSES) - 1);
      return FALSE;
    }
    cmdline.clear_default.id = id;
  } else {
    cmdline.clear_default.id = SPA_ID_INVALID;
  }

  return TRUE;
}

static gboolean
clear_default_prepare (WpCtl * self, GError ** error)
{
  return TRUE;
}

static void
clear_default_run (WpCtl * self)
{
  g_autoptr (WpPlugin) def_nodes_api = NULL;
  g_autoptr (GError) error = NULL;
  gboolean res = FALSE;

  def_nodes_api = wp_plugin_find (self->core, "default-nodes-api");
  if (!def_nodes_api) {
    fprintf (stderr, "Default nodes API not loaded\n");
    goto out;
  }

  /* clear all defaults if id was not given */
  if (cmdline.clear_default.id == SPA_ID_INVALID) {
    for (guint i = 0; i < G_N_ELEMENTS (DEFAULT_NODE_MEDIA_CLASSES); i++) {
      g_signal_emit_by_name (def_nodes_api, "set-default-configured-node-name",
          DEFAULT_NODE_MEDIA_CLASSES[i], NULL, &res);
      if (!res) {
        fprintf (stderr, "failed to clear default configured node (%s)\n",
            DEFAULT_NODE_MEDIA_CLASSES[i]);
        goto out;
      }
    }
  } else {
    g_signal_emit_by_name (def_nodes_api, "set-default-configured-node-name",
        DEFAULT_NODE_MEDIA_CLASSES[cmdline.clear_default.id], NULL, &res);
    if (!res) {
      fprintf (stderr, "failed to clear default configured node (%s)\n",
          DEFAULT_NODE_MEDIA_CLASSES[cmdline.clear_default.id]);
      goto out;
    }
  }

  wp_core_sync (self->core, NULL, (GAsyncReadyCallback) async_quit, self);
  return;

out:
  self->exit_code = 3;
  g_main_loop_quit (self->loop);
}

#define N_ENTRIES 3

static const struct subcommand {
  /* the name to match on the command line */
  const gchar *name;
  /* description of positional arguments, shown in the help message */
  const gchar *positional_args;
  /* short description, shown at the top of the help message */
  const gchar *summary;
  /* long description, shown at the bottom of the help message */
  const gchar *description;
  /* additional cmdline arguments for this subcommand */
  const GOptionEntry entries[N_ENTRIES];
  /* function to parse positional arguments */
  gboolean (*parse_positional) (gint, gchar **, GError **);
  /* function to prepare the object manager */
  gboolean (*prepare) (WpCtl *, GError **);
  /* function to run after the object manager is installed */
  void (*run) (WpCtl *);
} subcommands[] = {
  {
    .name = "status",
    .positional_args = "",
    .summary = "Displays the current state of objects in PipeWire",
    .description = NULL,
    .entries = {
      { "nick", 'k', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
        &cmdline.status.display_nicknames,
        "Display device and node nicknames instead of descriptions", NULL },
      { "name", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
        &cmdline.status.display_names,
        "Display device and node names instead of descriptions", NULL },
      { NULL }
    },
    .parse_positional = NULL,
    .prepare = status_prepare,
    .run = status_run,
  },
  {
    .name = "get-volume",
    .positional_args = "ID",
    .summary = "Displays volume information about the specified node in PipeWire",
    .description = NULL,
    .entries = { { NULL } },
    .parse_positional = get_volume_parse_positional,
    .prepare = get_volume_prepare,
    .run = get_volume_run,
  },
  {
    .name = "inspect",
    .positional_args = "ID",
    .summary = "Displays information about the specified object",
    .description = NULL,
    .entries = {
      { "referenced", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
        &cmdline.inspect.show_referenced,
        "Show objects that are referenced in properties", NULL },
      { "associated", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
        &cmdline.inspect.show_associated, "Show associated objects", NULL },
      { NULL }
    },
    .parse_positional = inspect_parse_positional,
    .prepare = inspect_prepare,
    .run = inspect_run,
  },
  {
    .name = "set-default",
    .positional_args = "ID",
    .summary = "Sets ID to be the default endpoint of its kind "
               "(capture/playback) in its session",
    .description = NULL,
    .entries = { { NULL } },
    .parse_positional = set_default_parse_positional,
    .prepare = set_default_prepare,
    .run = set_default_run,
  },
  {
    .name = "set-volume",
    .positional_args = "ID VOL[%][-/+]",
    .summary =
        "Sets the volume of ID from specified argument. (floating point, 1.0 is 100%)\n"
        "  VOL%[-/+] - Step up/down volume by specified percent (Example: 0.5%+)\n"
        "  VOL[-/+] - Step up/down volume by specified value (Example: 0.5+)\n"
        "  VOL - Set volume as the specified value (Example: 0.5)",
    .description = NULL,
    .entries = {
      { "pid", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
        &cmdline.set_volume.is_pid,
        "Selects all nodes associated to the given PID number", NULL },
      { "limit", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_DOUBLE,
        &cmdline.set_volume.limit,
        "Limits the final volume percentage to below this value. (floating point, 1.0 is 100%)", NULL },
      { NULL }
    },
    .parse_positional = set_volume_parse_positional,
    .prepare = set_volume_prepare,
    .run = set_volume_run,
  },
  {
    .name = "set-mute",
    .positional_args = "ID 1|0|toggle",
    .summary = "Changes the mute state of ID",
    .description = NULL,
    .entries = {
      { "pid", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
        &cmdline.set_mute.is_pid,
        "Selects all nodes associated to the given PID number", NULL },
      { NULL }
    },
    .parse_positional = set_mute_parse_positional,
    .prepare = set_mute_prepare,
    .run = set_mute_run,
  },
  {
    .name = "set-profile",
    .positional_args = "ID INDEX",
    .summary = "Sets the profile of ID to INDEX (integer, 0 is 'off')",
    .description = NULL,
    .entries = { { NULL } },
    .parse_positional = set_profile_parse_positional,
    .prepare = set_profile_prepare,
    .run = set_profile_run,
  },
  {
    .name = "clear-default",
    .positional_args = "[ID]",
    .summary = "Clears the default configured node (no ID means 'all')",
    .description = NULL,
    .entries = { { NULL } },
    .parse_positional = clear_default_parse_positional,
    .prepare = clear_default_prepare,
    .run = clear_default_run,
  }
};

static void
on_plugin_activated (WpObject * p, GAsyncResult * res, WpCtl * ctl)
{
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (p, res, &error)) {
    fprintf (stderr, "%s", error->message);
    ctl->exit_code = 1;
    g_main_loop_quit (ctl->loop);
    return;
  }

  if (--ctl->pending_plugins == 0)
    wp_core_install_object_manager (ctl->core, ctl->om);
}

gint
main (gint argc, gchar **argv)
{
  WpCtl ctl = {0};
  const struct subcommand *cmd = NULL;
  g_autoptr (GError) error = NULL;
  g_autofree gchar *summary = NULL;

  setlocale (LC_ALL, "");
  setlocale (LC_NUMERIC, "C");
  wp_init (WP_INIT_ALL);

  ctl.context = g_option_context_new (
      "COMMAND [COMMAND_OPTIONS] - WirePlumber Control CLI");
  ctl.loop = g_main_loop_new (NULL, FALSE);
  ctl.core = wp_core_new (NULL, NULL);
  ctl.apis = g_ptr_array_new_with_free_func (g_object_unref);
  ctl.om = wp_object_manager_new ();

  /* find the subcommand */
  if (argc > 1) {
    for (guint i = 0; i < G_N_ELEMENTS (subcommands); i++) {
      if (!g_strcmp0 (argv[1], subcommands[i].name)) {
        cmd = &subcommands[i];
        break;
      }
    }
  }

  /* prepare the subcommand options */
  if (cmd) {
    GOptionGroup *group;

    /* options */
    group = g_option_group_new (cmd->name, NULL, NULL, &ctl, NULL);
    g_option_group_add_entries (group, cmd->entries);
    g_option_context_set_main_group (ctl.context, group);

    /* summary */
    summary = g_strdup_printf ("Command: %s %s\n  %s",
        cmd->name, cmd->positional_args, cmd->summary);
    g_option_context_set_summary (ctl.context, summary);

    /* description */
    if (cmd->description)
      g_option_context_set_description (ctl.context, cmd->description);
  }
  else {
    /* build the generic summary */
    GString *summary_str = g_string_new ("Commands:");
    for (guint i = 0; i < G_N_ELEMENTS (subcommands); i++) {
      g_string_append_printf (summary_str, "\n  %s %s", subcommands[i].name,
          subcommands[i].positional_args);
    }
    summary = g_string_free (summary_str, FALSE);
    g_option_context_set_summary (ctl.context, summary);
    g_option_context_set_description (ctl.context, "Pass -h after a command "
        "to see command-specific options\n");
  }

  /* parse options */
  if (!g_option_context_parse (ctl.context, &argc, &argv, &error) ||
      (cmd && cmd->parse_positional &&
          !cmd->parse_positional (argc, argv, &error))) {
    fprintf (stderr, "Error: %s\n\n", error->message);
    cmd = NULL;
  }

  /* no active subcommand, show usage and exit */
  if (!cmd) {
    g_autofree gchar *help =
        g_option_context_get_help (ctl.context, FALSE, NULL);
    printf ("%s", help);
    return 1;
  }

  /* prepare the subcommand */
  if (!cmd->prepare (&ctl, &error)) {
    fprintf (stderr, "%s\n", error->message);
    return 1;
  }

  /* load required API modules */
  if (!wp_core_load_component (ctl.core,
      "libwireplumber-module-default-nodes-api", "module", NULL, &error)) {
    fprintf (stderr, "%s\n", error->message);
    return 1;
  }
  if (!wp_core_load_component (ctl.core,
      "libwireplumber-module-mixer-api", "module", NULL, &error)) {
    fprintf (stderr, "%s\n", error->message);
    return 1;
  }
  g_ptr_array_add (ctl.apis, wp_plugin_find (ctl.core, "default-nodes-api"));
  g_ptr_array_add (ctl.apis, ({
    WpPlugin *p = wp_plugin_find (ctl.core, "mixer-api");
    g_object_set (G_OBJECT (p), "scale", 1 /* cubic */, NULL);
    p;
  }));

  /* connect */
  if (!wp_core_connect (ctl.core)) {
    fprintf (stderr, "Could not connect to PipeWire\n");
    return 2;
  }

  /* run */
  g_signal_connect_swapped (ctl.core, "disconnected",
      (GCallback) g_main_loop_quit, ctl.loop);
  g_signal_connect_swapped (ctl.om, "installed",
      (GCallback) cmd->run, &ctl);

  for (guint i = 0; i < ctl.apis->len; i++) {
    WpPlugin *plugin = g_ptr_array_index (ctl.apis, i);
    ctl.pending_plugins++;
    wp_object_activate (WP_OBJECT (plugin), WP_PLUGIN_FEATURE_ENABLED, NULL,
        (GAsyncReadyCallback) on_plugin_activated, &ctl);
  }

  g_main_loop_run (ctl.loop);

  wp_ctl_clear (&ctl);
  return ctl.exit_code;
}
0707010000015B000081A4000000000000000000000001656CC35F00001CEB000000000000000000000000000000000000002600000000wireplumber-0.4.17/src/tools/wpexec.c /* WirePlumber
 *
 * Copyright © 2019-2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <glib-unix.h>
#include <pipewire/keys.h>
#include <stdio.h>
#include <locale.h>

#define WP_DOMAIN_DAEMON (wp_domain_daemon_quark ())
static G_DEFINE_QUARK (wireplumber-daemon, wp_domain_daemon);

enum WpExitCode
{
  /* based on sysexits.h */
  WP_EXIT_OK = 0,
  WP_EXIT_USAGE = 64,       /* command line usage error */
  WP_EXIT_UNAVAILABLE = 69, /* service unavailable */
  WP_EXIT_SOFTWARE = 70,    /* internal software error */
};

static gchar * exec_script = NULL;
static GVariantBuilder exec_args_b =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

static gboolean
parse_exec_script_arg (const gchar *option_name, const gchar *value,
    gpointer data, GError **error)
{
  /* the first argument is the script */
  if (!exec_script) {
    exec_script = g_strdup (value);
    return TRUE;
  }

  g_auto(GStrv) tokens = g_strsplit (value, "=", 2);
  if (!tokens[0] || *g_strstrip (tokens[0]) == '\0') {
    g_set_error (error, WP_DOMAIN_DAEMON, WP_EXIT_USAGE,
        "invalid script argument '%s'; must be in key=value format", value);
    return FALSE;
  }

  g_variant_builder_add (&exec_args_b, "{sv}", tokens[0], tokens[1] ?
          g_variant_new_string (g_strstrip (tokens[1])) :
          g_variant_new_boolean (TRUE));
  return TRUE;
}

static GOptionEntry entries[] =
{
  { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
    parse_exec_script_arg, NULL, NULL },
  { NULL }
};

/*** WpInitTransition ***/

struct _WpInitTransition
{
  WpTransition parent;
};

enum {
  STEP_LOAD_ENGINE = WP_TRANSITION_STEP_CUSTOM_START,
  STEP_LOAD_SCRIPT,
  STEP_CONNECT,
  STEP_ACTIVATE_ENGINE,
  STEP_ACTIVATE_SCRIPT,
};

G_DECLARE_FINAL_TYPE (WpInitTransition, wp_init_transition,
                      WP, INIT_TRANSITION, WpTransition)
G_DEFINE_TYPE (WpInitTransition, wp_init_transition, WP_TYPE_TRANSITION)

static void
wp_init_transition_init (WpInitTransition * self)
{
}

static guint
wp_init_transition_get_next_step (WpTransition * transition, guint step)
{
  switch (step) {
  case WP_TRANSITION_STEP_NONE: return STEP_LOAD_ENGINE;
  case STEP_LOAD_ENGINE:        return STEP_LOAD_SCRIPT;
  case STEP_LOAD_SCRIPT:        return STEP_CONNECT;
  case STEP_CONNECT:            return STEP_ACTIVATE_ENGINE;
  case STEP_ACTIVATE_ENGINE:    return STEP_ACTIVATE_SCRIPT;
  case STEP_ACTIVATE_SCRIPT:    return WP_TRANSITION_STEP_NONE;
  default:
    g_return_val_if_reached (WP_TRANSITION_STEP_ERROR);
  }
}

static void
on_plugin_activated (WpObject * p, GAsyncResult * res, WpInitTransition *self)
{
  GError *error = NULL;
  if (!wp_object_activate_finish (p, res, &error)) {
    wp_transition_return_error (WP_TRANSITION (self), error);
    return;
  }
  wp_transition_advance (WP_TRANSITION (self));
}

static void
wp_init_transition_execute_step (WpTransition * transition, guint step)
{
  WpInitTransition *self = WP_INIT_TRANSITION (transition);
  WpCore *core = wp_transition_get_source_object (transition);
  GError *error = NULL;

  switch (step) {
  case STEP_LOAD_ENGINE:
    if (!wp_core_load_component (core, "libwireplumber-module-lua-scripting",
            "module", NULL, &error)) {
      wp_transition_return_error (transition, error);
      return;
    }
    wp_transition_advance (transition);
    break;

  case STEP_LOAD_SCRIPT: {
    GVariant *args = g_variant_builder_end (&exec_args_b);
    if (!wp_core_load_component (core, exec_script, "script/lua", args, &error)) {
      wp_transition_return_error (transition, error);
      return;
    }
    wp_transition_advance (transition);
    break;
  }

  case STEP_CONNECT:
    g_signal_connect_object (core, "connected",
        G_CALLBACK (wp_transition_advance), transition, G_CONNECT_SWAPPED);

    if (!wp_core_connect (core)) {
      wp_transition_return_error (transition, g_error_new (WP_DOMAIN_DAEMON,
          WP_EXIT_UNAVAILABLE, "Failed to connect to PipeWire"));
      return;
    }
    break;

  case STEP_ACTIVATE_ENGINE: {
    g_autoptr (WpPlugin) p = wp_plugin_find (core, "lua-scripting");
    wp_object_activate (WP_OBJECT (p), WP_PLUGIN_FEATURE_ENABLED, NULL,
        (GAsyncReadyCallback) on_plugin_activated, self);
    break;
  }

  case STEP_ACTIVATE_SCRIPT: {
    g_autofree gchar *name = g_strdup_printf ("script:%s", exec_script);
    g_autoptr (WpPlugin) p = wp_plugin_find (core, name);
    wp_object_activate (WP_OBJECT (p), WP_PLUGIN_FEATURE_ENABLED, NULL,
        (GAsyncReadyCallback) on_plugin_activated, self);
    break;
  }

  case WP_TRANSITION_STEP_ERROR:
    break;

  default:
    g_assert_not_reached ();
  }
}

static void
wp_init_transition_class_init (WpInitTransitionClass * klass)
{
  WpTransitionClass * transition_class = (WpTransitionClass *) klass;

  transition_class->get_next_step = wp_init_transition_get_next_step;
  transition_class->execute_step = wp_init_transition_execute_step;
}

/*** WpExec ***/

typedef struct
{
  WpCore *core;
  GMainLoop *loop;
  gint exit_code;
} WpExec;

static void
wpexec_clear (WpExec * self)
{
  g_clear_pointer (&self->loop, g_main_loop_unref);
  g_clear_object (&self->core);
}

G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (WpExec, wpexec_clear)

static gboolean
signal_handler (gpointer data)
{
  WpExec *d = data;
  g_main_loop_quit (d->loop);
  return G_SOURCE_REMOVE;
}

static gboolean
init_start (WpTransition * transition)
{
  wp_transition_advance (transition);
  return G_SOURCE_REMOVE;
}

static void
init_done (WpCore * core, GAsyncResult * res, WpExec * d)
{
  g_autoptr (GError) error = NULL;
  if (!wp_transition_finish (res, &error)) {
    fprintf (stderr, "%s\n", error->message);
    d->exit_code = (error->domain == WP_DOMAIN_DAEMON) ?
        error->code : WP_EXIT_SOFTWARE;
    g_main_loop_quit (d->loop);
  }
}

gint
main (gint argc, gchar **argv)
{
  g_auto (WpExec) d = {0};
  g_autoptr (GOptionContext) context = NULL;
  g_autoptr (GError) error = NULL;

  setlocale (LC_ALL, "");
  setlocale (LC_NUMERIC, "C");
  wp_init (WP_INIT_ALL);

  context = g_option_context_new ("- WirePlumber script interpreter");
  g_option_context_add_main_entries (context, entries, NULL);
  if (!g_option_context_parse (context, &argc, &argv, &error)) {
    fprintf (stderr, "%s\n", error->message);
    return WP_EXIT_USAGE;
  }

  /* init wireplumber core */
  d.loop = g_main_loop_new (NULL, FALSE);
  d.core = wp_core_new (NULL, wp_properties_new (
          PW_KEY_APP_NAME, "wpexec",
          NULL));
  g_signal_connect_swapped (d.core, "disconnected",
      G_CALLBACK (g_main_loop_quit), d.loop);

  /* at the very least, enable warnings...
     this is required to spot lua runtime errors, otherwise
     there is silence and nothing is happening */
  if (!wp_log_level_is_enabled (G_LOG_LEVEL_WARNING))
    wp_log_set_level ("1");

  /* watch for exit signals */
  g_unix_signal_add (SIGINT, signal_handler, &d);
  g_unix_signal_add (SIGTERM, signal_handler, &d);
  g_unix_signal_add (SIGHUP, signal_handler, &d);

  /* initialization transition */
  g_idle_add ((GSourceFunc) init_start,
      wp_transition_new (wp_init_transition_get_type (), d.core,
          NULL, (GAsyncReadyCallback) init_done, &d));

  /* run */
  g_main_loop_run (d.loop);
  wp_core_disconnect (d.core);
  return d.exit_code;
}
 0707010000015C000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001F00000000wireplumber-0.4.17/subprojects    0707010000015D000081A4000000000000000000000001656CC35F000001A0000000000000000000000000000000000000002800000000wireplumber-0.4.17/subprojects/lua.wrap   [wrap-file]
directory = lua-5.4.4
source_url = https://www.lua.org/ftp/lua-5.4.4.tar.gz
source_filename = lua-5.4.4.tar.gz
source_hash = 164c7849653b80ae67bec4b7473b884bf5cc8d2dca05653475ec2ed27b9ebf61
patch_filename = lua_5.4.4-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.4-1/get_patch
patch_hash = e61cd965c629d6543176f41a9f1cb9050edfd1566cf00ce768ff211086e40bdc

[provide]
lua-5.4 = lua_dep

0707010000015E000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001900000000wireplumber-0.4.17/tests  0707010000015F000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002000000000wireplumber-0.4.17/tests/common   07070100000160000081A4000000000000000000000001656CC35F000013AB000000000000000000000000000000000000003400000000wireplumber-0.4.17/tests/common/base-test-fixture.h   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "test-server.h"
#include <wp/wp.h>

typedef enum {
  WP_BASE_TEST_FLAG_CLIENT_CORE = (1<<0),
  WP_BASE_TEST_FLAG_DONT_CONNECT = (1<<1),
} WpBaseTestFlags;

typedef struct {
  /* the local pipewire server */
  WpTestServer server;

  /* the main loop */
  GMainContext *context;
  GMainLoop *loop;

  /* watchdog */
  GSource *timeout_source;

  /* our session manager core */
  WpCore *core;

  /* the "client" core, which receives proxies
    (second client to our internal server) */
  WpCore *client_core;

} WpBaseTestFixture;

static gboolean
timeout_callback (WpBaseTestFixture * self)
{
  wp_message ("test timed out");
  g_test_fail ();
  g_main_loop_quit (self->loop);

  return G_SOURCE_REMOVE;
}

static void
disconnected_callback (WpCore *core, WpBaseTestFixture * self)
{
  wp_message_object (core, "%s core disconnected",
      (core == self->client_core) ? "client" : "sm");
  g_test_fail ();
  g_main_loop_quit (self->loop);
}

static void
wp_base_test_fixture_setup (WpBaseTestFixture * self, WpBaseTestFlags flags)
{
  g_autoptr (WpProperties) props = NULL;

  /* init test server */
  wp_test_server_setup (&self->server);

  /* init our main loop */
  self->context = g_main_context_new ();
  self->loop = g_main_loop_new (self->context, FALSE);
  g_main_context_push_thread_default (self->context);

  /* watchdog */
  self->timeout_source = g_timeout_source_new_seconds (8);
  g_source_set_callback (self->timeout_source, (GSourceFunc) timeout_callback,
      self, NULL);
  g_source_attach (self->timeout_source, self->context);

  /* init our core */
  props = wp_properties_new (PW_KEY_REMOTE_NAME, self->server.name, NULL);
  self->core = wp_core_new (self->context, wp_properties_ref (props));
  g_signal_connect (self->core, "disconnected",
      (GCallback) disconnected_callback, self);

  if (!(flags & WP_BASE_TEST_FLAG_DONT_CONNECT))
    g_assert_true (wp_core_connect (self->core));

  /* init the second client's core */
  if (flags & WP_BASE_TEST_FLAG_CLIENT_CORE) {
    self->client_core = wp_core_new (self->context, wp_properties_ref (props));
    g_signal_connect (self->client_core, "disconnected",
        (GCallback) disconnected_callback, self);

    if (!(flags & WP_BASE_TEST_FLAG_DONT_CONNECT))
      g_assert_true (wp_core_connect (self->client_core));
  }
}

static void
test_core_done_cb (WpCore *core, GAsyncResult *res, WpBaseTestFixture *self)
{
  g_autoptr (GError) error = NULL;
  g_assert_true (wp_core_sync_finish (core, res, &error));
  g_assert_null (error);
  g_main_loop_quit (self->loop);
}

static void
wp_base_test_fixture_teardown (WpBaseTestFixture * self)
{
  /* wait for all client core pending tasks to be done */
  if (self->client_core && wp_core_is_connected (self->client_core)) {
    wp_core_sync (self->client_core, NULL,
        (GAsyncReadyCallback) test_core_done_cb, self);
    g_main_loop_run (self->loop);
    g_signal_handlers_disconnect_by_data (self->client_core, self);
    wp_core_disconnect (self->client_core);
  }

  /* wait for all core pending tasks to be done */
  if (self->core && wp_core_is_connected (self->core)) {
    wp_core_sync (self->core, NULL, (GAsyncReadyCallback) test_core_done_cb,
        self);
    g_main_loop_run (self->loop);
    g_signal_handlers_disconnect_by_data (self->core, self);
    wp_core_disconnect (self->core);
  }

  /* double check and ensure that there is no event pending */
  while (g_main_context_pending (self->context))
    g_main_context_iteration (self->context, TRUE);

  g_main_context_pop_thread_default (self->context);
  g_clear_object (&self->client_core);
  g_clear_object (&self->core);
  g_clear_pointer (&self->timeout_source, g_source_unref);
  g_clear_pointer (&self->loop, g_main_loop_unref);
  g_clear_pointer (&self->context, g_main_context_unref);
  wp_test_server_teardown (&self->server);
}

static G_GNUC_UNUSED void
test_object_activate_finish_cb (WpObject * object, GAsyncResult * res,
    WpBaseTestFixture * f)
{
  g_autoptr (GError) error = NULL;
  gboolean augment_ret = wp_object_activate_finish (object, res, &error);
  g_assert_no_error (error);
  g_assert_true (augment_ret);

  g_main_loop_quit (f->loop);
}

static G_GNUC_UNUSED void
test_ensure_object_manager_is_installed (WpObjectManager * om, WpCore * core,
    GMainLoop * loop)
{
  gulong id = g_signal_connect_swapped (om, "installed",
      G_CALLBACK (g_main_loop_quit), loop);
  wp_core_install_object_manager (core, om);
  if (!wp_object_manager_is_installed (om))
    g_main_loop_run (loop);
  g_signal_handler_disconnect (om, id);
}

static G_GNUC_UNUSED gboolean
test_is_spa_lib_installed (WpBaseTestFixture *f, const gchar *factory_name) {
  struct spa_handle *handle;

  handle = pw_context_load_spa_handle (f->server.context, factory_name, NULL);
  if (!handle)
    return FALSE;

  pw_unload_spa_handle (handle);
  return TRUE;
}
 07070100000161000081A4000000000000000000000001656CC35F00000692000000000000000000000000000000000000002E00000000wireplumber-0.4.17/tests/common/test-server.h /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <pipewire/pipewire.h>
#include <pipewire/impl.h>
#include <glib.h>
#include <unistd.h>

typedef struct {
  gchar *name;
  struct pw_thread_loop *thread_loop;
  struct pw_context *context;
} WpTestServer;

static inline void
wp_test_server_setup (WpTestServer *self)
{
  struct pw_properties *properties;

  self->name = g_strdup_printf ("wp-test-server-%d-%d", getpid(),
      g_random_int ());
  properties = pw_properties_new(
      PW_KEY_CORE_DAEMON, "1",
      PW_KEY_CORE_NAME, self->name,
      NULL);

  self->thread_loop = pw_thread_loop_new ("wp-test-server", NULL);
  self->context = pw_context_new (pw_thread_loop_get_loop (self->thread_loop),
      properties, 0);

  pw_context_load_module (self->context, "libpipewire-module-access", NULL, NULL);

  pw_thread_loop_start (self->thread_loop);
}

static inline void
wp_test_server_teardown (WpTestServer *self)
{
  if (self->thread_loop)
    pw_thread_loop_stop (self->thread_loop);
  g_clear_pointer (&self->context, pw_context_destroy);
  g_clear_pointer (&self->thread_loop, pw_thread_loop_destroy);
  g_clear_pointer (&self->name, g_free);
}

typedef void WpTestServerLocker;

static inline WpTestServerLocker *
wp_test_server_locker_new (WpTestServer * self)
{
  pw_thread_loop_lock (self->thread_loop);
  return self;
}

static inline void
wp_test_server_locker_free (WpTestServerLocker * self)
{
  pw_thread_loop_unlock (((WpTestServer *)self)->thread_loop);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpTestServerLocker, wp_test_server_locker_free)
  07070100000162000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002200000000wireplumber-0.4.17/tests/examples 07070100000163000081A4000000000000000000000001656CC35F000030A9000000000000000000000000000000000000003600000000wireplumber-0.4.17/tests/examples/audiotestsrc-play.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

/*
 * This is a very simplistic session manager example that also runs an internal
 * PipeWire server for ease of use. The PipeWire server runs in its own thread
 * and our main thread's WpCore (the AppData.core) connects to it through
 * a socket, as if the PipeWire server was in a different process.
 *
 * This example starts 2 media nodes in the media graph: audiotestsrc & alsasink
 * Then, the session management part constructs endpoints for these nodes
 * and links them by creating an endpoint link.
 */

#include <wp/wp.h>
#include <glib-unix.h>
#include "../common/test-server.h"

#define APP_ERROR_DOMAIN (app_error_domain_quark ())
G_DEFINE_QUARK (app-error, app_error_domain)

typedef struct {
  /* our internal test PipeWire server */
  WpTestServer server;

  /* cmdline arguments */
  const gchar *alsa_device;

  /* our main loop and core */
  GMainContext *context;
  GMainLoop *loop;
  WpCore *core;
  WpSession *session;

  /* nodes provider data */
  WpNode *audiotestsrc;
  WpNode *alsasink;

  /* endpoints provider data */
  WpObjectManager *nodes_om;
  GPtrArray *session_items;

  /* policy manager data */
  GSource *interrupt_source;

} AppData;

/*
 * policy manager: link endpoints together
 */

static void
on_endpoints_changed (WpSession * session, AppData * d)
{
  g_autoptr (WpEndpoint) src = NULL;
  g_autoptr (WpEndpoint) sink = NULL;

  g_print ("Endpoints changed, n_endpoints=%u\n",
      wp_session_get_n_endpoints (session));

  /* a very simplistic lookup, since we don't expect any other endpoints
     to show up here, but this is the general idea...
     match endpoints, create links, cache the state and move forward */
  src = wp_session_lookup_endpoint (session,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", "=s", "Audio/Source", NULL);
  sink = wp_session_lookup_endpoint (session,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", "=s", "Audio/Sink", NULL);

  if (src) {
    g_print ("Got endpoint src: %s\n", wp_endpoint_get_name (src));
  }
  if (sink) {
    g_print ("Got endpoint sink: %s\n", wp_endpoint_get_name (sink));
  }

  if (src && sink) {
    g_autoptr (WpProperties) props = NULL;
    g_autofree gchar * id =
        g_strdup_printf ("%u", wp_proxy_get_bound_id (WP_PROXY (sink)));

    /* only the peer endpoint id is required when linking the default streams;
       everything else will be discovered */
    props = wp_properties_new ("endpoint-link.input.endpoint", id, NULL);
    wp_endpoint_create_link (src, props);
  }
}

static void
on_links_changed (WpSession * session, AppData * d)
{
  guint n_links = wp_session_get_n_links (session);

  /* activate the link - when endpoint links are created,
     they don't do anything unless they are activated first */
  if (n_links == 1) {
    /* lookup with no constraints will just return the first available object */
    g_autoptr (WpEndpointLink) link = wp_session_lookup_link (session, NULL);

    g_print ("Requesting link activation...\n");
    wp_endpoint_link_request_state (link, WP_ENDPOINT_LINK_STATE_ACTIVE);
  }
  else if (n_links == 0) {
    g_print ("Last endpoint link was destroyed; exiting...\n");
    g_main_loop_quit (d->loop);
  }
}

static gboolean
on_interrupted (AppData * d)
{
  g_print ("interrupted; let's try to destroy the link...\n");

  g_autoptr (WpEndpointLink) link = wp_session_lookup_link (d->session, NULL);
  if (link)
    wp_global_proxy_request_destroy (WP_GLOBAL_PROXY (link));

  /* remove the interrupt handler so that we can actually
     interrupt if things get stuck */
  g_clear_pointer (&d->interrupt_source, g_source_unref);
  return G_SOURCE_REMOVE;
}

static void
start_policy_manager (AppData * d)
{
  /* reuse the session pointer that we already have in AppData;
     under other circumstances, we would retrieve the session
     with a WpObjectManager */
  g_signal_connect (d->session, "endpoints-changed",
      G_CALLBACK (on_endpoints_changed), d);
  g_signal_connect (d->session, "links-changed",
      G_CALLBACK (on_links_changed), d);

  d->interrupt_source = g_unix_signal_source_new (SIGINT);
  g_source_set_callback (d->interrupt_source,
      G_SOURCE_FUNC (on_interrupted), d, NULL);
  g_source_attach (d->interrupt_source, d->context);
}

/*
 * endpoints provider: creates endpoints for the discovered nodes
 */

static void
on_si_activated (WpObject * object, GAsyncResult * res, AppData * d)
{
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (object, res, &error)) {
    g_printerr ("Failed to activate session item: %s\n", error->message);
    g_main_loop_quit (d->loop);
    return;
  }

  g_print ("Item " WP_OBJECT_FORMAT " exported\n", WP_OBJECT_ARGS (object));
}

static void
on_node_added (WpObjectManager * om, WpNode *node, AppData * d)
{
  g_autoptr (WpSessionItem) item = NULL;
  g_autoptr (WpProperties) props = wp_properties_new_empty ();

  g_print ("Node " WP_OBJECT_FORMAT " added, creating session item\n",
      WP_OBJECT_ARGS (node));

  /* load the "si-audio-adapter" Session Item */
  item = wp_session_item_make (d->core, "si-audio-adapter");

  /* and configure it */
  wp_properties_setf (props, "node", "%p", node);
  wp_properties_setf (props, "session", "%p", d->session);
  if (!wp_session_item_configure (item, g_steal_pointer (&props))) {
    g_printerr ("Failed to configure session item\n");
    g_main_loop_quit (d->loop);
    return;
  }

  wp_object_activate (WP_OBJECT (item),
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED,
      NULL, (GAsyncReadyCallback) on_si_activated, d);
  g_ptr_array_add (d->session_items, g_steal_pointer (&item));
}

static void
start_endpoints_provider (AppData * d)
{
  g_print ("Installing watch for nodes...\n");

  /* register a WpObjectManager to listen for available nodes */
  /* for example purposes, we pretend we don't have access to the data set by
     start_nodes_provider(), i.e. d->audiotestsrc & d->alsasink */
  d->nodes_om = wp_object_manager_new ();
  wp_object_manager_add_interest (d->nodes_om, WP_TYPE_NODE, NULL);
  wp_object_manager_request_object_features (d->nodes_om, WP_TYPE_NODE,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);

  d->session_items = g_ptr_array_new_with_free_func (g_object_unref);

  /* the object manager will emit 'object-added' for every node that is
     made available, once the node has all the features we requested above */
  g_signal_connect (d->nodes_om, "object-added", G_CALLBACK (on_node_added), d);
  wp_core_install_object_manager (d->core, d->nodes_om);
}

/*
 * nodes provider: creates the nodes
 */

static void
on_node_ready (WpObject * node, GAsyncResult * res, AppData * d)
{
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (node, res, &error)) {
    g_printerr ("Failed to prepare node: %s\n", error->message);
    g_main_loop_quit (d->loop);
    return;
  }

  g_print ("Node " WP_OBJECT_FORMAT " is ready\n", WP_OBJECT_ARGS (node));
}

static void
start_nodes_provider (AppData * d)
{
  g_print ("Creating nodes...\n");

  d->audiotestsrc = wp_node_new_from_factory (d->core,
      "adapter", /* the pipewire factory name */
      wp_properties_new (
          /* the spa factory name */
          "factory.name", "audiotestsrc",
          /* a friendly name for our node */
          "node.name", "audiotestsrc",
          NULL));
  g_assert (d->audiotestsrc);
  wp_object_activate (WP_OBJECT (d->audiotestsrc),
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL, NULL,
      (GAsyncReadyCallback) on_node_ready, d);

  d->alsasink = wp_node_new_from_factory (d->core,
      "adapter", /* the pipewire factory name */
      wp_properties_new (
          /* the spa factory name */
          "factory.name", "api.alsa.pcm.sink",
          /* a friendly name for our node */
          "node.name", "alsasink",
          /* set the device handle (ex. hw:0,0) on the sink */
          "api.alsa.path", d->alsa_device,
          NULL));
  g_assert (d->alsasink);
  wp_object_activate (WP_OBJECT (d->alsasink),
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL, NULL,
      (GAsyncReadyCallback) on_node_ready, d);
}

/*
 * main application: loads modules and the session
 */

static void
on_session_ready (WpObject * session, GAsyncResult * res, AppData * d)
{
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (session, res, &error)) {
    g_printerr ("Failed to prepare session: %s\n", error->message);
    g_main_loop_quit (d->loop);
    return;
  }

  g_print ("Session is ready, starting components...\n");

  start_nodes_provider (d);
  start_endpoints_provider (d);
  start_policy_manager (d);
}

static gboolean
appdata_init (AppData * d, GError ** error)
{
  WpImplSession *session;

  /* setup the internal test PipeWire server */
  wp_test_server_setup (&d->server);
  {
    /* load server modules (pipewire.conf) */
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&d->server);

    pw_context_add_spa_lib (d->server.context,
        "audiotestsrc", "audiotestsrc/libspa-audiotestsrc");
    pw_context_add_spa_lib (d->server.context,
        "api.alsa.*", "alsa/libspa-alsa");

    if (!pw_context_load_module (d->server.context,
            "libpipewire-module-spa-node-factory", NULL, NULL)) {
        g_set_error (error, APP_ERROR_DOMAIN, 0,
            "Failed to load libpipewire-module-spa-node-factory");
        return FALSE;
    }
    if (!pw_context_load_module (d->server.context,
            "libpipewire-module-link-factory", NULL, NULL)) {
        g_set_error (error, APP_ERROR_DOMAIN, 0,
            "Failed to load libpipewire-module-link-factory");
        return FALSE;
    }
    /* adapter is loaded by pw_context */
  }

  /* init our main loop */
  d->context = g_main_context_new ();
  d->loop = g_main_loop_new (d->context, FALSE);

  /* push the context as the thread default for GTask to work with it,
     otherwise it will try to use the "default" main context, which we are
     not using in our main loop, for demonstration purposes */
  g_main_context_push_thread_default (d->context);

  /* init our core; the "remote.name" key tells it to connect to our
     test server instead of the default "pipewire-0" */
  d->core = wp_core_new (d->context, wp_properties_new (
          "remote.name", d->server.name,
          NULL));

  /* load wireplumber modules (wireplumber.conf) */
  if (!(wp_core_load_component (d->core,
          "libwireplumber-module-si-node", "module", NULL, error)))
    return FALSE;

  if (!(wp_core_load_component (d->core,
          "libwireplumber-module-si-audio-adapter", "module", NULL, error)))
    return FALSE;

  if (!(wp_core_load_component (d->core,
          "libwireplumber-module-si-standard-link", "module", NULL, error)))
    return FALSE;

  /* connect */
  if (!wp_core_connect (d->core)) {
    g_set_error (error, APP_ERROR_DOMAIN, 0,
        "Failed to connect to the test server");
    return FALSE;
  }

  g_print ("Creating session...\n");

  /* create a session */
  d->session = WP_SESSION (session = wp_impl_session_new (d->core));
  wp_impl_session_set_property (session, "session.name", "audio");
  wp_object_activate (WP_OBJECT (session), WP_OBJECT_FEATURES_ALL, NULL,
      (GAsyncReadyCallback) on_session_ready, d);
  return TRUE;
}

static void
appdata_clear (AppData * d)
{
  /* policy manager data */
  g_clear_pointer (&d->interrupt_source, g_source_unref);

  /* endpoints provider data */
  g_clear_pointer (&d->session_items, g_ptr_array_unref);
  g_clear_object (&d->nodes_om);

  /* nodes provider data */
  g_clear_object (&d->audiotestsrc);
  g_clear_object (&d->alsasink);

  /* main app data */
  g_clear_object (&d->session);
  g_clear_object (&d->core);
  g_clear_pointer (&d->loop, g_main_loop_unref);
  g_clear_pointer (&d->context, g_main_context_unref);
  wp_test_server_teardown (&d->server);
}

G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (AppData, appdata_clear)

gint
main (gint argc, gchar *argv[])
{
  g_auto (AppData) data = {0};
  g_autoptr (GError) error = NULL;

  wp_init (WP_INIT_ALL);

  if (argc > 1)
    data.alsa_device = argv[1];
  else
    data.alsa_device = "hw:0,0";

  if (!appdata_init (&data, &error)) {
    g_printerr ("Initialization failed:\n  %s\n", error->message);
    return 1;
  }

  g_main_loop_run (data.loop);
  return 0;
}
   07070100000164000081ED000000000000000000000001656CC35F00000C20000000000000000000000000000000000000003300000000wireplumber-0.4.17/tests/examples/bt-pinephone.lua    #!/usr/bin/wpexec
--
-- WirePlumber
--
-- Copyright © 2022 Collabora Ltd.
--    @author Frédéric Danis <frederic.danis@collabora.com>
--
-- SPDX-License-Identifier: MIT
--
-- This is an example of the offload SCO nodes platform specific management,
-- in this case for the PinePhone.
--
-- The PinePhone provides specific ALSA ports to route audio to the Bluetooth
-- chipset. This script selects these ports when the offload SCO nodes state
-- change to 'running'.
--
-- This scriptcan be executed as a standalone executable, or it can be placed
-- in WirePlumber's scripts directory and loaded together with other scripts.
-----------------------------------------------------------------------------

devices_om = ObjectManager {
  Interest {
    type = "device",
  }
}

nodes_om = ObjectManager {
  Interest {
    type = "node",
    Constraint { "node.name", "#", "*.bluez_*put*"},
    Constraint { "device.id", "+" },
  }
}

function parseParam(param, id)
  local route = param:parse()
  if route.pod_type == "Object" and route.object_id == id then
    return route.properties
  else
    return nil
  end
end

function setPlatformRoute (route_name, direction)
  local platform_sound = nil
  local device_id = nil
  local route_save = nil

  local interest = Interest {
    type = "device",
    Constraint { "device.name", "=", "alsa_card.platform-sound"}
  }
  for d in devices_om:iterate (interest) do
    platform_sound = d

    for p in platform_sound:iterate_params("Route") do
      route = parseParam(p, "Route")
      if route.direction == direction then
        device_id = route.device
        route_save = route.save
      end
    end

    for p in platform_sound:iterate_params("EnumRoute") do
      enum_route = parseParam(p, "EnumRoute")

      if enum_route.name == route_name then
        route_index = enum_route.index
      end
    end
  end

  if route.index == route_index then
    return
  end

  -- default props
  local props = {
    "Spa:Pod:Object:Param:Props", "Route",
    mute = false,
  }

  -- construct Route param
  local param = Pod.Object {
    "Spa:Pod:Object:Param:Route", "Route",
    index = route_index,
    device = device_id,
    props = Pod.Object(props),
    save = route_save,
  }

  Log.info(param, "setting route on " .. tostring(platform_sound))
  platform_sound:set_param("Route", param)

  route.prev_active = true
  route.active = true
end

nodes_om:connect("object-added", function(_, node)
  node:connect("state-changed", function(node, old_state, cur_state)
    local interest = Interest {
      type = "device",
      Constraint { "object.id", "=", node.properties["device.id"]}
    }
    for d in devices_om:iterate (interest) do
      if cur_state == "running" then
        -- Both direction should be set to allow audio streaming
        setPlatformRoute("[Out] BluetoothHeadset", "Output")
        setPlatformRoute("[In] BluetoothHeadset", "Input")
      else
        setPlatformRoute("[Out] Earpiece", "Output")
        setPlatformRoute("[In] DigitalMic", "Input")
      end
    end
  end)
end)

nodes_om:activate()
devices_om:activate()
07070100000165000081ED000000000000000000000001656CC35F00000939000000000000000000000000000000000000003800000000wireplumber-0.4.17/tests/examples/bt-profile-switch.lua   #!/usr/bin/wpexec
--
-- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT
--
-- This is an example of a standalone policy making script. It can be executed
-- either on top of another instance of wireplumber or pipewire-media-session,
-- as a standalone executable, or it can be placed in WirePlumber's scripts
-- directory and loaded together with other scripts.
--
-- The script basically watches for a client application called
-- "ZOOM VoiceEngine", and when it appears (i.e. Zoom starts), it switches
-- the profile of all connected bluetooth devices to the "headset-head-unit"
-- (a.k.a HSP Headset Audio) profile. When Zoom exits, it switches again the
-- profile of all bluetooth devices to A2DP Sink.
--
-- The script can be customized further to look for other clients and/or
-- change the profile of a specific device, by customizing the constraints.
-----------------------------------------------------------------------------

devices_om = ObjectManager {
  Interest { type = "device",
    Constraint { "device.api", "=", "bluez5" },
  }
}

clients_om = ObjectManager {
  Interest { type = "client",
    Constraint { "application.name", "=", "ZOOM VoiceEngine" },
  }
}

function set_profile(profile_name)
  for device in devices_om:iterate() do
    local index = nil
    local desc = nil

    for profile in device:iterate_params("EnumProfile") do
      local p = profile:parse()
      if p.properties.name == profile_name then
        index = p.properties.index
        desc = p.properties.description
        break
      end
    end

    if index then
      local pod = Pod.Object {
        "Spa:Pod:Object:Param:Profile", "Profile",
        index = index
      }

      print("Setting profile of '"
            .. device.properties["device.description"]
            .. "' to: " .. desc)
      device:set_params("Profile", pod)
    end
  end
end

clients_om:connect("object-added", function (om, client)
  print("Client '" .. client.properties["application.name"] .. "' connected")
  set_profile("headset-head-unit")
end)

clients_om:connect("object-removed", function (om, client)
  print("Client '" .. client.properties["application.name"] .. "' disconnected")
  set_profile("a2dp-sink")
end)

devices_om:activate()
clients_om:activate()
   07070100000166000081A4000000000000000000000001656CC35F00000BC3000000000000000000000000000000000000003300000000wireplumber-0.4.17/tests/examples/filter-chain.lua    #!/usr/bin/wpexec
--
-- WirePlumber
--
-- Copyright © 2022 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT
--
-- This is an example of how to load a pipewire module that takes a JSON
-- string as arguments.
--
-- For illustration purposes, this loads module-filter-chain with a 6-band
-- equalizer. This is the same configuration found in the sink-eq6.conf
-- file that can be found in pipewire's source tree.
-----------------------------------------------------------------------------

-- For illustration purposes, we declare 'args' here as a native lua table,
-- we populate it in multiple steps and we transform it later to a json object
local args = {
  ["node.description"] = "Equalizer Sink",
  ["media.name"] = "Equalizer Sink",
  ["audio.channels"] = 2,
  ["audio.position"] = Json.Array { "FL", "FR" },
}

args["filter.graph"] = Json.Object {
  nodes = Json.Array {
    Json.Object {
        type  = "builtin",
        name  = "eq_band_1",
        label = "bq_lowshelf",
        control = Json.Object { Freq = 100.0, Q = 1.0, Gain = 0.0 },
    },
    Json.Object {
        type  = "builtin",
        name  = "eq_band_2",
        label = "bq_peaking",
        control = Json.Object { Freq = 100.0, Q = 1.0, Gain = 0.0 },
    },
    Json.Object {
        type  = "builtin",
        name  = "eq_band_3",
        label = "bq_peaking",
        control = Json.Object { Freq = 500.0, Q = 1.0, Gain = 0.0 },
    },
    Json.Object {
        type  = "builtin",
        name  = "eq_band_4",
        label = "bq_peaking",
        control = Json.Object { Freq = 2000.0, Q = 1.0, Gain = 0.0 },
    },
    Json.Object {
        type  = "builtin",
        name  = "eq_band_5",
        label = "bq_peaking",
        control = Json.Object { Freq = 5000.0, Q = 1.0, Gain = 0.0 },
    },
    Json.Object {
        type  = "builtin",
        name  = "eq_band_6",
        label = "bq_highshelf",
        control = Json.Object { Freq = 5000.0, Q = 1.0, Gain = 0.0 },
    },
  },
  links = Json.Array {
    Json.Object { output = "eq_band_1:Out", input = "eq_band_2:In" },
    Json.Object { output = "eq_band_2:Out", input = "eq_band_3:In" },
    Json.Object { output = "eq_band_3:Out", input = "eq_band_4:In" },
    Json.Object { output = "eq_band_4:Out", input = "eq_band_5:In" },
    Json.Object { output = "eq_band_5:Out", input = "eq_band_6:In" },
  },
}

args["capture.props"] = Json.Object {
  ["node.name"]   = "effect_input.eq6",
  ["media.class"] = "Audio/Sink",
}

args["playback.props"] = Json.Object {
  ["node.name"]   = "effect_output.eq6",
  ["node.passive"] = true,
}

-- Transform 'args' to a json object here
local args_json = Json.Object(args)

-- and get the final JSON as a string from the json object
local args_string = args_json:get_data()

local properties = {}

print("Loading module-filter-chain with arguments = ")
print(args_string)

filter_chain = LocalModule("libpipewire-module-filter-chain", args_string, properties)
 07070100000167000081A4000000000000000000000001656CC35F000002B3000000000000000000000000000000000000003E00000000wireplumber-0.4.17/tests/examples/get-default-sink-volume.lua #!/usr/bin/wpexec
--
-- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT
--

-- Load the necessary wireplumber api modules
Core.require_api("default-nodes", "mixer", function(...)
  local default_nodes, mixer = ...

  -- configure volumes to be printed in the cubic scale
  -- this is also what the pulseaudio API shows
  mixer.scale = "cubic"

  local id = default_nodes:call("get-default-node", "Audio/Sink")
  local volume = mixer:call("get-volume", id)

  -- dump everything
  Debug.dump_table(volume)

  -- or maybe just the volume...
  -- print(volume.volume)

  Core.quit()
end)
 07070100000168000081ED000000000000000000000001656CC35F00000FA5000000000000000000000000000000000000003200000000wireplumber-0.4.17/tests/examples/interactive.lua #!/usr/bin/wpexec
--
-- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT
--
-- This is an example of an interactive script
--
-- Execute with:
--   wpexec ./interactive.lua option1=value1 option2=value2 ...
-- or:
--   ./interactive.lua option1=value1 option2=value2
-----------------------------------------------------------------------------

--
-- Collects arguments passed in from the command line
-- Assuming option1=value1 option2=value2 were passed, this will be a table
-- like this: { ["option1"] = "value1", ["option2"] = "value2" }
--
local argv = ...

print ("Command-line arguments:")
for k, v in pairs(argv) do
  print ("\t" .. k .. ": " .. v)
end

--
-- Retrieve remote core info
--
local info = Core.get_info()

print ("\nPipeWire daemon info:")
for k, v in pairs(info) do
  if k == "properties" then
    print ("\tproperties:")
    for kk, vv in pairs(v) do
      print ("\t\t" .. kk .. ": " .. vv)
    end
  else
    print ("\t" .. k .. ": " .. v)
  end
end

--
-- Retrieve objects using an ObjectManager
--
-- Note: obj_mgr here cannot be a local variable; we need it to stay alive
-- after the execution has returned to the main loop
--
obj_mgr = ObjectManager {
  Interest { type = "client" },
  Interest { type = "device" },
  Interest { type = "node" },
}

-- Listen for the 'installed' signal from the ObjectManager
-- and execute a function when it is fired
-- This function will be called from the main loop at some point later
obj_mgr:connect("installed", function (om)

  --
  -- Print connected clients
  --
  print ("\nClients:")
  for obj in om:iterate { type = "client" } do

    --
    -- 'bound-id' and 'global-properties' are GObject
    -- properties of WpProxy / WpGlobalProxy
    --
    local id = obj["bound-id"]
    local global_props = obj["global-properties"]

    print ("\t" .. id .. ": " .. global_props["application.name"]
                .. " (PID: " .. global_props["pipewire.sec.pid"] .. ")")
  end

  --
  -- Print devices
  --
  print ("\nDevices:")
  for obj in om:iterate { type = "device" } do
    local id = obj["bound-id"]
    local global_props = obj["global-properties"]

    print ("\t" .. id .. ": " .. global_props["device.name"]
                .. " (" .. global_props["device.description"] .. ")")
  end

  -- Common function to print nodes
  local function printNode(node)
    local id = node["bound-id"]
    local global_props = node["global-properties"]

    -- standard lua string.match() function used here
    if global_props["media.class"]:match("Stream/.*") then
      print ("\t" .. id .. ": " .. global_props["application.name"])
    else
      print ("\t" .. id .. ": " .. global_props["object.path"]
                .. " (" .. global_props["node.description"] .. ")")
    end
  end

  --
  -- Print device sinks
  --
  print ("\nSinks:")
  --
  -- Interest can have additional constraints that can be used to filter
  -- the results. In this case we are only interested in nodes with their
  -- "media.class" global property matching the glob expression "*/Sink"
  --
  local interest = Interest { type = "node",
    Constraint { "media.class", "matches", "*/Sink" }
  }
  for obj in om:iterate(interest) do
    printNode(obj)
  end

  --
  -- Print device sources
  --
  print ("\nSources:")
  local interest = Interest { type = "node",
    Constraint { "media.class", "matches", "*/Source" }
  }
  for obj in om:iterate(interest) do
    printNode(obj)
  end

  --
  -- Print client streams
  --
  print ("\nStreams:")
  local interest = Interest { type = "node",
    Constraint { "media.class", "matches", "Stream/*" }
  }
  for obj in om:iterate(interest) do
    printNode(obj)
  end

  -- Disconnect from pipewire and quit wireplumber
  -- This only works in script interactive mode
  Core.quit()
end)

-- Activate the object manager. This is required to start it,
-- otherwise it doesn't do anything
obj_mgr:activate()
   07070100000169000081A4000000000000000000000001656CC35F00000118000000000000000000000000000000000000002E00000000wireplumber-0.4.17/tests/examples/meson.build # disabled temporarily
if false
executable('audiotestsrc-play',
  'audiotestsrc-play.c',
  c_args : [
    '-D_GNU_SOURCE',
    '-DG_LOG_USE_STRUCTURED',
    '-DG_LOG_DOMAIN="audiotestsrc-play"',
  ],
  install: false,
  dependencies : [giounix_dep, wp_dep, pipewire_dep],
)
endif
0707010000016A000081A4000000000000000000000001656CC35F000007FF000000000000000000000000000000000000002500000000wireplumber-0.4.17/tests/meson.build  valgrind = find_program('valgrind', required: false)
if valgrind.found()

  glib_supp = get_option('glib-supp')
  if glib_supp == ''
    glib_supp = glib_dep.get_variable(pkgconfig: 'prefix')
    glib_supp = glib_supp / 'share' / 'glib-2.0' / 'valgrind' / 'glib.supp'
  endif
  if fs.is_file(glib_supp)
    message('Using glib.supp:', glib_supp)
  else
    message('glib.supp not found, valgrind tests will not work correctly')
  endif

  valgrind_env = environment({
    'G_SLICE': 'always-malloc',
  })

  add_test_setup('valgrind',
    exe_wrapper: [ valgrind,
      '--suppressions=' + glib_supp,
      '--leak-check=full',
      '--gen-suppressions=all',
      '--error-exitcode=3',
      '--keep-debuginfo=yes',
    ],
    env: valgrind_env,
    timeout_multiplier: 2)
endif

# The common test environment
common_test_env = environment({
  'HOME': '/invalid',
  'XDG_RUNTIME_DIR': '/invalid',
  'PIPEWIRE_RUNTIME_DIR': '/tmp',
  'XDG_CONFIG_HOME': meson.current_build_dir() / '.config',
  'XDG_STATE_HOME': meson.current_build_dir() / '.local' / 'state',
  'FILE_MONITOR_DIR': meson.current_build_dir() / '.local' / 'file_monitor',
  'WIREPLUMBER_CONFIG_DIR': '/invalid',
  'WIREPLUMBER_DATA_DIR': '/invalid',
  'WIREPLUMBER_MODULE_DIR': meson.current_build_dir() / '..' / 'modules',
  'WIREPLUMBER_DEBUG': '7',
})

spa_plugindir = spa_dep.get_variable(
  pkgconfig: 'plugindir', internal: 'plugindir', default_value: '')
pipewire_moduledir = pipewire_dep.get_variable(
  pkgconfig: 'moduledir', internal: 'moduledir', default_value: '')
pipewire_confdatadir = pipewire_dep.get_variable(
  pkgconfig: 'confdatadir', internal: 'confdatadir', default_value: '')

if spa_plugindir != ''
  common_test_env.set('SPA_PLUGIN_DIR', spa_plugindir)
endif
if pipewire_moduledir != ''
  common_test_env.set('PIPEWIRE_MODULE_DIR', pipewire_moduledir)
endif
if pipewire_confdatadir != ''
  common_test_env.set('PIPEWIRE_CONFIG_DIR', pipewire_confdatadir)
endif

subdir('wp')
if build_modules
  subdir('wplua')
  subdir('modules')
endif
subdir('examples')
 0707010000016B000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002100000000wireplumber-0.4.17/tests/modules  0707010000016C000081A4000000000000000000000001656CC35F00000C3C000000000000000000000000000000000000003000000000wireplumber-0.4.17/tests/modules/file-monitor.c   /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <fcntl.h>
#include <sys/inotify.h>

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;
  WpPlugin *plugin;
  gchar *path;
  gchar *file;
  gchar *evtype;
} TestFixture;

static void
test_file_monitor_setup (TestFixture * f, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&f->base, WP_BASE_TEST_FLAG_DONT_CONNECT);
  g_autoptr (GError) error = NULL;

  wp_core_load_component (f->base.core,
      "libwireplumber-module-file-monitor-api", "module", NULL, &error);
  g_assert_no_error (error);

  f->plugin = wp_plugin_find (f->base.core, "file-monitor-api");
  g_assert_nonnull (f->plugin);

  f->path = g_strdup (g_getenv ("FILE_MONITOR_DIR"));
  g_mkdir_with_parents (f->path, 0700);
}

static void
test_file_monitor_teardown (TestFixture * f, gconstpointer user_data)
{
  g_clear_pointer (&f->evtype, g_free);
  g_clear_pointer (&f->file, g_free);
  g_clear_pointer (&f->path, g_free);
  g_clear_object (&f->plugin);
  wp_base_test_fixture_teardown (&f->base);
}

static void
on_plugin_activated (WpObject * plugin, GAsyncResult * res, TestFixture * f)
{
  g_autoptr (GError) error = NULL;
  if (!wp_object_activate_finish (plugin, res, &error)) {
    wp_critical_object (plugin, "%s", error->message);
    g_main_loop_quit (f->base.loop);
  }
}

static void
on_changed (WpPlugin *plugin, const gchar *file, const gchar *old,
    const char *evtype, TestFixture * f)
{
  g_assert_nonnull (file);
  g_assert_nonnull (evtype);
  f->file = g_strdup (file);
  f->evtype = g_strdup (evtype);
  g_main_loop_quit (f->base.loop);
}

static void
test_file_monitor_basic (TestFixture * f, gconstpointer user_data)
{
  gboolean res = FALSE;

  /* activate plugin */
  g_assert_nonnull (f->plugin);
  wp_object_activate (WP_OBJECT (f->plugin), WP_PLUGIN_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) on_plugin_activated, f);

  /* delete the 'foo' file if it exists in path */
  g_autofree gchar *filename = g_build_filename (f->path, "foo", NULL);
  (void) remove (filename);

  /* handle changed signal */
  f->file = NULL;
  f->evtype = NULL;
  g_signal_connect (f->plugin, "changed", G_CALLBACK (on_changed), f);

  /* add watch */
  g_signal_emit_by_name (f->plugin, "add-watch", f->path, "m", &res);
  g_assert_true (res);

  /* create the foo file in path */
  int fd = open (filename, O_CREAT | O_EXCL, 0700);
  g_assert_cmpint (fd, >=, 0);

  /* run */
  g_main_loop_run (f->base.loop);
  g_assert_cmpstr (f->file, ==, filename);
  g_assert_cmpstr (f->evtype, ==, "created");

  /* removed watch */
  g_signal_emit_by_name (f->plugin, "remove-watch", f->path);

  /* remove 'foo' */
  close (fd);
  (void) remove (filename);
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/modules/file-monitor/basic",
      TestFixture, NULL,
      test_file_monitor_setup,
      test_file_monitor_basic,
      test_file_monitor_teardown);

  return g_test_run ();
}
0707010000016D000081A4000000000000000000000001656CC35F0000050E000000000000000000000000000000000000002D00000000wireplumber-0.4.17/tests/modules/meson.build  common_deps = [gobject_dep, gio_dep, wp_dep, pipewire_dep]
common_env = common_test_env
common_env.set('G_TEST_SRCDIR', meson.current_source_dir())
common_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
common_args = [
  '-D_GNU_SOURCE',
  '-DG_LOG_USE_STRUCTURED',
]

if get_option('dbus-tests')
  test(
    'test-reserve-device',
    executable('test-reserve-device', 'reserve-device.c',
      dependencies: common_deps, c_args: common_args),
    env: common_env,
  )
endif

test(
  'test-file-monitor',
  executable('test-file-monitor', 'file-monitor.c',
    dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-si-node',
  executable('test-si-node', 'si-node.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-si-audio-adapter',
  executable('test-si-audio-adapter', 'si-audio-adapter.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-si-audio-endpoint',
  executable('test-si-audio-endpoint', 'si-audio-endpoint.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-si-standard-link',
  executable('test-si-standard-link', 'si-standard-link.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)
  0707010000016E000081A4000000000000000000000001656CC35F000031CE000000000000000000000000000000000000003200000000wireplumber-0.4.17/tests/modules/reserve-device.c /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;
  GTestDBus *test_dbus;
  WpPlugin *rd_plugin_1;
  WpPlugin *rd_plugin_2;
  WpDbus *dbus_1;
  WpDbus *dbus_2;
  gint expected_rd1_state;
  gint expected_rd2_state;
} RdTestFixture;

static void
test_rd_setup (RdTestFixture *f, gconstpointer data)
{
  wp_base_test_fixture_setup (&f->base,
      WP_BASE_TEST_FLAG_CLIENT_CORE | WP_BASE_TEST_FLAG_DONT_CONNECT);

  f->test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
  g_test_dbus_up (f->test_dbus);

  {
    g_autoptr (GError) error = NULL;
    wp_core_load_component (f->base.core,
        "libwireplumber-module-reserve-device", "module", NULL, &error);
    g_assert_no_error (error);
  }
  {
    g_autoptr (GError) error = NULL;
    wp_core_load_component (f->base.client_core,
        "libwireplumber-module-reserve-device", "module", NULL, &error);
    g_assert_no_error (error);
  }

  f->rd_plugin_1 = wp_plugin_find (f->base.core, "reserve-device");
  g_assert_nonnull (f->rd_plugin_1);

  f->rd_plugin_2 = wp_plugin_find (f->base.client_core, "reserve-device");
  g_assert_nonnull (f->rd_plugin_2);

  g_signal_emit_by_name (f->rd_plugin_1, "get-dbus", &f->dbus_1);
  g_assert_nonnull (f->dbus_1);

  g_signal_emit_by_name (f->rd_plugin_2, "get-dbus", &f->dbus_2);
  g_assert_nonnull (f->dbus_2);
}

static void
test_rd_teardown (RdTestFixture *f, gconstpointer data)
{
  g_clear_object (&f->dbus_1);
  g_clear_object (&f->dbus_2);
  g_clear_object (&f->rd_plugin_1);
  g_clear_object (&f->rd_plugin_2);
  g_test_dbus_down (f->test_dbus);
  g_clear_object (&f->test_dbus);
  wp_base_test_fixture_teardown (&f->base);
}

static void
on_plugin_activated (WpObject * plugin, GAsyncResult * res, RdTestFixture * f)
{
  g_autoptr (GError) error = NULL;
  if (!wp_object_activate_finish (plugin, res, &error)) {
    wp_critical_object (plugin, "%s", error->message);
    g_main_loop_quit (f->base.loop);
  }
}

static void
ensure_plugins_stable_state (GObject * obj, GParamSpec * spec, RdTestFixture *f)
{
  gint state1 = 0, state2 = 0;
  g_object_get (f->dbus_1, "state", &state1, NULL);
  g_object_get (f->dbus_2, "state", &state2, NULL);
  if (state1 != 1 && state2 != 1 && state1 == state2)
    g_main_loop_quit (f->base.loop);
}

static void
test_rd_plugin (RdTestFixture *f, gconstpointer data)
{
  GObject *rd1 = NULL, *rd2 = NULL, *rd_video = NULL, *tmp = NULL;
  gint state = 0xffff;
  gchar *str;

  g_object_get (f->dbus_1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);
  g_object_get (f->dbus_2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);

  wp_object_activate (WP_OBJECT (f->rd_plugin_1), WP_PLUGIN_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) on_plugin_activated, f);
  wp_object_activate (WP_OBJECT (f->rd_plugin_2), WP_PLUGIN_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) on_plugin_activated, f);

  g_signal_connect (f->dbus_1, "notify::state",
      G_CALLBACK (ensure_plugins_stable_state), f);
  g_signal_connect (f->dbus_2, "notify::state",
      G_CALLBACK (ensure_plugins_stable_state), f);
  g_main_loop_run (f->base.loop);

  g_object_get (f->dbus_1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);
  g_object_get (f->dbus_2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);

  g_signal_emit_by_name (f->rd_plugin_1, "create-reservation",
      "Audio0", "WirePlumber", "hw:0,0", 10, &rd1);
  g_assert_nonnull (rd1);
  g_signal_emit_by_name (f->rd_plugin_2, "create-reservation",
      "Audio0", "Other Server", "hw:0,0", 15, &rd2);
  g_assert_nonnull (rd2);
  g_signal_emit_by_name (f->rd_plugin_1, "create-reservation",
      "Video0", "WirePlumber", "/dev/video0", 10, &rd_video);
  g_assert_nonnull (rd_video);

  g_signal_emit_by_name (f->rd_plugin_1, "get-reservation", "Video1", &tmp);
  g_assert_null (tmp);
  g_signal_emit_by_name (f->rd_plugin_2, "get-reservation", "Video0", &tmp);
  g_assert_null (tmp);

  g_signal_emit_by_name (f->rd_plugin_1, "get-reservation", "Audio0", &tmp);
  g_assert_nonnull (tmp);
  g_assert_true (tmp == rd1);
  g_clear_object (&tmp);

  g_object_get (rd1, "name", &str, NULL);
  g_assert_cmpstr (str, ==, "Audio0");
  g_free (str);
  g_object_get (rd2, "name", &str, NULL);
  g_assert_cmpstr (str, ==, "Audio0");
  g_free (str);
  g_object_get (rd_video, "name", &str, NULL);
  g_assert_cmpstr (str, ==, "Video0");
  g_free (str);
  g_object_get (rd1, "application-name", &str, NULL);
  g_assert_cmpstr (str, ==, "WirePlumber");
  g_free (str);
  g_object_get (rd1, "application-device-name", &str, NULL);
  g_assert_cmpstr (str, ==, "hw:0,0");
  g_free (str);
  g_object_get (rd1, "priority", &state, NULL);
  g_assert_cmpint (state, ==, 10);
  g_object_get (rd2, "priority", &state, NULL);
  g_assert_cmpint (state, ==, 15);

  g_signal_emit_by_name (f->rd_plugin_1, "destroy-reservation", "Audio0");
  g_signal_emit_by_name (f->rd_plugin_1, "get-reservation", "Audio0", &tmp);
  g_assert_null (tmp);
  g_signal_emit_by_name (f->rd_plugin_2, "get-reservation", "Audio0", &tmp);
  g_assert_nonnull (tmp);
  g_assert_true (tmp == rd2);
  g_clear_object (&tmp);
  g_clear_object (&rd2);
  g_clear_object (&rd1);
  g_clear_object (&rd_video);

  wp_object_deactivate (WP_OBJECT (f->rd_plugin_1), WP_PLUGIN_FEATURE_ENABLED);
  wp_object_deactivate (WP_OBJECT (f->rd_plugin_2), WP_PLUGIN_FEATURE_ENABLED);

  wp_object_deactivate (WP_OBJECT (f->dbus_1), WP_DBUS_FEATURE_ENABLED);
  wp_object_deactivate (WP_OBJECT (f->dbus_2), WP_DBUS_FEATURE_ENABLED);

  g_object_get (f->dbus_1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);
  g_object_get (f->dbus_2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);
}

static void
test_rd_conn_closed (RdTestFixture *f, gconstpointer data)
{
  GObject *rd1 = NULL;
  gint state = 0xffff;

  g_object_get (f->dbus_1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);
  g_object_get (f->dbus_2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);

  wp_object_activate (WP_OBJECT (f->rd_plugin_1), WP_PLUGIN_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) on_plugin_activated, f);
  wp_object_activate (WP_OBJECT (f->rd_plugin_2), WP_PLUGIN_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) on_plugin_activated, f);

  g_signal_connect (f->dbus_1, "notify::state",
      G_CALLBACK (ensure_plugins_stable_state), f);
  g_signal_connect (f->dbus_2, "notify::state",
      G_CALLBACK (ensure_plugins_stable_state), f);
  g_main_loop_run (f->base.loop);

  g_object_get (f->dbus_1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);
  g_object_get (f->dbus_2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);

  g_signal_emit_by_name (f->rd_plugin_1, "create-reservation",
      "Audio0", "WirePlumber", "hw:0,0", 10, &rd1);
  g_assert_nonnull (rd1);
  g_clear_object (&rd1);

  /* stop the bus, expect the connections to close
     and state to go back to CLOSED */
  g_test_dbus_stop (f->test_dbus);
  g_main_loop_run (f->base.loop);

  g_object_get (f->dbus_1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);
  g_object_get (f->dbus_2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);

  g_signal_emit_by_name (f->rd_plugin_1, "get-reservation", "Audio0", &rd1);
  g_assert_null (rd1);

  wp_object_deactivate (WP_OBJECT (f->dbus_1), WP_DBUS_FEATURE_ENABLED);
  wp_object_deactivate (WP_OBJECT (f->dbus_2), WP_DBUS_FEATURE_ENABLED);
}

static void
expect_rd1_state (GObject * rd, GParamSpec * spec, RdTestFixture *f)
{
  gint state;
  g_object_get (rd, "state", &state, NULL);
  if (state == f->expected_rd1_state)
    g_main_loop_quit (f->base.loop);
}

static void
expect_rd2_state (GObject * rd, GParamSpec * spec, RdTestFixture *f)
{
  gint state;
  g_object_get (rd, "state", &state, NULL);
  if (state == f->expected_rd2_state)
    g_main_loop_quit (f->base.loop);
}

static void
handle_release_requested (GObject * rd, gboolean forced, RdTestFixture *f)
{
  gint state = 0xffff;
  g_signal_emit_by_name (rd, "release");
  g_object_get (rd, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);
  g_main_loop_quit (f->base.loop);
}

static void
test_rd_acquire_release (RdTestFixture *f, gconstpointer data)
{
  GObject *rd1 = NULL, *rd2 = NULL;
  gint state = 0xffff;
  gchar *str = NULL;

  g_object_get (f->dbus_1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);
  g_object_get (f->dbus_2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 0);

  wp_object_activate (WP_OBJECT (f->rd_plugin_1), WP_PLUGIN_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) on_plugin_activated, f);
  wp_object_activate (WP_OBJECT (f->rd_plugin_2), WP_PLUGIN_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) on_plugin_activated, f);

  g_signal_connect (f->dbus_1, "notify::state",
      G_CALLBACK (ensure_plugins_stable_state), f);
  g_signal_connect (f->dbus_2, "notify::state",
      G_CALLBACK (ensure_plugins_stable_state), f);
  g_main_loop_run (f->base.loop);

  g_object_get (f->dbus_1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);
  g_object_get (f->dbus_2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);

  g_signal_emit_by_name (f->rd_plugin_1, "create-reservation",
      "Audio0", "WirePlumber", "hw:0,0", 10, &rd1);
  g_assert_nonnull (rd1);
  g_signal_emit_by_name (f->rd_plugin_2, "create-reservation",
      "Audio0", "Other Server", "hw:0,0", 15, &rd2);
  g_assert_nonnull (rd2);

  g_signal_connect (rd1, "notify::state", G_CALLBACK (expect_rd1_state), f);
  g_signal_connect (rd2, "notify::state", G_CALLBACK (expect_rd2_state), f);

  /* acquire */
  wp_info ("rd1 acquire");

  f->expected_rd1_state = 3;
  g_signal_emit_by_name (rd1, "acquire");
  g_main_loop_run (f->base.loop);
  g_object_get (rd1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 3);
  g_object_get (rd1, "owner-application-name", &str, NULL);
  g_assert_cmpstr (str, ==, "WirePlumber");
  g_free (str);

  g_signal_connect (rd1, "release-requested",
      G_CALLBACK (handle_release_requested), f);

  /* acquire with higher priority */
  wp_info ("rd2 acquire, higher prio");
  g_signal_emit_by_name (rd2, "acquire");

  /* rd1 is now released */
  g_main_loop_run (f->base.loop);
  g_object_get (rd1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);

  /* rd2 acquired */
  f->expected_rd2_state = 3;
  g_main_loop_run (f->base.loop);
  g_object_get (rd2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 3);

  /* rd1 busy */
  g_signal_connect_swapped (rd1, "notify::owner-application-name",
      G_CALLBACK (g_main_loop_quit), f->base.loop);
  g_main_loop_run (f->base.loop);
  g_object_get (rd1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 1);
  g_object_get (rd1, "owner-application-name", &str, NULL);
  g_assert_cmpstr (str, ==, "Other Server");
  g_free (str);
  g_signal_handlers_disconnect_by_func (rd1, G_CALLBACK (g_main_loop_quit),
      f->base.loop);

  /* try to acquire back with lower priority */
  wp_info ("rd1 acquire, lower prio");
  g_signal_emit_by_name (rd1, "acquire");

  /* ... expect this to fail */
  f->expected_rd1_state = 1;
  g_main_loop_run (f->base.loop);
  g_object_get (rd1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 1);

  g_object_get (rd1, "owner-application-name", &str, NULL);
  g_assert_cmpstr (str, ==, "Other Server");
  g_free (str);

  /* release */
  wp_info ("rd2 release");
  g_signal_emit_by_name (rd2, "release");
  g_object_get (rd2, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);

  f->expected_rd1_state = 2;
  g_main_loop_run (f->base.loop);
  g_object_get (rd1, "state", &state, NULL);
  g_assert_cmpint (state, ==, 2);

  g_object_get (rd1, "owner-application-name", &str, NULL);
  g_assert_null (str);

  g_clear_object (&rd1);
  g_clear_object (&rd2);

  wp_object_deactivate (WP_OBJECT (f->rd_plugin_1), WP_PLUGIN_FEATURE_ENABLED);
  wp_object_deactivate (WP_OBJECT (f->rd_plugin_2), WP_PLUGIN_FEATURE_ENABLED);

  wp_object_deactivate (WP_OBJECT (f->dbus_1), WP_DBUS_FEATURE_ENABLED);
  wp_object_deactivate (WP_OBJECT (f->dbus_2), WP_DBUS_FEATURE_ENABLED);
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/modules/rd/plugin", RdTestFixture, NULL,
      test_rd_setup, test_rd_plugin, test_rd_teardown);
  g_test_add ("/modules/rd/conn_closed", RdTestFixture, NULL,
      test_rd_setup, test_rd_conn_closed, test_rd_teardown);
  g_test_add ("/modules/rd/acquire_release", RdTestFixture, NULL,
      test_rd_setup, test_rd_acquire_release, test_rd_teardown);

  return g_test_run ();
}
  0707010000016F000081A4000000000000000000000001656CC35F00000F68000000000000000000000000000000000000003400000000wireplumber-0.4.17/tests/modules/si-audio-adapter.c   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;
} TestFixture;

static void
test_si_audio_adapter_setup (TestFixture * f, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&f->base, 0);

  /* load modules */
  {
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&f->base.server);

    g_assert_cmpint (pw_context_add_spa_lib (f->base.server.context,
            "audiotestsrc", "audiotestsrc/libspa-audiotestsrc"), ==, 0);
    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-spa-node-factory", NULL, NULL));
    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-adapter", NULL, NULL));
  }
  {
    g_autoptr (GError) error = NULL;
    wp_core_load_component (f->base.core,
        "libwireplumber-module-si-audio-adapter", "module", NULL, &error);
    g_assert_no_error (error);
  }
}

static void
test_si_audio_adapter_teardown (TestFixture * f, gconstpointer user_data)
{
  wp_base_test_fixture_teardown (&f->base);
}

static void
test_si_audio_adapter_configure_activate (TestFixture * f,
    gconstpointer user_data)
{
  g_autoptr (WpNode) node = NULL;
  g_autoptr (WpSessionItem) adapter = NULL;

  /* skip test if audiotestsrc is not installed */
  if (!test_is_spa_lib_installed (&f->base, "audiotestsrc")) {
    g_test_skip ("The pipewire audiotestsrc factory was not found");
    return;
  }

  /* create audiotestsrc adapter node */
  node = wp_node_new_from_factory (f->base.core,
      "adapter",
      wp_properties_new (
          "factory.name", "audiotestsrc",
          "node.name", "audiotestsrc.adapter",
          NULL));
  g_assert_nonnull (node);
  wp_object_activate (WP_OBJECT (node), WP_OBJECT_FEATURES_ALL,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);

  /* create adapter */
  adapter = wp_session_item_make (f->base.core, "si-audio-adapter");
  g_assert_nonnull (adapter);
  g_assert_true (WP_IS_SI_LINKABLE (adapter));

  /* configure */
  {
    WpProperties *props = wp_properties_new_empty ();
    wp_properties_setf (props, "item.node", "%p", node);
    wp_properties_set (props, "media.class", "Audio/Source");
    g_assert_true (wp_session_item_configure (adapter, props));
    g_assert_true (wp_session_item_is_configured (adapter));
  }

  /* validate configuration */
  {
    const gchar *str = NULL;
    g_autoptr (WpProperties) props = wp_session_item_get_properties (adapter);
    g_assert_nonnull (props);
    str = wp_properties_get (props, "item.factory.name");
    g_assert_nonnull (str);
    g_assert_cmpstr ("si-audio-adapter", ==, str);
  }

  /* activate */
  wp_object_activate (WP_OBJECT (adapter), WP_SESSION_ITEM_FEATURE_ACTIVE,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);
  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (adapter)), ==,
      WP_SESSION_ITEM_FEATURE_ACTIVE);

  /* deactivate - configuration should not be altered  */
  wp_object_deactivate (WP_OBJECT (adapter), WP_SESSION_ITEM_FEATURE_ACTIVE);
  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (adapter)), ==, 0);
  g_assert_true (wp_session_item_is_configured (adapter));

  /* reset */
  wp_session_item_reset (adapter);
  g_assert_false (wp_session_item_is_configured (adapter));
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  /* configure-activate */

  g_test_add ("/modules/si-audio-adapter/configure-activate",
      TestFixture, NULL,
      test_si_audio_adapter_setup,
      test_si_audio_adapter_configure_activate,
      test_si_audio_adapter_teardown);

  return g_test_run ();
}
07070100000170000081A4000000000000000000000001656CC35F00001940000000000000000000000000000000000000003500000000wireplumber-0.4.17/tests/modules/si-audio-endpoint.c  /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;
} TestFixture;

static void
test_si_audio_endpoint_setup (TestFixture * f, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&f->base, 0);

  /* load modules */
  {
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&f->base.server);

    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-spa-node-factory", NULL, NULL));
    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-adapter", NULL, NULL));
  }
  {
    g_autoptr (GError) error = NULL;
    wp_core_load_component (f->base.core,
        "libwireplumber-module-si-audio-adapter", "module", NULL, &error);
    g_assert_no_error (error);
  }
  {
    g_autoptr (GError) error = NULL;
    wp_core_load_component (f->base.core,
        "libwireplumber-module-si-audio-endpoint", "module", NULL, &error);
    g_assert_no_error (error);
  }
}

static void
test_si_audio_endpoint_teardown (TestFixture * f, gconstpointer user_data)
{
  wp_base_test_fixture_teardown (&f->base);
}

static void
test_si_audio_endpoint_configure_activate (TestFixture * f,
    gconstpointer user_data)
{
  g_autoptr (WpSessionItem) endpoint = NULL;

  /* skip the test if null-audio-sink factory is not installed */
  if (!test_is_spa_lib_installed (&f->base, "support.null-audio-sink")) {
    g_test_skip ("The pipewire null-audio-sink factory was not found");
    return;
  }

  /* create endpoint */

  endpoint = wp_session_item_make (f->base.core, "si-audio-endpoint");
  g_assert_nonnull (endpoint);
  g_assert_true (WP_IS_SI_ENDPOINT (endpoint));

  /* configure endpoint */

  {
    WpProperties *props = wp_properties_new_empty ();
    wp_properties_set (props, "name", "endpoint");
    wp_properties_set (props, "media.class", "Audio/Source");
    g_assert_true (wp_session_item_configure (endpoint, props));
    g_assert_true (wp_session_item_is_configured (endpoint));
  }

  {
    const gchar *str = NULL;
    g_autoptr (WpProperties) props = wp_session_item_get_properties (endpoint);
    g_assert_nonnull (props);
    str = wp_properties_get (props, "name");
    g_assert_nonnull (str);
    g_assert_cmpstr ("endpoint", ==, str);
    str = wp_properties_get (props, "direction");
    g_assert_nonnull (str);
    g_assert_cmpstr ("1", ==, str);
    str = wp_properties_get (props, "item.factory.name");
    g_assert_nonnull (str);
    g_assert_cmpstr ("si-audio-endpoint", ==, str);
  }

  /* activate endpoint */

  wp_object_activate (WP_OBJECT (endpoint), WP_SESSION_ITEM_FEATURE_ACTIVE,
      NULL,  (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);
  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (endpoint)), ==,
      WP_SESSION_ITEM_FEATURE_ACTIVE);

  /* reset */
  wp_session_item_reset (endpoint);
  g_assert_false (wp_session_item_is_configured (endpoint));
}

static void
test_si_audio_endpoint_export (TestFixture * f, gconstpointer user_data)
{
  g_autoptr (WpSessionItem) endpoint = NULL;
  g_autoptr (WpObjectManager) clients_om = NULL;
  g_autoptr (WpClient) self_client = NULL;

  /* skip the test if null-audio-sink factory is not installed */
  if (!test_is_spa_lib_installed (&f->base, "support.null-audio-sink")) {
    g_test_skip ("The pipewire null-audio-sink factory was not found");
    return;
  }

  /* find self_client, to be used for verifying endpoint.client.id */

  clients_om = wp_object_manager_new ();
  wp_object_manager_add_interest (clients_om, WP_TYPE_CLIENT, NULL);
  wp_object_manager_request_object_features (clients_om,
      WP_TYPE_CLIENT, WP_PROXY_FEATURE_BOUND);
  g_signal_connect_swapped (clients_om, "objects-changed",
      G_CALLBACK (g_main_loop_quit), f->base.loop);
  wp_core_install_object_manager (f->base.core, clients_om);
  g_main_loop_run (f->base.loop);
  self_client = wp_object_manager_lookup (clients_om, WP_TYPE_CLIENT, NULL);
  g_assert_nonnull (self_client);

  /* create endpoint */

  endpoint = wp_session_item_make (f->base.core, "si-audio-endpoint");
  g_assert_nonnull (endpoint);

  /* configure endpoint */
  {
    WpProperties *props = wp_properties_new_empty ();
    wp_properties_set (props, "name", "endpoint");
    wp_properties_set (props, "media.class", "Audio/Source");
    g_assert_true (wp_session_item_configure (endpoint, props));
    g_assert_true (wp_session_item_is_configured (endpoint));
  }

  /* activate endpoint */

  {
    wp_object_activate (WP_OBJECT (endpoint),
        WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED,
        NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
    g_main_loop_run (f->base.loop);
    g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (endpoint)), ==,
        WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);
  }

  {
    g_autoptr (WpEndpoint) ep = NULL;
    g_autoptr (WpProperties) props = NULL;

    ep = wp_session_item_get_associated_proxy (endpoint, WP_TYPE_ENDPOINT);
    g_assert_nonnull (ep);
    props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (ep));
    g_assert_nonnull (props);

    g_assert_cmpstr (wp_endpoint_get_name (ep), ==, "endpoint");
    g_assert_cmpstr (wp_endpoint_get_media_class (ep), ==,
        "Audio/Source");
    g_assert_cmpint (wp_endpoint_get_direction (ep), ==, WP_DIRECTION_OUTPUT);
    g_assert_cmpstr (wp_properties_get (props, "endpoint.name"), ==,
        "endpoint");
    g_assert_cmpstr (wp_properties_get (props, "media.class"), ==,
        "Audio/Source");
  }

  /* reset */
  wp_session_item_reset (endpoint);
  g_assert_false (wp_session_item_is_configured (endpoint));
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  /* configure-activate */
  g_test_add ("/modules/si-audio-endpoint/configure-activate",
      TestFixture, NULL,
      test_si_audio_endpoint_setup,
      test_si_audio_endpoint_configure_activate,
      test_si_audio_endpoint_teardown);

 /* export */
 g_test_add ("/modules/si-audio-endpoint/export",
      TestFixture, NULL,
      test_si_audio_endpoint_setup,
      test_si_audio_endpoint_export,
      test_si_audio_endpoint_teardown);

  return g_test_run ();
}
07070100000171000081A4000000000000000000000001656CC35F0000196E000000000000000000000000000000000000002B00000000wireplumber-0.4.17/tests/modules/si-node.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;
} TestFixture;

typedef struct {
  const gchar *factory;
  const gchar *name;
  const gchar *media_class;
  const gchar *expected_media_class;
  WpDirection expected_direction;
} TestData;

static void
test_si_node_setup (TestFixture * f, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&f->base, 0);

  /* load modules */
  {
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&f->base.server);

    g_assert_cmpint (pw_context_add_spa_lib (f->base.server.context,
            "audiotestsrc", "audiotestsrc/libspa-audiotestsrc"), ==, 0);
    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-spa-node-factory", NULL, NULL));
  }
  {
    g_autoptr (GError) error = NULL;
    wp_core_load_component (f->base.core,
        "libwireplumber-module-si-node", "module", NULL, &error);
    g_assert_no_error (error);
  }
}

static void
test_si_node_teardown (TestFixture * f, gconstpointer user_data)
{
  wp_base_test_fixture_teardown (&f->base);
}

static void
test_si_node_configure_activate (TestFixture * f, gconstpointer user_data)
{
  const TestData *data = user_data;
  g_autoptr (WpNode) node = NULL;
  g_autoptr (WpSessionItem) item = NULL;

  /* skip test if spa factory is not insalled */
  if (!test_is_spa_lib_installed (&f->base, data->factory)) {
    g_autofree gchar *msg = NULL;
    msg = g_strdup_printf ("The pipewire %s factory was not found",
        data->factory);
    g_test_skip (msg);
    return;
  }

  /* create item */

  item = wp_session_item_make (f->base.core, "si-node");
  g_assert_nonnull (item);
  g_assert_true (WP_IS_SI_LINKABLE (item));

  node = wp_node_new_from_factory (f->base.core,
      "spa-node-factory",
      wp_properties_new (
          "factory.name", data->factory,
          "node.name", data->name,
          NULL));
  g_assert_nonnull (node);

  wp_object_activate (WP_OBJECT (node), WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);

  /* configure */

  {
    WpProperties *props = wp_properties_new_empty ();
    wp_properties_setf (props, "item.node", "%p", node);
    wp_properties_set (props, "media.class", data->media_class);
    g_assert_true (wp_session_item_configure (item, props));
    g_assert_true (wp_session_item_is_configured (item));
  }

  {
    const gchar *str = NULL;
    g_autoptr (WpProperties) props = wp_session_item_get_properties (item);
    g_assert_nonnull (props);
    str = wp_properties_get (props, "media.class");
    g_assert_nonnull (str);
    g_assert_cmpstr (data->expected_media_class, ==, str);
    str = wp_properties_get (props, "item.factory.name");
    g_assert_nonnull (str);
    g_assert_cmpstr ("si-node", ==, str);
  }

  /* activate */

  wp_object_activate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_ACTIVE,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);
  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (item)), ==,
      WP_SESSION_ITEM_FEATURE_ACTIVE);
  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (node)), ==,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | WP_NODE_FEATURE_PORTS);

  if (data->expected_direction == WP_DIRECTION_INPUT)
    g_assert_cmpuint (wp_node_get_n_input_ports (node, NULL), ==, 1);
  else
    g_assert_cmpuint (wp_node_get_n_output_ports (node, NULL), ==, 1);
  g_assert_cmpuint (wp_node_get_n_ports (node), ==, 1);

  {
    guint32 node_id, port_id, channel;
    g_autoptr (GVariant) v =
        wp_si_linkable_get_ports (WP_SI_LINKABLE (item),
        (data->expected_direction == WP_DIRECTION_INPUT) ? "input" : "output");

    g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE ("a(uuu)")));
    g_assert_cmpint (g_variant_n_children (v), ==, 1);
    g_variant_get_child (v, 0, "(uuu)", &node_id, &port_id, &channel);
    g_assert_cmpuint (node_id, ==, wp_proxy_get_bound_id (WP_PROXY (node)));
    g_assert_cmpuint (channel, ==, 0);

    {
      g_autoptr (WpIterator) it = wp_node_new_ports_iterator (node);
      g_auto (GValue) val = G_VALUE_INIT;
      WpProxy *port;

      g_assert_true (wp_iterator_next (it, &val));
      port = g_value_get_object (&val);
      g_assert_nonnull (port);
      g_assert_cmpuint (port_id, ==, wp_proxy_get_bound_id (port));
    }
  }

  /* deactivate - configuration should not be altered  */

  wp_object_deactivate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_ACTIVE);

  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (item)), ==, 0);
  g_assert_true (wp_session_item_is_configured (item));
  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (node)), ==,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | WP_NODE_FEATURE_PORTS);

  {
    const gchar *str = NULL;
    g_autoptr (WpProperties) props = wp_session_item_get_properties (item);
    g_assert_nonnull (props);
    str = wp_properties_get (props, "media.class");
    g_assert_nonnull (str);
    g_assert_cmpstr (data->expected_media_class, ==, str);
    str = wp_properties_get (props, "item.factory.name");
    g_assert_nonnull (str);
    g_assert_cmpstr ("si-node", ==, str);
  }

  /* reset - configuration resets */

  wp_session_item_reset (item);
  g_assert_false (wp_session_item_is_configured (item));

  {
    g_autoptr (WpProperties) props =
        wp_session_item_get_properties (item);
    g_assert_null (props);
  }
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  /* data */

  const TestData nullsink_data = {
    "support.null-audio-sink", "nullsink0", "Fake/Sink", "Fake/Sink", WP_DIRECTION_INPUT
  };
  const TestData audiotestsrc_data = {
    "audiotestsrc", "audiotestsrc0", "Audio/Source", "Audio/Source", WP_DIRECTION_OUTPUT
  };

  /* configure-activate */

  g_test_add ("/modules/si-node/configure-activate/nullsink",
      TestFixture, &nullsink_data,
      test_si_node_setup,
      test_si_node_configure_activate,
      test_si_node_teardown);

  g_test_add ("/modules/si-node/configure-activate/audiotestsrc",
      TestFixture, &audiotestsrc_data,
      test_si_node_setup,
      test_si_node_configure_activate,
      test_si_node_teardown);

  return g_test_run ();
}
  07070100000172000081A4000000000000000000000001656CC35F000022F2000000000000000000000000000000000000003400000000wireplumber-0.4.17/tests/modules/si-standard-link.c   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;

  WpSessionItem *src_item;
  WpSessionItem *sink_item;

} TestFixture;

static WpSessionItem *
load_node (TestFixture * f, const gchar * factory, const gchar * media_class,
    const gchar * type)
{
  g_autoptr (WpNode) node = NULL;
  g_autoptr (WpSessionItem) adapter = NULL;

  /* create audiotestsrc adapter node */
  node = wp_node_new_from_factory (f->base.core,
      "adapter",
      wp_properties_new (
          "factory.name", factory,
          "node.name", factory,
          "media.class", media_class,
          "audio.channels", "2",
          "audio.position", "[ FL, FR ]",
          NULL));
  g_assert_nonnull (node);
  wp_object_activate (WP_OBJECT (node), WP_OBJECT_FEATURES_ALL,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);

  /* create adapter */
  adapter = wp_session_item_make (f->base.core, "si-audio-adapter");
  g_assert_nonnull (adapter);
  g_assert_true (WP_IS_SI_LINKABLE (adapter));

  /* configure */
  {
    WpProperties *props = wp_properties_new_empty ();
    wp_properties_setf (props, "item.node", "%p", node);
    wp_properties_set (props, "media.class", media_class);
    wp_properties_set (props, "item.node.type", type);
    g_assert_true (wp_session_item_configure (adapter, props));
    g_assert_true (wp_session_item_is_configured (adapter));
  }

  /* activate adapter */

  wp_object_activate (WP_OBJECT (adapter),
      WP_SESSION_ITEM_FEATURE_ACTIVE,
      NULL,  (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);
  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (adapter)), ==,
      WP_SESSION_ITEM_FEATURE_ACTIVE);

  return g_steal_pointer (&adapter);
}

static void
test_si_standard_link_setup (TestFixture * f, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&f->base, 0);

  /* load modules */
  {
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&f->base.server);

    g_assert_cmpint (pw_context_add_spa_lib (f->base.server.context,
            "audiotestsrc", "audiotestsrc/libspa-audiotestsrc"), ==, 0);
    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-adapter", NULL, NULL));
    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-link-factory", NULL, NULL));
  }
  {
    g_autoptr (GError) error = NULL;
    wp_core_load_component (f->base.core,
        "libwireplumber-module-si-audio-adapter", "module", NULL, &error);
    g_assert_no_error (error);

    wp_core_load_component (f->base.core,
        "libwireplumber-module-si-standard-link", "module", NULL, &error);
    g_assert_no_error (error);
  }

  if (test_is_spa_lib_installed (&f->base, "audiotestsrc"))
    f->src_item = load_node (f, "audiotestsrc", "Stream/Output/Audio", "stream");
  if (test_is_spa_lib_installed (&f->base, "support.null-audio-sink"))
    f->sink_item = load_node (f, "support.null-audio-sink", "Audio/Sink", "device");
}

static void
test_si_standard_link_teardown (TestFixture * f, gconstpointer user_data)
{
  g_clear_object (&f->sink_item);
  g_clear_object (&f->src_item);
  wp_base_test_fixture_teardown (&f->base);
}

static void
test_si_standard_link_main (TestFixture * f, gconstpointer user_data)
{
  g_autoptr (WpSessionItem) link = NULL;

  /* skip the test if audiotestsrc endpoint could not be loaded */
  if (!f->src_item) {
    g_test_skip ("The pipewire audiotestsrc factory was not found");
    return;
  }

  /* skip the test if null-audio-sink endpoint could not be loaded */
  if (!f->sink_item) {
    g_test_skip ("The pipewire null-audio-sink factory was not found");
    return;
  }

  /* create the link */
  link = wp_session_item_make (f->base.core, "si-standard-link");
  g_assert_nonnull (link);

  /* configure the link */
  {
    g_autoptr (WpProperties) props = wp_properties_new_empty ();
    wp_properties_setf (props, "out.item", "%p", f->src_item);
    wp_properties_setf (props, "in.item", "%p", f->sink_item);
    wp_properties_set (props, "out.item.port.context", "output");
    wp_properties_set (props, "in.item.port.context", "input");
    g_assert_true (wp_session_item_configure (link, g_steal_pointer (&props)));
  }

  /* activate */
  wp_object_activate (WP_OBJECT (link), WP_SESSION_ITEM_FEATURE_ACTIVE,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);

  /* verify the graph state */
  {
    g_autoptr (WpNode) out_node = NULL;
    g_autoptr (WpNode) in_node = NULL;
    g_autoptr (WpIterator) it = NULL;
    g_auto (GValue) val = G_VALUE_INIT;
    g_autoptr (WpObjectManager) om = wp_object_manager_new ();
    guint total_links = 0;

    wp_object_manager_add_interest (om, WP_TYPE_NODE, NULL);
    wp_object_manager_add_interest (om, WP_TYPE_PORT, NULL);
    wp_object_manager_add_interest (om, WP_TYPE_LINK, NULL);
    wp_object_manager_request_object_features (om, WP_TYPE_PROXY,
        WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
    test_ensure_object_manager_is_installed (om, f->base.core,
        f->base.loop);

    out_node = wp_object_manager_lookup (om, WP_TYPE_NODE,
        WP_CONSTRAINT_TYPE_PW_PROPERTY, "node.name", "=s", "audiotestsrc",
        NULL);
    g_assert_nonnull (out_node);
    in_node = wp_object_manager_lookup (om, WP_TYPE_NODE,
        WP_CONSTRAINT_TYPE_PW_PROPERTY, "node.name", "=s", "support.null-audio-sink",
        NULL);
    g_assert_nonnull (in_node);
    g_assert_cmpuint (wp_object_manager_get_n_objects (om), ==, 8);

    it = wp_object_manager_new_filtered_iterator (om, WP_TYPE_LINK, NULL);
    for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
      guint32 out_nd_id, out_pt_id, in_nd_id, in_pt_id;
      g_autoptr (WpPort) out_port = NULL;
      g_autoptr (WpPort) in_port = NULL;
      WpLink *link = g_value_get_object (&val);
      wp_link_get_linked_object_ids (link, &out_nd_id, &out_pt_id, &in_nd_id,
          &in_pt_id);
      g_assert_cmpuint (out_nd_id, ==, wp_proxy_get_bound_id (WP_PROXY (out_node)));
      g_assert_cmpuint (in_nd_id, ==, wp_proxy_get_bound_id (WP_PROXY (in_node)));
      out_port = wp_object_manager_lookup (om, WP_TYPE_PORT,
          WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", out_pt_id,
          WP_CONSTRAINT_TYPE_PW_PROPERTY, "node.id", "=u", out_nd_id,
          NULL);
      g_assert_nonnull (out_port);
      in_port = wp_object_manager_lookup (om, WP_TYPE_PORT,
          WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", in_pt_id,
          WP_CONSTRAINT_TYPE_PW_PROPERTY, "node.id", "=u", in_nd_id,
          NULL);
      g_assert_nonnull (in_port);
      total_links++;
    }
    g_assert_cmpuint (total_links, ==, 2);
  }

  /* deactivate */
  wp_object_deactivate (WP_OBJECT (link), WP_SESSION_ITEM_FEATURE_ACTIVE);

  /* verify the graph state */
  {
    g_autoptr (WpNode) out_node = NULL;
    g_autoptr (WpNode) in_node = NULL;
    g_autoptr (WpPort) out_port = NULL;
    g_autoptr (WpPort) in_port = NULL;
    g_autoptr (WpLink) link = NULL;
    g_autoptr (WpObjectManager) om = wp_object_manager_new ();

    wp_object_manager_add_interest (om, WP_TYPE_NODE, NULL);
    wp_object_manager_add_interest (om, WP_TYPE_PORT, NULL);
    wp_object_manager_add_interest (om, WP_TYPE_LINK, NULL);
    wp_object_manager_request_object_features (om, WP_TYPE_PROXY,
        WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
    test_ensure_object_manager_is_installed (om, f->base.core,
        f->base.loop);

    out_node = wp_object_manager_lookup (om, WP_TYPE_NODE,
        WP_CONSTRAINT_TYPE_PW_PROPERTY, "node.name", "=s", "audiotestsrc",
        NULL);
    g_assert_nonnull (out_node);
    in_node = wp_object_manager_lookup (om, WP_TYPE_NODE,
        WP_CONSTRAINT_TYPE_PW_PROPERTY, "node.name", "=s", "support.null-audio-sink",
        NULL);
    g_assert_nonnull (in_node);
    out_port = wp_object_manager_lookup (om, WP_TYPE_PORT,
        WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.direction", "=s", "out",
        NULL);
    g_assert_nonnull (out_port);
    in_port = wp_object_manager_lookup (om, WP_TYPE_PORT,
        WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.direction", "=s", "in",
        NULL);
    g_assert_nonnull (in_port);
    link = wp_object_manager_lookup (om, WP_TYPE_LINK, NULL);
    g_assert_null (link);
    g_assert_cmpuint (wp_object_manager_get_n_objects (om), ==, 6);
  }
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/modules/si-standard-link/main",
      TestFixture, NULL,
      test_si_standard_link_setup,
      test_si_standard_link_main,
      test_si_standard_link_teardown);

  return g_test_run ();
}
  07070100000173000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001C00000000wireplumber-0.4.17/tests/wp   07070100000174000081A4000000000000000000000001656CC35F000011A8000000000000000000000000000000000000002300000000wireplumber-0.4.17/tests/wp/core.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"
#include <wp/wp.h>

typedef struct {
  WpBaseTestFixture base;
  WpObjectManager *om;
  gboolean disconnected;
} TestFixture;

static void
test_core_setup (TestFixture *self, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&self->base, WP_BASE_TEST_FLAG_DONT_CONNECT);
  /* remove the "disconnected" handler that fails the test */
  g_signal_handlers_disconnect_by_data (self->base.core, &self->base);
  self->om = wp_object_manager_new ();
  self->disconnected = FALSE;
}

static void
test_core_teardown (TestFixture *self, gconstpointer user_data)
{
  g_clear_object (&self->om);
  wp_base_test_fixture_teardown (&self->base);
}

static void
expect_disconnected (WpCore * core, TestFixture * f)
{
  f->disconnected = TRUE;
  g_main_loop_quit (f->base.loop);
}

static void
expect_object_added (WpObjectManager *om, WpProxy *proxy, TestFixture *f)
{
  g_assert_true (WP_IS_CLIENT (proxy));
  g_main_loop_quit (f->base.loop);
}

static void
test_core_server_disconnected (TestFixture *f, gconstpointer data)
{
  g_signal_connect (f->base.core, "disconnected",
      G_CALLBACK (expect_disconnected), f);
  g_signal_connect (f->om, "object-added",
      G_CALLBACK (expect_object_added), f);

  wp_object_manager_add_interest (f->om, WP_TYPE_CLIENT, NULL);
  wp_core_install_object_manager (f->base.core, f->om);

  /* connect */
  g_assert_true (wp_core_connect (f->base.core));
  g_assert_true (wp_core_is_connected (f->base.core));

  /* wait for the object manager to collect the client proxy */
  g_main_loop_run (f->base.loop);
  g_assert_cmpuint (wp_object_manager_get_n_objects (f->om), ==, 1);

  /* destroy the server and wait for the disconnected signal */
  wp_test_server_teardown (&f->base.server);
  g_main_loop_run (f->base.loop);
  g_assert_true (f->disconnected);

  g_assert_false (wp_core_is_connected (f->base.core));
  g_assert_cmpuint (wp_object_manager_get_n_objects (f->om), ==, 0);
}

static void
test_core_client_disconnected (TestFixture *f, gconstpointer data)
{
  g_signal_connect (f->base.core, "disconnected",
      G_CALLBACK (expect_disconnected), f);
  g_signal_connect (f->om, "object-added",
      G_CALLBACK (expect_object_added), f);

  wp_object_manager_add_interest (f->om, WP_TYPE_CLIENT, NULL);
  wp_core_install_object_manager (f->base.core, f->om);

  /* connect */
  g_assert_true (wp_core_connect (f->base.core));
  g_assert_true (wp_core_is_connected (f->base.core));

  /* wait for the object manager to collect the client proxy */
  g_main_loop_run (f->base.loop);
  g_assert_cmpuint (wp_object_manager_get_n_objects (f->om), ==, 1);

  /* disconnect and expect the disconnected signal */
  wp_core_disconnect (f->base.core);
  g_assert_true (f->disconnected);

  g_assert_false (wp_core_is_connected (f->base.core));
  g_assert_cmpuint (wp_object_manager_get_n_objects (f->om), ==, 0);
}

static void
test_core_clone (TestFixture *f, gconstpointer data)
{
  g_assert_false (wp_core_is_connected (f->base.core));

  /* clone */
  g_autoptr (WpCore) clone = wp_core_clone (f->base.core);
  g_assert_nonnull (clone);
  g_assert_false (wp_core_is_connected (clone));

  /* connect clone */
  g_assert_true (wp_core_connect (clone));
  g_assert_true (wp_core_is_connected (clone));
  g_assert_false (wp_core_is_connected (f->base.core));

  /* connect core */
  g_assert_true (wp_core_connect (f->base.core));
  g_assert_true (wp_core_is_connected (clone));
  g_assert_true (wp_core_is_connected (f->base.core));

  /* disconnect clone */
  wp_core_disconnect (clone);
  g_assert_false (wp_core_is_connected (clone));
  g_assert_true (wp_core_is_connected (f->base.core));

  /* disconnect core */
  wp_core_disconnect (f->base.core);
  g_assert_false (wp_core_is_connected (f->base.core));
  g_assert_false (wp_core_is_connected (clone));
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/wp/core/server-disconnected", TestFixture, NULL,
      test_core_setup, test_core_server_disconnected, test_core_teardown);
  g_test_add ("/wp/core/client-disconnected", TestFixture, NULL,
      test_core_setup, test_core_client_disconnected, test_core_teardown);
  g_test_add ("/wp/core/cline", TestFixture, NULL,
      test_core_setup, test_core_clone, test_core_teardown);

  return g_test_run ();
}
07070100000175000081A4000000000000000000000001656CC35F00000B05000000000000000000000000000000000000002300000000wireplumber-0.4.17/tests/wp/dbus.c    /* WirePlumber
 *
 * Copyright © 2022 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;
  GTestDBus *test_dbus;
} TestFixture;

static void
test_dbus_setup (TestFixture *self, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&self->base, 0);
  self->test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
  g_test_dbus_up (self->test_dbus);
}

static void
test_dbus_teardown (TestFixture *self, gconstpointer user_data)
{
  g_test_dbus_down (self->test_dbus);
  g_clear_object (&self->test_dbus);
  wp_base_test_fixture_teardown (&self->base);
}

static void
test_dbus_basic (TestFixture *f, gconstpointer user_data)
{
  g_autoptr (WpDbus) dbus = wp_dbus_get_instance (f->base.core,
      G_BUS_TYPE_SESSION);
  g_assert_nonnull (dbus);
  g_autoptr (WpDbus) dbus2 = wp_dbus_get_instance (f->base.core,
      G_BUS_TYPE_SESSION);
  g_assert_nonnull (dbus2);

  GBusType bus_type = wp_dbus_get_bus_type (dbus);
  g_assert_true (bus_type == G_BUS_TYPE_SESSION);
  GBusType bus_type2 = wp_dbus_get_bus_type (dbus);
  g_assert_true (bus_type2 == G_BUS_TYPE_SESSION);
  g_assert_true (dbus == dbus2);
}

static void
on_dbus_activated (WpObject * dbus, GAsyncResult * res, TestFixture * f)
{
  g_autoptr (GError) error = NULL;
  if (!wp_object_activate_finish (dbus, res, &error)) {
    wp_critical_object (dbus, "%s", error->message);
    g_main_loop_quit (f->base.loop);
  }
}

static void
on_dbus_state_changed (GObject * obj, GParamSpec * spec, TestFixture *f)
{
  WpDBusState state = wp_dbus_get_state (WP_DBUS (obj));
  if (state == WP_DBUS_STATE_CONNECTED)
    g_main_loop_quit (f->base.loop);
}

static void
test_dbus_activation (TestFixture *f, gconstpointer user_data)
{
  g_autoptr (WpDbus) dbus = wp_dbus_get_instance (
    f->base.core, G_BUS_TYPE_SESSION);
  g_assert_nonnull (dbus);

  wp_object_activate (WP_OBJECT (dbus), WP_DBUS_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) on_dbus_activated, f);

  g_signal_connect (dbus, "notify::state", G_CALLBACK (on_dbus_state_changed),
     f);

  g_main_loop_run (f->base.loop);
  g_assert_cmpint (wp_dbus_get_state (WP_DBUS (dbus)), ==,
      WP_DBUS_STATE_CONNECTED);

  wp_object_deactivate (WP_OBJECT (dbus), WP_DBUS_FEATURE_ENABLED);
  g_assert_cmpint (wp_dbus_get_state (WP_DBUS (dbus)), ==,
      WP_DBUS_STATE_CLOSED);
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/wp/dbus/basic", TestFixture, NULL,
      test_dbus_setup, test_dbus_basic, test_dbus_teardown);
  g_test_add ("/wp/dbus/activation", TestFixture, NULL,
      test_dbus_setup, test_dbus_activation, test_dbus_teardown);

  return g_test_run ();
}
   07070100000176000081A4000000000000000000000001656CC35F000066EC000000000000000000000000000000000000002700000000wireplumber-0.4.17/tests/wp/endpoint.c    /* WirePlumber
 *
 * Copyright © 2019-2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"
#include <pipewire/extensions/session-manager/keys.h>

struct _TestSiEndpoint
{
  WpSessionItem parent;
  const gchar *name;
  const gchar *media_class;
  WpNode *node;
  WpDirection direction;
  gboolean changed_properties;
  WpProxy *impl_endpoint;
};

G_DECLARE_FINAL_TYPE (TestSiEndpoint, test_si_endpoint,
                      TEST, SI_ENDPOINT, WpSessionItem)

static GVariant *
test_si_endpoint_get_registration_info (WpSiEndpoint * item)
{
  TestSiEndpoint *self = TEST_SI_ENDPOINT (item);
  GVariantBuilder b;

  g_variant_builder_init (&b, G_VARIANT_TYPE ("(ssya{ss})"));
  g_variant_builder_add (&b, "s", self->name);
  g_variant_builder_add (&b, "s", self->media_class);
  g_variant_builder_add (&b, "y", (guchar) self->direction);
  g_variant_builder_add (&b, "a{ss}", NULL);

  return g_variant_builder_end (&b);
}

static WpProperties *
test_si_endpoint_get_properties (WpSiEndpoint * item)
{
  TestSiEndpoint *self = TEST_SI_ENDPOINT (item);
  if (self->changed_properties)
    return wp_properties_new ("test.property", "changed-value", NULL);
  else
    return wp_properties_new ("test.property", "test-value", NULL);
}

static void
test_si_endpoint_endpoint_init (WpSiEndpointInterface * iface)
{
  iface->get_registration_info = test_si_endpoint_get_registration_info;
  iface->get_properties = test_si_endpoint_get_properties;
}

G_DEFINE_TYPE_WITH_CODE (TestSiEndpoint, test_si_endpoint, WP_TYPE_SESSION_ITEM,
    G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ENDPOINT, test_si_endpoint_endpoint_init))

static void
test_si_endpoint_init (TestSiEndpoint * self)
{
}

static gpointer
si_endpoint_get_associated_proxy (WpSessionItem * item, GType proxy_type)
{
  TestSiEndpoint * self = TEST_SI_ENDPOINT (item);

  if (proxy_type == WP_TYPE_NODE)
    return self->node ? g_object_ref (self->node) : NULL;

  return NULL;
}

static void
si_endpoint_reset (WpSessionItem * item)
{
  TestSiEndpoint * self = TEST_SI_ENDPOINT (item);

  wp_object_deactivate (WP_OBJECT (self),
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);

  g_clear_object (&self->node);

  WP_SESSION_ITEM_CLASS (test_si_endpoint_parent_class)->reset (item);
}

static void
si_endpoint_disable_active (WpSessionItem *si)
{
  TestSiEndpoint * self = TEST_SI_ENDPOINT (si);

  wp_object_update_features (WP_OBJECT (self), 0,
      WP_SESSION_ITEM_FEATURE_ACTIVE);
}

static void
si_endpoint_disable_exported (WpSessionItem *si)
{
  TestSiEndpoint * self = TEST_SI_ENDPOINT (si);

  g_clear_object (&self->impl_endpoint);
  wp_object_update_features (WP_OBJECT (self), 0,
      WP_SESSION_ITEM_FEATURE_EXPORTED);
}

static void
si_endpoint_enable_active (WpSessionItem *si, WpTransition *transition)
{
  TestSiEndpoint * self = TEST_SI_ENDPOINT (si);

  wp_object_update_features (WP_OBJECT (self),
      WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
}

static void
on_impl_endpoint_activated (WpObject * object, GAsyncResult * res,
    WpTransition * transition)
{
  TestSiEndpoint *self = wp_transition_get_source_object (transition);
  g_autoptr (GError) error = NULL;

  if (!wp_object_activate_finish (object, res, &error)) {
    wp_transition_return_error (transition, g_steal_pointer (&error));
    return;
  }

  wp_object_update_features (WP_OBJECT (self),
          WP_SESSION_ITEM_FEATURE_EXPORTED, 0);
}

static void
si_endpoint_enable_exported (WpSessionItem *si, WpTransition *transition)
{
  TestSiEndpoint * self = TEST_SI_ENDPOINT (si);
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));

  self->impl_endpoint = WP_PROXY (wp_impl_endpoint_new (core,
      WP_SI_ENDPOINT (self)));
  g_signal_connect_object (self->impl_endpoint, "pw-proxy-destroyed",
      G_CALLBACK (wp_session_item_handle_proxy_destroyed), self, 0);

  wp_object_activate (WP_OBJECT (self->impl_endpoint),
      WP_OBJECT_FEATURES_ALL, NULL,
      (GAsyncReadyCallback) on_impl_endpoint_activated, transition);
}

static void
test_si_endpoint_class_init (TestSiEndpointClass * klass)
{
  WpSessionItemClass *item_class = (WpSessionItemClass *) klass;

  item_class->reset = si_endpoint_reset;
  item_class->get_associated_proxy = si_endpoint_get_associated_proxy;
  item_class->disable_active = si_endpoint_disable_active;
  item_class->disable_exported = si_endpoint_disable_exported;
  item_class->enable_active = si_endpoint_enable_active;
  item_class->enable_exported = si_endpoint_enable_exported;
}

/*******************/

typedef struct {
  WpBaseTestFixture base;

  WpObjectManager *export_om;
  WpObjectManager *proxy_om;

  WpProxy *impl_endpoint;
  WpProxy *proxy_endpoint;

  gint n_events;

} TestEndpointFixture;

static void
test_endpoint_setup (TestEndpointFixture *self, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&self->base, WP_BASE_TEST_FLAG_CLIENT_CORE);
  self->export_om = wp_object_manager_new ();
  self->proxy_om = wp_object_manager_new ();
}

static void
test_endpoint_teardown (TestEndpointFixture *self, gconstpointer user_data)
{
  g_clear_object (&self->proxy_om);
  g_clear_object (&self->export_om);
  wp_base_test_fixture_teardown (&self->base);
}

static void
test_endpoint_impl_object_added (WpObjectManager *om,
    WpEndpoint *endpoint, TestEndpointFixture *fixture)
{
  g_debug ("impl object added");

  g_assert_true (WP_IS_ENDPOINT (endpoint));
  g_assert_cmpstr (G_OBJECT_TYPE_NAME (endpoint), ==, "WpImplEndpoint");

  g_assert_null (fixture->impl_endpoint);
  fixture->impl_endpoint = WP_PROXY (endpoint);

  if (++fixture->n_events == 3)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_endpoint_impl_object_removed (WpObjectManager *om,
    WpEndpoint *endpoint, TestEndpointFixture *fixture)
{
  g_debug ("impl object removed");

  g_assert_true (WP_IS_ENDPOINT (endpoint));
  g_assert_cmpstr (G_OBJECT_TYPE_NAME (endpoint), ==, "WpImplEndpoint");

  g_assert_nonnull (fixture->impl_endpoint);
  fixture->impl_endpoint = NULL;

  if (++fixture->n_events == 2)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_endpoint_proxy_object_added (WpObjectManager *om,
    WpEndpoint *endpoint, TestEndpointFixture *fixture)
{
  g_debug ("proxy object added");

  g_assert_true (WP_IS_ENDPOINT (endpoint));
  g_assert_cmpstr (G_OBJECT_TYPE_NAME (endpoint), ==, "WpEndpoint");

  g_assert_null (fixture->proxy_endpoint);
  fixture->proxy_endpoint = WP_PROXY (endpoint);

  if (++fixture->n_events == 3)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_endpoint_proxy_object_removed (WpObjectManager *om,
    WpEndpoint *endpoint, TestEndpointFixture *fixture)
{
  g_debug ("proxy object removed");

  g_assert_true (WP_IS_ENDPOINT (endpoint));
  g_assert_cmpstr (G_OBJECT_TYPE_NAME (endpoint), ==, "WpEndpoint");

  g_assert_nonnull (fixture->proxy_endpoint);
  fixture->proxy_endpoint = NULL;

  if (++fixture->n_events == 2)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_endpoint_activate_done (WpObject * object, GAsyncResult * res,
    TestEndpointFixture *fixture)
{
  g_autoptr (GError) error = NULL;

  g_debug ("activate done");

  g_assert_true (wp_object_activate_finish (object, res, &error));
  g_assert_no_error (error);

  if (++fixture->n_events == 3)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_endpoint_params_changed (WpPipewireObject * proxy,
    const gchar * param_name, TestEndpointFixture *fixture)
{
  wp_debug_object (proxy, "params changed: %s", param_name);

  /* only count changes of id 2 (Props); PipeWire 0.3.22+git changed
     behaviour and emits changes to PropInfo as well then the Props change */
  if (!g_strcmp0 (param_name, "Props") && ++fixture->n_events == 3)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_endpoint_notify_properties (WpEndpoint * endpoint, GParamSpec * param,
    TestEndpointFixture *fixture)
{
  g_debug ("properties changed: %s", G_OBJECT_TYPE_NAME (endpoint));

  g_assert_true (WP_IS_ENDPOINT (endpoint));

  if (++fixture->n_events == 2)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_endpoint_no_props (TestEndpointFixture *fixture, gconstpointer data)
{
  g_autoptr (TestSiEndpoint) endpoint = NULL;

  /* set up the export side */
  g_signal_connect (fixture->export_om, "object-added",
      (GCallback) test_endpoint_impl_object_added, fixture);
  g_signal_connect (fixture->export_om, "object-removed",
      (GCallback) test_endpoint_impl_object_removed, fixture);
  wp_object_manager_add_interest (fixture->export_om, WP_TYPE_ENDPOINT, NULL);
  wp_object_manager_request_object_features (fixture->export_om,
      WP_TYPE_ENDPOINT, WP_OBJECT_FEATURES_ALL);
  wp_core_install_object_manager (fixture->base.core, fixture->export_om);

  /* set up the proxy side */
  g_signal_connect (fixture->proxy_om, "object-added",
      (GCallback) test_endpoint_proxy_object_added, fixture);
  g_signal_connect (fixture->proxy_om, "object-removed",
      (GCallback) test_endpoint_proxy_object_removed, fixture);
  wp_object_manager_add_interest (fixture->proxy_om, WP_TYPE_ENDPOINT, NULL);
  wp_object_manager_request_object_features (fixture->proxy_om,
      WP_TYPE_ENDPOINT, WP_OBJECT_FEATURES_ALL);
  wp_core_install_object_manager (fixture->base.client_core, fixture->proxy_om);

  /* create endpoint */
  endpoint = g_object_new (test_si_endpoint_get_type (),
      "core", fixture->base.core, NULL);
  endpoint->name = "test-endpoint";
  endpoint->media_class = "Audio/Source";
  endpoint->direction = WP_DIRECTION_OUTPUT;
  wp_object_activate (WP_OBJECT (endpoint),
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED,
      NULL, (GAsyncReadyCallback) test_endpoint_activate_done, fixture);

  /* run until objects are created and features are cached */
  fixture->n_events = 0;
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (endpoint)), ==,
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);
  g_assert_cmpint (fixture->n_events, ==, 3);
  g_assert_nonnull (fixture->impl_endpoint);
  g_assert_nonnull (fixture->proxy_endpoint);

  /* verify the values on the proxy */

  g_assert_cmphex (
      wp_object_get_active_features (WP_OBJECT (fixture->proxy_endpoint)), ==,
      wp_object_get_supported_features (WP_OBJECT (fixture->proxy_endpoint)));
  g_assert_cmpuint (wp_proxy_get_bound_id (fixture->proxy_endpoint), ==,
      wp_proxy_get_bound_id (fixture->impl_endpoint));

  g_assert_cmpstr (wp_pipewire_object_get_property (
          WP_PIPEWIRE_OBJECT (fixture->proxy_endpoint), "test.property"),
      ==, "test-value");

  {
    g_autoptr (WpProperties) props = wp_global_proxy_get_global_properties (
        WP_GLOBAL_PROXY (fixture->proxy_endpoint));

    g_assert_cmpstr (wp_properties_get (props, PW_KEY_ENDPOINT_NAME), ==,
        "test-endpoint");
    g_assert_cmpstr (wp_properties_get (props, PW_KEY_MEDIA_CLASS), ==,
        "Audio/Source");
  }

  g_assert_cmpstr ("test-endpoint", ==,
      wp_endpoint_get_name (WP_ENDPOINT (fixture->proxy_endpoint)));
  g_assert_cmpstr ("Audio/Source", ==,
      wp_endpoint_get_media_class (WP_ENDPOINT (fixture->proxy_endpoint)));
  g_assert_cmpint (WP_DIRECTION_OUTPUT, ==,
      wp_endpoint_get_direction (WP_ENDPOINT (fixture->proxy_endpoint)));

  /* test property changes */
  g_signal_connect (fixture->proxy_endpoint, "notify::properties",
      (GCallback) test_endpoint_notify_properties, fixture);
  g_signal_connect (fixture->impl_endpoint, "notify::properties",
      (GCallback) test_endpoint_notify_properties, fixture);

  /* change a property on the impl */
  fixture->n_events = 0;
  endpoint->changed_properties = TRUE;
  g_signal_emit_by_name (endpoint, "endpoint-properties-changed");

  /* run until the change is on both sides */
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 2);

  /* verify the property change on both sides */
  {
    g_autoptr (WpProperties) props = wp_pipewire_object_get_properties (
        WP_PIPEWIRE_OBJECT (fixture->impl_endpoint));
    g_assert_cmpstr (wp_properties_get (props, "test.property"), ==,
        "changed-value");
  }
  {
    g_autoptr (WpProperties) props = wp_pipewire_object_get_properties (
        WP_PIPEWIRE_OBJECT (fixture->proxy_endpoint));
    g_assert_cmpstr (wp_properties_get (props, "test.property"), ==,
        "changed-value");
  }

  /* destroy impl endpoint */
  fixture->n_events = 0;
  g_clear_object (&endpoint);

  /* run until objects are destroyed */
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 2);
  g_assert_null (fixture->impl_endpoint);
  g_assert_null (fixture->proxy_endpoint);
}

static void
test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
{
  g_autoptr (TestSiEndpoint) endpoint = NULL;

  /* load modules */
  {
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&fixture->base.server);

    g_assert_cmpint (pw_context_add_spa_lib (fixture->base.server.context,
            "audiotestsrc", "audiotestsrc/libspa-audiotestsrc"), ==, 0);
    if (!test_is_spa_lib_installed (&fixture->base, "audiotestsrc")) {
      g_test_skip ("The pipewire audiotestsrc factory was not found");
      return;
    }

    g_assert_nonnull (pw_context_load_module (fixture->base.server.context,
            "libpipewire-module-adapter", NULL, NULL));
  }

  /* set up the export side */
  g_signal_connect (fixture->export_om, "object-added",
      (GCallback) test_endpoint_impl_object_added, fixture);
  g_signal_connect (fixture->export_om, "object-removed",
      (GCallback) test_endpoint_impl_object_removed, fixture);
  wp_object_manager_add_interest (fixture->export_om, WP_TYPE_ENDPOINT, NULL);
  wp_object_manager_request_object_features (fixture->export_om,
      WP_TYPE_ENDPOINT, WP_OBJECT_FEATURES_ALL);
  wp_core_install_object_manager (fixture->base.core, fixture->export_om);

  /* set up the proxy side */
  g_signal_connect (fixture->proxy_om, "object-added",
      (GCallback) test_endpoint_proxy_object_added, fixture);
  g_signal_connect (fixture->proxy_om, "object-removed",
      (GCallback) test_endpoint_proxy_object_removed, fixture);
  wp_object_manager_add_interest (fixture->proxy_om, WP_TYPE_ENDPOINT, NULL);
  wp_object_manager_request_object_features (fixture->proxy_om,
      WP_TYPE_ENDPOINT, WP_OBJECT_FEATURES_ALL);
  wp_core_install_object_manager (fixture->base.client_core, fixture->proxy_om);

  /* create endpoint */
  endpoint = g_object_new (test_si_endpoint_get_type (),
      "core", fixture->base.core, NULL);
  endpoint->name = "test-endpoint";
  endpoint->media_class = "Audio/Source";
  endpoint->direction = WP_DIRECTION_OUTPUT;

  /* associate a node that has props */
  endpoint->node = wp_node_new_from_factory (fixture->base.core,
      "adapter",
      wp_properties_new (
          "factory.name", "audiotestsrc",
          "node.name", "audiotestsrc.adapter",
          NULL));
  g_assert_nonnull (endpoint->node);
  wp_object_activate (WP_OBJECT (endpoint->node),
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL, NULL,
      (GAsyncReadyCallback) test_object_activate_finish_cb, fixture);
  g_main_loop_run (fixture->base.loop);

  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (endpoint->node)),
      ==, WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);

  /* activate & export the endpoint */
  wp_object_activate (WP_OBJECT (endpoint),
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED,
      NULL, (GAsyncReadyCallback) test_endpoint_activate_done, fixture);

  /* run until objects are created and features are cached */
  fixture->n_events = 0;
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (endpoint)), ==,
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);
  g_assert_cmpint (fixture->n_events, ==, 3);
  g_assert_nonnull (fixture->impl_endpoint);
  g_assert_nonnull (fixture->proxy_endpoint);

  /* verify features; the endpoint must have also augmented the node */
  g_assert_cmpint (
      wp_object_get_active_features (WP_OBJECT (endpoint->node)), &,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS);
  g_assert_cmphex (
      wp_object_get_active_features (WP_OBJECT (fixture->proxy_endpoint)), &,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS);

  /* verify props */
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (fixture->proxy_endpoint), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    g_assert_nonnull ((pod = g_value_dup_boxed (&item)));

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 1.0f, 0.001);
    g_assert_cmpint (boolean_value, ==, FALSE);
  }

  /* setup change signals */
  g_signal_connect (fixture->proxy_endpoint, "params-changed",
      (GCallback) test_endpoint_params_changed, fixture);
  g_signal_connect (fixture->impl_endpoint, "params-changed",
      (GCallback) test_endpoint_params_changed, fixture);
  g_signal_connect (endpoint->node, "params-changed",
      (GCallback) test_endpoint_params_changed, fixture);

  /* change control on the proxy */
  fixture->n_events = 0;
  wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (fixture->proxy_endpoint),
      "Props", 0,
      wp_spa_pod_new_object ("Spa:Pod:Object:Param:Props", "Props",
          "volume", "f", 0.7f, NULL));

  /* run until the change is on all sides */
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 3);

  /* verify the value change on all sides */
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (fixture->proxy_endpoint), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    pod = g_value_dup_boxed (&item);
    g_assert_nonnull (pod);

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 0.7f, 0.001);
    g_assert_cmpint (boolean_value, ==, FALSE);
  }
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (fixture->impl_endpoint), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    g_assert_nonnull ((pod = g_value_dup_boxed (&item)));

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 0.7f, 0.001);
    g_assert_cmpint (boolean_value, ==, FALSE);
  }
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (endpoint->node), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    g_assert_nonnull ((pod = g_value_dup_boxed (&item)));

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 0.7f, 0.001);
    g_assert_cmpint (boolean_value, ==, FALSE);
  }

  /* change control on the impl */
  fixture->n_events = 0;
  wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (fixture->impl_endpoint),
      "Props", 0,
      wp_spa_pod_new_object ("Spa:Pod:Object:Param:Props", "Props",
          "mute", "b", TRUE, NULL));

  /* run until the change is on all sides */
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 3);

  /* verify the value change on all sides */
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (fixture->proxy_endpoint), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    pod = g_value_dup_boxed (&item);
    g_assert_nonnull (pod);

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 0.7f, 0.001);
    g_assert_cmpint (boolean_value, ==, TRUE);
  }
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (fixture->impl_endpoint), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    g_assert_nonnull ((pod = g_value_dup_boxed (&item)));

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 0.7f, 0.001);
    g_assert_cmpint (boolean_value, ==, TRUE);
  }
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (endpoint->node), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    g_assert_nonnull ((pod = g_value_dup_boxed (&item)));

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 0.7f, 0.001);
    g_assert_cmpint (boolean_value, ==, TRUE);
  }

  /* change control on the node */
  fixture->n_events = 0;
  wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (endpoint->node),
      "Props", 0,
      wp_spa_pod_new_object ("Spa:Pod:Object:Param:Props", "Props",
          "volume", "f", 0.2f, NULL));

  /* run until the change is on all sides */
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 3);

  /* verify the value change on all sides */
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (fixture->proxy_endpoint), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    g_assert_nonnull ((pod = g_value_dup_boxed (&item)));

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 0.2f, 0.001);
    g_assert_cmpint (boolean_value, ==, TRUE);
  }
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (fixture->impl_endpoint), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    g_assert_nonnull ((pod = g_value_dup_boxed (&item)));

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 0.2f, 0.001);
    g_assert_cmpint (boolean_value, ==, TRUE);
  }
  {
    g_autoptr (WpIterator) iterator = NULL;
    g_auto (GValue) item = G_VALUE_INIT;
    g_autoptr (WpSpaPod) pod = NULL;
    gfloat float_value = 0.0f;
    gboolean boolean_value = TRUE;

    iterator = wp_pipewire_object_enum_params_sync (
        WP_PIPEWIRE_OBJECT (endpoint->node), "Props", NULL);
    g_assert_nonnull (iterator);

    g_assert_true (wp_iterator_next (iterator, &item));
    g_assert_nonnull ((pod = g_value_dup_boxed (&item)));

    g_assert_true (wp_spa_pod_get_object (pod, NULL,
            "volume", "f", &float_value,
            "mute", "b", &boolean_value,
            NULL));
    g_assert_cmpfloat_with_epsilon (float_value, 0.2f, 0.001);
    g_assert_cmpint (boolean_value, ==, TRUE);
  }

  /* destroy impl endpoint */
  fixture->n_events = 0;
  g_clear_object (&endpoint);

  /* run until objects are destroyed */
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 2);
  g_assert_null (fixture->impl_endpoint);
  g_assert_null (fixture->proxy_endpoint);
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/wp/endpoint/no-props", TestEndpointFixture, NULL,
      test_endpoint_setup, test_endpoint_no_props, test_endpoint_teardown);
  g_test_add ("/wp/endpoint/with-props", TestEndpointFixture, NULL,
      test_endpoint_setup, test_endpoint_with_props, test_endpoint_teardown);

  return g_test_run ();
}
07070100000177000081A4000000000000000000000001656CC35F000006DF000000000000000000000000000000000000002600000000wireplumber-0.4.17/tests/wp/factory.c /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */
#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;
  WpObjectManager *om;
} TestFixture;

static void
test_factory_setup (TestFixture *self, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&self->base, 0);
  self->om = wp_object_manager_new ();
}

static void
test_factory_teardown (TestFixture *self, gconstpointer user_data)
{
  g_clear_object (&self->om);
  wp_base_test_fixture_teardown (&self->base);
}

static void
test_factory_enumeration_object_added (WpObjectManager *om,
    WpFactory *factory, TestFixture *fixture)
{
  g_autoptr (WpProperties) properties =
    wp_global_proxy_get_global_properties(WP_GLOBAL_PROXY(factory));
  const gchar* name = wp_properties_get (properties, PW_KEY_FACTORY_NAME);
  g_assert_nonnull(name);
  g_debug("factory name=%s", name);

  /* among all the pw factory objects look for client-node-factory object */
  if (!g_strcmp0(name, "client-node"))
    g_main_loop_quit(fixture->base.loop);
}

static void
test_factory_enumeration (TestFixture *self, gconstpointer user_data)
{
  g_signal_connect (self->om, "object_added",
      (GCallback) test_factory_enumeration_object_added, self);

  wp_object_manager_add_interest(self->om, WP_TYPE_FACTORY, NULL);
  wp_core_install_object_manager(self->base.core, self->om);
  g_main_loop_run(self->base.loop);
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/wp/factory/enumeration", TestFixture, NULL,
      test_factory_setup, test_factory_enumeration, test_factory_teardown);

  return g_test_run ();
}
 07070100000178000081A4000000000000000000000001656CC35F000009DD000000000000000000000000000000000000002800000000wireplumber-0.4.17/tests/wp/meson.build   common_deps = [gobject_dep, gio_dep, wp_dep, pipewire_dep]
common_env = common_test_env
common_env.set('G_TEST_SRCDIR', meson.current_source_dir())
common_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
common_args = [
  '-D_GNU_SOURCE',
  '-DG_LOG_USE_STRUCTURED',
]

test(
  'test-core',
  executable('test-core', 'core.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-endpoint',
  executable('test-endpoint', 'endpoint.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-metadata',
  executable('test-metadata', 'metadata.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-object-interest',
  executable('test-object-interest', 'object-interest.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-object-manager',
  executable('test-object-manager', 'object-manager.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-properties',
  executable('test-properties', 'properties.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-proxy',
  executable('test-proxy', 'proxy.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-session-item',
  executable('test-session-item', 'session-item.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-spa-json',
  executable('test-spa-json', 'spa-json.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-spa-pod',
  executable('test-spa-pod', 'spa-pod.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-spa-type',
  executable('test-spa-type', 'spa-type.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-state',
  executable('test-state', 'state.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

if get_option('dbus-tests')
  test(
    'test-dbus',
    executable('test-dbus', 'dbus.c',
        dependencies: common_deps, c_args: common_args),
    env: common_env,
  )
endif

test(
  'test-transition',
  executable('test-transition', 'transition.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)

test(
  'test-factory',
  executable('test-factory', 'factory.c',
      dependencies: common_deps, c_args: common_args),
  env: common_env,
)
   07070100000179000081A4000000000000000000000001656CC35F00003E0D000000000000000000000000000000000000002700000000wireplumber-0.4.17/tests/wp/metadata.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;

  WpObjectManager *export_om;
  WpObjectManager *proxy_om;

  WpMetadata *impl_metadata;
  WpMetadata *proxy_metadata;

  gint n_events;

} TestFixture;

static void
test_metadata_setup (TestFixture *self, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&self->base, WP_BASE_TEST_FLAG_CLIENT_CORE);
  self->export_om = wp_object_manager_new ();
  self->proxy_om = wp_object_manager_new ();
}

static void
test_metadata_teardown (TestFixture *self, gconstpointer user_data)
{
  g_clear_object (&self->proxy_om);
  g_clear_object (&self->export_om);
  wp_base_test_fixture_teardown (&self->base);
}

static void
test_metadata_basic_exported_object_added (WpObjectManager *om,
    WpMetadata *metadata, TestFixture *fixture)
{
  g_debug ("exported object added");

  g_assert_true (WP_IS_IMPL_METADATA (metadata));

  g_assert_null (fixture->impl_metadata);
  fixture->impl_metadata = WP_METADATA (metadata);

  if (++fixture->n_events == 3)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_metadata_basic_exported_object_removed (WpObjectManager *om,
    WpMetadata *metadata, TestFixture *fixture)
{
  g_debug ("exported object removed");

  g_assert_true (WP_IS_IMPL_METADATA (metadata));

  g_assert_nonnull (fixture->impl_metadata);
  fixture->impl_metadata = NULL;

  if (++fixture->n_events == 2)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_metadata_basic_proxy_object_added (WpObjectManager *om,
    WpMetadata *metadata, TestFixture *fixture)
{
  g_debug ("proxy object added");

  g_assert_true (WP_IS_METADATA (metadata));

  g_assert_null (fixture->proxy_metadata);
  fixture->proxy_metadata = WP_METADATA (metadata);

  if (++fixture->n_events == 3)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_metadata_basic_proxy_object_removed (WpObjectManager *om,
    WpMetadata *metadata, TestFixture *fixture)
{
  g_debug ("proxy object removed");

  g_assert_true (WP_IS_METADATA (metadata));

  g_assert_nonnull (fixture->proxy_metadata);
  fixture->proxy_metadata = NULL;

  if (++fixture->n_events == 2)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_metadata_basic_export_done (WpObject * metadata, GAsyncResult * res,
    TestFixture *fixture)
{
  g_autoptr (GError) error = NULL;

  g_debug ("export done");

  g_assert_true (wp_object_activate_finish (metadata, res, &error));
  g_assert_no_error (error);

  g_assert_true (WP_IS_IMPL_METADATA (metadata));

  if (++fixture->n_events == 3)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_metadata_basic_changed (WpMetadata *metadata, guint32 subject,
    const gchar *key, const gchar *type, const gchar *value,
    TestFixture *fixture)
{
  g_debug ("changed: %u %s", subject, key);

  g_assert_true (WP_IS_METADATA (metadata));
  g_assert_cmpuint (subject, !=, PW_ID_ANY);

  if (++fixture->n_events == 4)
    g_main_loop_quit (fixture->base.loop);
}

static void
test_metadata_basic (TestFixture *fixture, gconstpointer data)
{
  g_autoptr (WpMetadata) metadata = NULL;

  /* set up the export side */
  g_signal_connect (fixture->export_om, "object_added",
      (GCallback) test_metadata_basic_exported_object_added, fixture);
  g_signal_connect (fixture->export_om, "object-removed",
      (GCallback) test_metadata_basic_exported_object_removed, fixture);
  wp_object_manager_add_interest (fixture->export_om, WP_TYPE_IMPL_METADATA,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s", "default",
      NULL);
  wp_object_manager_request_object_features (fixture->export_om,
      WP_TYPE_IMPL_METADATA, WP_OBJECT_FEATURES_ALL);
  wp_core_install_object_manager (fixture->base.core, fixture->export_om);

  /* set up the proxy side */
  g_signal_connect (fixture->proxy_om, "object_added",
      (GCallback) test_metadata_basic_proxy_object_added, fixture);
  g_signal_connect (fixture->proxy_om, "object-removed",
      (GCallback) test_metadata_basic_proxy_object_removed, fixture);
  wp_object_manager_add_interest (fixture->proxy_om, WP_TYPE_METADATA,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s", "default",
      NULL);
  wp_object_manager_request_object_features (fixture->proxy_om,
      WP_TYPE_METADATA, WP_OBJECT_FEATURES_ALL);
  wp_core_install_object_manager (fixture->base.client_core, fixture->proxy_om);

  /* create metadata */
  metadata = WP_METADATA (wp_impl_metadata_new (fixture->base.core));
  wp_metadata_set (metadata, 0, "test-key", NULL, "test-value");
  wp_metadata_set (metadata, 15, "toast", "Spa:Int", "15");

  /* verify properties are set before export */
  {
    g_autoptr (WpIterator) iter = wp_metadata_new_iterator (metadata, PW_ID_ANY);
    g_auto (GValue) val = G_VALUE_INIT;
    guint subject = -1;
    const gchar *key = NULL, *type = NULL, *value = NULL;

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "test-key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "test-value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 15);
    g_assert_cmpstr (key, ==, "toast");
    g_assert_cmpstr (type, ==, "Spa:Int");
    g_assert_cmpstr (value, ==, "15");
    g_value_unset (&val);

    g_assert_false (wp_iterator_next (iter, &val));
  }

  /* do export */
  wp_object_activate (WP_OBJECT (metadata), WP_OBJECT_FEATURES_ALL, NULL,
      (GAsyncReadyCallback) test_metadata_basic_export_done, fixture);

  /* run until objects are created and features are cached */
  fixture->n_events = 0;
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 3);
  g_assert_nonnull (fixture->impl_metadata);
  g_assert_nonnull (fixture->proxy_metadata);
  g_assert_true (fixture->impl_metadata == metadata);

  /* test round 1: verify the values on the proxy */
  {
    g_autoptr (WpIterator) iter =
        wp_metadata_new_iterator (fixture->proxy_metadata, PW_ID_ANY);
    g_auto (GValue) val = G_VALUE_INIT;
    guint subject = -1;
    const gchar *key = NULL, *type = NULL, *value = NULL;

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "test-key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "test-value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 15);
    g_assert_cmpstr (key, ==, "toast");
    g_assert_cmpstr (type, ==, "Spa:Int");
    g_assert_cmpstr (value, ==, "15");
    g_value_unset (&val);

    g_assert_false (wp_iterator_next (iter, &val));
  }

  /* setup change signals */
  g_signal_connect (fixture->proxy_metadata, "changed",
      (GCallback) test_metadata_basic_changed, fixture);
  g_signal_connect (metadata, "changed",
      (GCallback) test_metadata_basic_changed, fixture);

  /* change properties on the proxy */
  wp_metadata_set (fixture->proxy_metadata, 15, "toast", "Spa:Int", "20");
  wp_metadata_set (fixture->proxy_metadata, 0, "3rd.key", NULL, "3rd.value");

  /* run until the change is on both sides */
  fixture->n_events = 0;
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 4);

  /* test round 2: verify the value change on both sides */
  {
    g_autoptr (WpIterator) iter =
        wp_metadata_new_iterator (fixture->proxy_metadata, PW_ID_ANY);
    g_auto (GValue) val = G_VALUE_INIT;
    guint subject = -1;
    const gchar *key = NULL, *type = NULL, *value = NULL;

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "test-key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "test-value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 15);
    g_assert_cmpstr (key, ==, "toast");
    g_assert_cmpstr (type, ==, "Spa:Int");
    g_assert_cmpstr (value, ==, "20");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "3rd.key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "3rd.value");
    g_value_unset (&val);

    g_assert_false (wp_iterator_next (iter, &val));
  }
  {
    g_autoptr (WpIterator) iter =
        wp_metadata_new_iterator (metadata, PW_ID_ANY);
    g_auto (GValue) val = G_VALUE_INIT;
    guint subject = -1;
    const gchar *key = NULL, *type = NULL, *value = NULL;

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "test-key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "test-value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 15);
    g_assert_cmpstr (key, ==, "toast");
    g_assert_cmpstr (type, ==, "Spa:Int");
    g_assert_cmpstr (value, ==, "20");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "3rd.key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "3rd.value");
    g_value_unset (&val);

    g_assert_false (wp_iterator_next (iter, &val));
  }

  /* change properties on the exported */
  fixture->n_events = 0;
  wp_metadata_set (metadata, 0, "4th.key", NULL, "4th.value");
  wp_metadata_set (metadata, 0, "test-key", NULL, "new.value");

  /* run until the change is on both sides */
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 4);

  /* test round 3: verify the value change on both sides */
  {
    g_autoptr (WpIterator) iter =
        wp_metadata_new_iterator (fixture->proxy_metadata, PW_ID_ANY);
    g_auto (GValue) val = G_VALUE_INIT;
    guint subject = -1;
    const gchar *key = NULL, *type = NULL, *value = NULL;

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "test-key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "new.value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 15);
    g_assert_cmpstr (key, ==, "toast");
    g_assert_cmpstr (type, ==, "Spa:Int");
    g_assert_cmpstr (value, ==, "20");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "3rd.key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "3rd.value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "4th.key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "4th.value");
    g_value_unset (&val);

    g_assert_false (wp_iterator_next (iter, &val));
  }
  {
    g_autoptr (WpIterator) iter =
        wp_metadata_new_iterator (metadata, PW_ID_ANY);
    g_auto (GValue) val = G_VALUE_INIT;
    guint subject = -1;
    const gchar *key = NULL, *type = NULL, *value = NULL;

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "test-key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "new.value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 15);
    g_assert_cmpstr (key, ==, "toast");
    g_assert_cmpstr (type, ==, "Spa:Int");
    g_assert_cmpstr (value, ==, "20");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "3rd.key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "3rd.value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "4th.key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "4th.value");
    g_value_unset (&val);

    g_assert_false (wp_iterator_next (iter, &val));
  }

  /* find with constraints */
  {
    g_autoptr (WpIterator) iter = wp_metadata_new_iterator (metadata, 0);
    g_auto (GValue) val = G_VALUE_INIT;
    guint subject = -1;
    const gchar *key = NULL, *type = NULL, *value = NULL;

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "test-key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "new.value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "3rd.key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "3rd.value");
    g_value_unset (&val);

    g_assert_true (wp_iterator_next (iter, &val));
    wp_metadata_iterator_item_extract (&val, &subject, &key, &type, &value);
    g_assert_cmpint (subject, ==, 0);
    g_assert_cmpstr (key, ==, "4th.key");
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "4th.value");
    g_value_unset (&val);

    g_assert_false (wp_iterator_next (iter, &val));
  }
  {
    const gchar *value = NULL, *type = NULL;
    value = wp_metadata_find (metadata, 0, "3rd.key", &type);
    g_assert_cmpstr (type, ==, "string");
    g_assert_cmpstr (value, ==, "3rd.value");
  }

  /* destroy impl metadata */
  fixture->n_events = 0;
  g_clear_object (&metadata);

  /* run until objects are destroyed */
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_events, ==, 2);
  g_assert_null (fixture->impl_metadata);
  g_assert_null (fixture->proxy_metadata);
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/wp/metadata/basic", TestFixture, NULL,
      test_metadata_setup, test_metadata_basic, test_metadata_teardown);

  return g_test_run ();
}
   0707010000017A000081A4000000000000000000000001656CC35F00007394000000000000000000000000000000000000002E00000000wireplumber-0.4.17/tests/wp/object-interest.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>

enum {
  PROP_0,
  PROP_TEST_STRING,
  PROP_TEST_INT,
  PROP_TEST_UINT,
  PROP_TEST_INT64,
  PROP_TEST_UINT64,
  PROP_TEST_FLOAT,
  PROP_TEST_DOUBLE,
  PROP_TEST_BOOLEAN,
};

struct _TestObjA
{
  GObject parent;
  gchar *test_string;
  gint test_int;
  guint test_uint;
  gint64 test_int64;
  guint64 test_uint64;
  gfloat test_float;
  gdouble test_double;
  gboolean test_boolean;
};

#define TEST_TYPE_A (test_obj_a_get_type ())
G_DECLARE_FINAL_TYPE (TestObjA, test_obj_a, TEST, OBJ_A, GObject)
G_DEFINE_TYPE (TestObjA, test_obj_a, G_TYPE_OBJECT)

static void
test_obj_a_init (TestObjA * self)
{
}

static void
test_obj_a_finalize (GObject * object)
{
  TestObjA *self = TEST_OBJ_A (object);
  g_free (self->test_string);
  G_OBJECT_CLASS (test_obj_a_parent_class)->finalize (object);
}

static void
test_obj_a_get_property (GObject * object, guint id, GValue * value,
    GParamSpec * pspec)
{
  TestObjA *self = TEST_OBJ_A (object);

  switch (id) {
    case PROP_TEST_STRING:
      g_value_set_string (value, self->test_string);
      break;
    case PROP_TEST_INT:
      g_value_set_int (value, self->test_int);
      break;
    case PROP_TEST_UINT:
      g_value_set_uint (value, self->test_uint);
      break;
    case PROP_TEST_INT64:
      g_value_set_int64 (value, self->test_int64);
      break;
    case PROP_TEST_UINT64:
      g_value_set_uint64 (value, self->test_uint64);
      break;
    case PROP_TEST_FLOAT:
      g_value_set_float (value, self->test_float);
      break;
    case PROP_TEST_DOUBLE:
      g_value_set_double (value, self->test_double);
      break;
    case PROP_TEST_BOOLEAN:
      g_value_set_boolean (value, self->test_boolean);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
      break;
  }
}

static void
test_obj_a_set_property (GObject * object, guint id, const GValue * value,
    GParamSpec * pspec)
{
  TestObjA *self = TEST_OBJ_A (object);

  switch (id) {
    case PROP_TEST_STRING:
      self->test_string = g_value_dup_string (value);
      break;
    case PROP_TEST_INT:
      self->test_int = g_value_get_int (value);
      break;
    case PROP_TEST_UINT:
      self->test_uint = g_value_get_uint (value);
      break;
    case PROP_TEST_INT64:
      self->test_int64 = g_value_get_int64 (value);
      break;
    case PROP_TEST_UINT64:
      self->test_uint64 = g_value_get_uint64 (value);
      break;
    case PROP_TEST_FLOAT:
      self->test_float = g_value_get_float (value);
      break;
    case PROP_TEST_DOUBLE:
      self->test_double = g_value_get_double (value);
      break;
    case PROP_TEST_BOOLEAN:
      self->test_boolean = g_value_get_boolean (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
      break;
  }
}

static void
test_obj_a_class_init (TestObjAClass * klass)
{
  GObjectClass *obj_class = (GObjectClass *) klass;

  obj_class->finalize = test_obj_a_finalize;
  obj_class->get_property = test_obj_a_get_property;
  obj_class->set_property = test_obj_a_set_property;

  g_object_class_install_property (obj_class, PROP_TEST_STRING,
      g_param_spec_string ("test-string", "test-string", "blurb", NULL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_INT,
      g_param_spec_int ("test-int", "test-int", "blurb",
          G_MININT, G_MAXINT, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_UINT,
      g_param_spec_uint ("test-uint", "test-uint", "blurb",
          0, G_MAXUINT, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_INT64,
      g_param_spec_int64 ("test-int64", "test-int64", "blurb",
          G_MININT64, G_MAXINT64, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_UINT64,
      g_param_spec_uint64 ("test-uint64", "test-uint64", "blurb",
          0, G_MAXUINT64, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_FLOAT,
      g_param_spec_float ("test-float", "test-float", "blurb",
          -20.0f, 20.0f, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_DOUBLE,
      g_param_spec_double ("test-double", "test-double", "blurb",
          -20.0, 20.0, 0.0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_BOOLEAN,
      g_param_spec_boolean ("test-boolean", "test-boolean", "blurb", FALSE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

struct _TestObjB
{
  TestObjA parent;
};

#define TEST_TYPE_B (test_obj_b_get_type ())
G_DECLARE_FINAL_TYPE (TestObjB, test_obj_b, TEST, OBJ_B, TestObjA)
G_DEFINE_TYPE (TestObjB, test_obj_b, TEST_TYPE_A)

static void
test_obj_b_init (TestObjB * self)
{
}

static void
test_obj_b_class_init (TestObjBClass * klass)
{
}

typedef struct {
  GObject *object;
} TestFixture;

static void
test_object_interest_setup (TestFixture * f, gconstpointer data)
{
  f->object = g_object_new (TEST_TYPE_B,
      "test-string", "toast",
      "test-int", -30,
      "test-uint", 50,
      "test-int64", G_GINT64_CONSTANT (-0x1d636b02300a7aa7),
      "test-uint64", G_GUINT64_CONSTANT (0x1d636b02300a7aa7),
      "test-float", 3.14f,
      "test-double", 3.1415926545897932384626433,
      "test-boolean", TRUE,
      NULL);
  g_assert_nonnull (f->object);
}

static void
test_object_interest_teardown (TestFixture * f, gconstpointer data)
{
  g_clear_object (&f->object);
}

#define TEST_EXPECT_MATCH(interest) \
  G_STMT_START { \
    g_autoptr (GError) error = NULL; \
    gboolean ret; \
    \
    g_assert_nonnull (interest); \
    \
    ret = wp_object_interest_validate (interest, &error); \
    g_assert_no_error (error); \
    g_assert_true (ret); \
    \
    g_assert_true (wp_object_interest_matches (interest, f->object)); \
    \
    g_clear_pointer (&interest, wp_object_interest_unref); \
  } G_STMT_END

#define TEST_EXPECT_NO_MATCH(interest) \
  G_STMT_START { \
    g_autoptr (GError) error = NULL; \
    gboolean ret; \
    \
    g_assert_nonnull (interest); \
    \
    ret = wp_object_interest_validate (interest, &error); \
    g_assert_no_error (error); \
    g_assert_true (ret); \
    \
    g_assert_false (wp_object_interest_matches (interest, f->object)); \
    \
    g_clear_pointer (&interest, wp_object_interest_unref); \
  } G_STMT_END

#define TEST_EXPECT_MATCH_WP_PROPS(interest, props, global_props) \
  G_STMT_START { \
    g_autoptr (GError) error = NULL; \
    gboolean ret; \
    \
    g_assert_nonnull (interest); \
    \
    ret = wp_object_interest_validate (interest, &error); \
    g_assert_no_error (error); \
    g_assert_true (ret); \
    \
    g_assert_cmphex (wp_object_interest_matches_full (interest, 0, \
        WP_TYPE_NODE, NULL, props, global_props), ==, WP_INTEREST_MATCH_ALL); \
    \
    g_clear_pointer (&interest, wp_object_interest_unref); \
  } G_STMT_END

#define TEST_EXPECT_NO_MATCH_WP_PROPS(interest, props, global_props) \
  G_STMT_START { \
    g_autoptr (GError) error = NULL; \
    gboolean ret; \
    \
    g_assert_nonnull (interest); \
    \
    ret = wp_object_interest_validate (interest, &error); \
    g_assert_no_error (error); \
    g_assert_true (ret); \
    \
    g_assert_cmphex (wp_object_interest_matches_full (interest, 0, \
        WP_TYPE_NODE, NULL, props, global_props), !=, WP_INTEREST_MATCH_ALL); \
    \
    g_clear_pointer (&interest, wp_object_interest_unref); \
  } G_STMT_END

#define TEST_EXPECT_VALIDATION_ERROR(interest) \
  G_STMT_START { \
    g_autoptr (GError) error = NULL; \
    gboolean ret; \
    \
    g_assert_nonnull (interest); \
    \
    ret = wp_object_interest_validate (interest, &error); \
    g_assert_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT); \
    g_assert_false (ret); \
    \
    g_clear_pointer (&interest, wp_object_interest_unref); \
  } G_STMT_END

static void
test_object_interest_unconstrained (TestFixture * f, gconstpointer data)
{
  g_autoptr (WpObjectInterest) i = NULL;

  i = wp_object_interest_new_type (TEST_TYPE_A);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new_type (WP_TYPE_PROXY);
  TEST_EXPECT_NO_MATCH (i);
}

static void
test_object_interest_constraint_equals (TestFixture * f, gconstpointer data)
{
  g_autoptr (WpObjectInterest) i = NULL;

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "=s", "toast", NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "=s", "fail", NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "=i", -30, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "=i", 100, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "=u", 50, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "=u", 100, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64",
      "=x", G_GINT64_CONSTANT (-0x1d636b02300a7aa7), NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64",
      "=x", G_GINT64_CONSTANT (100), NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64",
      "=t", G_GUINT64_CONSTANT (0x1d636b02300a7aa7), NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64",
      "=t", G_GUINT64_CONSTANT (100), NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double",
      "=d", 3.1415926545897932384626433, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "=d", 3.14, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "=d", 3.14, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "=d", 1.0, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-boolean", "=b", TRUE, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-boolean", "=b", FALSE, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "=d", 3.1415926545897932384626433,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "=u", 50,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "=s", "toast",
      NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double",
      "=d", 3.1415926545897932384626433,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "=u", 50,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "=s", "FAIL",
      NULL);
  TEST_EXPECT_NO_MATCH (i);
}

static void
test_object_interest_constraint_not_equals (TestFixture * f, gconstpointer data)
{
  g_autoptr (WpObjectInterest) i = NULL;

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "!s", "toast", NULL);
  TEST_EXPECT_NO_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "!s", "fail", NULL);
  TEST_EXPECT_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "!i", -30, NULL);
  TEST_EXPECT_NO_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "!i", 100, NULL);
  TEST_EXPECT_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "!u", 50, NULL);
  TEST_EXPECT_NO_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "!u", 100, NULL);
  TEST_EXPECT_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64",
      "!x", G_GINT64_CONSTANT (-0x1d636b02300a7aa7), NULL);
  TEST_EXPECT_NO_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64",
      "!x", G_GINT64_CONSTANT (100), NULL);
  TEST_EXPECT_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64",
      "!t", G_GUINT64_CONSTANT (0x1d636b02300a7aa7), NULL);
  TEST_EXPECT_NO_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64",
      "!t", G_GUINT64_CONSTANT (100), NULL);
  TEST_EXPECT_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double",
      "!d", 3.1415926545897932384626433, NULL);
  TEST_EXPECT_NO_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "!d", 3.14, NULL);
  TEST_EXPECT_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "!d", 3.14, NULL);
  TEST_EXPECT_NO_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "!d", 1.0, NULL);
  TEST_EXPECT_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-boolean", "!b", TRUE, NULL);
  TEST_EXPECT_NO_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-boolean", "!b", FALSE, NULL);
  TEST_EXPECT_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "!d", 2.0,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "!u", 51,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "!s", "FAIL",
      WP_CONSTRAINT_TYPE_G_PROPERTY, "unknown-property", "!s", "FAIL",
      NULL);
  TEST_EXPECT_MATCH (i);
}

static void
test_object_interest_constraint_list (TestFixture * f, gconstpointer data)
{
  g_autoptr (WpObjectInterest) i = NULL;

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string",
      "c(sss)", "success", "toast", "test", NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string",
      "c(ss)", "not-a-toast", "fail", NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "c(iii)", -30, 20, -10, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "c(i)", 100, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "c(uu)", 100, 50, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "c(u)", 100, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64", "c(xx)",
      G_GINT64_CONSTANT (100), G_GINT64_CONSTANT (-0x1d636b02300a7aa7), NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64",
      "c(x)", G_GINT64_CONSTANT (100), NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64",
      "c(t)", G_GUINT64_CONSTANT (0x1d636b02300a7aa7), NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64",
      "c(t)", G_GUINT64_CONSTANT (100), NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double",
      "c(dd)", 2.0, 3.1415926545897932384626433, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "c(d)", 3.14, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "c(dd)", 2.0, 3.14, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "c(dd)", 1.0, 2.0, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double",
      "c(d)", 3.1415926545897932384626433,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "c(u)", 50,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "c(ss)", "random", "toast",
      NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double",
      "c(d)", 3.1415926545897932384626433,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "c(u)", 50,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "c(s)", "FAIL",
      NULL);
  TEST_EXPECT_NO_MATCH (i);
}

static void
test_object_interest_constraint_range (TestFixture * f, gconstpointer data)
{
  g_autoptr (WpObjectInterest) i = NULL;

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "~(ii)", -40, 20, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "~(ii)", 10, 100, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "~(uu)", 40, 100, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "~(uu)", 100, 150, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64", "~(xx)",
      G_GINT64_CONSTANT (-0x1d636b02300a7aaa),
      G_GINT64_CONSTANT (-0x1d636b02300a7aa0),
      NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64", "~(xx)",
      G_GINT64_CONSTANT (0),
      G_GINT64_CONSTANT (100),
      NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64", "~(tt)",
      G_GUINT64_CONSTANT (0x1d636b02300a7aa0),
      G_GUINT64_CONSTANT (0x1d636b02300a7aaa),
      NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64", "~(tt)",
      G_GUINT64_CONSTANT (0),
      G_GUINT64_CONSTANT (100),
      NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "~(dd)", 2.0, 4.0, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "~(dd)", -1.0, 3.14, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "~(dd)", 2.0, 4.0, NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "~(dd)", -1.0, 3.13, NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "~(dd)", 0.0, 10.0,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "~(uu)", 0, 100,
      NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "~(dd)", 10.0, 20.0,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "~(uu)", 0, 100,
      NULL);
  TEST_EXPECT_NO_MATCH (i);
}

static void
test_object_interest_constraint_matches (TestFixture * f, gconstpointer data)
{
  g_autoptr (WpObjectInterest) i = NULL;

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "to*", NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "t*st", NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "*a?t", NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "egg*", NULL);
  TEST_EXPECT_NO_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "t?est", NULL);
  TEST_EXPECT_NO_MATCH (i);
}

static void
test_object_interest_constraint_present_absent (TestFixture * f,
    gconstpointer data)
{
  g_autoptr (WpObjectInterest) i = NULL;

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "+", NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "toast", "+", NULL);
  TEST_EXPECT_NO_MATCH (i);


  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "toast", "-", NULL);
  TEST_EXPECT_MATCH (i);

  i = wp_object_interest_new (TEST_TYPE_A,
      WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "-", NULL);
  TEST_EXPECT_NO_MATCH (i);
}

static void
test_object_interest_pw_props (TestFixture * f, gconstpointer data)
{
  g_autoptr (WpProperties) props = NULL;
  g_autoptr (WpProperties) global_props = NULL;
  g_autoptr (WpObjectInterest) i = NULL;

  props = wp_properties_new (
      "object.id", "10",
      "port.name", "test",
      "port.physical", "true",
      "audio.channel", "FR",
      "audio.volume", "0.8",
      "format.dsp", "32 bit float mono audio",
      NULL);

  global_props = wp_properties_new (
      "object.id", "10",
      "format.dsp", "32 bit float mono audio",
      NULL);

  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~(ii)", 0, 100, NULL);
  TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props);

  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=i", 11, NULL);
  TEST_EXPECT_NO_MATCH_WP_PROPS (i, props, global_props);

  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "format.dsp", "#s", "*audio*", NULL);
  TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props);

  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.physical", "=b", TRUE, NULL);
  TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props);

  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "audio.channel", "c(sss)",
      "MONO", "FL", "FR", NULL);
  TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props);

  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "audio.volume", "=d", 0.8, NULL);
  TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props);

  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "audio.volume", "~(dd)", 0.0, 0.5,
      NULL);
  TEST_EXPECT_NO_MATCH_WP_PROPS (i, props, global_props);

  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "=i", 10,
      NULL);
  TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props);

  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "+",
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "format.dsp", "+",
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "port.name", "-",
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "port.physical", "-",
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.name", "+",
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.physical", "+",
      NULL);
  TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props);
}

static void
test_object_interest_validate (TestFixture * f, gconstpointer data)
{
  g_autoptr (WpObjectInterest) i = NULL;

  /* invalid type */
  i = wp_object_interest_new (WP_TYPE_NODE, 32, "object.id", "+", NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);

  /* bad verb; the varargs constructor would assert here */
  i = wp_object_interest_new_type (WP_TYPE_NODE);
  wp_object_interest_add_constraint (i, WP_CONSTRAINT_TYPE_PW_PROPERTY,
      "object.id", 0, g_variant_new_string ("10"));
  TEST_EXPECT_VALIDATION_ERROR (i);

  /* no subject; the varargs version would assert here */
  i = wp_object_interest_new_type (WP_TYPE_NODE);
  wp_object_interest_add_constraint (i, WP_CONSTRAINT_TYPE_PW_PROPERTY,
      NULL, WP_CONSTRAINT_VERB_EQUALS, g_variant_new_int32 (10));
  TEST_EXPECT_VALIDATION_ERROR (i);

  /* no value for verb that requires it */
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=", NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~", NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "c", NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "#", NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);

  /* value given for verb that doesn't require it */
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "+s", "10", NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "-s", "10", NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);

  /* tuple required */
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "ci", 10, NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~i", 10, NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);

  /* invalid value type */
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=y", (guchar) 10, NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=n", (gint16) 10, NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=q", (guint16) 10, NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "c(bb)", TRUE, FALSE, NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~(ss)", "0", "20", NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "#i", 10, NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);

  /* tuple with different types */
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "c(si)", "9", 10, NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
  i = wp_object_interest_new (WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~(iu)", -10, 20, NULL);
  TEST_EXPECT_VALIDATION_ERROR (i);
}

int
main (int argc, char *argv[])
{
  g_test_init (&argc, &argv, NULL);
  g_log_set_writer_func (wp_log_writer_default, NULL, NULL);

  g_test_add ("/wp/object-interest/unconstrained",
      TestFixture, NULL,
      test_object_interest_setup,
      test_object_interest_unconstrained,
      test_object_interest_teardown);

  g_test_add ("/wp/object-interest/equals",
      TestFixture, NULL,
      test_object_interest_setup,
      test_object_interest_constraint_equals,
      test_object_interest_teardown);

  g_test_add ("/wp/object-interest/not-equals",
      TestFixture, NULL,
      test_object_interest_setup,
      test_object_interest_constraint_not_equals,
      test_object_interest_teardown);

  g_test_add ("/wp/object-interest/list",
      TestFixture, NULL,
      test_object_interest_setup,
      test_object_interest_constraint_list,
      test_object_interest_teardown);

  g_test_add ("/wp/object-interest/range",
      TestFixture, NULL,
      test_object_interest_setup,
      test_object_interest_constraint_range,
      test_object_interest_teardown);

  g_test_add ("/wp/object-interest/matches",
      TestFixture, NULL,
      test_object_interest_setup,
      test_object_interest_constraint_matches,
      test_object_interest_teardown);

  g_test_add ("/wp/object-interest/present-absent",
      TestFixture, NULL,
      test_object_interest_setup,
      test_object_interest_constraint_present_absent,
      test_object_interest_teardown);

  g_test_add ("/wp/object-interest/pw-props",
      TestFixture, NULL,
      test_object_interest_setup,
      test_object_interest_pw_props,
      test_object_interest_teardown);

  g_test_add ("/wp/object-interest/validate",
      TestFixture, NULL,
      test_object_interest_setup,
      test_object_interest_validate,
      test_object_interest_teardown);

  return g_test_run ();
}
0707010000017B000081A4000000000000000000000001656CC35F00001548000000000000000000000000000000000000002D00000000wireplumber-0.4.17/tests/wp/object-manager.c  /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"

struct _TestSiDummy
{
  WpSessionItem parent;
};

G_DECLARE_FINAL_TYPE (TestSiDummy, si_dummy, TEST, SI_DUMMY, WpSessionItem)
G_DEFINE_TYPE (TestSiDummy, si_dummy, WP_TYPE_SESSION_ITEM)

static void
si_dummy_init (TestSiDummy * self)
{
}

static gboolean
si_dummy_configure (WpSessionItem * item, WpProperties * props)
{
  TestSiDummy *self = TEST_SI_DUMMY (item);
  wp_session_item_set_properties (WP_SESSION_ITEM (self), props);
  return TRUE;
}

static void
si_dummy_class_init (TestSiDummyClass * klass)
{
  WpSessionItemClass *si_class = (WpSessionItemClass *) klass;
  si_class->configure = si_dummy_configure;
}

typedef struct {
  WpBaseTestFixture base;
  WpObjectManager *om;
} TestFixture;

static void
test_om_setup (TestFixture *self, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&self->base, WP_BASE_TEST_FLAG_CLIENT_CORE);
}

static void
test_om_teardown (TestFixture *self, gconstpointer user_data)
{
  wp_base_test_fixture_teardown (&self->base);
}

static void
test_om_interest_on_pw_props (TestFixture *f, gconstpointer user_data)
{
  g_autoptr (WpNode) node = NULL;
  g_autoptr (WpObjectManager) om = NULL;

  /* load modules on the server side */
  {
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&f->base.server);

    g_assert_cmpint (pw_context_add_spa_lib (f->base.server.context,
            "audiotestsrc", "audiotestsrc/libspa-audiotestsrc"), ==, 0);
    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-adapter", NULL, NULL));
  }

  /* export node on the client core */
  node = wp_node_new_from_factory (f->base.client_core,
      "adapter",
      wp_properties_new (
          "factory.name", "audiotestsrc",
          "node.name", "Test Source",
          "test.answer", "42",
          NULL));
  g_assert_nonnull (node);

  wp_object_activate (WP_OBJECT (node), WP_OBJECT_FEATURES_ALL,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);

  /* ensure the base core is in sync */
  wp_core_sync (f->base.core, NULL, (GAsyncReadyCallback) test_core_done_cb, f);
  g_main_loop_run (f->base.loop);

  /* request that node from the base core */
  om = wp_object_manager_new ();
  wp_object_manager_add_interest (om, WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "node.name", "=s", "Test Source",
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "test.answer", "=s", "42",
      NULL);
  test_ensure_object_manager_is_installed (om, f->base.core, f->base.loop);

  g_assert_cmpuint (wp_object_manager_get_n_objects (om), ==, 1);
  g_clear_object (&om);

  /* request "test.answer" to be absent... this will not match */
  om = wp_object_manager_new ();
  wp_object_manager_add_interest (om, WP_TYPE_NODE,
      WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "node.name", "=s", "Test Source",
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "test.answer", "-",
      NULL);
  test_ensure_object_manager_is_installed (om, f->base.core, f->base.loop);

  g_assert_cmpuint (wp_object_manager_get_n_objects (om), ==, 0);
}

static void
test_om_iterate_remove (TestFixture *f, gconstpointer user_data)
{
  g_autoptr (WpObjectManager) om = NULL;
  WpSessionItem *si = NULL;

  si = g_object_new (si_dummy_get_type (), "core", f->base.core, NULL);
  g_assert_true (wp_session_item_configure (si,
      wp_properties_new ("property1", "4321", NULL)));
  wp_session_item_register (si);

  si = g_object_new (si_dummy_get_type (), "core", f->base.core, NULL);
  g_assert_true (wp_session_item_configure (si,
      wp_properties_new ("property1", "2345", NULL)));
  wp_session_item_register (si);

  si = g_object_new (si_dummy_get_type (), "core", f->base.core, NULL);
  g_assert_true (wp_session_item_configure (si,
      wp_properties_new ("property1", "1234", NULL)));
  wp_session_item_register (si);

  si = g_object_new (si_dummy_get_type (), "core", f->base.core, NULL);
  g_assert_true (wp_session_item_configure (si,
      wp_properties_new ("property1", "1234", NULL)));
  wp_session_item_register (si);

  om = wp_object_manager_new ();
  wp_object_manager_add_interest (om, si_dummy_get_type (), NULL);
  test_ensure_object_manager_is_installed (om, f->base.core, f->base.loop);

  {
    g_autoptr (WpIterator) it = wp_object_manager_new_iterator (om);
    g_auto (GValue) value = G_VALUE_INIT;
    while (wp_iterator_next (it, &value)) {
      si = g_value_get_object (&value);
      g_assert_true (WP_IS_SESSION_ITEM (si));
      if (!g_strcmp0 (wp_session_item_get_property (si, "property1"), "1234")) {
        wp_session_item_remove (si);
      }
      g_value_unset (&value);
    }
  }

  g_assert_cmpint (wp_object_manager_get_n_objects (om), ==, 2);
  g_assert_null (wp_object_manager_lookup (om, si_dummy_get_type (),
      WP_CONSTRAINT_TYPE_PW_PROPERTY, "property1", "=s", "1234", NULL));
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/wp/om/interest-on-pw-props", TestFixture, NULL,
      test_om_setup, test_om_interest_on_pw_props, test_om_teardown);
  g_test_add ("/wp/om/iterate_remove", TestFixture, NULL,
      test_om_setup, test_om_iterate_remove, test_om_teardown);

  return g_test_run ();
}
0707010000017C000081A4000000000000000000000001656CC35F000016F7000000000000000000000000000000000000002900000000wireplumber-0.4.17/tests/wp/properties.c  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <pipewire/pipewire.h>

static void
test_properties_basic (void)
{
  g_autoptr (WpProperties) p = NULL;

  p = wp_properties_new_empty ();
  g_assert_nonnull (p);

  g_assert_cmpint (wp_properties_set (p, "foo.bar", "test-value"), ==, 1);
  g_assert_cmpstr (wp_properties_get (p, "foo.bar"), ==, "test-value");
  g_assert_cmpstr (wp_properties_get (p, "nonexistent"), ==, NULL);

  /* remove the key */
  g_assert_cmpint (wp_properties_set (p, "foo.bar", NULL), ==, 1);
  g_assert_cmpstr (wp_properties_get (p, "foo.bar"), ==, NULL);
  /* now returns 0 because it does not exist */
  g_assert_cmpint (wp_properties_set (p, "foo.bar", NULL), ==, 0);

  g_assert_true (wp_properties_ref (p) == p);
  wp_properties_unref (p);
}

static void
test_properties_wrap_dict (void)
{
  g_autoptr (WpProperties) p = NULL;
  const struct spa_dict_item dict_items[] = {
    { "key1", "value1" },
    { "key2", "value2" },
  };
  const struct spa_dict dict = SPA_DICT_INIT_ARRAY(dict_items);

  p = wp_properties_new_wrap_dict (&dict);
  g_assert_nonnull (p);

  g_assert_cmpint (wp_properties_get_count (p), ==,
      SPA_N_ELEMENTS (dict_items));
  g_assert_cmpstr (wp_properties_get (p, "key1"), ==, "value1");
  g_assert_cmpstr (wp_properties_get (p, "key2"), ==, "value2");
  g_assert_cmpstr (wp_properties_get (p, "key3"), ==, NULL);

  g_assert_true (wp_properties_peek_dict (p) == &dict);
}

static void
test_properties_copy_dict (void)
{
  g_autoptr (WpProperties) p = NULL;
  const struct spa_dict_item dict_items[] = {
    { "key1", "value1" },
    { "key2", "value2" },
  };
  const struct spa_dict dict = SPA_DICT_INIT_ARRAY(dict_items);

  p = wp_properties_new_copy_dict (&dict);
  g_assert_nonnull (p);

  g_assert_cmpstr (wp_properties_get (p, "key1"), ==, "value1");
  g_assert_cmpstr (wp_properties_get (p, "key2"), ==, "value2");
  g_assert_cmpstr (wp_properties_get (p, "key3"), ==, NULL);

  g_assert_true (wp_properties_peek_dict (p) != &dict);
}

static void
test_properties_wrap (void)
{
  g_autoptr (WpProperties) p = NULL;
  struct pw_properties *props;

  props = pw_properties_new ("key1", "value1", NULL);
  g_assert_nonnull (props);
  p = wp_properties_new_wrap (props);
  g_assert_nonnull (p);

  g_assert_true (wp_properties_peek_dict (p) == &props->dict);
  g_assert_cmpstr (wp_properties_get (p, "key1"), ==, "value1");

  wp_properties_unref (g_steal_pointer (&p));
  /* because wrap does not free the original object, this should not crash */
  pw_properties_free (props);
}

static void
test_properties_take (void)
{
  g_autoptr (WpProperties) p = NULL;
  struct pw_properties *props;

  props = pw_properties_new ("key1", "value1", NULL);
  g_assert_nonnull (props);
  p = wp_properties_new_take (props);
  g_assert_nonnull (p);

  g_assert_true (wp_properties_peek_dict (p) == &props->dict);
  g_assert_cmpstr (wp_properties_get (p, "key1"), ==, "value1");

  /* value changes should be reflected on both objects */
  g_assert_cmpint (wp_properties_setf (p, "foobar", "%d", 2), ==, 1);
  g_assert_cmpstr (pw_properties_get (props, "foobar"), ==, "2");

  g_assert_cmpint (pw_properties_setf (props, "test", "some-%s", "value"), ==, 1);
  g_assert_cmpstr (wp_properties_get (p, "test"), ==, "some-value");

  /* no leaks because p frees props */
}

static void
test_properties_to_pw_props (void)
{
  g_autoptr (WpProperties) p = NULL;
  struct pw_properties *props;

  p = wp_properties_new ("key1", "value1", NULL);
  g_assert_nonnull (p);
  g_assert_cmpstr (wp_properties_get (p, "key1"), ==, "value1");

  props = wp_properties_to_pw_properties (p);
  g_assert_nonnull (props);
  g_assert_cmpstr (pw_properties_get (props, "key1"), ==, "value1");

  /* we have different underlying objects */
  g_assert_true (wp_properties_peek_dict (p) != &props->dict);
  g_assert_cmpint (pw_properties_set (props, "test", "some-value"), ==, 1);
  g_assert_cmpstr (wp_properties_get (p, "test"), ==, NULL);

  pw_properties_free (props);
}

static void
test_properties_iterate (void)
{
  g_autoptr (WpProperties) p = NULL;

  p = wp_properties_new_empty ();
  g_assert_nonnull (p);
  g_assert_cmpint (wp_properties_set (p, "key0", "value0"), ==, 1);
  g_assert_cmpint (wp_properties_set (p, "key1", "value1"), ==, 1);
  g_assert_cmpint (wp_properties_set (p, "key2", "value2"), ==, 1);
  g_assert_cmpint (wp_properties_set (p, "key3", "value3"), ==, 1);
  g_assert_cmpint (wp_properties_set (p, "key4", "value4"), ==, 1);

  g_autoptr (WpIterator) it = NULL;
  g_auto (GValue) item = G_VALUE_INIT;
  gint i = 0;
  for (it = wp_properties_new_iterator (p);
          wp_iterator_next (it, &item);
          g_value_unset (&item)) {
    WpPropertiesItem *pi = g_value_get_boxed (&item);
    g_autofree gchar *expected_key = g_strdup_printf ("key%d", i);
    g_autofree gchar *expected_value = g_strdup_printf ("value%d", i);
    g_assert_cmpstr (expected_value, ==, wp_properties_item_get_value (pi));
    g_assert_cmpstr (expected_key, ==, wp_properties_item_get_key (pi));
    i++;
  }
  g_assert_cmpint (i, ==, 5);
}

int
main (int argc, char *argv[])
{
  g_test_init (&argc, &argv, NULL);
  g_log_set_writer_func (wp_log_writer_default, NULL, NULL);

  g_test_add_func ("/wp/properties/basic", test_properties_basic);
  g_test_add_func ("/wp/properties/wrap_dict", test_properties_wrap_dict);
  g_test_add_func ("/wp/properties/copy_dict", test_properties_copy_dict);
  g_test_add_func ("/wp/properties/wrap", test_properties_wrap);
  g_test_add_func ("/wp/properties/take", test_properties_take);
  g_test_add_func ("/wp/properties/to_pw_props", test_properties_to_pw_props);
  g_test_add_func ("/wp/properties/iterate", test_properties_iterate);

  return g_test_run ();
}
 0707010000017D000081A4000000000000000000000001656CC35F00002598000000000000000000000000000000000000002400000000wireplumber-0.4.17/tests/wp/proxy.c   /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;
  /* the object manager that listens for proxies */
  WpObjectManager *om;
} TestFixture;

static void
test_proxy_setup (TestFixture *self, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&self->base, 0);
  self->om = wp_object_manager_new ();
}

static void
test_proxy_teardown (TestFixture *self, gconstpointer user_data)
{
  g_clear_object (&self->om);
  wp_base_test_fixture_teardown (&self->base);
}

static void
test_proxy_basic_activated (WpObject *proxy, GAsyncResult *res,
    TestFixture *fixture)
{
  g_autoptr (GError) error = NULL;
  g_assert_true (wp_object_activate_finish (proxy, res, &error));
  g_assert_no_error (error);

  g_assert_true (wp_object_get_active_features (proxy) & WP_PROXY_FEATURE_BOUND);
  g_assert_nonnull (wp_proxy_get_pw_proxy (WP_PROXY (proxy)));

  g_main_loop_quit (fixture->base.loop);
}

static void
test_proxy_basic_object_added (WpObjectManager *om, WpGlobalProxy *proxy,
    TestFixture *fixture)
{
  g_assert_nonnull (proxy);
  {
    g_autoptr (WpCore) pcore = NULL;
    g_autoptr (WpCore) omcore = NULL;
    g_object_get (proxy, "core", &pcore, NULL);
    g_object_get (om, "core", &omcore, NULL);
    g_assert_nonnull (pcore);
    g_assert_nonnull (omcore);
    g_assert_true (pcore == omcore);
  }
  g_assert_cmphex (wp_global_proxy_get_permissions (proxy), ==, PW_PERM_ALL);
  g_assert_true (WP_IS_CLIENT (proxy));

  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (proxy)), ==, 0);
  g_assert_null (wp_proxy_get_pw_proxy (WP_PROXY (proxy)));

  {
    g_autoptr (WpProperties) props =
        wp_global_proxy_get_global_properties (proxy);
    g_assert_nonnull (props);
    g_assert_cmpstr (wp_properties_get (props, PW_KEY_PROTOCOL), ==,
        "protocol-native");
  }

  wp_object_activate (WP_OBJECT (proxy), WP_PROXY_FEATURE_BOUND, NULL,
      (GAsyncReadyCallback) test_proxy_basic_activated, fixture);
}

static void
test_proxy_basic (TestFixture *fixture, gconstpointer data)
{
  /* our test server should advertise exactly one
   * client: our core; use this to test WpProxy */
  g_signal_connect (fixture->om, "object-added",
      (GCallback) test_proxy_basic_object_added, fixture);

  wp_object_manager_add_interest (fixture->om, WP_TYPE_CLIENT, NULL);
  wp_core_install_object_manager (fixture->base.core, fixture->om);

  g_main_loop_run (fixture->base.loop);
}

static void
test_node_enum_params_done (WpPipewireObject *node, GAsyncResult *res,
    TestFixture *f)
{
  g_autoptr (WpIterator) params = NULL;
  g_autoptr (GError) error = NULL;
  g_auto (GValue) item = G_VALUE_INIT;
  guint n_params = 0;

  params = wp_pipewire_object_enum_params_finish (node, res, &error);
  g_assert_no_error (error);
  g_assert_nonnull (params);

  for (; wp_iterator_next (params, &item); g_value_unset (&item)) {
    WpSpaPod *pod = NULL;
    g_assert_cmpuint (G_VALUE_TYPE (&item), ==, WP_TYPE_SPA_POD);
    pod = g_value_get_boxed (&item);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_object (pod));
    g_assert_cmpuint (wp_spa_type_from_name ("Spa:Pod:Object:Param:PropInfo"),
        ==, wp_spa_pod_get_spa_type (pod));
    n_params++;
  }
  g_assert_cmpint (n_params, >, 0);

  g_main_loop_quit (f->base.loop);
}

static void
test_node (TestFixture *f, gconstpointer data)
{
  g_autoptr (WpPipewireObject) proxy = NULL;
  const struct pw_node_info *info;

  /* load audiotestsrc on the server side */
  {
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&f->base.server);

    g_assert_cmpint (pw_context_add_spa_lib (f->base.server.context,
            "audiotestsrc", "audiotestsrc/libspa-audiotestsrc"), ==, 0);
    if (!test_is_spa_lib_installed (&f->base, "audiotestsrc")) {
      g_test_skip ("The pipewire audiotestsrc factory was not found");
      return;
    }

    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-adapter", NULL, NULL));
  }

  proxy = WP_PIPEWIRE_OBJECT (wp_node_new_from_factory (f->base.core,
      "adapter",
      wp_properties_new (
          "factory.name", "audiotestsrc",
          "node.name", "audiotestsrc.adapter",
          NULL)));
  g_assert_nonnull (proxy);
  wp_object_activate (WP_OBJECT (proxy), WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);

  /* basic tests */
  g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (proxy)), ==,
      WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
  g_assert_nonnull (wp_proxy_get_pw_proxy (WP_PROXY (proxy)));
  g_assert_true (WP_IS_NODE (proxy));

  /* info */
  {
    info = wp_pipewire_object_get_native_info (proxy);
    g_assert_nonnull (info);
    g_assert_cmpint (wp_proxy_get_bound_id (WP_PROXY (proxy)), ==, info->id);
  }

  /* properties */
  {
    const gchar *id = wp_pipewire_object_get_property (proxy, PW_KEY_OBJECT_ID);
    g_assert_nonnull (id);
    g_assert_cmpint (info->id, ==, atoi(id));
  }
  {
    const char *id;
    g_autoptr (WpProperties) props = NULL;

    props = wp_pipewire_object_get_properties (proxy);
    g_assert_nonnull (props);
    g_assert_true (wp_properties_peek_dict (props) == info->props);
    id = wp_properties_get (props, PW_KEY_OBJECT_ID);
    g_assert_nonnull (id);
    g_assert_cmpint (info->id, ==, atoi(id));
  }

  /* param info */
  {
    const gchar *flags_str;
    g_autoptr (GVariant) param_info = wp_pipewire_object_get_param_info (proxy);

    g_assert_nonnull (param_info);
    g_assert_true (g_variant_is_of_type (param_info, G_VARIANT_TYPE ("a{ss}")));
    g_assert_cmpuint (g_variant_n_children (param_info), ==, info->n_params);
    g_assert_true (g_variant_lookup (param_info, "PropInfo", "&s", &flags_str));
    g_assert_cmpstr (flags_str, ==, "r");
    g_assert_true (g_variant_lookup (param_info, "Props", "&s", &flags_str));
    g_assert_cmpstr (flags_str, ==, "rw");
  }

  /* enum params */
  wp_pipewire_object_enum_params (proxy, "PropInfo", NULL, NULL,
      (GAsyncReadyCallback) test_node_enum_params_done, f);
  g_main_loop_run (f->base.loop);
}

static void
activate_error_cb (WpObject * object, GAsyncResult * res,
    WpBaseTestFixture * f)
{
  g_autoptr (GError) error = NULL;
  gboolean augment_ret = wp_object_activate_finish (object, res, &error);
  g_assert_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED);
  g_assert_false (augment_ret);

  g_main_loop_quit (f->loop);
}

static void
test_link_error (TestFixture *f, gconstpointer data)
{
  g_autoptr (WpPipewireObject) proxy = NULL;

  /* load audiotestsrc on the server side */
  {
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&f->base.server);

    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-link-factory", NULL, NULL));
  }

  proxy = WP_PIPEWIRE_OBJECT (wp_link_new_from_factory (f->base.core,
      "link-factory",
      wp_properties_new (
          "link.output.node", "invalid",
          "link.input.node", "invalid",
          NULL)));
  g_assert_nonnull (proxy);
  wp_object_activate (WP_OBJECT (proxy), WP_OBJECT_FEATURES_ALL,
      NULL, (GAsyncReadyCallback) activate_error_cb, f);
  g_main_loop_run (f->base.loop);
}

static void
enum_params_error_cb (WpPipewireObject * object, GAsyncResult * res,
    WpBaseTestFixture * f)
{
  g_autoptr (GError) error = NULL;
  g_autoptr (WpIterator) it =
      wp_pipewire_object_enum_params_finish (object, res, &error);
  g_assert_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED);
  g_assert_null (it);

  g_main_loop_quit (f->loop);
}

static void
test_enum_params_error (TestFixture *f, gconstpointer data)
{
  g_autoptr (WpNode) node = NULL;

  /* load audiotestsrc on the server side */
  {
    g_autoptr (WpTestServerLocker) lock =
        wp_test_server_locker_new (&f->base.server);

    g_assert_cmpint (pw_context_add_spa_lib (f->base.server.context,
            "audiotestsrc", "audiotestsrc/libspa-audiotestsrc"), ==, 0);
    g_assert_nonnull (pw_context_load_module (f->base.server.context,
            "libpipewire-module-adapter", NULL, NULL));
  }

  node = wp_node_new_from_factory (f->base.core,
      "adapter",
      wp_properties_new (
          "factory.name", "audiotestsrc",
          "node.name", "audiotestsrc",
          NULL));
  g_assert_nonnull (node);

  wp_object_activate (WP_OBJECT (node), WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);

  /* EnumRoute doesn't exist on audiotestsrc, obviously */
  wp_pipewire_object_enum_params (WP_PIPEWIRE_OBJECT (node), "EnumRoute", NULL,
      NULL, (GAsyncReadyCallback) enum_params_error_cb, f);
  g_main_loop_run (f->base.loop);
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/wp/proxy/basic", TestFixture, NULL,
      test_proxy_setup, test_proxy_basic, test_proxy_teardown);
  g_test_add ("/wp/proxy/node", TestFixture, NULL,
      test_proxy_setup, test_node, test_proxy_teardown);
  g_test_add ("/wp/proxy/link_error", TestFixture, NULL,
      test_proxy_setup, test_link_error, test_proxy_teardown);
  g_test_add ("/wp/proxy/enum_params_error", TestFixture, NULL,
      test_proxy_setup, test_enum_params_error, test_proxy_teardown);

  return g_test_run ();
}
0707010000017E000081A4000000000000000000000001656CC35F00003FE3000000000000000000000000000000000000002B00000000wireplumber-0.4.17/tests/wp/session-item.c    ﻿/* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <stdio.h>

#include <wp/wp.h>
#include "../common/base-test-fixture.h"

G_DEFINE_QUARK (test-domain, test_domain)

typedef struct {
  WpBaseTestFixture base;
  WpObjectManager *si_om;
  gint n_items;
} TestSessionItemFixture;

static void
test_session_item_setup (TestSessionItemFixture *self, gconstpointer user_data)
{
  wp_base_test_fixture_setup (&self->base, WP_BASE_TEST_FLAG_CLIENT_CORE);
  self->si_om = wp_object_manager_new ();
  self->n_items = 0;
}

static void
test_session_item_teardown (TestSessionItemFixture *self, gconstpointer user_data)
{
  g_clear_object (&self->si_om);
  wp_base_test_fixture_teardown (&self->base);
}

struct _TestSiDummy
{
  WpSessionItem parent;
  const gchar *name;
  gboolean fail;
  gboolean activate_done;
  gboolean export_done;
};

G_DECLARE_FINAL_TYPE (TestSiDummy, si_dummy, TEST, SI_DUMMY, WpSessionItem)
G_DEFINE_TYPE (TestSiDummy, si_dummy, WP_TYPE_SESSION_ITEM)

static void
si_dummy_init (TestSiDummy * self)
{
}

static void
si_dummy_reset (WpSessionItem * item)
{
  TestSiDummy *self = TEST_SI_DUMMY (item);

  /* deactivate first */
  wp_object_deactivate (WP_OBJECT (self),
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);

  /* reset */
  self->fail = FALSE;

  WP_SESSION_ITEM_CLASS (si_dummy_parent_class)->reset (item);
}

static gboolean
si_dummy_configure (WpSessionItem * item, WpProperties * props)
{
  TestSiDummy *self = TEST_SI_DUMMY (item);
  const gchar *str = NULL;

  /* reset previous config */
  si_dummy_reset (item);

  str = wp_properties_get (props, "fail");
  if (!str || sscanf(str, "%u", &self->fail) != 1)
    return FALSE;

  wp_session_item_set_properties (WP_SESSION_ITEM (self), props);
  return TRUE;
}

static gpointer
si_dummy_get_associated_proxy (WpSessionItem * item, GType proxy_type)
{
  return NULL;
}

static void
si_dummy_disable_active (WpSessionItem *si)
{
  TestSiDummy *self = TEST_SI_DUMMY (si);

  self->activate_done = FALSE;
  wp_object_update_features (WP_OBJECT (self), 0,
      WP_SESSION_ITEM_FEATURE_ACTIVE);
}

static void
si_dummy_disable_exported (WpSessionItem *si)
{
  TestSiDummy *self = TEST_SI_DUMMY (si);

  self->export_done = FALSE;
  wp_object_update_features (WP_OBJECT (self), 0,
      WP_SESSION_ITEM_FEATURE_EXPORTED);
}

static gboolean
si_dummy_step_activate (gpointer data)
{
  WpTransition *transition = data;
  g_assert_true (WP_IS_TRANSITION (transition));

  TestSiDummy *self = wp_transition_get_source_object (transition);
  g_assert_true (TEST_IS_SI_DUMMY (self));

  if (self->fail) {
    wp_transition_return_error (transition,
        g_error_new (test_domain_quark (), 0, "error"));
  } else {
    self->activate_done = TRUE;
    wp_object_update_features (WP_OBJECT (self),
        WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
  }

  return G_SOURCE_REMOVE;
}

static void
si_dummy_enable_active (WpSessionItem *si, WpTransition *transition)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (si));
  wp_core_idle_add (core, NULL, si_dummy_step_activate, transition, NULL);
}

static gboolean
si_dummy_step_export (gpointer data)
{
  WpTransition *transition = data;
  g_assert_true (WP_IS_TRANSITION (transition));

  TestSiDummy *self = wp_transition_get_source_object (transition);
  g_assert_true (TEST_IS_SI_DUMMY (self));

  if (self->fail) {
    wp_transition_return_error (transition,
        g_error_new (test_domain_quark (), 0, "error"));
  } else {
    self->export_done = TRUE;
    wp_object_update_features (WP_OBJECT (self),
        WP_SESSION_ITEM_FEATURE_EXPORTED, 0);
  }

  return G_SOURCE_REMOVE;
}

static void
si_dummy_enable_exported (WpSessionItem *si, WpTransition *transition)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (si));
  wp_core_idle_add (core, NULL, si_dummy_step_export, transition, NULL);
}

static void
si_dummy_class_init (TestSiDummyClass * klass)
{
  WpSessionItemClass *si_class = (WpSessionItemClass *) klass;

  si_class->reset = si_dummy_reset;
  si_class->configure = si_dummy_configure;
  si_class->get_associated_proxy = si_dummy_get_associated_proxy;
  si_class->disable_active = si_dummy_disable_active;
  si_class->disable_exported = si_dummy_disable_exported;
  si_class->enable_active = si_dummy_enable_active;
  si_class->enable_exported = si_dummy_enable_exported;
}

static void
expect_activate_success (WpObject * object, GAsyncResult * res, gpointer data)
{
  GMainLoop *loop = data;
  g_autoptr (GError) error = NULL;
  g_assert_true (TEST_IS_SI_DUMMY (object));
  g_assert_true (wp_object_activate_finish (object, res, &error));
  g_assert_no_error (error);
  g_main_loop_quit (loop);
}

static void
expect_activate_failure (WpObject * object, GAsyncResult * res, gpointer data)
{
  GMainLoop *loop = data;
  g_autoptr (GError) error = NULL;
  g_assert_true (TEST_IS_SI_DUMMY (object));
  g_assert_false (wp_object_activate_finish (object, res, &error));
  g_assert_error (error, test_domain_quark (), 0);
  g_main_loop_quit (loop);
}

static void
test_configuration (TestSessionItemFixture *fixture, gconstpointer data)
{
  g_autoptr (WpSessionItem) item = NULL;
  TestSiDummy *dummy;

  item = g_object_new (si_dummy_get_type (), "core", fixture->base.core, NULL);
  dummy = TEST_SI_DUMMY (item);

  {
    g_autoptr (WpProperties) p = wp_properties_new_empty ();
    wp_properties_setf (p, "fail", "%u", TRUE);
    g_assert_true (wp_session_item_configure (item, g_steal_pointer (&p)));
    g_assert_true (wp_session_item_is_configured (item));
    g_assert_true (dummy->fail);
  }

  {
    g_autoptr (WpProperties) p = wp_session_item_get_properties (item);
    g_assert_nonnull (p);
    const gchar * str = wp_properties_get (p, "fail");
    gboolean fail = FALSE;
    g_assert_nonnull (str);
    g_assert_true (sscanf(str, "%u", &fail) == 1);
    g_assert_true (fail);
  }
}

static void
test_activation (TestSessionItemFixture *fixture, gconstpointer data)
{
  g_autoptr (WpSessionItem) item = NULL;
  TestSiDummy *dummy;

  item = g_object_new (si_dummy_get_type (), "core", fixture->base.core, NULL);
  dummy = TEST_SI_DUMMY (item);

  {
    g_autoptr (WpProperties) p = wp_properties_new_empty ();
    wp_properties_setf (p, "fail", "%u", FALSE);
    g_assert_true (wp_session_item_configure (item, g_steal_pointer (&p)));
    g_assert_true (wp_session_item_is_configured (item));
  }

  wp_object_activate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_ACTIVE,
      NULL, (GAsyncReadyCallback) expect_activate_success, fixture->base.loop);
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (item)), ==,
      WP_SESSION_ITEM_FEATURE_ACTIVE);
  g_assert_true (dummy->activate_done);

  wp_object_deactivate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_ACTIVE);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (item)), ==, 0);
  g_assert_false (dummy->activate_done);
}

static void
test_activation_error (TestSessionItemFixture *fixture, gconstpointer data)
{
  g_autoptr (WpSessionItem) item = NULL;
  TestSiDummy *dummy;

  item = g_object_new (si_dummy_get_type (), "core", fixture->base.core, NULL);
  dummy = TEST_SI_DUMMY (item);

  {
    g_autoptr (WpProperties) p = wp_properties_new_empty ();
    wp_properties_setf (p, "fail", "%u", TRUE);
    g_assert_true (wp_session_item_configure (item, g_steal_pointer (&p)));
    g_assert_true (wp_session_item_is_configured (item));
  }

  wp_object_activate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_ACTIVE,
      NULL, (GAsyncReadyCallback) expect_activate_failure, fixture->base.loop);
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (item)), ==, 0);
  g_assert_false (dummy->activate_done);
  g_assert_true (dummy->fail);

  wp_object_deactivate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_ACTIVE);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (item)), ==, 0);
  g_assert_true (dummy->fail);
  g_assert_false (dummy->activate_done);

  wp_session_item_reset (item);
  g_assert_false (dummy->fail);
  g_assert_false (dummy->activate_done);
  g_assert_false (wp_session_item_is_configured (item));
}

static void
test_export (TestSessionItemFixture *fixture, gconstpointer data)
{
  g_autoptr (WpSessionItem) item = NULL;
  TestSiDummy *dummy;

  item = g_object_new (si_dummy_get_type (), "core", fixture->base.core, NULL);
  dummy = TEST_SI_DUMMY (item);

  {
    g_autoptr (WpProperties) p = wp_properties_new_empty ();
    wp_properties_setf (p, "fail", "%u", FALSE);
    g_assert_true (wp_session_item_configure (item, g_steal_pointer (&p)));
    g_assert_true (wp_session_item_is_configured (item));
  }

  wp_object_activate (WP_OBJECT (item),
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED,
      NULL, (GAsyncReadyCallback) expect_activate_success, fixture->base.loop);
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (item)), ==,
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);
  g_assert_true (dummy->activate_done);
  g_assert_true (dummy->export_done);

  wp_object_deactivate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_EXPORTED);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (item)), ==,
      WP_SESSION_ITEM_FEATURE_ACTIVE);
  g_assert_true (dummy->activate_done);
  g_assert_false (dummy->export_done);

  wp_session_item_reset (item);
  g_assert_false (dummy->activate_done);
  g_assert_false (wp_session_item_is_configured (item));
}

static void
test_export_error (TestSessionItemFixture *fixture, gconstpointer data)
{
  g_autoptr (WpSessionItem) item = NULL;
  TestSiDummy *dummy;

  item = g_object_new (si_dummy_get_type (), "core", fixture->base.core, NULL);
  dummy = TEST_SI_DUMMY (item);

  {
    g_autoptr (WpProperties) p = wp_properties_new_empty ();
    wp_properties_setf (p, "fail", "%u", TRUE);
    g_assert_true (wp_session_item_configure (item, g_steal_pointer (&p)));
    g_assert_true (wp_session_item_is_configured (item));
  }

  wp_object_activate (WP_OBJECT (item),
      WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED,
      NULL, (GAsyncReadyCallback) expect_activate_failure, fixture->base.loop);
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (item)), ==, 0);
  g_assert_false (dummy->activate_done);
  g_assert_false (dummy->export_done);

  wp_object_deactivate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_EXPORTED);
  g_assert_cmpint (wp_object_get_active_features (WP_OBJECT (item)), ==, 0);
  g_assert_true (dummy->fail);
  g_assert_false (dummy->activate_done);
  g_assert_false (dummy->export_done);

  wp_session_item_reset (item);
  g_assert_false (dummy->fail);
  g_assert_false (dummy->activate_done);
  g_assert_false (dummy->export_done);
  g_assert_false (wp_session_item_is_configured (item));
}

static void
test_session_item_added (WpObjectManager *om, WpSessionItem *item,
    TestSessionItemFixture *fixture)
{
  g_assert_true (WP_IS_SESSION_ITEM (item));
  fixture->n_items++;
  g_main_loop_quit (fixture->base.loop);
}

static void
test_session_item_removed (WpObjectManager *om, WpSessionItem *item,
    TestSessionItemFixture *fixture)
{
  g_assert_true (WP_IS_SESSION_ITEM (item));
  fixture->n_items--;
  g_main_loop_quit (fixture->base.loop);
}

static gboolean
idle_register (gpointer data)
{
  WpSessionItem *item = data;
  wp_session_item_register (g_object_ref (item));
  return G_SOURCE_REMOVE;
}

static gboolean
idle_remove (gpointer data)
{
  WpSessionItem *item = data;
  wp_session_item_remove (item);
  return G_SOURCE_REMOVE;
}

static void
test_registration (TestSessionItemFixture *fixture, gconstpointer data)
{
  g_autoptr (WpSessionItem) item = NULL;
  g_autoptr (WpSessionItem) item2 = NULL;

  g_signal_connect (fixture->si_om, "object-added",
      (GCallback) test_session_item_added, fixture);
  g_signal_connect (fixture->si_om, "object-removed",
      (GCallback) test_session_item_removed, fixture);
  wp_object_manager_add_interest (fixture->si_om, WP_TYPE_SESSION_ITEM, NULL);
  wp_core_install_object_manager (fixture->base.core, fixture->si_om);

  /* create, configure and export first session item */
  item = g_object_new (si_dummy_get_type (), "core", fixture->base.core, NULL);
  g_assert_nonnull (item);
  {
    g_autoptr (WpProperties) p = wp_properties_new_empty ();
    wp_properties_setf (p, "fail", "%u", FALSE);
    g_assert_true (wp_session_item_configure (item, g_steal_pointer (&p)));
    g_assert_true (wp_session_item_is_configured (item));
  }

  /* create and configure second session item */
  item2 = g_object_new (si_dummy_get_type (), "core", fixture->base.core, NULL);
  g_assert_nonnull (item2);
  {
    g_autoptr (WpProperties) p = wp_properties_new_empty ();
    wp_properties_setf (p, "fail", "%u", TRUE);
    g_assert_true (wp_session_item_configure (item2, g_steal_pointer (&p)));
    g_assert_true (wp_session_item_is_configured (item2));
  }

  /* register item */
  wp_core_idle_add (fixture->base.core, NULL, idle_register, item, NULL);
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_items, ==, 1);

  /* register item2 */
  wp_core_idle_add (fixture->base.core, NULL, idle_register, item2, NULL);
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_items, ==, 2);

  /* lookup item, which has fail set to false */
  {
    g_autoptr (WpSessionItem) si = wp_object_manager_lookup (
        fixture->si_om, WP_TYPE_SESSION_ITEM,
        WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "fail", "=b", FALSE, NULL);
    g_assert_nonnull (si);
    g_assert_true (si == item);
  }

  /* lookup item2, which has fail set to true */
  {
    g_autoptr (WpSessionItem) si = wp_object_manager_lookup (
        fixture->si_om, WP_TYPE_SESSION_ITEM,
        WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "fail", "=b", TRUE, NULL);
    g_assert_nonnull (si);
    g_assert_true (si == item2);
  }

  /* remove item */
  wp_core_idle_add (fixture->base.core, NULL, idle_remove, item, NULL);
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_items, ==, 1);

  /* make sure item cannot be found */
  {
    g_autoptr (WpSessionItem) si = wp_object_manager_lookup (
        fixture->si_om, WP_TYPE_SESSION_ITEM,
        WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "fail", "=b", FALSE, NULL);
    g_assert_null (si);
  }

  /* make sure item2 is still there */
  {
    g_autoptr (WpSessionItem) si = wp_object_manager_lookup (
        fixture->si_om, WP_TYPE_SESSION_ITEM,
        WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "fail", "=b", TRUE, NULL);
    g_assert_nonnull (si);
    g_assert_true (si == item2);
  }

  /* remove item2 */
  wp_core_idle_add (fixture->base.core, NULL, idle_remove, item2, NULL);
  g_main_loop_run (fixture->base.loop);
  g_assert_cmpint (fixture->n_items, ==, 0);

  /* make sure item cannot be found */
  {
    g_autoptr (WpSessionItem) si = wp_object_manager_lookup (
        fixture->si_om, WP_TYPE_SESSION_ITEM,
        WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "fail", "=b", TRUE, NULL);
    g_assert_null (si);
  }
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add ("/wp/session-item/configuration", TestSessionItemFixture, NULL,
      test_session_item_setup, test_configuration, test_session_item_teardown);
  g_test_add ("/wp/session-item/activation", TestSessionItemFixture, NULL,
      test_session_item_setup, test_activation, test_session_item_teardown);
  g_test_add ("/wp/session-item/activation-error", TestSessionItemFixture, NULL,
      test_session_item_setup, test_activation_error, test_session_item_teardown);
  g_test_add ("/wp/session-item/export", TestSessionItemFixture, NULL,
      test_session_item_setup, test_export, test_session_item_teardown);
  g_test_add ("/wp/session-item/export-error", TestSessionItemFixture, NULL,
      test_session_item_setup, test_export_error, test_session_item_teardown);
  g_test_add ("/wp/session-item/registration", TestSessionItemFixture, NULL,
      test_session_item_setup, test_registration, test_session_item_teardown);

  return g_test_run ();
}
 0707010000017F000081A4000000000000000000000001656CC35F0000A0BB000000000000000000000000000000000000002700000000wireplumber-0.4.17/tests/wp/spa-json.c    ﻿/* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>

static void
test_spa_json_basic (void)
{
  /* Null */
  {
    g_autoptr (WpSpaJson) json = wp_spa_json_new_null ();
    g_assert_nonnull (json);
    g_assert_true (wp_spa_json_is_null (json));
    g_assert_cmpmem (wp_spa_json_get_data (json), wp_spa_json_get_size (json),
        "null", 4);
  }

  /* Boolean */
  {
    g_autoptr (WpSpaJson) json = wp_spa_json_new_boolean (TRUE);
    g_assert_nonnull (json);
    g_assert_true (wp_spa_json_is_boolean (json));
    gboolean v = FALSE;
    g_assert_true (wp_spa_json_parse_boolean (json, &v));
    g_assert_true (v);
    g_assert_cmpmem (wp_spa_json_get_data (json), wp_spa_json_get_size (json),
        "true", 4);
  }

  /* Int */
  {
    g_autoptr (WpSpaJson) json = wp_spa_json_new_int (8);
    g_assert_nonnull (json);
    g_assert_true (wp_spa_json_is_int (json));
    gint32 v = 0;
    g_assert_true (wp_spa_json_parse_int (json, &v));
    g_assert_cmpint (v, ==, 8);
    g_assert_cmpmem (wp_spa_json_get_data (json), wp_spa_json_get_size (json),
        "8", 1);
  }

  /* Float */
  {
    g_autoptr (WpSpaJson) json = wp_spa_json_new_float (3.14f);
    g_assert_nonnull (json);
    g_assert_true (wp_spa_json_is_float (json));
    float v = 0;
    g_assert_true (wp_spa_json_parse_float (json, &v));
    g_assert_cmpfloat_with_epsilon (v, 3.14f, 0.001f);
  }

  /* String */
  {
    g_autoptr (WpSpaJson) json = wp_spa_json_new_string ("wireplumber");
    g_assert_nonnull (json);
    g_assert_true (wp_spa_json_is_string (json));
    g_autofree gchar *v1 = wp_spa_json_parse_string (json);
    g_assert_nonnull (v1);
    g_assert_cmpstr (v1, ==, "wireplumber");

    g_autoptr (WpSpaJson) jsone = wp_spa_json_new_string ("");
    g_assert_nonnull (jsone);
    g_assert_true (wp_spa_json_is_string (jsone));
    g_autofree gchar *v2 = wp_spa_json_parse_string (jsone);
    g_assert_nonnull (v2);
    g_assert_cmpstr (v2, ==, "");

    g_autoptr (WpSpaJson) jsonl = wp_spa_json_new_string (
        "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong");
    g_assert_nonnull (jsonl);
    g_assert_true (wp_spa_json_is_string (jsonl));
    g_autofree gchar *v3 = wp_spa_json_parse_string (jsonl);
    g_assert_nonnull (v3);
    g_assert_cmpstr (v3, ==,
        "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong");

    g_autoptr (WpSpaJson) jsons = wp_spa_json_new_string ("\v\v\v\v");
    g_assert_nonnull (jsons);
    g_assert_true (wp_spa_json_is_string (jsons));
    g_autofree gchar *v4 = wp_spa_json_parse_string (jsons);
    g_assert_nonnull (v4);
    g_assert_cmpstr (v4, ==, "\v\v\v\v");
  }

  /* Array */
  {
    g_autoptr (WpSpaJson) empty = wp_spa_json_new_array (NULL, NULL);
    g_assert_nonnull (empty);
    g_assert_true (wp_spa_json_is_array (empty));
    g_assert_cmpmem (wp_spa_json_get_data (empty), wp_spa_json_get_size (empty),
        "[]", 2);

    g_autoptr (WpSpaJson) json = wp_spa_json_new_array ("i", 1, "i", 2, NULL);
    g_assert_nonnull (json);
    g_assert_true (wp_spa_json_is_array (json));
    gint32 v1 = 0, v2 = 0;
    g_assert_true (wp_spa_json_parse_array (json, "i", &v1, "i", &v2, NULL));
    g_assert_cmpint (v1, ==, 1);
    g_assert_cmpint (v2, ==, 2);
    g_assert_cmpmem (wp_spa_json_get_data (json), wp_spa_json_get_size (json),
        "[1, 2]", 6);
  }

  /* Object */
  {
    g_autoptr (WpSpaJson) empty = wp_spa_json_new_object (NULL, NULL, NULL);
    g_assert_nonnull (empty);
    g_assert_true (wp_spa_json_is_object (empty));
    g_assert_cmpmem (wp_spa_json_get_data (empty), wp_spa_json_get_size (empty),
        "{}", 2);

    g_autoptr (WpSpaJson) subjson = wp_spa_json_new_array ("b", TRUE, NULL);
    g_autoptr (WpSpaJson) json = wp_spa_json_new_object (
        "key1", "n",
        "key2", "b", TRUE,
        "key3", "i", 3,
        "key4", "f", 2.72f,
        "key5", "s", "str",
        "key6", "J", subjson,
        NULL);
    g_assert_nonnull (json);
    g_assert_true (wp_spa_json_is_object (json));

    {
      g_autofree gchar *key1 = NULL, *key2 = NULL, *key3 = NULL, *key4 = NULL,
          *key5 = NULL, *key6 = NULL;
      gboolean v2 = FALSE;
      gint32 v3 = 0;
      float v4 = 0.0f;
      g_autofree gchar *v5 = NULL;
      g_autoptr (WpSpaJson) v6 = NULL;
      g_assert_true (wp_spa_json_parse_object (json,
          &key1, "n",
          &key2, "b", &v2,
          &key3, "i", &v3,
          &key4, "f", &v4,
          &key5, "s", &v5,
          &key6, "J", &v6,
          NULL));
      g_assert_cmpstr (key1, ==, "key1");
      g_assert_cmpstr (key2, ==, "key2");
      g_assert_true (v2);
      g_assert_cmpstr (key3, ==, "key3");
      g_assert_cmpint (v3, ==, 3);
      g_assert_cmpstr (key4, ==, "key4");
      g_assert_cmpfloat_with_epsilon (v4, 2.72f, 0.001f);
      g_assert_cmpstr (key5, ==, "key5");
      g_assert_cmpstr (v5, ==, "str");
      g_assert_cmpstr (key6, ==, "key6");
      g_assert_nonnull (v6);
      g_assert_cmpmem (wp_spa_json_get_data (v6), wp_spa_json_get_size (v6),
          "[true]", 6);
    }

    {
      gboolean v2 = FALSE;
      gint32 v3 = 0;
      float v4 = 0.0f;
      g_autofree gchar *v5 = NULL;
      g_autoptr (WpSpaJson) v6 = NULL;
      g_assert_true (wp_spa_json_object_get (json,
          "key6", "J", &v6,
          "key3", "i", &v3,
          "key5", "s", &v5,
          "key1", "n",
          "key2", "b", &v2,
          "key4", "f", &v4,
          NULL));
      g_assert_true (v2);
      g_assert_cmpint (v3, ==, 3);
      g_assert_cmpfloat_with_epsilon (v4, 2.72f, 0.001f);
      g_assert_cmpstr (v5, ==, "str");
      g_assert_nonnull (v6);
      g_assert_cmpmem (wp_spa_json_get_data (v6), wp_spa_json_get_size (v6),
          "[true]", 6);
    }
  }
}

static void
test_spa_json_array_builder_parser_iterator (void)
{
  g_autoptr (WpSpaJson) json = NULL;

  {
    g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_array ();
    g_assert_nonnull (b);
    wp_spa_json_builder_add_int (b, 1);
    wp_spa_json_builder_add_int (b, 2);
    wp_spa_json_builder_add_int (b, 3);
    json = wp_spa_json_builder_end (b);
  }

  g_assert_true (wp_spa_json_is_array (json));
  g_assert_cmpmem (wp_spa_json_get_data (json), wp_spa_json_get_size (json),
      "[1, 2, 3]", 9);

  {
    g_autoptr (WpSpaJsonParser) p = wp_spa_json_parser_new_array (json);
    g_assert_nonnull (p);
    gint32 v = 0;
    g_assert_true (wp_spa_json_parser_get_int (p, &v));
    g_assert_cmpint (v, ==, 1);
    g_assert_true (wp_spa_json_parser_get_int (p, &v));
    g_assert_cmpint (v, ==, 2);
    g_assert_true (wp_spa_json_parser_get_int (p, &v));
    g_assert_cmpint (v, ==, 3);
    wp_spa_json_parser_end (p);
    g_assert_false (wp_spa_json_parser_get_null (p));
  }

  g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
  g_assert_nonnull (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_int (j));
    gint32 v = 0;
    g_assert_true (wp_spa_json_parse_int (j, &v));
    g_assert_cmpint (v, ==, 1);
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_int (j));
    gint32 v = 0;
    g_assert_true (wp_spa_json_parse_int (j, &v));
    g_assert_cmpint (v, ==, 2);
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_int (j));
    gint32 v = 0;
    g_assert_true (wp_spa_json_parse_int (j, &v));
    g_assert_cmpint (v, ==, 3);
    g_value_unset (&next);
  }

  g_assert_false (wp_iterator_next (it, NULL));
  wp_iterator_reset (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_int (j));
    gint32 v = 0;
    g_assert_true (wp_spa_json_parse_int (j, &v));
    g_assert_cmpint (v, ==, 1);
    g_value_unset (&next);
  }
}

static void
test_spa_json_object_builder_parser_iterator (void)
{
  g_autoptr (WpSpaJson) json = NULL;

  {
    g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_object ();
    g_assert_nonnull (b);
    wp_spa_json_builder_add_property (b, "key-null");
    wp_spa_json_builder_add_null (b);
    wp_spa_json_builder_add_property (b, "key-boolean");
    wp_spa_json_builder_add_boolean (b, TRUE);
    wp_spa_json_builder_add_property (b, "key-int");
    wp_spa_json_builder_add_int (b, 7);
    wp_spa_json_builder_add_property (b, "key-float");
    wp_spa_json_builder_add_float (b, 0.12f);
    wp_spa_json_builder_add_property (b, "key-string");
    wp_spa_json_builder_add_string (b, "str");
    wp_spa_json_builder_add_property (b, "key-empty-string");
    wp_spa_json_builder_add_string (b, "");
    wp_spa_json_builder_add_property (b, "key-special-char-string");
    wp_spa_json_builder_add_string (b, "\v\v\v\v");
    json = wp_spa_json_builder_end (b);
  }

  g_assert_true (wp_spa_json_is_object (json));

  {
    g_autoptr (WpSpaJsonParser) p = wp_spa_json_parser_new_object (json);
    g_assert_nonnull (p);

    g_autofree gchar *key_null = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (key_null);
    g_assert_cmpstr (key_null, ==, "key-null");
    g_assert_true (wp_spa_json_parser_get_null (p));

    g_autofree gchar *key_boolean = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (key_boolean);
    g_assert_cmpstr (key_boolean, ==, "key-boolean");
    gboolean v_boolean = FALSE;
    g_assert_true (wp_spa_json_parser_get_boolean (p, &v_boolean));
    g_assert_true (v_boolean);

    g_autofree gchar *key_int = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (key_int);
    g_assert_cmpstr (key_int, ==, "key-int");
    gint32 v_int = 0;
    g_assert_true (wp_spa_json_parser_get_int (p, &v_int));
    g_assert_cmpint (v_int, ==, 7);

    g_autofree gchar *key_float = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (key_float);
    g_assert_cmpstr (key_float, ==, "key-float");
    float v_float = 0.0f;
    g_assert_true (wp_spa_json_parser_get_float (p, &v_float));
    g_assert_cmpfloat_with_epsilon (v_float, 0.12f, 0.001f);

    g_autofree gchar *key_string = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (key_string);
    g_assert_cmpstr (key_string, ==, "key-string");
    g_autofree gchar *v_string = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (v_string);
    g_assert_cmpstr (v_string, ==, "str");

    g_autofree gchar *key_empty_string = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (key_empty_string);
    g_assert_cmpstr (key_empty_string, ==, "key-empty-string");
    g_autofree gchar *v_empty_string = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (v_empty_string);
    g_assert_cmpstr (v_empty_string, ==, "");

    g_autofree gchar *key_special_char_string = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (key_special_char_string);
    g_assert_cmpstr (key_special_char_string, ==, "key-special-char-string");
    g_autofree gchar *v_special_char_string = wp_spa_json_parser_get_string (p);
    g_assert_nonnull (v_special_char_string);
    g_assert_cmpstr (v_special_char_string, ==, "\v\v\v\v");

    wp_spa_json_parser_end (p);
    g_assert_false (wp_spa_json_parser_get_null (p));
  }

  g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
  g_assert_nonnull (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-null");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_null (j));
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-boolean");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_boolean (j));
    gboolean v = FALSE;
    g_assert_true (wp_spa_json_parse_boolean (j, &v));
    g_assert_true (v);
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-int");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_int (j));
    gint32 v = 0;
    g_assert_true (wp_spa_json_parse_int (j, &v));
    g_assert_cmpint (v, ==, 7);
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-float");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_float (j));
    float v = 0;
    g_assert_true (wp_spa_json_parse_float (j, &v));
    g_assert_cmpfloat_with_epsilon (v, 0.12f, 0.001f);
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-string");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "str");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-empty-string");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-special-char-string");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "\v\v\v\v");
    g_value_unset (&next);
  }

  g_assert_false (wp_iterator_next (it, NULL));
  wp_iterator_reset (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-null");
    g_value_unset (&next);
  }
}

static void
test_spa_json_nested (void)
{
  g_autoptr (WpSpaJson) array = NULL;
  g_autoptr (WpSpaJson) array2 = NULL;
  g_autoptr (WpSpaJson) object = NULL;
  g_autoptr (WpSpaJson) json = NULL;

  {
    g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_array ();
    g_assert_nonnull (b);
    wp_spa_json_builder_add_int (b, 5);
    wp_spa_json_builder_add_int (b, 10);
    wp_spa_json_builder_add_int (b, 15);
    array = wp_spa_json_builder_end (b);
  }
  g_assert_true (wp_spa_json_is_array (array));
  g_assert_cmpuint (wp_spa_json_get_size (array), ==, 11);
  g_assert_cmpmem (wp_spa_json_get_data (array), wp_spa_json_get_size (array),
      "[5, 10, 15]", 11);

  {
    g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_array ();
    g_assert_nonnull (b);
    wp_spa_json_builder_add_int (b, 2);
    wp_spa_json_builder_add_int (b, 4);
    array2 = wp_spa_json_builder_end (b);
  }
  g_assert_true (wp_spa_json_is_array (array2));
  g_assert_cmpuint (wp_spa_json_get_size (array2), ==, 6);
  g_assert_cmpmem (wp_spa_json_get_data (array2), wp_spa_json_get_size (array2),
      "[2, 4]", 6);

  {
    g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_object ();
    g_assert_nonnull (b);
    wp_spa_json_builder_add_property (b, "key-boolean");
    wp_spa_json_builder_add_boolean (b, FALSE);
    wp_spa_json_builder_add_property (b, "key-int");
    wp_spa_json_builder_add_int (b, 8);
    wp_spa_json_builder_add_property (b, "key-array");
    wp_spa_json_builder_add_json (b, array2);
    object = wp_spa_json_builder_end (b);
  }
  g_assert_true (wp_spa_json_is_object (object));
  g_assert_cmpuint (wp_spa_json_get_size (object), ==, 54);
  g_assert_cmpmem (wp_spa_json_get_data (object), wp_spa_json_get_size (object),
      "{\"key-boolean\":false, \"key-int\":8, \"key-array\":[2, 4]}", 54);

  {
    g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_object ();
    g_assert_nonnull (b);
    wp_spa_json_builder_add_property (b, "key-array");
    wp_spa_json_builder_add_json (b, array);
    wp_spa_json_builder_add_property (b, "key-object");
    wp_spa_json_builder_add_json (b, object);
    json = wp_spa_json_builder_end (b);
  }
  g_assert_true (wp_spa_json_is_object (json));
  g_assert_cmpuint (wp_spa_json_get_size (json), ==, 94);
  g_assert_cmpmem (wp_spa_json_get_data (json), wp_spa_json_get_size (json),
      "{\"key-array\":[5, 10, 15], \"key-object\":{\"key-boolean\":false, "
      "\"key-int\":8, \"key-array\":[2, 4]}}", 94);
  g_assert_cmpstr (wp_spa_json_get_data (json), ==,
      "{\"key-array\":[5, 10, 15], \"key-object\":{\"key-boolean\":false, "
      "\"key-int\":8, \"key-array\":[2, 4]}}");

  g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
  g_assert_nonnull (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-array");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_array (j));
    g_assert_cmpuint (wp_spa_json_get_size (j), ==, 11);
    g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
        "[5, 10, 15]", 11);

    g_autoptr (WpIterator) it2 = wp_spa_json_new_iterator (j);
    g_assert_nonnull (it2);

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_int (j));
      gint32 v = 0;
      g_assert_true (wp_spa_json_parse_int (j, &v));
      g_assert_cmpint (v, ==, 5);
      g_value_unset (&next2);
    }

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_int (j));
      gint32 v = 0;
      g_assert_true (wp_spa_json_parse_int (j, &v));
      g_assert_cmpint (v, ==, 10);
      g_value_unset (&next2);
    }

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_int (j));
      gint32 v = 0;
      g_assert_true (wp_spa_json_parse_int (j, &v));
      g_assert_cmpint (v, ==, 15);
      g_value_unset (&next2);
    }

    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-object");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_object (j));
    g_assert_cmpuint (wp_spa_json_get_size (j), ==, 54);
    g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
        "{\"key-boolean\":false, \"key-int\":8, \"key-array\":[2, 4]}", 54);

    g_autoptr (WpIterator) it2 = wp_spa_json_new_iterator (j);
    g_assert_nonnull (it2);

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_string (j));
      g_autofree gchar *v = wp_spa_json_parse_string (j);
      g_assert_nonnull (v);
      g_assert_cmpstr (v, ==, "key-boolean");
      g_value_unset (&next2);
    }

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_boolean (j));
      gboolean v = TRUE;
      g_assert_true (wp_spa_json_parse_boolean (j, &v));
      g_assert_false (v);
      g_value_unset (&next2);
    }

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_string (j));
      g_autofree gchar *v = wp_spa_json_parse_string (j);
      g_assert_nonnull (v);
      g_assert_cmpstr (v, ==, "key-int");
      g_value_unset (&next2);
    }

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_int (j));
      gint32 v = 0;
      g_assert_true (wp_spa_json_parse_int (j, &v));
      g_assert_cmpint (v, ==, 8);
      g_value_unset (&next2);
    }

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_string (j));
      g_autofree gchar *v = wp_spa_json_parse_string (j);
      g_assert_nonnull (v);
      g_assert_cmpstr (v, ==, "key-array");
      g_value_unset (&next2);
    }

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_array (array2));
      g_assert_cmpuint (wp_spa_json_get_size (array2), ==, 6);
      g_assert_cmpmem (wp_spa_json_get_data (array2),
          wp_spa_json_get_size (array2), "[2, 4]", 6);

      g_autoptr (WpIterator) it3 = wp_spa_json_new_iterator (j);
      g_assert_nonnull (it3);

      {
        GValue next3 = G_VALUE_INIT;
        g_assert_true (wp_iterator_next (it3, &next3));
        WpSpaJson *j = g_value_get_boxed (&next3);
        g_assert_nonnull (j);
        g_assert_true (wp_spa_json_is_int (j));
        gint32 v = 0;
        g_assert_true (wp_spa_json_parse_int (j, &v));
        g_assert_cmpint (v, ==, 2);
        g_value_unset (&next3);
      }

      {
        GValue next3 = G_VALUE_INIT;
        g_assert_true (wp_iterator_next (it3, &next3));
        WpSpaJson *j = g_value_get_boxed (&next3);
        g_assert_nonnull (j);
        g_assert_true (wp_spa_json_is_int (j));
        gint32 v = 0;
        g_assert_true (wp_spa_json_parse_int (j, &v));
        g_assert_cmpint (v, ==, 4);
        g_value_unset (&next3);
      }

      g_value_unset (&next2);
    }

    g_value_unset (&next);
  }

  g_assert_false (wp_iterator_next (it, NULL));
  wp_iterator_reset (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "key-array");
    g_value_unset (&next);
  }
}

static void
test_spa_json_nested2 (void)
{
  const gchar json_str[] = "[[[[1], [2]], [3]], [4]]";
  g_autoptr (WpSpaJson) json = wp_spa_json_new_from_string (json_str);

  g_assert_true (wp_spa_json_is_array (json));
  g_assert_cmpmem (wp_spa_json_get_data (json), wp_spa_json_get_size (json),
        "[[[[1], [2]], [3]], [4]]", 24);

  {
    g_autoptr (WpSpaJsonParser) p = wp_spa_json_parser_new_array (json);
    g_assert_nonnull (p);
    g_autoptr (WpSpaJson) j0 = wp_spa_json_parser_get_json (p);
    g_assert_nonnull (j0);
    g_assert_cmpmem (wp_spa_json_get_data (j0), wp_spa_json_get_size (j0),
        "[[[1], [2]], [3]]", 17);
    g_autoptr (WpSpaJson) j1 = wp_spa_json_parser_get_json (p);
    g_assert_nonnull (j1);
    g_assert_cmpmem (wp_spa_json_get_data (j1), wp_spa_json_get_size (j1),
        "[4]", 3);
    wp_spa_json_parser_end (p);
    g_assert_false (wp_spa_json_parser_get_null (p));
  }

  g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
  g_assert_nonnull (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_array (j));
    g_assert_cmpuint (wp_spa_json_get_size (j), ==, 17);
    g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
        "[[[1], [2]], [3]]", 17);

    g_autoptr (WpIterator) it2 = wp_spa_json_new_iterator (j);
    g_assert_nonnull (it2);

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_array (j));
      g_assert_cmpuint (wp_spa_json_get_size (j), ==, 10);
      g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
          "[[1], [2]]", 10);

      g_autoptr (WpIterator) it3 = wp_spa_json_new_iterator (j);
      g_assert_nonnull (it3);

      {
        GValue next3 = G_VALUE_INIT;
        g_assert_true (wp_iterator_next (it3, &next3));
        WpSpaJson *j = g_value_get_boxed (&next3);
        g_assert_nonnull (j);
        g_assert_true (wp_spa_json_is_array (j));
        g_assert_cmpuint (wp_spa_json_get_size (j), ==, 3);
        g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
            "[1]", 3);

        g_autoptr (WpIterator) it4 = wp_spa_json_new_iterator (j);
        g_assert_nonnull (it4);

        {
          GValue next4 = G_VALUE_INIT;
          g_assert_true (wp_iterator_next (it4, &next4));
          WpSpaJson *j = g_value_get_boxed (&next4);
          g_assert_nonnull (j);
          g_assert_true (wp_spa_json_is_int (j));
          g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
              "1", 1);
          g_value_unset (&next4);
        }

        g_value_unset (&next3);
      }

      {
        GValue next3 = G_VALUE_INIT;
        g_assert_true (wp_iterator_next (it3, &next3));
        WpSpaJson *j = g_value_get_boxed (&next3);
        g_assert_nonnull (j);
        g_assert_true (wp_spa_json_is_array (j));
        g_assert_cmpuint (wp_spa_json_get_size (j), ==, 3);
        g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
            "[2]", 3);

        g_autoptr (WpIterator) it4 = wp_spa_json_new_iterator (j);
        g_assert_nonnull (it4);

        {
          GValue next4 = G_VALUE_INIT;
          g_assert_true (wp_iterator_next (it4, &next4));
          WpSpaJson *j = g_value_get_boxed (&next4);
          g_assert_nonnull (j);
          g_assert_true (wp_spa_json_is_int (j));
          g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
              "2", 1);
          g_value_unset (&next4);
        }

        g_value_unset (&next3);
      }

      g_value_unset (&next2);
    }

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_array (j));
      g_assert_cmpuint (wp_spa_json_get_size (j), ==, 3);
      g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
          "[3]", 3);

      g_autoptr (WpIterator) it3 = wp_spa_json_new_iterator (j);
      g_assert_nonnull (it3);

      {
        GValue next3 = G_VALUE_INIT;
        g_assert_true (wp_iterator_next (it3, &next3));
        WpSpaJson *j = g_value_get_boxed (&next3);
        g_assert_nonnull (j);
        g_assert_true (wp_spa_json_is_int (j));
        g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
            "3", 1);
        g_value_unset (&next3);
      }

      g_value_unset (&next2);
    }

    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_array (j));
    g_assert_cmpuint (wp_spa_json_get_size (j), ==, 3);
    g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
        "[4]", 3);

    g_autoptr (WpIterator) it2 = wp_spa_json_new_iterator (j);
    g_assert_nonnull (it2);

    {
      GValue next2 = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it2, &next2));
      WpSpaJson *j = g_value_get_boxed (&next2);
      g_assert_nonnull (j);
      g_assert_true (wp_spa_json_is_int (j));
      g_assert_cmpmem (wp_spa_json_get_data (j), wp_spa_json_get_size (j),
          "4", 1);
      g_value_unset (&next2);
    }

    g_value_unset (&next);
  }
}

static void
test_spa_json_nested3 (void)
{
  const gchar json_str[] =
      "{ test-setting-json3: { key1: \"value\", key2: 2, key3: true } }";
  g_autoptr (WpSpaJson) json = wp_spa_json_new_from_string (json_str);
  g_assert_nonnull (json);
  g_assert_true (wp_spa_json_is_object (json));

  g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
  g_assert_nonnull (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_cmpstr (v, ==, "test-setting-json3");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_object (j));
    g_autofree gchar *v = wp_spa_json_to_string (j);
    g_assert_cmpstr (v, ==, "{ key1: \"value\", key2: 2, key3: true }");
    g_value_unset (&next);
  }
}

static void
test_spa_json_ownership (void)
{
  g_autoptr (WpSpaJson) json = NULL;

  {
    const gchar json_str[] = "{\"name\":\"John\", \"age\":30, \"car\":null}";
    json = wp_spa_json_new_from_string (json_str);
    g_assert_nonnull (json);

    g_assert_false (wp_spa_json_is_unique_owner (json));

    g_assert_true (wp_spa_json_is_object (json));
    g_assert_cmpmem (wp_spa_json_get_data (json), wp_spa_json_get_size (json),
        "{\"name\":\"John\", \"age\":30, \"car\":null}", 37);

    json = wp_spa_json_ensure_unique_owner (json);
    g_assert_nonnull (json);
    g_assert_true (wp_spa_json_is_unique_owner (json));
  }

  g_assert_true (wp_spa_json_is_object (json));
  g_assert_cmpmem (wp_spa_json_get_data (json), wp_spa_json_get_size (json),
      "{\"name\":\"John\", \"age\":30, \"car\":null}", 37);

  g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
  g_assert_nonnull (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "name");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "John");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "age");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_int (j));
    gint32 v = 0;
    g_assert_true (wp_spa_json_parse_int (j, &v));
    g_assert_cmpint (v, ==, 30);
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "car");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_null (j));
    g_value_unset (&next);
  }
}

static void
test_spa_json_spa_format (void)
{
  g_autoptr (WpSpaJson) json = NULL;

  const gchar json_str[] = "{ name = John age:30, \"car\" null }";
  json = wp_spa_json_new_from_string (json_str);
  g_assert_nonnull (json);

  g_assert_true (wp_spa_json_is_object (json));

  g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
  g_assert_nonnull (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_false (wp_spa_json_is_string (j));  // FALSE because no quotes
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "name");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_false (wp_spa_json_is_string (j));  // FALSE because no quotes
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "John");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_false (wp_spa_json_is_string (j));  // FALSE because no quotes
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "age");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_int (j));
    gint32 v = 0;
    g_assert_true (wp_spa_json_parse_int (j, &v));
    g_assert_cmpint (v, ==, 30);
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_string (j));
    g_autofree gchar *v = wp_spa_json_parse_string (j);
    g_assert_nonnull (v);
    g_assert_cmpstr (v, ==, "car");
    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *j = g_value_get_boxed (&next);
    g_assert_nonnull (j);
    g_assert_true (wp_spa_json_is_null (j));
    g_value_unset (&next);
  }
}

static void
test_spa_json_to_string (void)
{
  const gchar json_str[] = "[{\"key0\":\"val0\"}, {\"key1\":\"val1\"}]";
  g_autoptr (WpSpaJson) json = wp_spa_json_new_from_string (json_str);
  g_assert_nonnull (json);

  {
    g_autofree gchar *str = wp_spa_json_to_string (json);
    g_assert_cmpstr (str, ==, wp_spa_json_get_data (json));
    g_assert_cmpstr (str, ==, json_str);
  }

  g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json);
  g_assert_nonnull (it);

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *o = g_value_get_boxed (&next);
    g_assert_nonnull (o);
    g_assert_true (wp_spa_json_is_object (o));
    g_autofree gchar *str = wp_spa_json_to_string (o);
    g_assert_cmpstr (str, ==, "{\"key0\":\"val0\"}");
    g_assert_cmpstr (str, !=, wp_spa_json_get_data (o));

    g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_array ();
    wp_spa_json_builder_add_json (b, o);
    g_autoptr (WpSpaJson) json2 = wp_spa_json_builder_end (b);
    g_autofree gchar *str2 = wp_spa_json_to_string (json2);
    g_assert_cmpstr (str2, ==, wp_spa_json_get_data (json2));
    g_assert_cmpstr (str2, ==, "[{\"key0\":\"val0\"}]");

    g_value_unset (&next);
  }

  {
    GValue next = G_VALUE_INIT;
    g_assert_true (wp_iterator_next (it, &next));
    WpSpaJson *o = g_value_get_boxed (&next);
    g_assert_nonnull (o);
    g_assert_true (wp_spa_json_is_object (o));
    g_autofree gchar *str = wp_spa_json_to_string (o);
    g_assert_cmpstr (str, ==, "{\"key1\":\"val1\"}");
    g_assert_cmpstr (str, !=, wp_spa_json_get_data (o));

    g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_array ();
    wp_spa_json_builder_add_json (b, o);
    g_autoptr (WpSpaJson) json2 = wp_spa_json_builder_end (b);
    g_autofree gchar *str2 = wp_spa_json_to_string (json2);
    g_assert_cmpstr (str2, ==, wp_spa_json_get_data (json2));
    g_assert_cmpstr (str2, ==, "[{\"key1\":\"val1\"}]");

    g_value_unset (&next);
  }
}

int
main (int argc, char *argv[])
{
  g_test_init (&argc, &argv, NULL);
  g_log_set_writer_func (wp_log_writer_default, NULL, NULL);

  g_test_add_func ("/wp/spa-json/basic", test_spa_json_basic);
  g_test_add_func ("/wp/spa-json/array-builder-parser-iterator",
      test_spa_json_array_builder_parser_iterator);
  g_test_add_func ("/wp/spa-json/object-builder-parser-iterator",
      test_spa_json_object_builder_parser_iterator);
  g_test_add_func ("/wp/spa-json/nested", test_spa_json_nested);
  g_test_add_func ("/wp/spa-json/nested2", test_spa_json_nested2);
  g_test_add_func ("/wp/spa-json/nested3", test_spa_json_nested3);
  g_test_add_func ("/wp/spa-json/ownership", test_spa_json_ownership);
  g_test_add_func ("/wp/spa-json/spa-format", test_spa_json_spa_format);
  g_test_add_func ("/wp/spa-json/to-string", test_spa_json_to_string);

  return g_test_run ();
}
 07070100000180000081A4000000000000000000000001656CC35F000091D8000000000000000000000000000000000000002600000000wireplumber-0.4.17/tests/wp/spa-pod.c /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>

static void
test_spa_pod_basic (void)
{
  /* None */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_none ();
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_none (pod));
    g_assert_false (wp_spa_pod_is_id (pod));
    g_assert_cmpstr ("Spa:None", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_none ();
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Boolean */
  {
    g_autoptr (WpSpaPod) copy = NULL;
    g_assert_null (copy);

    {
      g_autoptr (WpSpaPod) pod = wp_spa_pod_new_boolean (TRUE);
      g_assert_nonnull (pod);
      g_assert_true (wp_spa_pod_is_boolean (pod));
      gboolean value = FALSE;
      g_assert_true (wp_spa_pod_get_boolean (pod, &value));
      g_assert_true (value);
      g_assert_cmpstr ("Spa:Bool", ==,
          wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
      g_assert_true (wp_spa_pod_set_boolean (pod, FALSE));
      g_assert_true (wp_spa_pod_get_boolean (pod, &value));
      g_assert_false (value);

      copy = wp_spa_pod_copy (pod);
    }

    g_assert_nonnull (copy);
    g_assert_true (wp_spa_pod_is_boolean (copy));
    gboolean value = FALSE;
    g_assert_true (wp_spa_pod_get_boolean (copy, &value));
    g_assert_false (value);
    g_assert_cmpstr ("Spa:Bool", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (copy)));
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_boolean (TRUE);
    g_assert_true (wp_spa_pod_set_pod (copy, other));
    g_assert_true (wp_spa_pod_get_boolean (copy, &value));
    g_assert_true (value);
    g_assert_true (wp_spa_pod_equal (copy, other));
  }

  /* Id */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_id (5);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_id (pod));
    guint32 value = 0;
    g_assert_true (wp_spa_pod_get_id (pod, &value));
    g_assert_cmpuint (value, ==, 5);
    g_assert_cmpstr ("Spa:Id", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_id (pod, 10));
    g_assert_true (wp_spa_pod_get_id (pod, &value));
    g_assert_cmpuint (value, ==, 10);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_id (20);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_id (pod, &value));
    g_assert_cmpuint (value, ==, 20);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Int */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_int (-12);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_int (pod));
    gint32 value = 0;
    g_assert_true (wp_spa_pod_get_int (pod, &value));
    g_assert_cmpint (value, ==, -12);
    g_assert_cmpstr ("Spa:Int", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_int (pod, 9999));
    g_assert_true (wp_spa_pod_get_int (pod, &value));
    g_assert_cmpint (value, ==, 9999);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_int (1000);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_int (pod, &value));
    g_assert_cmpuint (value, ==, 1000);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Long */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_long (LONG_MAX);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_long (pod));
    gint64 value = 0;
    g_assert_true (wp_spa_pod_get_long (pod, &value));
    g_assert_cmpint (value, ==, LONG_MAX);
    g_assert_cmpstr ("Spa:Long", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_long (pod, LONG_MIN));
    g_assert_true (wp_spa_pod_get_long (pod, &value));
    g_assert_cmpuint (value, ==, LONG_MIN);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_long (0);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_long (pod, &value));
    g_assert_cmpuint (value, ==, 0);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Float */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_float (3.14);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_float (pod));
    float value = 0;
    g_assert_true (wp_spa_pod_get_float (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 3.14, 0.001);
    g_assert_cmpstr ("Spa:Float", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_float (pod, 1.0));
    g_assert_true (wp_spa_pod_get_float (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 1.0, 0.001);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_float (-3.14);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_float (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, -3.14, 0.001);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Double */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_double (2.718281828);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_double (pod));
    double value = 0;
    g_assert_true (wp_spa_pod_get_double (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 2.718281828, 0.0000000001);
    g_assert_cmpstr ("Spa:Double", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_double (pod, 2.0));
    g_assert_true (wp_spa_pod_get_double (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 2.0, 0.0000000001);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_double (3.0);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_double (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 3.0, 0.0000000001);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* String */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_string ("WirePlumber");
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_string (pod));
    const char *value = NULL;
    g_assert_true (wp_spa_pod_get_string (pod, &value));
    g_assert_nonnull (value);
    g_assert_cmpstr (value, ==, "WirePlumber");
    g_assert_cmpstr ("Spa:String", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_string ("Other");
    g_assert_nonnull (other);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_string (pod, &value));
    g_assert_nonnull (value);
    g_assert_cmpstr (value, ==, "Other");
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Bytes */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_bytes ("bytes", 5);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_bytes (pod));
    gconstpointer value = NULL;
    guint32 len = 0;
    g_assert_true (wp_spa_pod_get_bytes (pod, &value, &len));
    g_assert_nonnull (value);
    g_assert_cmpmem (value, len, "bytes", 5);
    g_assert_cmpuint (len, ==, 5);
    g_assert_cmpstr ("Spa:Bytes", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_bytes ("pod", 3);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_bytes (pod, &value, &len));
    g_assert_nonnull (value);
    g_assert_cmpmem (value, len, "pod", 3);
    g_assert_cmpuint (len, ==, 3);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Pointer */
  {
    gint i = 3;
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_pointer ("Spa:Pointer:Buffer", &i);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_pointer (pod));
    gconstpointer p = NULL;
    g_assert_true (wp_spa_pod_get_pointer (pod, &p));
    g_assert_nonnull (p);
    g_assert_true (p == &i);
    g_assert_cmpint (*(gint *)p, ==, 3);
    g_assert_cmpstr ("Spa:Pointer:Buffer", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    float f = 1.1;
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_pointer ("Spa:Pointer:Meta", &f);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_pointer (pod, &p));
    g_assert_nonnull (p);
    g_assert_true (p == &f);
    g_assert_cmpfloat_with_epsilon (*(float *)p, 1.1, 0.01);
    g_assert_cmpstr ("Spa:Pointer:Meta", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Fd */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_fd (4);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_fd (pod));
    gint64 value = 0;
    g_assert_true (wp_spa_pod_get_fd (pod, &value));
    g_assert_cmpint (value, ==, 4);
    g_assert_cmpstr ("Spa:Fd", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_fd (pod, 1));
    g_assert_true (wp_spa_pod_get_fd (pod, &value));
    g_assert_cmpuint (value, ==, 1);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_fd (10);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_fd (pod, &value));
    g_assert_cmpuint (value, ==, 10);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Rectangle */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_rectangle (1920, 1080);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_rectangle (pod));
    guint32 width = 0;
    guint32 height = 0;
    g_assert_true (wp_spa_pod_get_rectangle (pod, &width, &height));
    g_assert_cmpint (width, ==, 1920);
    g_assert_cmpint (height, ==, 1080);
    g_assert_cmpstr ("Spa:Rectangle", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_rectangle (pod, 640, 480));
    g_assert_true (wp_spa_pod_get_rectangle (pod, &width, &height));
    g_assert_cmpint (width, ==, 640);
    g_assert_cmpint (height, ==, 480);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_rectangle (200, 100);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_rectangle (pod, &width, &height));
    g_assert_cmpint (width, ==, 200);
    g_assert_cmpint (height, ==, 100);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Fraction */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_fraction (16, 9);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_fraction (pod));
    guint32 num = 0;
    guint32 denom = 0;
    g_assert_true (wp_spa_pod_get_fraction (pod, &num, &denom));
    g_assert_cmpint (num, ==, 16);
    g_assert_cmpint (denom, ==, 9);
    g_assert_cmpstr ("Spa:Fraction", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_fraction (pod, 4, 3));
    g_assert_true (wp_spa_pod_get_fraction (pod, &num, &denom));
    g_assert_cmpint (num, ==, 4);
    g_assert_cmpint (denom, ==, 3);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_fraction (2, 1);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_fraction (pod, &num, &denom));
    g_assert_cmpint (num, ==, 2);
    g_assert_cmpint (denom, ==, 1);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }
}

static void
test_spa_pod_choice (void)
{
  /* Static Enum */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_choice (
        "Enum", "i", 0, "i", 1, "i", 2, NULL);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_choice (pod));
    g_assert_cmpstr ("Spa:Pod:Choice", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_cmpstr ("Enum", ==,
        wp_spa_id_value_short_name (wp_spa_pod_get_choice_type (pod)));

    g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
    g_assert_nonnull (child);
    g_assert_cmpstr ("Spa:Int", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
    gint32 value = 1;
    g_assert_true (wp_spa_pod_get_int (child, &value));
    g_assert_cmpint (value, ==, 0);
    g_assert_true (wp_spa_pod_set_int (child, 3));
    g_assert_true (wp_spa_pod_get_int (child, &value));
    g_assert_cmpint (value, ==, 3);
  }

  /* Static None */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_choice ("None", "s",
        "default value", NULL);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_choice (pod));
    g_assert_cmpstr ("Spa:Pod:Choice", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    {
      g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
      g_assert_nonnull (child);
      g_assert_cmpstr ("Spa:String", ==,
          wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
      const char *value = NULL;
      g_assert_true (wp_spa_pod_get_string (child, &value));
      g_assert_nonnull (value);
      g_assert_cmpstr ("default value", ==, value);
      g_autoptr (WpSpaPod) str_pod = wp_spa_pod_new_string ("new value");
      g_assert_true (wp_spa_pod_set_pod (child, str_pod));
      g_assert_true (wp_spa_pod_get_string (child, &value));
      g_assert_cmpstr ("new value", ==, value);
    }

    {
      g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
      g_assert_nonnull (child);
      g_assert_cmpstr ("Spa:String", ==,
          wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
      const char *value = NULL;
      g_assert_true (wp_spa_pod_get_string (child, &value));
      g_assert_nonnull (value);
      g_assert_cmpstr ("new value", ==, value);
    }
  }

  /* Dynamic */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_choice ("Enum");
    wp_spa_pod_builder_add (b, "i", 0, NULL);
    wp_spa_pod_builder_add (b, "i", 1, NULL);
    wp_spa_pod_builder_add (b, "i", 2, NULL);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_choice (pod));
    g_assert_cmpstr ("Spa:Pod:Choice", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
  }

  /* It is not possible to use the parser to get the contents of a choice, you
   * need to use the iterator API to achieve that. This is because there is no
   * `spa_pod_parser_get_choice` API in the SPA library */
}

static void
test_spa_pod_array (void)
{
  /* Dynamic */
  {
    WpSpaPodBuilder *b = wp_spa_pod_builder_new_array ();
    wp_spa_pod_builder_add (b, "b", FALSE, NULL);
    wp_spa_pod_builder_add (b, "b", TRUE, NULL);
    wp_spa_pod_builder_add (b, "b", TRUE, NULL);
    wp_spa_pod_builder_add (b, "b", FALSE, NULL);
    wp_spa_pod_builder_add (b, "b", TRUE, NULL);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_array (pod));
    g_assert_cmpstr ("Spa:Array", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    wp_spa_pod_builder_unref (b);
    g_assert_true (wp_spa_pod_is_array (pod));

    g_autoptr (WpSpaPod) child = wp_spa_pod_get_array_child (pod);
    g_assert_nonnull (child);
    g_assert_cmpstr ("Spa:Bool", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
    gboolean value = TRUE;
    g_assert_true (wp_spa_pod_get_boolean (child, &value));
    g_assert_false (value);
  }

  /* It is not possible to use the parser to get the contents of an array, you
   * need to use the iterator API to achieve that. This is because there is no
   * `spa_pod_parser_get_array` API in the SPA library. */
}

static void
test_spa_pod_object (void)
{
  /* Static */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object (
        "Spa:Pod:Object:Param:Props", "Props",
        "mute", "b", FALSE,
        "volume", "f", 0.5,
        "frequency", "i", 440,
        "device", "s", "device-name",
        "deviceFd", "h", 5,
        "id-01000000", "b", TRUE,
        NULL);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_object (pod));
    g_assert_cmpstr ("Spa:Pod:Object:Param:Props", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    const char *id_name;
    gboolean mute = TRUE;
    float vol = 0.0;
    gint32 frequency;
    const char *device;
    gint64 device_fd;
    gboolean custom = FALSE;
    g_assert_true (wp_spa_pod_get_object (pod,
        &id_name,
        "mute", "b", &mute,
        "volume", "f", &vol,
        "frequency", "i", &frequency,
        "device", "s", &device,
        "deviceFd", "h", &device_fd,
        "id-01000000", "b", &custom,
        NULL));
    g_assert_cmpstr (id_name, ==, "Props");
    g_assert_false (mute);
    g_assert_cmpfloat_with_epsilon (vol, 0.5, 0.01);
    g_assert_cmpint (frequency, ==, 440);
    g_assert_cmpstr (device, ==, "device-name");
    g_assert_cmpint (device_fd, ==, 5);
    g_assert_true (custom);
  }

  /* Dynamic */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_object (
        "Spa:Pod:Object:Param:Props", "Props");
    wp_spa_pod_builder_add_property (b, "mute");
    wp_spa_pod_builder_add_boolean (b, FALSE);
    wp_spa_pod_builder_add_property (b, "volume");
    wp_spa_pod_builder_add_float (b, 0.5);
    wp_spa_pod_builder_add_property (b, "frequency");
    wp_spa_pod_builder_add_int (b, 440);
    wp_spa_pod_builder_add_property (b, "device");
    wp_spa_pod_builder_add_string (b, "device-name");
    wp_spa_pod_builder_add_property (b, "deviceFd");
    wp_spa_pod_builder_add_fd (b, 5);
    wp_spa_pod_builder_add_property (b, "id-01000000");
    wp_spa_pod_builder_add_boolean (b, TRUE);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_object (pod));
    g_assert_cmpstr ("Spa:Pod:Object:Param:Props", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    const char *id_name;
    gboolean mute = TRUE;
    float vol = 0.0;
    gint32 frequency;
    const char *device;
    gint64 device_fd;
    gboolean custom = FALSE;
    g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_object (pod, &id_name);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_parser_get (p, "mute", "b", &mute, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "volume", "f", &vol, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "frequency", "i", &frequency, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "device", "s", &device, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "deviceFd", "h", &device_fd, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "id-01000000", "b", &custom, NULL));
    wp_spa_pod_parser_end (p);
    g_assert_cmpstr (id_name, ==, "Props");
    g_assert_false (mute);
    g_assert_cmpfloat_with_epsilon (vol, 0.5, 0.01);
    g_assert_cmpint (frequency, ==, 440);
    g_assert_cmpstr (device, ==, "device-name");
    g_assert_cmpint (device_fd, ==, 5);
    g_assert_true (custom);
  }
}

static void
test_spa_pod_struct (void)
{
  /* Dynamic */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_struct ();
    wp_spa_pod_builder_add_boolean (b, TRUE);
    wp_spa_pod_builder_add_id (b, 2);
    wp_spa_pod_builder_add_int (b, 8);
    wp_spa_pod_builder_add_long (b, 64);
    wp_spa_pod_builder_add_float (b, 3.14);
    wp_spa_pod_builder_add_double (b, 2.718281828);
    wp_spa_pod_builder_add_string (b, "WirePlumber");
    wp_spa_pod_builder_add_bytes (b, "bytes", 5);
    wp_spa_pod_builder_add_pointer (b, "Spa:Pointer:Buffer", b);
    wp_spa_pod_builder_add_fd (b, 4);
    wp_spa_pod_builder_add_rectangle (b, 1920, 1080);
    wp_spa_pod_builder_add_fraction (b, 16, 9);
    {
      g_autoptr (WpSpaPod) pod = wp_spa_pod_new_int (35254);
      wp_spa_pod_builder_add_pod (b, pod);
    }
    {
      g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object (
        "Spa:Pod:Object:Param:Props", "Props",
        "mute", "b", FALSE,
        NULL);
      wp_spa_pod_builder_add (b, "P", pod, NULL);
    }
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_struct (pod));
    g_assert_cmpstr ("Spa:Pod:Struct", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_struct (pod);
    g_assert_nonnull (pod);

    gboolean value_boolean;
    g_assert_true (wp_spa_pod_parser_get_boolean (p, &value_boolean));
    g_assert_true (value_boolean);

    guint32 value_id;
    g_assert_true (wp_spa_pod_parser_get_id (p, &value_id));
    g_assert_cmpuint (value_id, ==, 2);

    gint32 value_int;
    g_assert_true (wp_spa_pod_parser_get_int (p, &value_int));
    g_assert_cmpint (value_int, ==, 8);

    gint64 value_long;
    g_assert_true (wp_spa_pod_parser_get_long (p, &value_long));
    g_assert_cmpint (value_long, ==, 64);

    float value_float;
    g_assert_true (wp_spa_pod_parser_get_float (p, &value_float));
    g_assert_cmpfloat_with_epsilon (value_float, 3.14, 0.001);

    double value_double;
    g_assert_true (wp_spa_pod_parser_get_double (p, &value_double));
    g_assert_cmpfloat_with_epsilon (value_double, 2.718281828, 0.0000000001);

    const char *value_string;
    g_assert_true (wp_spa_pod_parser_get_string (p, &value_string));
    g_assert_cmpstr (value_string, ==, "WirePlumber");

    gconstpointer value_bytes;
    guint32 len_bytes;
    g_assert_true (wp_spa_pod_parser_get_bytes (p, &value_bytes, &len_bytes));
    g_assert_cmpmem (value_bytes, len_bytes, "bytes", 5);
    g_assert_cmpuint (len_bytes, ==, 5);

    gconstpointer value_pointer;
    g_assert_true (wp_spa_pod_parser_get_pointer (p, &value_pointer));
    g_assert_nonnull (value_pointer);
    g_assert_true (value_pointer == b);

    gint64 value_fd;
    g_assert_true (wp_spa_pod_parser_get_fd (p, &value_fd));
    g_assert_cmpint (value_fd, ==, 4);

    guint32 value_width;
    guint32 value_height;
    g_assert_true (wp_spa_pod_parser_get_rectangle (p, &value_width, &value_height));
    g_assert_cmpuint (value_width, ==, 1920);
    g_assert_cmpuint (value_height, ==, 1080);

    guint32 value_num;
    guint32 value_denom;
    g_assert_true (wp_spa_pod_parser_get_fraction (p, &value_num, &value_denom));
    g_assert_cmpuint (value_num, ==, 16);
    g_assert_cmpuint (value_denom, ==, 9);

    g_autoptr (WpSpaPod) value_pod = wp_spa_pod_parser_get_pod (p);
    g_assert_nonnull (value_pod);
    gint value_pod_int;
    g_assert_true (wp_spa_pod_get_int (value_pod, &value_pod_int));
    g_assert_cmpint (value_pod_int, ==, 35254);

    g_autoptr (WpSpaPod) value_object = NULL;
    g_assert_true (wp_spa_pod_parser_get (p, "P", &value_object, NULL));
    g_assert_nonnull (value_object);
    const char *id_name;
    gboolean mute = TRUE;

    g_assert_true (wp_spa_pod_get_object (value_object,
        &id_name,
        "mute", "b", &mute,
        NULL));
    g_assert_cmpstr (id_name, ==, "Props");
    g_assert_false (mute);
  }
}

static void
test_spa_pod_sequence (void)
{
  /* Static */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_sequence (0,
        10, "Properties", "l", G_GINT64_CONSTANT (9999), NULL);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_sequence (pod));
    g_assert_cmpstr ("Spa:Pod:Sequence", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
  }

  /* Dynamic */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_sequence (0);
    wp_spa_pod_builder_add_control (b, 10, "Properties");
    wp_spa_pod_builder_add_long (b, G_GINT64_CONSTANT (9999));
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_sequence (pod));
    g_assert_cmpstr ("Spa:Pod:Sequence", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
  }

  /* It is not possible to use the parser to get the contents of a sequence, you
   * need to use the iterator API to achieve that. This is because there is no
   * `spa_pod_parser_get_sequence` API in the SPA library. */
}

static void
choice_foreach (const GValue *item, gpointer data)
{
  gint32 *total = data;
  const gint32 *value = g_value_get_pointer (item);
  *total += *value;
}

static void
array_foreach (const GValue *item, gpointer data)
{
  gint32 *total = data;
  const gint32 *value = g_value_get_pointer (item);
  *total += *value;
}

static void
object_foreach (const GValue *item, gpointer data)
{
  guint32 *total_props = data;
  WpSpaPod *prop = g_value_get_boxed (item);
  g_assert_true (wp_spa_pod_is_property (prop));
  *total_props += 1;
}

static void
struct_foreach (const GValue *item, gpointer data)
{
  guint32 *total_fields = data;
  *total_fields += 1;
}

static void
sequence_foreach (const GValue *item, gpointer data)
{
  guint32 *offset_total = data;
  WpSpaPod *control = g_value_get_boxed (item);
  g_assert_true (wp_spa_pod_is_control (control));
  guint32 offset = 0;
  g_assert_true (wp_spa_pod_get_control (control, &offset, NULL, NULL));
  *offset_total += offset;
}

static void
test_spa_pod_iterator (void)
{
  /* Choice */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_choice ("Enum");
    wp_spa_pod_builder_add (b, "i", 0, NULL);
    wp_spa_pod_builder_add (b, "i", 1, NULL);
    wp_spa_pod_builder_add (b, "i", 2, NULL);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 0);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 1);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 2);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    gint32 total = 0;
    g_assert_true (wp_iterator_foreach (it, choice_foreach, &total));
    g_assert_cmpint (total, ==, 3);

  }

  /* Array */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_array ();
    wp_spa_pod_builder_add_int (b, 1);
    wp_spa_pod_builder_add_int (b, 2);
    wp_spa_pod_builder_add_int (b, 3);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 1);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 2);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 3);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    gint total = 0;
    g_assert_true (wp_iterator_foreach (it, array_foreach, &total));
    g_assert_cmpint (total, ==, 6);
  }

  /* Object */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_object (
        "Spa:Pod:Object:Param:Props", "Props");
    wp_spa_pod_builder_add_property (b, "mute");
    wp_spa_pod_builder_add_boolean (b, FALSE);
    wp_spa_pod_builder_add_property (b, "device");
    wp_spa_pod_builder_add_string (b, "device-name");
    wp_spa_pod_builder_add_property (b, "id-01000000");
    wp_spa_pod_builder_add_boolean (b, TRUE);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_property (p));
      const char *key = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_property (p, &key, &value));
      g_assert_cmpstr (key, ==, "mute");
      gboolean b = TRUE;
      g_assert_true (wp_spa_pod_get_boolean (value, &b));
      g_assert_false (b);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_property (p));
      const char *key = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_property (p, &key, &value));
      g_assert_cmpstr (key, ==, "device");
      const char *s = NULL;
      g_assert_true (wp_spa_pod_get_string (value, &s));
      g_assert_cmpstr (s, ==, "device-name");
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_property (p));
      const char *key = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_property (p, &key, &value));
      g_assert_cmpstr (key, ==, "id-01000000");
      gboolean b = FALSE;
      g_assert_true (wp_spa_pod_get_boolean (value, &b));
      g_assert_true (b);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    guint32 total_props = 0;
    g_assert_true (wp_iterator_foreach (it, object_foreach, &total_props));
    g_assert_cmpuint (total_props, ==, 3);
  }

  /* Struct */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_struct ();
    wp_spa_pod_builder_add_boolean (b, TRUE);
    wp_spa_pod_builder_add_id (b, 2);
    wp_spa_pod_builder_add_int (b, 8);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      gboolean v = FALSE;
      g_assert_true (wp_spa_pod_get_boolean (p, &v));
      g_assert_true (v);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      guint32 v = 0;
      g_assert_true (wp_spa_pod_get_id (p, &v));
      g_assert_cmpuint (v, ==, 2);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      gint v = 0;
      g_assert_true (wp_spa_pod_get_int (p, &v));
      g_assert_cmpint (v, ==, 8);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    guint32 total_fields = 0;
    g_assert_true (wp_iterator_foreach (it, struct_foreach, &total_fields));
    g_assert_cmpuint (total_fields, ==, 3);
  }

  /* Sequence */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_sequence (0);
    wp_spa_pod_builder_add_control (b, 10, "Properties");
    wp_spa_pod_builder_add_float (b, 0.33);
    wp_spa_pod_builder_add_control (b, 40, "Properties");
    wp_spa_pod_builder_add_float (b, 0.66);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_control (p));
      guint32 offset = 0;
      const char *type_name = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_control (p, &offset, &type_name, &value));
      g_assert_cmpuint (offset, ==, 10);
      g_assert_cmpstr (type_name, ==, "Properties");
      float f = 0;
      g_assert_true (wp_spa_pod_get_float (value, &f));
      g_assert_cmpfloat_with_epsilon (f, 0.33, 0.001);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_control (p));
      guint32 offset = 0;
      const char *type_name = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_control (p, &offset, &type_name, &value));
      g_assert_cmpuint (offset, ==, 40);
      g_assert_cmpstr (type_name, ==, "Properties");
      float f = 0;
      g_assert_true (wp_spa_pod_get_float (value, &f));
      g_assert_cmpfloat_with_epsilon (f, 0.66, 0.001);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    guint32 offset_total = 0;
    g_assert_true (wp_iterator_foreach (it, sequence_foreach, &offset_total));
    g_assert_cmpuint (offset_total, ==, 50);
  }
}

static void
test_spa_pod_unique_owner (void)
{
  /* Create an object */
  WpSpaPod *pod = wp_spa_pod_new_object (
        "Spa:Pod:Object:Param:PropInfo", "PropInfo",
        "id", "K", "unknown",
        "name", "s", "prop-info-name",
        NULL);
  g_assert_nonnull (pod);
  g_assert_true (wp_spa_pod_is_unique_owner (pod));

  /* Get the first property using an iterator */
  GValue next = G_VALUE_INIT;
  g_autoptr (WpSpaPod) property = NULL;
  {
    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);
    g_assert_true (wp_iterator_next (it, &next));
    property = g_value_dup_boxed (&next);
  }
  g_assert_nonnull (property);
  g_assert_true (wp_spa_pod_is_property (property));
  {
    g_autoptr (WpSpaPod) value = NULL;
    const char *key = NULL;
    g_assert_true (wp_spa_pod_get_property (property, &key, &value));
    g_assert_nonnull (key);
    g_assert_cmpstr (key, ==, "id");
    g_assert_nonnull (value);
    guint32 id = 0;
    g_assert_true (wp_spa_pod_get_id (value, &id));
    g_assert_cmpuint (id, ==, 1);
  }

  /* Own the data */
  g_assert_true (wp_spa_pod_is_unique_owner (pod));
  g_assert_false (wp_spa_pod_is_unique_owner (property));
  property = wp_spa_pod_ensure_unique_owner (property);
  g_assert_true (wp_spa_pod_is_unique_owner (pod));
  g_assert_true (wp_spa_pod_is_unique_owner (property));

  /* Destroy the object */
  wp_spa_pod_unref (pod);
  g_assert_true (wp_spa_pod_is_unique_owner (property));

  /* Make sure the property data is still valid */
  {
    g_autoptr (WpSpaPod) value = NULL;
    const char *key = NULL;
    g_assert_true (wp_spa_pod_get_property (property, &key, &value));
    g_assert_nonnull (key);
    g_assert_cmpstr (key, ==, "id");
    g_assert_nonnull (value);
    guint32 id = 0;
    g_assert_true (wp_spa_pod_get_id (value, &id));
    g_assert_cmpuint (id, ==, 1);
  }

  /* Destroy the property */
  g_value_unset (&next);
}

static void
test_spa_pod_port_config (void)
{
  const gint32 rate = 48000;
  const gint32 channels = 2;

  /* Build the format to make sure the types exist */
  g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_object (
     "Spa:Pod:Object:Param:Format", "Format");
  wp_spa_pod_builder_add (builder,
     "mediaType",    "K", "audio",
     "mediaSubtype", "K", "raw",
     "format",       "K", "S16LE",
     "rate",         "i", rate,
     "channels",     "i", channels,
     NULL);
  g_autoptr (WpSpaPodBuilder) position_builder = wp_spa_pod_builder_new_array ();
  for (gint i = 0; i < channels; i++)
    wp_spa_pod_builder_add_id (position_builder, 0);
  wp_spa_pod_builder_add_property (builder, "position");
  g_autoptr (WpSpaPod) position = wp_spa_pod_builder_end (position_builder);
  wp_spa_pod_builder_add_pod (builder, position);
  g_autoptr (WpSpaPod) format = wp_spa_pod_builder_end (builder);
  g_assert_nonnull (format);

  /* Build the port config to make sure the types exist */
  g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object (
      "Spa:Pod:Object:Param:PortConfig", "PortConfig",
      "direction",  "K", "Input",
      "mode",       "K", "dsp",
      "monitor",    "b", FALSE,
      "control",    "b", FALSE,
      "format",     "P", format,
      NULL);
  g_assert_nonnull (pod);
}

int
main (int argc, char *argv[])
{
  g_test_init (&argc, &argv, NULL);
  g_log_set_writer_func (wp_log_writer_default, NULL, NULL);

  g_test_add_func ("/wp/spa-pod/basic", test_spa_pod_basic);
  g_test_add_func ("/wp/spa-pod/choice", test_spa_pod_choice);
  g_test_add_func ("/wp/spa-pod/array", test_spa_pod_array);
  g_test_add_func ("/wp/spa-pod/object", test_spa_pod_object);
  g_test_add_func ("/wp/spa-pod/struct", test_spa_pod_struct);
  g_test_add_func ("/wp/spa-pod/sequence", test_spa_pod_sequence);
  g_test_add_func ("/wp/spa-pod/iterator", test_spa_pod_iterator);
  g_test_add_func ("/wp/spa-pod/unique-owner", test_spa_pod_unique_owner);
  g_test_add_func ("/wp/spa-pod/port-config", test_spa_pod_port_config);

  return g_test_run ();
}
07070100000181000081A4000000000000000000000001656CC35F00003C9F000000000000000000000000000000000000002700000000wireplumber-0.4.17/tests/wp/spa-type.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <spa/utils/type-info.h>

static void
test_spa_type_basic (void)
{
  g_assert_cmpuint (WP_SPA_TYPE_INVALID, ==, SPA_ID_INVALID);

  {
    WpSpaType type = SPA_TYPE_Int;
    g_assert_cmpstr (wp_spa_type_name (type), ==, "Spa:Int");
    g_assert_true (wp_spa_type_is_fundamental (type));
    g_assert_cmpuint (wp_spa_type_parent (type), ==, SPA_TYPE_Int);
  }

  {
    WpSpaType type = wp_spa_type_from_name ("Spa:Enum:ParamId");
    g_assert_cmpuint (type, ==, WP_SPA_TYPE_INVALID);

    WpSpaIdTable table = wp_spa_id_table_from_name ("Spa:Enum:ParamId");
    g_assert_nonnull (table);
  }

  {
    WpSpaType type = SPA_TYPE_OBJECT_Props;
    g_assert_cmpstr (wp_spa_type_name (type), ==, "Spa:Pod:Object:Param:Props");
    g_assert_cmpuint (wp_spa_type_from_name (SPA_TYPE_INFO_Props), ==, type);
    g_assert_true (wp_spa_type_is_object (type));
    g_assert_false (wp_spa_type_is_fundamental (type));
    g_assert_cmpuint (wp_spa_type_parent (type), ==, SPA_TYPE_Object);
    g_assert_nonnull (wp_spa_type_get_object_id_values_table (type));
    g_assert_true (wp_spa_type_get_object_id_values_table (type) ==
        wp_spa_id_table_from_name ("Spa:Enum:ParamId"));
  }

  /* enums */
  {
    WpSpaIdValue id = wp_spa_id_value_from_name ("Spa:Enum:ParamId:Props");
    g_assert_nonnull (id);
    g_assert_cmpstr (wp_spa_id_value_name (id), ==, "Spa:Enum:ParamId:Props");
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Props");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PARAM_Props);

    g_assert_true (id == wp_spa_id_value_from_short_name (
            "Spa:Enum:ParamId", "Props"));
    g_assert_true (id == wp_spa_id_value_from_number (
            "Spa:Enum:ParamId", SPA_PARAM_Props));
  }

  {
    WpSpaIdValue id =
        wp_spa_id_value_from_name ("Spa:Enum:Control:Properties");
    g_assert_nonnull (id);
    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
        "Spa:Enum:Control:Properties");
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Properties");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CONTROL_Properties);

    g_assert_true (id == wp_spa_id_value_from_short_name (
            "Spa:Enum:Control", "Properties"));
    g_assert_true (id == wp_spa_id_value_from_number (
            "Spa:Enum:Control", SPA_CONTROL_Properties));
  }

  {
    WpSpaIdValue id = wp_spa_id_value_from_name ("Spa:Enum:Choice:Enum");
    g_assert_nonnull (id);
    g_assert_cmpstr (wp_spa_id_value_name (id), ==, "Spa:Enum:Choice:Enum");
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Enum");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Enum);

    g_assert_true (id == wp_spa_id_value_from_short_name (
            "Spa:Enum:Choice", "Enum"));
    g_assert_true (id == wp_spa_id_value_from_number (
            "Spa:Enum:Choice", SPA_CHOICE_Enum));
  }

  /* objects */
  {
    WpSpaIdValue id =
        wp_spa_id_value_from_name ("Spa:Pod:Object:Param:Props:mute");
    g_assert_nonnull (id);
    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
        "Spa:Pod:Object:Param:Props:mute");
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "mute");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_mute);

    g_assert_true (id == wp_spa_id_value_from_short_name (
            SPA_TYPE_INFO_Props, "mute"));
    g_assert_true (id == wp_spa_id_value_from_number (
            SPA_TYPE_INFO_Props, SPA_PROP_mute));
  }

  {
    WpSpaIdValue id =
        wp_spa_id_value_from_name ("Spa:Pod:Object:Param:PropInfo:id");
    g_assert_nonnull (id);
    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
        "Spa:Pod:Object:Param:PropInfo:id");
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "id");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_id);

    /* WpSpaIdValue is a pointer to static spa_type_info,
       so it should be the same on all queries */
    g_assert_true (id == wp_spa_id_value_from_short_name (
            SPA_TYPE_INFO_PropInfo, "id"));
    g_assert_true (id == wp_spa_id_value_from_number (
            SPA_TYPE_INFO_PropInfo, SPA_PROP_INFO_id));
  }

  /* array value type check */
  {
    WpSpaIdValue id =
        wp_spa_id_value_from_name ("Spa:Pod:Object:Param:Props:channelVolumes");
    g_assert_nonnull (id);
    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
        "Spa:Pod:Object:Param:Props:channelVolumes");
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "channelVolumes");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_channelVolumes);

    g_assert_cmpuint (wp_spa_id_value_array_get_item_type (id, NULL), ==,
        SPA_TYPE_Float);
  }

  {
    WpSpaIdValue id =
        wp_spa_id_value_from_name ("Spa:Pod:Object:Param:Props:channelMap");
    g_assert_nonnull (id);
    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
        "Spa:Pod:Object:Param:Props:channelMap");
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "channelMap");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_channelMap);

    WpSpaIdTable table = NULL;
    g_assert_cmpuint (wp_spa_id_value_array_get_item_type (id, &table), ==,
        SPA_TYPE_Id);
    g_assert_nonnull (table);
    g_assert_true (table == wp_spa_id_table_from_name ("Spa:Enum:AudioChannel"));
  }
}

static void
test_spa_type_iterate (void)
{
  {
    WpSpaType type = wp_spa_type_from_name (SPA_TYPE_INFO_PropInfo);
    g_assert_cmpuint (type, !=, WP_SPA_TYPE_INVALID);
    g_assert_true (wp_spa_type_is_object (type));

    WpSpaIdTable table = wp_spa_type_get_values_table (type);
    g_autoptr (WpIterator) it = wp_spa_id_table_new_iterator (table);
    g_auto (GValue) value = G_VALUE_INIT;
    WpSpaIdValue id = NULL;

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_START);
    table = NULL;
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Id);
    g_assert_nonnull (table);
    g_assert_true (table == wp_spa_id_table_from_name ("Spa:Enum:ParamId"));
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "id");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_id);
    table = NULL;
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Id);
    g_assert_nonnull (table);
    g_assert_true (table ==
        wp_spa_id_table_from_name ("Spa:Pod:Object:Param:Props"));
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "name");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_name);
    table = NULL;
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_String);
    g_assert_null (table);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "type");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_type);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Pod);
    g_assert_null (table);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "labels");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_labels);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Struct);
    g_assert_null (table);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "container");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_container);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Id);
    g_assert_null (table);
    g_value_unset (&value);
  }

  {
    WpSpaIdTable table = wp_spa_id_table_from_name ("Spa:Enum:Choice");
    g_assert_nonnull (table);

    g_autoptr (WpIterator) it = wp_spa_id_table_new_iterator (table);
    g_auto (GValue) value = G_VALUE_INIT;
    WpSpaIdValue id = NULL;

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "None");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_None);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Range");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Range);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Step");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Step);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Enum");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Enum);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Flags");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Flags);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
    g_value_unset (&value);
  }
}

static void
test_spa_type_register (void)
{
  static const struct spa_type_info custom_enum_info[] = {
    { 0, SPA_TYPE_Int, "Spa:Enum:CustomEnum:Invalid", NULL  },
    { 1, SPA_TYPE_Int, "Spa:Enum:CustomEnum:Valid", NULL  },
    { 0, 0, NULL, NULL }
  };

  static const struct spa_type_info custom_obj_info[] = {
    { 0, SPA_TYPE_Id, "Spa:Pod:Object:CustomObj:", custom_enum_info },
    { 1, SPA_TYPE_Int, "Spa:Pod:Object:CustomObj:id", NULL },
    { 2, SPA_TYPE_String, "Spa:Pod:Object:CustomObj:name", NULL },
    { 3, SPA_TYPE_Float, "Spa:Pod:Object:CustomObj:volume", NULL },
    { 4, SPA_TYPE_Rectangle, "Spa:Pod:Object:CustomObj:box", NULL },
    { 5, SPA_TYPE_Bytes, "Spa:Pod:Object:CustomObj:data", NULL },
    { 0, 0, NULL, NULL },
  };

  wp_spa_dynamic_type_init ();

  WpSpaIdTable enum_table =
      wp_spa_dynamic_id_table_register ("Spa:Enum:CustomEnum", custom_enum_info);
  WpSpaType obj_type = wp_spa_dynamic_type_register ("Spa:Pod:Object:CustomObj",
      SPA_TYPE_Object, custom_obj_info);

  g_assert_nonnull (enum_table);
  g_assert_true (obj_type != WP_SPA_TYPE_INVALID);

  g_assert_true (enum_table ==
      wp_spa_id_table_from_name ("Spa:Enum:CustomEnum"));

  {
    g_autoptr (WpIterator) it = wp_spa_id_table_new_iterator (enum_table);
    g_auto (GValue) value = G_VALUE_INIT;
    WpSpaIdValue id = NULL;

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Invalid");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 0);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Valid");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 1);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
    g_value_unset (&value);

    g_assert_false (wp_iterator_next (it, &value));
  }

  g_assert_cmpstr (wp_spa_type_name (obj_type), ==, "Spa:Pod:Object:CustomObj");
  g_assert_true (wp_spa_type_is_object (obj_type));
  g_assert_false (wp_spa_type_is_fundamental (obj_type));
  g_assert_cmpuint (wp_spa_type_parent (obj_type), ==, SPA_TYPE_Object);
  g_assert_cmpuint (obj_type, ==,
      wp_spa_type_from_name ("Spa:Pod:Object:CustomObj"));
  g_assert_true (enum_table ==
      wp_spa_type_get_object_id_values_table (obj_type));

  {
    WpSpaIdTable table = wp_spa_type_get_values_table (obj_type);
    g_autoptr (WpIterator) it = wp_spa_id_table_new_iterator (table);
    g_auto (GValue) value = G_VALUE_INIT;
    WpSpaIdValue id = NULL;

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 0);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Id);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "id");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 1);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "name");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 2);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_String);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "volume");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 3);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Float);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "box");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 4);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Rectangle);
    g_value_unset (&value);

    g_assert_true (wp_iterator_next (it, &value));
    id = g_value_get_pointer (&value);
    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "data");
    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 5);
    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Bytes);
    g_value_unset (&value);

    g_assert_false (wp_iterator_next (it, &value));
  }

  wp_spa_dynamic_type_deinit ();
}

int
main (int argc, char *argv[])
{
  g_test_init (&argc, &argv, NULL);
  g_log_set_writer_func (wp_log_writer_default, NULL, NULL);

  g_test_add_func ("/wp/spa-type/basic", test_spa_type_basic);
  g_test_add_func ("/wp/spa-type/iterate", test_spa_type_iterate);
  g_test_add_func ("/wp/spa-type/register", test_spa_type_register);

  return g_test_run ();
}
 07070100000182000081A4000000000000000000000001656CC35F00001B84000000000000000000000000000000000000002400000000wireplumber-0.4.17/tests/wp/state.c   /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>

static void
test_state_basic (void)
{
  g_autoptr (GError) error = NULL;
  g_autoptr (WpState) state = wp_state_new ("basic");
  g_assert_nonnull (state);

  g_assert_cmpstr (wp_state_get_name (state), ==, "basic");
  g_assert_true (g_str_has_suffix (wp_state_get_location (state), "basic"));

  /* Save */
  {
    g_autoptr (WpProperties) props = wp_properties_new_empty ();
    wp_properties_set (props, "key1", "value1");
    wp_properties_set (props, "key2", "value2");
    wp_properties_set (props, "key3", "value3");
    g_assert_true (wp_state_save (state, props, &error));
    g_assert_no_error (error);
  }

  /* Load */
  {
    g_autoptr (WpProperties) props = wp_state_load (state);
    g_assert_nonnull (props);
    g_assert_cmpstr (wp_properties_get (props, "key1"), ==, "value1");
    g_assert_cmpstr (wp_properties_get (props, "key2"), ==, "value2");
    g_assert_cmpstr (wp_properties_get (props, "key2"), ==, "value2");
    g_assert_null (wp_properties_get (props, "invalid"));
  }

  /* Re-Save */
  {
    g_autoptr (WpProperties) props = wp_properties_new_empty ();
    wp_properties_set (props, "new-key", "new-value");
    g_assert_true (wp_state_save (state, props, &error));
    g_assert_no_error (error);
  }

  /* Re-Load */
  {
    g_autoptr (WpProperties) props = wp_state_load (state);
    g_assert_nonnull (props);
    g_assert_cmpstr (wp_properties_get (props, "new-key"), ==, "new-value");
    g_assert_null (wp_properties_get (props, "key1"));
    g_assert_null (wp_properties_get (props, "key2"));
    g_assert_null (wp_properties_get (props, "key3"));
  }

  wp_state_clear (state);

  /* Load empty */
  {
    g_autoptr (WpProperties) props = wp_state_load (state);
    g_assert_nonnull (props);
    g_assert_null (wp_properties_get (props, "new-key"));
    g_assert_null (wp_properties_get (props, "key1"));
    g_assert_null (wp_properties_get (props, "key2"));
    g_assert_null (wp_properties_get (props, "key3"));
  }

  wp_state_clear (state);
}

static void
test_state_empty (void)
{
  g_autoptr (GError) error = NULL;
  g_autoptr (WpState) state = wp_state_new ("empty");
  g_assert_nonnull (state);

  /* Save */
  {
    g_autoptr (WpProperties) props = wp_properties_new_empty ();
    wp_properties_set (props, "key", "value");
    g_assert_true (wp_state_save (state, props, &error));
    g_assert_no_error (error);
  }

  /* Load */
  {
    g_autoptr (WpProperties) props = wp_state_load (state);
    g_assert_nonnull (props);
    g_assert_cmpstr (wp_properties_get (props, "key"), ==, "value");
  }

  /* Save empty */
  {
    g_autoptr (WpProperties) props = wp_properties_new_empty ();
    g_assert_true (wp_state_save (state, props, &error));
    g_assert_no_error (error);
  }

  /* Load empty */
  {
    g_autoptr (WpProperties) props = wp_state_load (state);
    g_assert_nonnull (props);
    g_assert_null (wp_properties_get (props, "key"));
  }

  wp_state_clear (state);
}

static void
test_state_spaces (void)
{
  g_autoptr (GError) error = NULL;
  g_autoptr (WpState) state = wp_state_new ("spaces");
  g_assert_nonnull (state);

  /* Save */
  {
    g_autoptr (WpProperties) props = wp_properties_new_empty ();
    wp_properties_set (props, "key", "value with spaces");
    g_assert_true (wp_state_save (state, props, &error));
    g_assert_no_error (error);
  }

  /* Load */
  {
    g_autoptr (WpProperties) props = wp_state_load (state);
    g_assert_nonnull (props);
    g_assert_cmpstr (wp_properties_get (props, "key"), ==, "value with spaces");
  }

  wp_state_clear (state);
}

static void
test_state_escaped (void)
{
  g_autoptr (GError) error = NULL;
  g_autoptr (WpState) state = wp_state_new ("escaped");
  g_assert_nonnull (state);

  /* Save */
  {
    g_autoptr (WpProperties) props = wp_properties_new_empty ();
    wp_properties_set (props, "[]", "v0");
    wp_properties_set (props, "[ ]", "v1");
    wp_properties_set (props, "[=]", "v2");
    wp_properties_set (props, " [=]", "v3");
    wp_properties_set (props, "[=] ", "v4");
    wp_properties_set (props, " [=] ", "v5");
    wp_properties_set (props, " [ =] ", "v6");
    wp_properties_set (props, " [= ] ", "v7");
    wp_properties_set (props, " [ = ] ", "v8");
    wp_properties_set (props, " [", "v9");
    wp_properties_set (props, "[ ", "v10");
    wp_properties_set (props, " [ ", "v11");
    wp_properties_set (props, " ]", "v12");
    wp_properties_set (props, "] ", "v13");
    wp_properties_set (props, " ] ", "v14");
    wp_properties_set (props, " ", "v15");
    wp_properties_set (props, "=", "v16");
    wp_properties_set (props, "\\", "v17");
    wp_properties_set (props, "\\[", "v18");
    wp_properties_set (props, "\\a", "v19");
    wp_properties_set (props, "\\\\", "v20");
    wp_properties_set (props, "[][", "][]");
    g_assert_true (wp_state_save (state, props, &error));
    g_assert_no_error (error);
  }

  /* Load */
  {
    g_autoptr (WpProperties) props = wp_state_load (state);
    g_assert_nonnull (props);
    g_assert_cmpstr (wp_properties_get (props, "[]"), ==, "v0");
    g_assert_cmpstr (wp_properties_get (props, "[ ]"), ==, "v1");
    g_assert_cmpstr (wp_properties_get (props, "[=]"), ==, "v2");
    g_assert_cmpstr (wp_properties_get (props, " [=]"), ==, "v3");
    g_assert_cmpstr (wp_properties_get (props, "[=] "), ==, "v4");
    g_assert_cmpstr (wp_properties_get (props, " [=] "), ==, "v5");
    g_assert_cmpstr (wp_properties_get (props, " [ =] "), ==, "v6");
    g_assert_cmpstr (wp_properties_get (props, " [= ] "), ==, "v7");
    g_assert_cmpstr (wp_properties_get (props, " [ = ] "), ==, "v8");
    g_assert_cmpstr (wp_properties_get (props, " ["), ==, "v9");
    g_assert_cmpstr (wp_properties_get (props, "[ "), ==, "v10");
    g_assert_cmpstr (wp_properties_get (props, " [ "), ==, "v11");
    g_assert_cmpstr (wp_properties_get (props, " ]"), ==, "v12");
    g_assert_cmpstr (wp_properties_get (props, "] "), ==, "v13");
    g_assert_cmpstr (wp_properties_get (props, " ] "), ==, "v14");
    g_assert_cmpstr (wp_properties_get (props, " "), ==, "v15");
    g_assert_cmpstr (wp_properties_get (props, "="), ==, "v16");
    g_assert_cmpstr (wp_properties_get (props, "\\"), ==, "v17");
    g_assert_cmpstr (wp_properties_get (props, "\\["), ==, "v18");
    g_assert_cmpstr (wp_properties_get (props, "\\a"), ==, "v19");
    g_assert_cmpstr (wp_properties_get (props, "\\\\"), ==, "v20");
    g_assert_cmpstr (wp_properties_get (props, "[]["), ==, "][]");
  }

  wp_state_clear (state);
}

int
main (int argc, char *argv[])
{
  g_test_init (&argc, &argv, NULL);
  g_log_set_writer_func (wp_log_writer_default, NULL, NULL);

  g_test_add_func ("/wp/state/basic", test_state_basic);
  g_test_add_func ("/wp/state/empty", test_state_empty);
  g_test_add_func ("/wp/state/spaces", test_state_spaces);
  g_test_add_func ("/wp/state/escaped", test_state_escaped);

  return g_test_run ();
}
07070100000183000081A4000000000000000000000001656CC35F00002130000000000000000000000000000000000000002900000000wireplumber-0.4.17/tests/wp/transition.c  /* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>

enum {
  STEP_FIRST = WP_TRANSITION_STEP_CUSTOM_START,
  STEP_SECOND,
  STEP_THIRD,
  STEP_FINISH,
};

struct data
{
  gboolean destroyed;

  /* steps that appeared in get_next_step */
  guint sta[10];
  guint sta_i;

  /* steps executed */
  guint ste[10];
  guint ste_i;
};

struct _WpTestTransition
{
  WpTransition parent;
  gboolean step_third_wait;
  gboolean step_third_error;
};

G_DECLARE_FINAL_TYPE (WpTestTransition, wp_test_transition, WP, TEST_TRANSITION, WpTransition)
G_DEFINE_TYPE (WpTestTransition, wp_test_transition, WP_TYPE_TRANSITION)

static void
data_destroy (gpointer data)
{
  struct data *d = data;
  d->destroyed = TRUE;
}

static gboolean
advance_on_idle (gpointer data)
{
  WpTransition * self = WP_TRANSITION (data);
  wp_transition_advance (self);
  return G_SOURCE_REMOVE;
}

static void
wp_test_transition_init (WpTestTransition *self)
{
  self->step_third_wait = TRUE;
  self->step_third_error = FALSE;
}

static guint
wp_test_transition_get_next_step (WpTransition * transition, guint step)
{
  WpTestTransition * self = WP_TEST_TRANSITION (transition);
  struct data *d = wp_transition_get_data (transition);

  g_assert_nonnull (d);
  g_assert_cmpint (d->sta_i, <, 10);
  d->sta[d->sta_i++] = step;

  switch (step) {
    case WP_TRANSITION_STEP_NONE:
      return STEP_FIRST;

    case STEP_FIRST:
    case STEP_SECOND:
      return step + 1;

    case STEP_THIRD:
      if (self->step_third_wait) {
        self->step_third_wait = FALSE;
        g_idle_add (advance_on_idle, transition);
        return STEP_THIRD;
      }
      return STEP_FINISH;

    case STEP_FINISH:
      return WP_TRANSITION_STEP_NONE;

    default:
      g_assert_not_reached ();
  }

  return WP_TRANSITION_STEP_ERROR;
}

static void
wp_test_transition_execute_step (WpTransition * transition, guint step)
{
  WpTestTransition * self = WP_TEST_TRANSITION (transition);
  struct data *d = wp_transition_get_data (transition);

  if (step != WP_TRANSITION_STEP_ERROR) {
    g_assert_cmpint (step, >=, STEP_FIRST);
    g_assert_cmpint (step, <=, STEP_FINISH);
  }

  g_assert_nonnull (d);
  g_assert_cmpint (d->ste_i, <, 10);
  d->ste[d->ste_i++] = step;

  if (step == STEP_THIRD && self->step_third_error) {
    wp_transition_return_error (transition,
        g_error_new (WP_DOMAIN_LIBRARY, 100, "error"));
    return;
  }

  if (step != WP_TRANSITION_STEP_ERROR)
    g_idle_add (advance_on_idle, transition);
}

static void
wp_test_transition_class_init (WpTestTransitionClass *klass)
{
  WpTransitionClass *transition_class = (WpTransitionClass *) klass;

  transition_class->get_next_step = wp_test_transition_get_next_step;
  transition_class->execute_step = wp_test_transition_execute_step;
}

static void test_transition_basic (void);

static void
test_transition_basic_done (GObject *source, GAsyncResult *res, gpointer data)
{
  g_autoptr (GError) error = NULL;

  g_assert_true (WP_IS_TEST_TRANSITION (res));
  g_assert_true (g_async_result_is_tagged (res, test_transition_basic));
  g_assert_true (wp_transition_get_completed (WP_TRANSITION (res)));
  g_assert_false (wp_transition_had_error (WP_TRANSITION (res)));
  g_assert_true (wp_transition_finish (res, &error));
  g_assert_no_error (error);

  g_main_loop_quit ((GMainLoop *) data);
}

static void
test_transition_basic (void)
{
  struct data data = {0};
  g_autoptr (GMainLoop) loop = g_main_loop_new (NULL, FALSE);
  g_autoptr (GObject) source_object = g_object_new (G_TYPE_OBJECT, NULL);

  WpTransition *t = wp_transition_new (wp_test_transition_get_type (),
      source_object, NULL, test_transition_basic_done, loop);
  g_assert_nonnull (t);
  g_assert_true (WP_IS_TRANSITION (t));
  g_assert_true (WP_IS_TEST_TRANSITION (t));
  g_assert_true (G_IS_ASYNC_RESULT (t));
  g_assert_true (wp_transition_get_source_object (t) == source_object);
  {
    g_autoptr (GObject) so = g_async_result_get_source_object (G_ASYNC_RESULT (t));
    g_assert_true (so == source_object);
  }
  g_assert_cmpint (source_object->ref_count, ==, 2);

  g_assert_null (wp_transition_get_data (t));
  wp_transition_set_data (t, &data, data_destroy);
  g_assert_true (wp_transition_get_data (t) == &data);
  g_assert_true (g_async_result_get_user_data (G_ASYNC_RESULT (t)) == &data);

  g_assert_null (wp_transition_get_source_tag (t));
  wp_transition_set_source_tag (t, test_transition_basic);
  g_assert_true (wp_transition_get_source_tag (t) == test_transition_basic);
  g_assert_true (wp_transition_is_tagged (t, test_transition_basic));

  g_object_ref (t);
  wp_transition_advance (t);
  g_assert_false (wp_transition_get_completed (t));
  g_assert_false (wp_transition_had_error (t));
  g_object_unref (t);

  g_main_loop_run (loop);

  g_assert_cmpint (source_object->ref_count, ==, 1);
  g_assert_cmpint (data.sta[0], ==, WP_TRANSITION_STEP_NONE);
  g_assert_cmpint (data.sta[1], ==, STEP_FIRST);
  g_assert_cmpint (data.sta[2], ==, STEP_SECOND);
  g_assert_cmpint (data.sta[3], ==, STEP_THIRD);
  g_assert_cmpint (data.sta[4], ==, STEP_THIRD);
  g_assert_cmpint (data.sta[5], ==, STEP_FINISH);
  g_assert_cmpint (data.sta_i, ==, 6);
  g_assert_cmpint (data.ste[0], ==, STEP_FIRST);
  g_assert_cmpint (data.ste[1], ==, STEP_SECOND);
  g_assert_cmpint (data.ste[2], ==, STEP_THIRD);
  g_assert_cmpint (data.ste[3], ==, STEP_FINISH);
  g_assert_cmpint (data.ste_i, ==, 4);
  g_assert_true (data.destroyed);
}

static void test_transition_error (void);

static void
test_transition_error_done (GObject *source, GAsyncResult *res, gpointer data)
{
  g_autoptr (GError) error = NULL;

  g_assert_true (WP_IS_TEST_TRANSITION (res));
  g_assert_true (g_async_result_is_tagged (res, test_transition_error));
  g_assert_true (wp_transition_get_completed (WP_TRANSITION (res)));
  g_assert_true (wp_transition_had_error (WP_TRANSITION (res)));
  g_assert_false (wp_transition_finish (res, &error));
  g_assert_error (error, WP_DOMAIN_LIBRARY, 100);

  g_main_loop_quit ((GMainLoop *) data);
}

static void
test_transition_error (void)
{
  struct data data = {0};
  g_autoptr (GMainLoop) loop = g_main_loop_new (NULL, FALSE);
  g_autoptr (GObject) source_object = g_object_new (G_TYPE_OBJECT, NULL);

  WpTransition *t = wp_transition_new (wp_test_transition_get_type (),
      source_object, NULL, test_transition_error_done, loop);
  g_assert_nonnull (t);
  g_assert_true (WP_IS_TRANSITION (t));
  g_assert_true (WP_IS_TEST_TRANSITION (t));
  g_assert_true (G_IS_ASYNC_RESULT (t));
  g_assert_true (wp_transition_get_source_object (t) == source_object);
  {
    g_autoptr (GObject) so = g_async_result_get_source_object (G_ASYNC_RESULT (t));
    g_assert_true (so == source_object);
  }
  g_assert_cmpint (source_object->ref_count, ==, 2);

  g_assert_null (wp_transition_get_data (t));
  wp_transition_set_data (t, &data, data_destroy);
  g_assert_true (wp_transition_get_data (t) == &data);
  g_assert_true (g_async_result_get_user_data (G_ASYNC_RESULT (t)) == &data);

  g_assert_null (wp_transition_get_source_tag (t));
  wp_transition_set_source_tag (t, test_transition_error);
  g_assert_true (wp_transition_get_source_tag (t) == test_transition_error);
  g_assert_true (wp_transition_is_tagged (t, test_transition_error));

  /* enable error condition */
  ((WpTestTransition *) t)->step_third_error = TRUE;

  g_object_ref (t);
  wp_transition_advance (t);
  g_assert_false (wp_transition_get_completed (t));
  g_assert_false (wp_transition_had_error (t));
  g_object_unref (t);

  g_main_loop_run (loop);

  g_assert_cmpint (source_object->ref_count, ==, 1);
  g_assert_cmpint (data.sta[0], ==, WP_TRANSITION_STEP_NONE);
  g_assert_cmpint (data.sta[1], ==, STEP_FIRST);
  g_assert_cmpint (data.sta[2], ==, STEP_SECOND);
  g_assert_cmpint (data.sta_i, ==, 3);
  g_assert_cmpint (data.ste[0], ==, STEP_FIRST);
  g_assert_cmpint (data.ste[1], ==, STEP_SECOND);
  g_assert_cmpint (data.ste[2], ==, STEP_THIRD);
  g_assert_cmpint (data.ste[3], ==, WP_TRANSITION_STEP_ERROR);
  g_assert_cmpint (data.ste_i, ==, 4);
  g_assert_true (data.destroyed);
}

int
main (int argc, char *argv[])
{
  g_test_init (&argc, &argv, NULL);
  g_log_set_writer_func (wp_log_writer_default, NULL, NULL);

  g_test_add_func ("/wp/transition/basic", test_transition_basic);
  g_test_add_func ("/wp/transition/error", test_transition_error);

  return g_test_run ();
}
07070100000184000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000001F00000000wireplumber-0.4.17/tests/wplua    07070100000185000081A4000000000000000000000001656CC35F00000406000000000000000000000000000000000000002B00000000wireplumber-0.4.17/tests/wplua/meson.build    common_deps = [wplua_dep, pipewire_dep, wp_dep]
common_env = common_test_env
common_env.set('G_TEST_SRCDIR', meson.current_source_dir())
common_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
common_env.set('WIREPLUMBER_DATA_DIR', meson.current_source_dir())
common_args = [
  '-D_GNU_SOURCE',
]

test(
  'test-wplua',
  executable('test-wplua', 'wplua.c',
    dependencies: common_deps, c_args: common_args),
  env: common_env,
)

script_tester = executable('script-tester',
    'script-tester.c',
    dependencies: common_deps, c_args: common_args
)

test(
  'test-lua-pod',
  script_tester,
  args: ['pod.lua'],
  env: common_env,
)
test(
  'test-lua-json',
  script_tester,
  args: ['json.lua'],
  env: common_env,
)
test(
  'test-lua-monitor-rules',
  script_tester,
  args: ['monitor-rules.lua'],
  env: common_env,
)
test(
  'test-lua-require',
  script_tester,
  args: ['require.lua'],
  env: common_env,
)
test(
  'test-lua-async-activation',
  script_tester,
  args: ['async-activation.lua'],
  env: common_env,
)
  07070100000186000081A4000000000000000000000001656CC35F000007DF000000000000000000000000000000000000002F00000000wireplumber-0.4.17/tests/wplua/script-tester.c    /* WirePlumber
 *
 * Copyright © 2021 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/base-test-fixture.h"

typedef struct {
  WpBaseTestFixture base;
} ScriptRunnerFixture;

static void
script_runner_setup (ScriptRunnerFixture *f, gconstpointer data)
{
  wp_base_test_fixture_setup (&f->base, 0);
}

static void
script_runner_teardown (ScriptRunnerFixture *f, gconstpointer data)
{
  wp_base_test_fixture_teardown (&f->base);
}

static void
script_run (ScriptRunnerFixture *f, gconstpointer data)
{
  g_autoptr (WpPlugin) plugin = NULL;
  g_autoptr (GError) error = NULL;
  g_autofree gchar *pluginname = NULL;

  /* TODO: we could do some more stuff here to provide the test script with an
     API to deal with the main loop and test asynchronous stuff, if necessary */

  wp_core_load_component (f->base.core,
      "libwireplumber-module-lua-scripting", "module", NULL, &error);
  g_assert_no_error (error);

  plugin = wp_plugin_find (f->base.core, "lua-scripting");
  wp_object_activate (WP_OBJECT (plugin), WP_PLUGIN_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);
  g_clear_object (&plugin);

  wp_core_load_component (f->base.core, (const gchar *) data, "script/lua",
      NULL, &error);
  g_assert_no_error (error);

  pluginname = g_strdup_printf ("script:%s", (const gchar *) data);

  plugin = wp_plugin_find (f->base.core, pluginname);
  g_assert_nonnull (plugin);
  wp_object_activate (WP_OBJECT (plugin), WP_PLUGIN_FEATURE_ENABLED,
      NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
  g_main_loop_run (f->base.loop);
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_assert_cmpint (argc, >=, 1);

  g_test_add ("/lua/script/run", ScriptRunnerFixture, argv[1],
      script_runner_setup, script_run, script_runner_teardown);

  return g_test_run ();
}
 07070100000187000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002700000000wireplumber-0.4.17/tests/wplua/scripts    07070100000188000081A4000000000000000000000001656CC35F000001FF000000000000000000000000000000000000003C00000000wireplumber-0.4.17/tests/wplua/scripts/async-activation.lua   Script.async_activation = true

tags = {}

function checkpoint(x)
  Log.info(x)
  table.insert(tags, x)
end

Core.timeout_add(100, function()
  checkpoint("timeout1")
  return false
end)

Core.timeout_add(200, function()
  checkpoint("timeout2")
  return false
end)

Core.timeout_add(300, function()
  checkpoint("timeout3")
  assert(#tags == 3)
  assert(tags[1] == "timeout1")
  assert(tags[2] == "timeout2")
  assert(tags[3] == "timeout3")
  Script:finish_activation()
  return false
end)

assert(#tags == 0)
 07070100000189000081A4000000000000000000000001656CC35F00001720000000000000000000000000000000000000003000000000wireplumber-0.4.17/tests/wplua/scripts/json.lua   -- Null
json = Json.Null ()
assert (json:is_null())
assert (json:parse() == nil)
assert (json:get_data() == "null")
assert (json:get_size() == 4)
assert (json:get_data() == json:to_string())

-- Boolean
json = Json.Boolean (true)
assert (json:is_boolean())
assert (json:parse())
assert (json:get_data() == "true")
assert (json:get_size() == 4)
assert (json:get_data() == json:to_string())
json = Json.Boolean (false)
assert (json:is_boolean())
assert (not json:parse())
assert (json:get_data() == "false")
assert (json:get_size() == 5)
assert (json:get_data() == json:to_string())

-- Int
json = Json.Int (3)
assert (json:is_int())
assert (json:parse() == 3)
assert (json:get_data() == "3")
assert (json:get_size() == 1)
assert (json:get_data() == json:to_string())

-- Float
json = Json.Float(3.14)
assert (json:is_float())
val = json:parse ()
assert (val > 3.13 and val < 3.15)

-- String
json = Json.String ("wireplumber")
assert (json:is_string())
assert (json:parse() == "wireplumber")
assert (json:get_data() == "\"wireplumber\"")
assert (json:get_size() == 13)
assert (json:get_data() == json:to_string())

-- Array
json = Json.Array { Json.Null (), Json.Null () }
assert (json:is_array())
val = json:parse ()
assert (val[1] == nil)
assert (val[2] == nil)
assert (json:get_data() == "[null, null]")
assert (json:get_size() == 12)
assert (json:get_data() == json:to_string())

json = Json.Array { true, false }
assert (json:is_array())
val = json:parse ()
assert (val[1])
assert (not val[2])
assert (json:get_data() == "[true, false]")
assert (json:get_size() == 13)
assert (json:get_data() == json:to_string())

json = Json.Array {1, 2, 3}
assert (json:is_array())
val = json:parse ()
assert (val[1] == 1)
assert (val[2] == 2)
assert (val[3] == 3)
assert (json:get_data() == "[1, 2, 3]")
assert (json:get_size() == 9)
assert (json:get_data() == json:to_string())

json = Json.Array {1.11, 2.22, 3.33}
assert (json:is_array())
val = json:parse ()
assert (val[1] > 1.10 and val[1] < 1.12)
assert (val[2] > 2.21 and val[2] < 2.23)
assert (val[3] > 3.32 and val[3] < 3.34)

json = Json.Array {"lua", "spa", "json"}
assert (json:is_array())
val = json:parse ()
assert (val[1] == "lua")
assert (val[2] == "spa")
assert (val[3] == "json")
assert (json:get_data() == "[\"lua\", \"spa\", \"json\"]")
assert (json:get_size() == 22)
assert (json:get_data() == json:to_string())

json = Json.Array {
  Json.Array {
    Json.Object {
      key1 = 1
    },
    Json.Object {
      key2 = 2
    },
  }
}
assert (json:is_array())
assert (json:get_data() == "[[{\"key1\":1}, {\"key2\":2}]]")
assert (json:get_data() == json:to_string())

table = {}
table[1] = 1
table[2] = 2
table[3] = 3
table["4"] = 4
json = Json.Array (table)
assert (json:is_array())
val = json:parse ()
assert (val[1] == 1)
assert (val[2] == 2)
assert (val[3] == 3)
assert (val["4"] == nil)

-- Object
json = Json.Object {
    key1 = Json.Null(),
    key2 = true,
    key3 = 3,
    key4 = 4.44,
    key5 = "foo",
    key6 = Json.Array {5, 6, 7},
    key7 = Json.Object {
      key_nested1 = "nested",
      key_nested2 = 8,
      key_nested3 = Json.Array {false, true, false},
      ["Key with spaces and (special % characters)"] = 50.0,
    }
}
assert (json:is_object())
val = json:parse ()
assert (val.key1 == nil)
assert (val.key2 == true)
assert (val.key3 == 3)
assert (val.key4 > 4.43 and val.key4 < 4.45)
assert (val.key5 == "foo")
assert (val.key6[1] == 5)
assert (val.key6[2] == 6)
assert (val.key6[3] == 7)
assert (val.key7.key_nested1 == "nested")
assert (val.key7.key_nested2 == 8)
assert (not val.key7.key_nested3[1])
assert (val.key7.key_nested3[2])
assert (not val.key7.key_nested3[3])
assert (val.key7["Key with spaces and (special % characters)"] == 50.0)

table = {}
table["1"] = 1
table["2"] = 2
table["3"] = 3
table[4] = 4
json = Json.Object (table)
assert (json:is_object())
val = json:parse ()
assert (val["1"] == 1)
assert (val["2"] == 2)
assert (val["3"] == 3)
assert (val[4] == nil)

-- Raw
json = Json.Raw ("[\"foo\", \"bar\"]")
assert (json:is_array())
assert (json:get_data() == "[\"foo\", \"bar\"]")
assert (json:get_data() == json:to_string())
val = json:parse ()
assert (val[1] == "foo")
assert (val[2] == "bar")

json = Json.Raw ("[foo, bar]")
assert (json:is_array())
assert (json:get_data() == "[foo, bar]")
assert (json:get_data() == json:to_string())
val = json:parse ()
assert (val[1] == "foo")
assert (val[2] == "bar")

json = Json.Raw ("{\"name\": \"wireplumber\", \"version\": [0, 4, 7]}")
assert (json:is_object())
assert (json:get_data() == "{\"name\": \"wireplumber\", \"version\": [0, 4, 7]}")
assert (json:get_data() == json:to_string())
val = json:parse ()
assert (val.name == "wireplumber")
assert (val.version[1] == 0)
assert (val.version[2] == 4)
assert (val.version[3] == 7)

-- recursion limit
json = Json.Raw ("{ name = wireplumber, version = [0, 4, 15], args = { test = [0, 1] } }")

val = json:parse (0)
assert (type (val) == "string")
assert (val == "{ name = wireplumber, version = [0, 4, 15], args = { test = [0, 1] } }")

val = json:parse (1)
assert (type (val) == "table")
assert (val.name == "wireplumber")
assert (type (val.version) == "string")
assert (val.version == "[0, 4, 15]")
assert (type (val.args) == "string")
assert (val.args == "{ test = [0, 1] }")

val = json:parse(2)
assert (type (val) == "table")
assert (val.name == "wireplumber")
assert (type (val.version) == "table")
assert (val.version[1] == 0)
assert (val.version[2] == 4)
assert (val.version[3] == 15)
assert (type (val.args) == "table")
assert (val.args.test == "[0, 1]")

val = json:parse(3)
assert (type (val) == "table")
assert (val.name == "wireplumber")
assert (type (val.version) == "table")
assert (val.version[1] == 0)
assert (val.version[2] == 4)
assert (val.version[3] == 15)
assert (type (val.args) == "table")
assert (type (val.args.test) == "table")
assert (val.args.test[1] == 0)
assert (val.args.test[2] == 1)
0707010000018A000041ED000000000000000000000002656CC35F00000000000000000000000000000000000000000000002B00000000wireplumber-0.4.17/tests/wplua/scripts/lib    0707010000018B000081A4000000000000000000000001656CC35F00000072000000000000000000000000000000000000003700000000wireplumber-0.4.17/tests/wplua/scripts/lib/testlib.lua    local testlib = {}

function testlib.test_add_ten (x)
  return x + 10
end

Log.info("in testlib")

return testlib
  0707010000018C000081A4000000000000000000000001656CC35F000006CD000000000000000000000000000000000000003900000000wireplumber-0.4.17/tests/wplua/scripts/monitor-rules.lua  -- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT

local config = {}
config.rules = {
  {
    matches = {
      {
        { "device.name", "matches", "bluez_card.*" },
      },
    },
    apply_properties = {
      ["device.nick"] = "My Device",
    },
  },
  {
    matches = {
      {
        { "node.name", "matches", "bluez_input.*" },
      },
      {
        { "node.name", "matches", "bluez_output.*" },
      },
    },
    apply_properties = {
      ["node.pause-on-idle"] = true,
    },
  },
}

for _, r in ipairs(config.rules or {}) do
  r.interests = {}
  for _, i in ipairs(r.matches) do
    local interest_desc = { type = "properties" }
    for _, c in ipairs(i) do
      c.type = "pw"
      table.insert(interest_desc, Constraint(c))
    end
    local interest = Interest(interest_desc)
    table.insert(r.interests, interest)
  end
  r.matches = nil
end

function rulesApplyProperties(properties)
  for _, r in ipairs(config.rules or {}) do
    if r.apply_properties then
      for _, interest in ipairs(r.interests) do
        if interest:matches(properties) then
          for k, v in pairs(r.apply_properties) do
            properties[k] = v
          end
        end
      end
    end
  end
end

local test1 = {
  ["node.name"] = "bluez_output.test1"
}
rulesApplyProperties(test1)
assert(test1["node.pause-on-idle"] == true)

local test2 = {
  ["device.name"] = "bluez_card.test2"
}
rulesApplyProperties(test2)
assert(test2["device.nick"] == "My Device")

local test3 = {
  ["device.name"] = "not_a_match"
}
rulesApplyProperties(test3)
assert(test3["device.nick"] == nil)
assert(test3["node.pause-on-idle"] == nil)
   0707010000018D000081A4000000000000000000000001656CC35F00001FC8000000000000000000000000000000000000002F00000000wireplumber-0.4.17/tests/wplua/scripts/pod.lua    -- None
pod = Pod.None ()
assert (pod:parse() == nil)
assert (pod:get_type_name() == "Spa:None")

-- Bool
pod = Pod.Boolean (true)
assert (pod:parse())
assert (pod:get_type_name() == "Spa:Bool")

-- Id
pod = Pod.Id (4)
assert (pod:parse() == 4)
assert (pod:get_type_name() == "Spa:Id")
pod = Pod.Id("Spa:Enum:AudioChannel", "FL")
assert (pod:parse() == 3)
assert (pod:get_type_name() == "Spa:Id")

-- Int
pod = Pod.Int (64)
assert (pod:parse() == 64)
assert (pod:get_type_name() == "Spa:Int")

-- Long
pod = Pod.Long (1024)
assert (pod:parse() == 1024)
assert (pod:get_type_name() == "Spa:Long")

-- Float
pod = Pod.Float (1.02)
val = pod:parse ()
assert (val > 1.01 and val < 1.03)
assert (pod:get_type_name() == "Spa:Float")

-- Double
pod = Pod.Double (3.14159265359)
val = pod:parse ()
assert (val > 3.14159265358 and val < 3.14159265360)
assert (pod:get_type_name() == "Spa:Double")

-- String
pod = Pod.String ("a pod string")
assert (pod:parse() == "a pod string")
assert (pod:get_type_name() == "Spa:String")

-- Bytes
pod = Pod.Bytes ("16characterbytes")
assert (pod:parse() == "16characterbytes")
assert (pod:get_type_name() == "Spa:Bytes")

-- Pointer
pod = Pod.Pointer ("Spa:Pointer:Buffer", nil)
assert (pod:parse() == nil)
assert (pod:get_type_name() == "Spa:Pointer:Buffer")

-- Fd
pod = Pod.Fd (5)
assert (pod:parse() == 5)
assert (pod:get_type_name() == "Spa:Fd")

-- Rectangle
pod = Pod.Rectangle (1920, 1080)
val = pod:parse()
assert (val.pod_type == "Rectangle")
assert (val.width == 1920 and val.height == 1080)
assert (pod:get_type_name() == "Spa:Rectangle")

-- Fraction
pod = Pod.Fraction (3, 4)
val = pod:parse()
assert (val.pod_type == "Fraction")
assert (val.num == 3 and val.denom == 4)
assert (pod:get_type_name() == "Spa:Fraction")

-- Struct
pod = Pod.Struct { true, 1, "string" }
val = pod:parse()
assert (val.pod_type == "Struct")
assert (val[1] and val[2] == 1 and val[3] == "string")
assert (pod:get_type_name() == "Spa:Pod:Struct")

-- Object
pod = Pod.Object {
  "Spa:Pod:Object:Param:PortConfig", "PortConfig",
  direction = "Input",
  mode = "dsp",
  monitor = true
}
val = pod:parse()
assert (val.pod_type == "Object")
assert (val.object_id == "PortConfig")
assert (val.properties.direction == "Input")
assert (val.properties.mode == "dsp")
assert (val.properties.monitor)
assert (pod:get_type_name() == "Spa:Pod:Object:Param:PortConfig")
pod = Pod.Object {
  "Spa:Pod:Object:Param:Props", "Props",
  device = "hw:Generic",
  deviceName = "",
  cardName = "",
  minLatency = 16,
  maxLatency = 8192,
  ["id-01000000"] = Pod.Boolean (true),
  latencyOffsetNsec = 0
}
val = pod:parse()
assert (val.pod_type == "Object")
assert (val.object_id == "Props")
assert (val.properties.device == "hw:Generic")
assert (val.properties.deviceName == "")
assert (val.properties.cardName == "")
assert (val.properties.minLatency == 16)
assert (val.properties.maxLatency == 8192)
assert (val.properties["id-01000000"] == true)
assert (val.properties.latencyOffsetNsec == 0)
assert (pod:get_type_name() == "Spa:Pod:Object:Param:Props")

-- Sequence
pod = Pod.Sequence {
  {offset = 10, typename = "Properties", value = 1},
  {offset = 20, typename = "Properties", value = 2},
  {offset = 40, typename = "Properties", value = 4},
}
val = pod:parse()
assert (val.pod_type == "Sequence")
assert (val[1].offset == 10 and val[1].typename == "Properties" and val[1].value == 1)
assert (val[2].offset == 20 and val[2].typename == "Properties" and val[2].value == 2)
assert (val[3].offset == 40 and val[3].typename == "Properties" and val[3].value == 4)
assert (pod:get_type_name() == "Spa:Pod:Sequence")

-- Array
pod = Pod.Array { "Spa:Bool", true, false, false, true }
val = pod:parse()
assert (val.pod_type == "Array")
assert (val.value_type == "Spa:Bool")
assert (val[1])
assert (not val[2])
assert (not val[3])
assert (val[4])
assert (pod:get_type_name() == "Spa:Array")
pod = Pod.Array { "Spa:Enum:AudioChannel", "FL", "FR" }
val = pod:parse()
assert (val.pod_type == "Array")
assert (val.value_type == "Spa:Id")
assert (val[1] == 3)
assert (val[2] == 4)

-- Choice
pod = Pod.Choice.None { "Spa:Bool", false }
val = pod:parse()
assert(val.pod_type == "Choice.None")
assert(val.value_type == "Spa:Bool")
assert(not val[1])
pod = Pod.Choice.Range { "Spa:Float", 1.0, 0.0, 1.0 }
val = pod:parse()
assert (pod:get_type_name() == "Spa:Pod:Choice")
assert(val.pod_type == "Choice.Range")
assert(val.value_type == "Spa:Float")
assert(val[1] == 1.0)
assert(val[2] == 0.0)
assert(val[3] == 1.0)
assert (pod:get_type_name() == "Spa:Pod:Choice")
pod = Pod.Choice.Step { "Spa:Int", 5, 10 }
val = pod:parse()
assert(val.pod_type == "Choice.Step")
assert(val.value_type == "Spa:Int")
assert(val[1] == 5)
assert(val[2] == 10)
assert (pod:get_type_name() == "Spa:Pod:Choice")
pod = Pod.Choice.Enum { "Spa:Enum:AudioChannel", "FL", "FL", "FR" }
val = pod:parse()
assert (val.pod_type == "Choice.Enum")
assert (val.value_type == "Spa:Id")
assert (val[1] == 3)
assert (val[2] == 3)
assert (val[3] == 4)
assert (pod:get_type_name() == "Spa:Pod:Choice")
pod = Pod.Choice.Flags { "Spa:Int", 1 << 0, 1 << 2, 1 << 3}
val = pod:parse()
assert (val.pod_type == "Choice.Flags")
assert (val.value_type == "Spa:Int")
assert (val[1] == 1 << 0)
assert (val[2] == 1 << 2)
assert (val[3] == 1 << 3)
assert (pod:get_type_name() == "Spa:Pod:Choice")


-- Nested Pods
pod = Pod.Object {
  "Spa:Pod:Object:Param:PortConfig", "PortConfig",
  direction = "Input",
  mode = "dsp",
  monitor = true,
  format = Pod.Object {
    "Spa:Pod:Object:Param:Format", "Format",
    mediaType = "audio",
    mediaSubtype = "raw",
    rate = 48000,
    format = Pod.Choice.Enum { "Spa:Enum:AudioFormat", "S16LE", "S16LE", "F32LE" },
    channels = Pod.Choice.Range { "Spa:Int", 2, 1, 32 },
    position = Pod.Array { "Spa:Enum:AudioChannel", "FL", "FR" }
  }
}
val = pod:parse()
assert (val.pod_type == "Object")
assert (val.object_id == "PortConfig")
assert (val.properties.direction == "Input")
assert (val.properties.mode == "dsp")
assert (val.properties.monitor)
assert (val.properties.format.pod_type == "Object")
assert (val.properties.format.object_id == "Format")
assert (val.properties.format.properties.mediaType == "audio")
assert (val.properties.format.properties.mediaSubtype == "raw")
assert (val.properties.format.properties.rate == 48000)
assert (val.properties.format.properties.format.pod_type == "Choice.Enum")
assert (val.properties.format.properties.format.value_type == "Spa:Id")
assert (val.properties.format.properties.format[1] == "S16LE")
assert (val.properties.format.properties.format[2] == "S16LE")
assert (val.properties.format.properties.format[3] == "F32LE")
assert (val.properties.format.properties.channels.pod_type == "Choice.Range")
assert (val.properties.format.properties.channels.value_type == "Spa:Int")
assert (val.properties.format.properties.channels[1] == 2)
assert (val.properties.format.properties.channels[2] == 1)
assert (val.properties.format.properties.channels[3] == 32)
assert (val.properties.format.properties.position.pod_type == "Array")
assert (val.properties.format.properties.position.value_type == "Spa:Id")
assert (val.properties.format.properties.position[1] == "FL")
assert (val.properties.format.properties.position[2] == "FR")
assert (pod:get_type_name() == "Spa:Pod:Object:Param:PortConfig")

-- Nested Object Id Properties
pod = Pod.Object {
  "Spa:Pod:Object:Param:Props", "Props",
  device = "my-device",
  ["id-01000000"] = Pod.Int (4),
  ["id-02000000"] = Pod.Object {
    "Spa:Pod:Object:Param:Props", "Props",
    device = "my-sub-device",
    ["id-03000000"] = Pod.Boolean (true),
    ["id-04000000"] = Pod.String ("string")
  }
}
val = pod:parse()
assert (val.pod_type == "Object")
assert (val.object_id == "Props")
assert (val.properties.device == "my-device")
assert (val.properties["id-01000000"] == 4)
assert (val.properties["id-02000000"].properties.device == "my-sub-device")
assert (val.properties["id-02000000"].properties["id-03000000"] == true)
assert (val.properties["id-02000000"].properties["id-04000000"] == "string")
assert (pod:get_type_name() == "Spa:Pod:Object:Param:Props")
0707010000018E000081A4000000000000000000000001656CC35F000000AB000000000000000000000000000000000000003300000000wireplumber-0.4.17/tests/wplua/scripts/require.lua    local testlib = require("testlib")

assert(type(testlib) == "table")
assert(package.loaded["testlib"] == testlib)

local x = 1
x = testlib.test_add_ten(x)
assert(x == 11)
 0707010000018F000081A4000000000000000000000001656CC35F0000549A000000000000000000000000000000000000002700000000wireplumber-0.4.17/tests/wplua/wplua.c    /* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "lua.h"
#include <wplua/wplua.h>
#include <wp/wp.h>

enum {
  PROP_0,
  PROP_TEST_STRING,
  PROP_TEST_INT,
  PROP_TEST_UINT,
  PROP_TEST_INT64,
  PROP_TEST_UINT64,
  PROP_TEST_FLOAT,
  PROP_TEST_DOUBLE,
  PROP_TEST_BOOLEAN,
};

typedef struct _TestObject TestObject;
struct _TestObject
{
  GObject parent;
  gchar *test_string;
  gint test_int;
  guint test_uint;
  gint64 test_int64;
  guint64 test_uint64;
  gfloat test_float;
  gdouble test_double;
  gboolean test_boolean;
};

typedef struct _TestObjectClass TestObjectClass;
struct _TestObjectClass
{
  GObjectClass parent_class;

  void (*change) (TestObject * self, const gchar * str, gint integer);
};

G_DEFINE_TYPE (TestObject, test_object, G_TYPE_OBJECT)

#define TEST_TYPE_OBJECT (test_object_get_type ())
_GLIB_DEFINE_AUTOPTR_CHAINUP (TestObject, GObject)

static inline TestObject * TEST_OBJECT (gpointer ptr) {
  return G_TYPE_CHECK_INSTANCE_CAST (ptr, TEST_TYPE_OBJECT, TestObject);
}

static void
test_object_init (TestObject * self)
{
}

static void
test_object_finalize (GObject * object)
{
  TestObject *self = TEST_OBJECT (object);
  g_free (self->test_string);
  G_OBJECT_CLASS (test_object_parent_class)->finalize (object);
}

static void
test_object_get_property (GObject * object, guint id, GValue * value,
    GParamSpec * pspec)
{
  TestObject *self = TEST_OBJECT (object);

  switch (id) {
    case PROP_TEST_STRING:
      g_value_set_string (value, self->test_string);
      break;
    case PROP_TEST_INT:
      g_value_set_int (value, self->test_int);
      break;
    case PROP_TEST_UINT:
      g_value_set_uint (value, self->test_uint);
      break;
    case PROP_TEST_INT64:
      g_value_set_int64 (value, self->test_int64);
      break;
    case PROP_TEST_UINT64:
      g_value_set_uint64 (value, self->test_uint64);
      break;
    case PROP_TEST_FLOAT:
      g_value_set_float (value, self->test_float);
      break;
    case PROP_TEST_DOUBLE:
      g_value_set_double (value, self->test_double);
      break;
    case PROP_TEST_BOOLEAN:
      g_value_set_boolean (value, self->test_boolean);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
      break;
  }
}

static void
test_object_set_property (GObject * object, guint id, const GValue * value,
    GParamSpec * pspec)
{
  TestObject *self = TEST_OBJECT (object);

  switch (id) {
    case PROP_TEST_STRING:
      g_free (self->test_string);
      self->test_string = g_value_dup_string (value);
      break;
    case PROP_TEST_INT:
      self->test_int = g_value_get_int (value);
      break;
    case PROP_TEST_UINT:
      self->test_uint = g_value_get_uint (value);
      break;
    case PROP_TEST_INT64:
      self->test_int64 = g_value_get_int64 (value);
      break;
    case PROP_TEST_UINT64:
      self->test_uint64 = g_value_get_uint64 (value);
      break;
    case PROP_TEST_FLOAT:
      self->test_float = g_value_get_float (value);
      break;
    case PROP_TEST_DOUBLE:
      self->test_double = g_value_get_double (value);
      break;
    case PROP_TEST_BOOLEAN:
      self->test_boolean = g_value_get_boolean (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
      break;
  }
}

static void
test_object_change (TestObject * self, const gchar * str, gint integer)
{
  g_free (self->test_string);
  self->test_string = g_strdup_printf ("changed: %s", str);
  g_object_notify (G_OBJECT (self), "test-string");
  self->test_int = integer;
  g_object_notify (G_OBJECT (self), "test-int");

  gint ret = 0;
  g_signal_emit_by_name (self, "acquire", &ret);
  self->test_int64 = ret;
  g_object_notify (G_OBJECT (self), "test-int64");
}

static void
test_object_class_init (TestObjectClass * klass)
{
  GObjectClass *obj_class = (GObjectClass *) klass;

  obj_class->finalize = test_object_finalize;
  obj_class->get_property = test_object_get_property;
  obj_class->set_property = test_object_set_property;

  g_object_class_install_property (obj_class, PROP_TEST_STRING,
      g_param_spec_string ("test-string", "test-string", "blurb", NULL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_INT,
      g_param_spec_int ("test-int", "test-int", "blurb",
          G_MININT, G_MAXINT, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_UINT,
      g_param_spec_uint ("test-uint", "test-uint", "blurb",
          0, G_MAXUINT, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_INT64,
      g_param_spec_int64 ("test-int64", "test-int64", "blurb",
          G_MININT64, G_MAXINT64, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_UINT64,
      g_param_spec_uint64 ("test-uint64", "test-uint64", "blurb",
          0, G_MAXUINT64, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_FLOAT,
      g_param_spec_float ("test-float", "test-float", "blurb",
          -20.0f, 20.0f, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_DOUBLE,
      g_param_spec_double ("test-double", "test-double", "blurb",
          -20.0, 20.0, 0.0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (obj_class, PROP_TEST_BOOLEAN,
      g_param_spec_boolean ("test-boolean", "test-boolean", "blurb", FALSE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_signal_new ("change", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_ACTION | G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET (TestObjectClass, change), NULL, NULL, NULL,
      G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
  klass->change = test_object_change;

  g_signal_new ("acquire", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
      0, NULL, NULL, NULL, G_TYPE_INT, 0);
}

static void
test_object_toggle (TestObject * self)
{
  self->test_boolean = !self->test_boolean;
  g_object_notify (G_OBJECT (self), "test-boolean");
}

static int
l_test_object_toggle (lua_State * L)
{
  TestObject * self = wplua_checkobject (L, 1, TEST_TYPE_OBJECT);
  test_object_toggle (self);
  return 0;
}

static const luaL_Reg l_test_object_methods[] = {
  { "toggle", l_test_object_toggle },
  { NULL, NULL }
};

static int
l_test_object_new (lua_State * L)
{
  wplua_pushobject (L, g_object_new (TEST_TYPE_OBJECT, NULL));
  return 1;
}

static gboolean
test_load_and_call (lua_State * L, const gchar *buf, gsize size,
    int nargs, int nres, GError **error)
{
  int argstop = lua_gettop (L);
  int sandbox = wplua_push_sandbox (L);

  if (!wplua_load_buffer (L, buf, size, error)) {
    lua_pop (L, nargs + sandbox);
    return FALSE;
  }

  /* push sandbox() and the chunk below the arguments */
  lua_rotate (L, argstop, -nargs);

  return wplua_pcall (L, nargs + sandbox, nres, error);
}

static void
test_wplua_basic ()
{
  lua_State *L = wplua_new ();
  wplua_unref (L);
}

static void
test_wplua_construct ()
{
  g_autoptr (GObject) obj = NULL;
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  wplua_register_type_methods(L, TEST_TYPE_OBJECT,
      l_test_object_new, l_test_object_methods);

  const gchar code[] =
    "o = TestObject_new()\n"
    "assert (type(o) == 'userdata')\n";
  test_load_and_call (L, code, sizeof (code) - 1, 0, 0, &error);
  g_assert_no_error (error);

  g_assert_cmpint (lua_getglobal (L, "o"), ==, LUA_TUSERDATA);
  g_assert_true (wplua_checkobject (L, -1, TEST_TYPE_OBJECT));
  obj = wplua_toobject (L, -1);
  g_assert_nonnull (obj);
  g_object_ref (obj);
  g_assert_cmpint (obj->ref_count, ==, 2);

  wplua_unref (L);
  g_assert_cmpint (obj->ref_count, ==, 1);
}

static void
test_wplua_properties ()
{
  TestObject *obj = NULL;
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  wplua_register_type_methods(L, TEST_TYPE_OBJECT,
      l_test_object_new, l_test_object_methods);

  const gchar code[] =
    "o = TestObject_new()\n"
    "o['test-string'] = 'string from lua'\n"
    "o['test-int'] = -15\n"
    "o['test-uint'] = 1123456789\n"
    "o['test-int64'] = -5123456789\n"
    "o['test-uint64'] = 15123456789\n"
    "o['test-float'] = 3.1415\n"
    "o['test-double'] = 0.123456789\n"
    "o['test-boolean'] = true\n";
  test_load_and_call (L, code, sizeof (code) - 1, 0, 0, &error);
  g_assert_no_error (error);

  g_assert_cmpint (lua_getglobal (L, "o"), ==, LUA_TUSERDATA);
  g_assert_true (wplua_checkobject (L, -1, TEST_TYPE_OBJECT));
  obj = wplua_toobject (L, -1);
  g_assert_nonnull (obj);

  g_assert_cmpstr (obj->test_string, ==, "string from lua");
  g_assert_cmpint (obj->test_int, ==, -15);
  g_assert_cmpuint (obj->test_uint, ==, 1123456789);
  g_assert_cmpint (obj->test_int64, ==, -5123456789);
  g_assert_cmpuint (obj->test_uint64, ==, 15123456789);
  g_assert_cmpfloat_with_epsilon (obj->test_float, 3.1415, 0.00001);
  g_assert_cmpfloat_with_epsilon (obj->test_double, 0.123456789, 0.0000000001);
  g_assert_true (obj->test_boolean);

  const gchar code2[] =
    "assert (o['test-string'] == 'string from lua')\n"
    "assert (o['test-int'] == -15)\n"
    "assert (o['test-uint'] == 1123456789)\n"
    "assert (o['test-int64'] == -5123456789)\n"
    "assert (o['test-uint64'] == 15123456789)\n"
    "assert (math.abs (o['test-float'] - 3.1415) < 0.00001)\n"
    "assert (math.abs (o['test-double'] - 0.123456789) < 0.0000000001)\n"
    "assert (o['test-boolean'] == true)\n";
  test_load_and_call (L, code2, sizeof (code2) - 1, 0, 0, &error);
  g_assert_no_error (error);

  wplua_unref (L);
}

static void
test_wplua_closure ()
{
  GClosure *closure;
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  lua_pushstring (L, "some string");
  lua_setglobal (L, "expected_str");

  const gchar code[] =
    "f_was_called = false\n"
    "function f(s)\n"
    "  assert(s == expected_str)\n"
    "  f_was_called = true\n"
    "end\n";
  test_load_and_call (L, code, sizeof (code) - 1, 0, 0, &error);
  g_assert_no_error (error);

  lua_getglobal (L, "f");
  closure = wplua_function_to_closure (L, -1);
  g_assert_nonnull (closure);
  g_closure_ref (closure);
  g_closure_sink (closure);
  lua_pop (L, 1);

  {
    GValue s = G_VALUE_INIT;
    g_value_init (&s, G_TYPE_STRING);
    g_value_set_static_string (&s, "some string");
    g_closure_invoke (closure, NULL, 1, &s, NULL);
  }

  lua_getglobal (L, "f_was_called");
  g_assert_true (lua_isboolean (L, -1));
  g_assert_true (lua_toboolean (L, -1));

  wplua_unref (L);

  g_assert_true (closure->is_invalid);
  g_closure_unref (closure);
}

static void
test_wplua_signals ()
{
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  wplua_register_type_methods(L, TEST_TYPE_OBJECT,
      l_test_object_new, l_test_object_methods);

  const gchar code[] =
    "o = TestObject_new()\n"
    "\n"
    "o:connect('acquire', function (obj)\n"
    "    assert(obj == o)\n"
    "    return 42\n"
    "  end)\n"
    "\n"
    "o:connect('notify::test-string', function (obj, pspec)\n"
    "    assert(pspec == 'test-string')\n"
    "    assert(obj[pspec] == 'changed: by Lua')\n"
    "  end)\n"
    "\n"
    "o:call('change', 'by Lua', 55)\n"
    "\n"
    "assert(o['test-string'] == 'changed: by Lua')\n"
    "assert(o['test-int'] == 55)\n"
    "assert(o['test-int64'] == 42)\n"
    "\n"
    "o['test-boolean'] = true\n"
    "o:toggle()\n"
    "assert(o['test-boolean'] == false)\n";
  test_load_and_call (L, code, sizeof (code) - 1, 0, 0, &error);
  g_assert_no_error (error);
  wplua_unref (L);
}

static void
test_wplua_sandbox_script ()
{
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  wplua_register_type_methods(L, TEST_TYPE_OBJECT,
      l_test_object_new, l_test_object_methods);

  const gchar code[] =
    "SANDBOX_EXPORT = {\n"
    "  Test = TestObject_new,\n"
    "  Table = { test = 'foobar' }\n"
    "}\n";
  test_load_and_call (L, code, sizeof (code) - 1, 0, 0, &error);
  g_assert_no_error (error);

  wplua_enable_sandbox (L, WP_LUA_SANDBOX_ISOLATE_ENV);

  const gchar code2[] =
    "o = TestObject_new()\n";
  test_load_and_call (L, code2, sizeof (code2) - 1, 0, 0, &error);
  g_debug ("expected error: %s", error ? error->message : "null");
  g_assert_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_RUNTIME);
  g_clear_error (&error);

  const gchar code3[] =
    "o = Test()\n"
    "o:toggle()\n";
  test_load_and_call (L, code3, sizeof (code3) - 1, 0, 0, &error);
  g_assert_no_error (error);

  const gchar code4[] =
    "assert(string.len(Table.test) == 6)\n";
  test_load_and_call (L, code4, sizeof (code4) - 1, 0, 0, &error);
  g_assert_no_error (error);

  const gchar code5[] =
    "o:call('change', 'by Lua', 55)\n";
  test_load_and_call (L, code5, sizeof (code5) - 1, 0, 0, &error);
  g_debug ("expected error: %s", error ? error->message : "null");
  g_assert_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_RUNTIME);
  g_clear_error (&error);

  const gchar code6[] =
    "string.test = 'hello world'\n";
  test_load_and_call (L, code6, sizeof (code6) - 1, 0, 0, &error);
  g_debug ("expected error: %s", error ? error->message : "null");
  g_assert_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_RUNTIME);
  g_clear_error (&error);

  const gchar code7[] =
    "Table.test = 'hello world'\n";
  test_load_and_call (L, code7, sizeof (code7) - 1, 0, 0, &error);
  g_debug ("expected error: %s", error ? error->message : "null");
  g_assert_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_RUNTIME);
  g_clear_error (&error);

  wplua_unref (L);
}

static void
test_wplua_sandbox_config ()
{
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  wplua_enable_sandbox (L, 0);

  const gchar code3[] =
    "o = { answer = 42 }\n";
  test_load_and_call (L, code3, sizeof (code3) - 1, 0, 0, &error);
  g_assert_no_error (error);

  /* no assert() in minimal_std mode, resort to other means of failure */
  const gchar code4[] =
    "if (o.answer ~= 42) then\n"
    "  non_existent_function()\n"
    "end\n";
  test_load_and_call (L, code4, sizeof (code4) - 1, 0, 0, &error);
  g_assert_no_error (error);

  /* string.* is protected */
  const gchar code6[] =
    "string.test = 'hello world'\n";
  test_load_and_call (L, code6, sizeof (code6) - 1, 0, 0, &error);
  g_debug ("expected error: %s", error ? error->message : "null");
  g_assert_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_RUNTIME);
  g_clear_error (&error);

  /* this would be an error if the assert function was exported, but it's not */
  const gchar code7[] =
    "assert = 'hello world'\n";
  test_load_and_call (L, code7, sizeof (code7) - 1, 0, 0, &error);
  g_assert_no_error (error);

  wplua_unref (L);
}

static void
test_wplua_convert_asv ()
{
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  g_autoptr (GVariant) v = g_variant_new_parsed ("@a{sv} { "
      "'test-int': <42>, "
      "'test-double': <3.14>, "
      "'test-string': <'foobar'>, "
      "'test-boolean': <true>, "
      "'nested-table': <@a{sv} { 'string': <'baz'> }> "
      "}");
  wplua_gvariant_to_lua (L, v);
  lua_setglobal (L, "o");

  const gchar code2[] =
    "assert (o['test-string'] == 'foobar')\n"
    "assert (o['test-int'] == 42)\n"
    "assert (math.abs (o['test-double'] - 3.14) < 0.0000000001)\n"
    "assert (o['test-boolean'] == true)\n"
    "assert (o['nested-table']['string'] == 'baz')\n";
  test_load_and_call (L, code2, sizeof (code2) - 1, 0, 0, &error);
  g_assert_no_error (error);

  lua_getglobal (L, "o");
  g_autoptr (GVariant) fromlua = wplua_lua_to_gvariant (L, -1);

  gint64 test_int = 0;
  g_assert_true (g_variant_lookup (fromlua, "test-int", "x", &test_int));
  g_assert_cmpint (test_int, ==, 42);

  gdouble test_double = 0;
  g_assert_true (g_variant_lookup (fromlua, "test-double", "d", &test_double));
  g_assert_cmpfloat_with_epsilon (test_double, 3.14, 0.0000000001);

  const gchar *test_str = NULL;
  g_assert_true (g_variant_lookup (fromlua, "test-string", "&s", &test_str));
  g_assert_cmpstr (test_str, ==, "foobar");

  gboolean test_boolean = FALSE;
  g_assert_true (g_variant_lookup (fromlua, "test-boolean", "b", &test_boolean));
  g_assert_true (test_boolean);

  g_autoptr (GVariant) nested = NULL;
  g_assert_true (g_variant_lookup (fromlua, "nested-table", "@a{sv}", &nested));
  g_assert_nonnull (nested);
  g_assert_true (g_variant_is_of_type (nested, G_VARIANT_TYPE_VARDICT));

  test_str = NULL;
  g_assert_true (g_variant_lookup (nested, "string", "&s", &test_str));
  g_assert_cmpstr (test_str, ==, "baz");

  wplua_unref (L);
}

static void
test_wplua_convert_gvariant_array ()
{
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  g_autoptr (GVariant) v = g_variant_new_parsed ("@av [ "
      "<42>, <3.14>, <'foobar'>, <true>, "
      "<@a{sv} { 'string': <'baz'> }> ]");
  wplua_gvariant_to_lua (L, v);
  lua_setglobal (L, "o");

  const gchar code2[] =
    "assert (o[1] == 42)\n"
    "assert (math.abs (o[2] - 3.14) < 0.0000000001)\n"
    "assert (o[3] == 'foobar')\n"
    "assert (o[4] == true)\n"
    "assert (o[5]['string'] == 'baz')\n";
  test_load_and_call (L, code2, sizeof (code2) - 1, 0, 0, &error);
  g_assert_no_error (error);

  lua_getglobal (L, "o");
  g_autoptr (GVariant) fromlua = wplua_lua_to_gvariant (L, -1);

  gint64 test_int = 0;
  g_assert_true (g_variant_lookup (fromlua, "1", "x", &test_int));
  g_assert_cmpint (test_int, ==, 42);

  gdouble test_double = 0;
  g_assert_true (g_variant_lookup (fromlua, "2", "d", &test_double));
  g_assert_cmpfloat_with_epsilon (test_double, 3.14, 0.0000000001);

  const gchar *test_str = NULL;
  g_assert_true (g_variant_lookup (fromlua, "3", "&s", &test_str));
  g_assert_cmpstr (test_str, ==, "foobar");

  gboolean test_boolean = FALSE;
  g_assert_true (g_variant_lookup (fromlua, "4", "b", &test_boolean));
  g_assert_true (test_boolean);

  g_autoptr (GVariant) nested = NULL;
  g_assert_true (g_variant_lookup (fromlua, "5", "@a{sv}", &nested));
  g_assert_nonnull (nested);
  g_assert_true (g_variant_is_of_type (nested, G_VARIANT_TYPE_VARDICT));

  test_str = NULL;
  g_assert_true (g_variant_lookup (nested, "string", "&s", &test_str));
  g_assert_cmpstr (test_str, ==, "baz");

  wplua_unref (L);
}
static void
test_wplua_convert_wp_properties ()
{
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  const gchar code[] =
    "props = { "
    "  ['test-int'] = 42, "
    "  ['test-double'] = 3.14, "
    "  ['test-string'] = 'foobar', "
    "  ['test-boolean'] = false, "
    "}\n";
  test_load_and_call (L, code, sizeof (code) - 1, 0, 0, &error);
  g_assert_no_error (error);

  lua_getglobal (L, "props");
  g_autoptr (WpProperties) fromlua = wplua_table_to_properties (L, -1);

  g_assert_cmpstr (wp_properties_get (fromlua, "test-int"), ==, "42");
  g_assert_cmpstr (wp_properties_get (fromlua, "test-double"), ==, "3.14");
  g_assert_cmpstr (wp_properties_get (fromlua, "test-string"), ==, "foobar");
  g_assert_cmpstr (wp_properties_get (fromlua, "test-boolean"), ==, "false");

  lua_pop(L, 1);
  wplua_properties_to_table (L, fromlua);
  lua_setglobal (L, "fromc");

  const gchar code2[] =
    "assert (fromc['test-string'] == 'foobar')\n"
    "assert (fromc['test-int'] == '42')\n"
    "assert (fromc['test-double'] == '3.14')\n"
    "assert (fromc['test-boolean'] == 'false')\n";
  test_load_and_call (L, code2, sizeof (code2) - 1, 0, 0, &error);
  g_assert_no_error (error);

  wplua_unref (L);
}

static void
test_wplua_script_arguments ()
{
  g_autoptr (GError) error = NULL;
  lua_State *L = wplua_new ();

  g_autoptr (GVariant) v = g_variant_new_parsed ("@a{sv} { "
      "'test-int': <42>, "
      "'test-double': <3.14>, "
      "'test-string': <'foobar'>, "
      "'test-boolean': <true>, "
      "'nested-table': <@a{sv} { 'string': <'baz'> }> "
      "}");
  wplua_gvariant_to_lua (L, v);

  const gchar code2[] =
    "local o = ...\n"
    "assert (o['test-string'] == 'foobar')\n"
    "assert (o['test-int'] == 42)\n"
    "assert (math.abs (o['test-double'] - 3.14) < 0.0000000001)\n"
    "assert (o['test-boolean'] == true)\n"
    "assert (o['nested-table']['string'] == 'baz')\n";
  test_load_and_call (L, code2, sizeof (code2) - 1, 1, 0, &error);
  g_assert_no_error (error);

  /* same test, but with sandbox enabled */
  wplua_enable_sandbox (L, WP_LUA_SANDBOX_ISOLATE_ENV);
  wplua_gvariant_to_lua (L, v);
  test_load_and_call (L, code2, sizeof (code2) - 1, 1, 0, &error);
  g_assert_no_error (error);

  wplua_unref (L);
}

gint
main (gint argc, gchar *argv[])
{
  g_test_init (&argc, &argv, NULL);
  wp_init (WP_INIT_ALL);

  g_test_add_func ("/wplua/basic", test_wplua_basic);
  g_test_add_func ("/wplua/construct", test_wplua_construct);
  g_test_add_func ("/wplua/properties", test_wplua_properties);
  g_test_add_func ("/wplua/closure", test_wplua_closure);
  g_test_add_func ("/wplua/signals", test_wplua_signals);
  g_test_add_func ("/wplua/sandbox/script", test_wplua_sandbox_script);
  g_test_add_func ("/wplua/sandbox/config", test_wplua_sandbox_config);
  g_test_add_func ("/wplua/convert/asv", test_wplua_convert_asv);
  g_test_add_func ("/wplua/convert/gvariant_array",
      test_wplua_convert_gvariant_array);
  g_test_add_func ("/wplua/convert/wp_properties",
      test_wplua_convert_wp_properties);
  g_test_add_func ("/wplua/script_arguments", test_wplua_script_arguments);

  return g_test_run ();
}
  07070100000190000081ED000000000000000000000001656CC35F000004F6000000000000000000000000000000000000002500000000wireplumber-0.4.17/wp-uninstalled.sh  #!/usr/bin/env bash

set -e

# This is unset by meson
# shellcheck disable=SC2157
if [ -z "@MESON@" ]; then
  SOURCEDIR="@MESON_SOURCE_ROOT@"
  BUILDDIR="@MESON_BUILD_ROOT@"
else
  SOURCEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
  BUILDDIR=$(find "${SOURCEDIR}" -maxdepth 2 -name build.ninja -printf "%h\n" -quit 2>/dev/null || echo "${SOURCEDIR}/build")
fi
CONFIGDIR=config

while getopts ":b:c:" opt; do
  case ${opt} in
    b)
      BUILDDIR=${OPTARG}
      ;;
    c)
      CONFIGDIR=${OPTARG}
      ;;
    \?)
      echo "Invalid option: -${OPTARG}"
      exit 1
      ;;
    :)
      echo "Option -${OPTARG} requires an argument"
      exit 1
      ;;
  esac
done

shift $((OPTIND-1))

if [ $# -eq 0 ]; then
  echo "Usage: $(basename "${BASH_SOURCE[0]}") [options] <wireplumber|wpctl|wpexec|...>"
  exit 1
fi

if [ ! -d "${BUILDDIR}" ]; then
  echo "Invalid build directory: ${BUILDDIR}"
  exit 1
fi

echo "Using build directory: ${BUILDDIR}"

export WIREPLUMBER_MODULE_DIR="${BUILDDIR}/modules"
export WIREPLUMBER_CONFIG_DIR="${SOURCEDIR}/src/${CONFIGDIR}"
export WIREPLUMBER_DATA_DIR="${SOURCEDIR}/src"
export PATH="${BUILDDIR}/src:${BUILDDIR}/src/tools:$PATH"
export LD_LIBRARY_PATH="${BUILDDIR}/lib/wp:$LD_LIBRARY_PATH"

exec "$@"
  07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!                                        4299 blocks
