# `Mob.Listener`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/listener.ex#L1)

The single process the native layer delivers interaction events to.

Native knows two things and neither of them is a screen: the registered name
`:mob_screen` (used by `enif_whereis_pid` for the back gesture, alert actions
and the launch-notification fallback, on both platforms) and whatever pid was
stored in a tap handle by `register_tap/1`. This module takes over the second.

## The envelope

`nif_register_tap` stores an arbitrary term as the handle's tag and echoes it
back verbatim — `mob_send_tap` sends `{:tap, tag}`, `mob_send_event` sends
`{event, tag}`. The tag is copied with `enif_make_copy`, so it can be any
shape, including a nested tuple.

So instead of registering `{screen_pid, tag}`, `Mob.Renderer` registers

    {listener_pid, {:mob_route, screen_pid, tag}}

Native then delivers `{:tap, {:mob_route, screen_pid, tag}}` here, and the
listener forwards `{:tap, tag}` to the screen. Native remains ignorant that
screens exist, and **no `.m`, `.zig` or generator-template change is
required** to move the inbound path off a single hard-wired screen process.

Native has **two** message shapes for handle-addressed events, and the
listener has to unwrap both:

* `{event, tag}` — `mob_send_tap`, `mob_send_event`, `mob_send_scrolled_past`.
  Covers `:tap`, `:focus`, `:blur`, `:submit`, `:dismiss`, `:select` and the
  other payload-free events.
* `{event, tag, payload}` — `mob_send_change`, `mob_send_compose`,
  `mob_send_swipe_with_direction`, `mob_send_scroll`, `mob_send_drag`,
  `mob_send_pinch`, `mob_send_rotate`, `mob_send_pointer_move`. This is
  everything carrying a value: text-field and toggle and slider `on_change`,
  tab selection, and every gesture stream.

Both are unwrapped on the event atom rather than one clause per event, so a
new event kind needs no change here — but a new *arity* would. Anything else
is logged rather than silently discarded, because an unmodelled shape is
invisible otherwise: the widget simply stops working.

## Why a hop at all

Today there is one screen process, so carrying its pid through the envelope
and forwarding is, on its own, a hop that buys nothing. What it buys is that
the ~35 `register_tap` call sites in `Mob.Renderer` stop naming a screen
process directly. When MOB-112 makes screens processes and MOB-113 adds the
router, the change is confined to `handler/1` and `handle_info/2` here rather
than spread across every interactive prop in the renderer.

## The escape hatch

A high-frequency stream — drag, scroll, `mob_touch` at display rate — pays one
extra hop and one extra copy per event. Registering the screen pid directly
bypasses this module entirely and still works, because that is exactly what
the renderer did before:

    nif.register_tap({screen_pid, tag})   # direct, no listener

Nothing bypasses it today. The hop has not been measured, and adding an
exception before there is a number to point at would be guessing.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `ensure_started`

```elixir
@spec ensure_started() :: :ok
```

Start the listener if it is not already running.

Unlinked, for the same reason `Mob.Sender.ensure_started/0` is: the caller is
usually a screen, and a screen crash must not take down the process every
screen's events arrive through.

# `handler`

```elixir
@spec handler(pid() | {pid(), term()}) :: pid() | {pid(), term()}
```

Wrap a `register_tap/1` target so native delivers the event here instead of
straight to the screen.

Accepts either shape the renderer uses — a bare pid, or `{pid, tag}` — and
returns the term to hand to `register_tap/1`.

Returns the target **unchanged** when the listener is not running, so events
go directly to the screen exactly as they did before this module existed.
That is the fallback for any boot path that does not start a listener, and it
is what keeps the renderer's own tests working without one.

# `running?`

```elixir
@spec running?() :: boolean()
```

Whether the listener is running.

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Start the listener. Named, so there is exactly one.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
