# Events & Hooks

This is the API that scripts use to participate in WirePlumber's event-driven session management logic. Almost all of the policy that ships with WirePlumber is implemented as hooks registered through this API.

For a conceptual introduction to events and hooks, read Events and Hooks first. The underlying C API is documented in Events, Event Dispatcher and Event Hooks.

## Overview

An event is something that happened, pushed onto the event stack. Events carry a type (for example `node-added`), a priority, a set of properties, a source and a subject.

A hook is a piece of code that runs when a matching event is dispatched. A hook declares:

- a unique `name`, which other hooks use to order themselves against it

- one or more `interests`, which decide whether the hook runs for a given event

- `before` / `after` constraints, which order it relative to other hooks running on the same event

- the code to run, either as a single `execute` function (`SimpleEventHook()`) or as a state machine (`AsyncEventHook()`)

Hooks do not run until they are registered with `EventHook.register()`. A minimal hook therefore looks like this:

    SimpleEventHook {
      name = "example/log-new-nodes",
      interests = {
        EventInterest {
          Constraint { "event.type", "=", "node-added" },
        },
      },
      execute = function (event)
        local node = event:get_subject ()
        log:info (node, "node added: " .. tostring (node.properties ["node.name"]))
      end
    }:register ()

## Event

The event object is passed to the hook's `execute` function.

Event.get_properties(self)

  Binds `wp_event_get_properties()`

  Returns the properties of the event. These always include `event.type`, and typically include properties copied from the subject, which is what makes it possible to match on them in an `EventInterest()`.

  Returns:

    the properties of the event

  Return type:

    Properties, see Properties

Event.get_source(self)

  Binds `wp_event_get_source()`

  Returns the object that pushed this event. For events pushed by the standard event source, this is the standard-event-source plugin, which is also the object that provides the `schedule-rescan` and `get-object-manager` actions used by the shipped scripts.

  Returns:

    the source of the event

  Return type:

    GObject

Event.get_subject(self)

  Binds `wp_event_get_subject()`

  Returns the object that this event is about; for a `node-added` event this is the `Node` that was added.

  Returns:

    the subject of the event

  Return type:

    GObject

Event.stop_processing(self)

  Binds `wp_event_stop_processing()`

  Stops the event from being processed any further. No hooks that would run after this one will run for this event.

Event.set_data(self, key, value)

  Binds `wp_event_set_data()`

  Stores an arbitrary value on the event, so that later hooks running on the same event can read it with `Event.get_data()`. This is the standard way for a chain of hooks to communicate; the `select-target` chain, for instance, uses it to pass the selected target from the `find-*` hooks to `linking/link-target`.

  Passing no value (or nil) removes the data.

  Parameters:

    - key (string) -- the key to store the value under

    - value -- the value to store; booleans, numbers, strings, tables (stored as `Properties`) and GValue userdata are supported

Event.get_data(self, key)

  Binds `wp_event_get_data()`

  Returns the value previously stored with `Event.set_data()`, or nil.

  Parameters:

    key (string) -- the key to look up

  Returns:

    the stored value, or nil

## EventInterest

EventInterest(decl)

  Creates an ObjectInterest that matches `Event` objects. It is a shorthand for `Interest { type = "event", ... }`.

  The constraints are matched against the event's properties, so `event.type` is almost always the first constraint. See Object Interest for the full constraint syntax.

      EventInterest {
        Constraint { "event.type", "=", "node-added" },
        Constraint { "media.class", "#", "Stream/*" },
      }

  When an event is created, the properties of its subject - both the info properties and the global properties - are merged into the event's own properties, which is why the `media.class` constraint above works without any further qualification. Consequently, the constraint `type` is irrelevant for `"pw"` and `"pw-global"` constraints on an event interest; both look up the same merged set of properties. A `"gobject"` constraint, on the other hand, is still matched against the GObject properties of the event's subject.

  A hook may declare several interests; the hook runs if any of them matches.

  Returns:

    the new event interest

  Return type:

    ObjectInterest

## EventHook

Both `SimpleEventHook()` and `AsyncEventHook()` return an object with the following methods.

EventHook.register(self)

  Binds `wp_event_dispatcher_register_hook()`

  Registers the hook with the event dispatcher, so that it starts running for matching events. A hook has no effect until this is called.

EventHook.remove(self)

  Binds `wp_event_dispatcher_unregister_hook()`

  Unregisters the hook. This is how scripts enable and disable hooks at runtime in response to a setting change; see the example below.

## SimpleEventHook

