docs: USB passthrough is a plugin now, not a recipe you assemble
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 6s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 10s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 9s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 18s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 26s
ci / web (push) Successful in 1m5s
docker / builders-arm64cross (push) Successful in 14s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 51s
ci / docs-site (push) Successful in 1m14s
ci / rust-arm64 (push) Successful in 1m18s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m14s
docker / deploy-docs (push) Successful in 31s
ci / rust (push) Successful in 7m20s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 6s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 10s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 9s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 18s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 26s
ci / web (push) Successful in 1m5s
docker / builders-arm64cross (push) Successful in 14s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 51s
ci / docs-site (push) Successful in 1m14s
ci / rust-arm64 (push) Successful in 1m18s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m14s
docker / deploy-docs (push) Successful in 31s
ci / rust (push) Successful in 7m20s
Adds a VirtualHere section to the plugins page — what it is, that both halves of VirtualHere are yours to install and licence, that the Devices tab writes a name-based rule so it survives the couch rebooting, and that Diagnostics is where to look when nothing happens. States the coverage limit up front rather than letting somebody discover it: there is no VirtualHere server for iOS or tvOS, so those clients cannot pass devices through, and nothing on our side can change that. Cuts the automation.md recipe from 75 lines to a pointer. It now leads with "use the plugin" and keeps only the zero-code two-hook version for people who would rather not install one — with its trade-offs stated instead of implied: the address is hard-coded so it breaks when the couch reboots, and an abnormal stream end strands the device on the host. Those two failures are exactly what the plugin exists to fix, so the reader gets to make an informed choice. Not build-verified: docs-site does not build standalone in this checkout. Checked by hand that Callout is in fumadocs' default MDX components with a valid `warn` type, that the JSX balances, and that the cross-page anchor matches github-slugger of the heading. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -193,29 +193,25 @@ The canonical "decide, don't just observe" pattern — approve pairing from your
|
||||
## Recipe: full controller passthrough (VirtualHere)
|
||||
|
||||
To get a controller's *native* features on the host — DualSense gyro, touchpad, adaptive
|
||||
triggers, USB rumble — instead of the emulated pad, hand the physical device from the couch to the
|
||||
host over [VirtualHere](https://www.virtualhere.com/) (USB-over-IP), bound only while a client is
|
||||
connected so the couch keeps its own controller the rest of the time.
|
||||
triggers, USB rumble — or to use a device no emulation can stand in for, like a racing wheel or a
|
||||
HOTAS, hand the physical device from the couch to the host over
|
||||
[VirtualHere](https://www.virtualhere.com/) (USB-over-IP) while you play.
|
||||
|
||||
**The two sides.** VirtualHere is a server/client pair, and you run both:
|
||||
**Use the plugin.** [VirtualHere passthrough](/docs/plugins#virtualhere-usb-passthrough) does all of
|
||||
this for you: it finds the device by name (so it survives the couch rebooting), brackets it around
|
||||
the session, gives it back if anything crashes, and tells you which half of the setup is broken when
|
||||
it isn't working. That is the supported route, and the rest of this section is only for people who
|
||||
would rather not install a plugin.
|
||||
|
||||
- **Server — on the couch** (where the pad is physically plugged in). Run the VirtualHere USB
|
||||
Server there; it shares the pad on the LAN. Leave it running.
|
||||
- **Client — on the host** (where the game and this automation run). Install the VirtualHere
|
||||
Client (as a service, or the tray app). It auto-discovers the couch's shared pad on the same
|
||||
LAN; across subnets, add it once with `<VH_CLIENT> -t "MANUAL HUB ADD,<couch-ip>:7575"`. The
|
||||
client binary is `vhclientx86_64` on Linux, `vhui64.exe` on Windows, `vhclientosx` on macOS,
|
||||
`vhclientarm64` on ARM Linux.
|
||||
|
||||
The client's `-t` flag is a one-shot IPC to the already-running client: `-t LIST` prints every
|
||||
visible device with its address (`server.port`, e.g. `couch-deck.11`); `-t "USE,<addr>"` mounts it
|
||||
onto the host; `-t "STOP USING,<addr>"` hands it back. The automation just brackets
|
||||
`USE` / `STOP USING` around a session.
|
||||
**The two sides.** VirtualHere is a server/client pair, and you run both: the **server on the couch**
|
||||
(where the device is plugged in) shares it, and the **client on the host** mounts it. The client's
|
||||
`-t` flag is a one-shot IPC to the already-running client — `-t LIST` prints every visible device
|
||||
with its address (`server.port`, e.g. `couch-deck.11`), `-t "USE,<addr>"` mounts it, and
|
||||
`-t "STOP USING,<addr>"` hands it back.
|
||||
|
||||
### Zero-code: two hooks
|
||||
|
||||
Bracket it on the stream with two [hooks](#hooks-hooksjson) — mount when video starts, release
|
||||
when it stops. This is all most setups need:
|
||||
Bracket it on the stream with two [hooks](#hooks-hooksjson):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -226,42 +222,14 @@ when it stops. This is all most setups need:
|
||||
}
|
||||
```
|
||||
|
||||
`couch-deck.11` is the device's VirtualHere address from `vhclientx86_64 -t LIST`.
|
||||
`couch-deck.11` is the device's address from `vhclientx86_64 -t LIST`.
|
||||
|
||||
### Scripted: resolve by name, release on shutdown
|
||||
|
||||
The hooks above hard-code the address, and can strand the pad on the host if it's stopped
|
||||
mid-stream (the `stream.stopped` hook never fires). The
|
||||
Know what this trades away, because the plugin exists to fix exactly these: the address is
|
||||
hard-coded, so it breaks when the couch reboots or the device moves port; and if the stream ends
|
||||
abnormally the `stream.stopped` hook never fires, leaving the device stranded on the host until
|
||||
somebody notices. There is also a
|
||||
[`virtualhere-dualsense.ts`](https://git.unom.io/unom/punktfunk/src/branch/main/sdk/examples/virtualhere-dualsense.ts)
|
||||
SDK example is the robust version: it resolves the device by **name substring** (survives the
|
||||
address changing), can filter to **one couch** in a multi-client setup, and **releases the pad on
|
||||
a clean shutdown** (`systemctl stop`, ^C) so the couch always gets its controller back.
|
||||
SDK example if you want a worked script to build your own on.
|
||||
|
||||
It's a standalone [`@punktfunk/host` script](https://git.unom.io/unom/punktfunk/src/branch/main/sdk#running-a-single-script-as-a-service)
|
||||
— run it in its own directory. The one edit is its import: the in-repo copy imports from
|
||||
`../src/index.js`; outside the repo it's the published package:
|
||||
|
||||
```sh
|
||||
mkdir ~/punktfunk-scripts && cd ~/punktfunk-scripts
|
||||
bun init -y
|
||||
bun add @punktfunk/host # point the @punktfunk scope at the registry first — see the SDK README
|
||||
# save the example as virtualhere-dualsense.ts, and change its first import to the package:
|
||||
# - import { connect } from "../src/index.js";
|
||||
# + import { connect } from "@punktfunk/host";
|
||||
VH_DEVICE=DualSense bun virtualhere-dualsense.ts
|
||||
```
|
||||
|
||||
`VH_DEVICE` is required — a VirtualHere address (`couch-deck.11`) or a device-name substring
|
||||
(`DualSense`). Optional: `VH_CLIENT` overrides the client binary (default `vhclientx86_64`);
|
||||
`VH_ONLY_CLIENT` binds only for one punktfunk client label. Running on the host box the SDK needs
|
||||
no token or URL — it reads the host's loopback credentials itself (see
|
||||
[Connection resolution](https://git.unom.io/unom/punktfunk/src/branch/main/sdk#connection-resolution)).
|
||||
|
||||
Keep it running as a
|
||||
[systemd user unit](https://git.unom.io/unom/punktfunk/src/branch/main/sdk#running-a-single-script-as-a-service)
|
||||
(its default `SIGTERM` triggers the script's own release step — so `systemctl stop` hands the pad
|
||||
back), or drop it under the [scripting runner](/docs/plugins) with your other units.
|
||||
|
||||
> The example brackets on `client.connected` / `client.disconnected` — the pad returns to the
|
||||
> couch the moment they disconnect. Switch to `stream.started` / `stream.stopped` if you'd rather
|
||||
> pass it through only while video is actually flowing; both are noted in the file's header.
|
||||
> VirtualHere is a commercial product, sold separately by VirtualHere Pty. Ltd. — free for one
|
||||
> shared device, licensed beyond that. Punktfunk is not affiliated with it.
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
---
|
||||
title: Plugins
|
||||
description: First-party plugins that sync your ROM collection or your Playnite library into the game library — and how to install them.
|
||||
description: First-party plugins — sync your ROM collection or Playnite library into the game library, or hand a real USB device on the couch to the host — and how to install them.
|
||||
---
|
||||
|
||||
Plugins extend the host through the **scripting runner** (see [Events & hooks](/docs/automation)). A
|
||||
plugin runs alongside the host, reconciles titles into your **game library** as a provider — so they
|
||||
appear in the grid on every client — and can add its own page to the [web console](/docs/web-console).
|
||||
|
||||
Two first-party plugins today:
|
||||
Three first-party plugins today:
|
||||
|
||||
| Plugin | What it does |
|
||||
|---|---|
|
||||
| **ROM Manager** | Scans your ROM directories, matches each platform to an installed emulator, and syncs them into the library with box art. |
|
||||
| **Playnite** | Mirrors your [Playnite](https://playnite.link) library — every store and emulator it manages — into the library, launched back through Playnite. |
|
||||
| **VirtualHere** | Hands a real USB device on the couch — wheel, HOTAS, pad — to the host while you play, and gives it back after. Needs [VirtualHere](https://www.virtualhere.com/), sold separately. |
|
||||
|
||||
## Installing from the console
|
||||
|
||||
@@ -200,6 +201,48 @@ seconds of any library change. Filters (installed-only, per-store, hidden) live
|
||||
`%ProgramData%\punktfunk\playnite\config.json`. Details are in
|
||||
[the plugin's repo](https://git.unom.io/unom/punktfunk-plugin-playnite).
|
||||
|
||||
## VirtualHere (USB passthrough)
|
||||
|
||||
`@punktfunk/plugin-virtualhere` — hands a **physical USB device** on your couch machine to the
|
||||
host while you play, and gives it back afterwards. The game sees the real device, so this is the
|
||||
answer for a racing wheel, a HOTAS, pedals, an arcade stick, or any controller whose value is that
|
||||
it is not emulated.
|
||||
|
||||
<Callout type="warn">
|
||||
This plugin drives [VirtualHere](https://www.virtualhere.com/), a commercial USB-over-IP product
|
||||
**sold separately** by VirtualHere Pty. Ltd. Nothing from VirtualHere is bundled or downloaded by
|
||||
Punktfunk — you install and license it yourself. The plugin is not affiliated with or endorsed by
|
||||
VirtualHere.
|
||||
</Callout>
|
||||
|
||||
You need both halves of VirtualHere running before the plugin is any use:
|
||||
|
||||
- **The USB Server on the couch**, sharing the device. Free for one device; beyond that, and to run
|
||||
the client as a service, VirtualHere requires a purchased licence.
|
||||
- **The USB Client on the host**, ideally installed as a service so it survives logging out.
|
||||
|
||||
Servers exist for Windows, Linux, macOS and Android couches. **There is no VirtualHere server for
|
||||
iOS or tvOS**, so iPhones, iPads and Apple TVs cannot pass devices through — nothing on the
|
||||
Punktfunk side can change that.
|
||||
|
||||
```sh
|
||||
punktfunk-host plugins add virtualhere
|
||||
punktfunk-host plugins enable
|
||||
```
|
||||
|
||||
Then open the console's **VirtualHere** page. The **Devices** tab lists whatever the couch is
|
||||
sharing; pick one and it writes a rule matching the device *by name*, which keeps working after the
|
||||
couch reboots or the device moves to another port. By default the device is handed over when video
|
||||
starts and returned when it stops, so the couch keeps its own controller the rest of the time —
|
||||
you can widen that to the whole session, or to the entire time a client is connected.
|
||||
|
||||
If nothing happens, the **Diagnostics** tab walks the whole two-sided setup and tells you which
|
||||
part to fix. The same checks are available as `punktfunk-plugin-virtualhere doctor`, which is the
|
||||
useful thing to paste into a support thread.
|
||||
|
||||
Full configuration is in
|
||||
[the plugin's repo](https://git.unom.io/unom/punktfunk-plugin-virtualhere).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`punktfunk-host: command not found`** — on Windows, open a new terminal so it picks up the
|
||||
|
||||
Reference in New Issue
Block a user