SimpleEventHook(decl)

  Binds `wp_simple_event_hook_new()`

  Constructs a hook whose action is a single synchronous function.

  The constructor takes a single table with the following fields:

  Field | Contains
  --- | ---
  name | (required) a string, the unique name of the hook
  execute | (required) the function to run; it takes the `Event` as its only argument and returns nothing
  interests | a list of `EventInterest()` objects; the hook runs if any of them matches
  before | a hook name, or a list of hook names, that must run after this hook
  after | a hook name, or a list of hook names, that must run before this hook

      SimpleEventHook {
        name = "linking/find-default-target",
        after = { "linking/find-defined-target",
                  "linking/find-filter-target" },
        before = "linking/prepare-link",
        interests = {
          EventInterest {
            Constraint { "event.type", "=", "select-target" },
          },
        },
        execute = function (event)
          -- ...
        end
      }:register ()

  Returns:

    the new hook

  Return type:

    EventHook

## AsyncEventHook

AsyncEventHook(decl)

  Binds `wp_async_event_hook_new()`

  Constructs a hook whose action is a state machine, for actions that cannot complete synchronously — typically because they need to wait for an object to activate or for a PipeWire round trip.

  The constructor takes the same `name`, `interests`, `before` and `after` fields as `SimpleEventHook()`, but instead of `execute` it takes a `steps` table.

  Each entry in `steps` is named by a string and contains:

  Field | Contains
  --- | ---
  next | (required) the name of the step to run next, or the string `"none"` to finish
  execute | (required) the function to run for this step; it takes the `Event` and a Transition

  Execution always starts at the step named `start` and follows the `next` links until a step whose `next` is `"none"` has run.

  Each step's `execute` function is responsible for advancing the transition, either immediately with `transition:advance ()` or later from an asynchronous callback. Calling `transition:return_error (message)` aborts the hook.

  A step named `error` is optional; if present, it runs when the transition fails.

      AsyncEventHook {
        name = "node/create-item",
        interests = {
          EventInterest {
            Constraint { "event.type", "=", "node-added" },
          },
        },
        steps = {
          start = {
            next = "register",
            execute = function (event, transition)
              local node = event:get_subject ()
              -- ... create and configure the item ...
              transition:advance ()
            end
          },
          register = {
            next = "none",
            execute = function (event, transition)
              -- ... register the item ...
              transition:advance ()
            end
          },
        },
      }:register ()

  Returns:

    the new hook

  Return type:

    EventHook

## EventDispatcher

EventDispatcher.push_event(event)

  Binds `wp_event_dispatcher_push_event()`

  Pushes an event onto the event stack, where it will be picked up and dispatched to all the hooks that are interested in it.

  The argument is either an existing `Event`, or a table describing a new event to construct and push:

  Field | Contains
  --- | ---
  type | (required) a string, the event type
  priority | (required) a number; higher priority events are dispatched before lower priority ones
  properties | a table or `Properties` with additional properties for the event
  source | the object pushing the event
  subject | the object the event is about

  Properties of the subject are automatically copied into the event's properties, so interests can match on them.

  In practice, the shipped scripts rarely build the table themselves. They ask the standard-event-source plugin to create the event instead, which assigns the priority that is configured for that event type, then attach any extra data to it and push it:

      source = source or Plugin.find ("standard-event-source")

      local e = source:call ("create-event", "create-v4l2-device", parent, nil)
      e:set_data ("device-properties", properties)
      e:set_data ("factory", factory)

      EventDispatcher.push_event (e)

  The `create-event` action takes the event type, the subject and an optional properties table.

  Returns:

    the event that was pushed

  Return type:

    Event

## Enabling and disabling hooks at runtime

Because `EventHook.register()` and `EventHook.remove()` can be called at any time, a script can react to a setting changing by adding or removing a hook, rather than by checking the setting on every event. This is the pattern used by, for example, `linking/rescan-trigger-on-target-metadata-changed`:

    local hook = nil

    local function updateEnabled (enable)
      if enable and not hook then
        hook = SimpleEventHook {
          name = "example/my-hook",
          interests = { EventInterest { Constraint { "event.type", "=", "node-added" } } },
          execute = function (event) --[[ ... ]] end
        }
        hook:register ()
      elseif hook and not enable then
        hook:remove ()
        hook = nil
      end
    end

    Settings.subscribe ("example.my-setting", function ()
      updateEnabled (Settings.get_boolean ("example.my-setting"))
    end)
    updateEnabled (Settings.get_boolean ("example.my-setting"))

See also Settings and Custom Scripts.