docs: explain how to actually run the VirtualHere passthrough example
apple / swift (push) Successful in 1m22s
ci / web (push) Successful in 1m11s
ci / docs-site (push) Successful in 1m11s
ci / bench (push) Successful in 5m39s
apple / screenshots (push) Successful in 6m38s
decky / build-publish (push) Successful in 20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 57s
deb / build-publish (push) Successful in 9m0s
deb / build-publish-host (push) Successful in 9m20s
windows-host / package (push) Successful in 9m46s
android / android (push) Successful in 18m37s
arch / build-publish (push) Successful in 18m48s
docker / deploy-docs (push) Successful in 28s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m44s
ci / rust (push) Successful in 26m30s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m2s
apple / swift (push) Successful in 1m22s
ci / web (push) Successful in 1m11s
ci / docs-site (push) Successful in 1m11s
ci / bench (push) Successful in 5m39s
apple / screenshots (push) Successful in 6m38s
decky / build-publish (push) Successful in 20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 57s
deb / build-publish (push) Successful in 9m0s
deb / build-publish-host (push) Successful in 9m20s
windows-host / package (push) Successful in 9m46s
android / android (push) Successful in 18m37s
arch / build-publish (push) Successful in 18m48s
docker / deploy-docs (push) Successful in 28s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m44s
ci / rust (push) Successful in 26m30s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m2s
The VirtualHere DualSense recipe linked the `virtualhere-dualsense.ts` SDK example as a deployable recipe but skipped everything needed to run it: that VirtualHere is a server (couch) + client (host) pair, and that the example — like every example — imports `../src/index.js`, which only resolves inside the SDK repo. A user copying it out had no way to know they must `bun add @punktfunk/host` and swap that import. - automation.md: rewrite the recipe into a full walkthrough — the two-sided VirtualHere setup, the `-t` verbs, the zero-code hooks version, and a new scripted section with exact deploy steps, env vars, and running it as a service (its own SIGTERM handler makes `systemctl stop` release the pad). - sdk/README.md: say how to run an example in-repo vs deployed on a host. - virtualhere-dualsense.ts: header note on the import swap + service setup, since that file is where the doc link lands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,12 +168,29 @@ The canonical "decide, don't just observe" pattern — approve pairing from your
|
|||||||
## Recipe: full controller passthrough (VirtualHere)
|
## Recipe: full controller passthrough (VirtualHere)
|
||||||
|
|
||||||
To get a controller's *native* features on the host — DualSense gyro, touchpad, adaptive
|
To get a controller's *native* features on the host — DualSense gyro, touchpad, adaptive
|
||||||
triggers, USB rumble — instead of the emulated pad, share the physical device from the couch with
|
triggers, USB rumble — instead of the emulated pad, hand the physical device from the couch to the
|
||||||
[VirtualHere](https://www.virtualhere.com/) (USB-over-IP) and bind it to the host only while a
|
host over [VirtualHere](https://www.virtualhere.com/) (USB-over-IP), bound only while a client is
|
||||||
client is connected. The couch runs the VirtualHere **server** (sharing the pad); the host runs
|
connected so the couch keeps its own controller the rest of the time.
|
||||||
the VirtualHere **client** and this automation drives its `-t` IPC.
|
|
||||||
|
|
||||||
Zero-code, bracketed on the stream with two hooks:
|
**The two sides.** VirtualHere is a server/client pair, and you run both:
|
||||||
|
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -184,8 +201,42 @@ Zero-code, bracketed on the stream with two hooks:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`couch-deck.11` is the device's VirtualHere address (`vhclientx86_64 -t LIST`); same-LAN setups
|
`couch-deck.11` is the device's VirtualHere address from `vhclientx86_64 -t LIST`.
|
||||||
auto-discover it, otherwise `MANUAL HUB ADD,<couch-ip>:7575` once. For a version that resolves the
|
|
||||||
device by name, filters to one couch, and releases the pad on a clean shutdown, see the
|
### 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
|
||||||
[`virtualhere-dualsense.ts`](https://git.unom.io/unom/punktfunk/src/branch/main/sdk/examples/virtualhere-dualsense.ts)
|
[`virtualhere-dualsense.ts`](https://git.unom.io/unom/punktfunk/src/branch/main/sdk/examples/virtualhere-dualsense.ts)
|
||||||
SDK example.
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|||||||
+4
-1
@@ -89,7 +89,10 @@ A complexity ladder in [`examples/`](./examples) — start at the top:
|
|||||||
4. [`couch-preset.effect.ts`](./examples/couch-preset.effect.ts) — **advanced, Effect-native**: only if you're composing Effect programs.
|
4. [`couch-preset.effect.ts`](./examples/couch-preset.effect.ts) — **advanced, Effect-native**: only if you're composing Effect programs.
|
||||||
|
|
||||||
Examples 1–3 are the plain Promise facade and cover most automation; you only need example 4's
|
Examples 1–3 are the plain Promise facade and cover most automation; you only need example 4's
|
||||||
Effect surface for composed, interruptible programs. Run any with `bun examples/<file>.ts`.
|
Effect surface for composed, interruptible programs. Run any **in the repo** with
|
||||||
|
`bun examples/<file>.ts`. To **deploy** one on a host, install the package into its own directory
|
||||||
|
(`bun add @punktfunk/host`) and change its `../src/…` import to `@punktfunk/host` — see
|
||||||
|
[Running a single script as a service](#running-a-single-script-as-a-service).
|
||||||
|
|
||||||
Plus a real-world recipe:
|
Plus a real-world recipe:
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,11 @@
|
|||||||
// VH_DEVICE=couch-deck.11 bun examples/virtualhere-dualsense.ts # address from `-t LIST`
|
// VH_DEVICE=couch-deck.11 bun examples/virtualhere-dualsense.ts # address from `-t LIST`
|
||||||
// VH_DEVICE=DualSense bun examples/virtualhere-dualsense.ts # …or match by name substring
|
// VH_DEVICE=DualSense bun examples/virtualhere-dualsense.ts # …or match by name substring
|
||||||
//
|
//
|
||||||
|
// To run this *outside* the repo (the normal case), drop it in its own dir, `bun add
|
||||||
|
// @punktfunk/host`, and change the import below from `../src/index.js` to `@punktfunk/host`; then
|
||||||
|
// keep it alive as a systemd user unit (its SIGTERM release hands the pad back on `systemctl
|
||||||
|
// stop`). Full walkthrough: docs → Events & hooks → "full controller passthrough (VirtualHere)".
|
||||||
|
//
|
||||||
// Env: VH_DEVICE required — a VirtualHere address (`server.port`) or a device-name substring.
|
// Env: VH_DEVICE required — a VirtualHere address (`server.port`) or a device-name substring.
|
||||||
// VH_CLIENT client binary. Default `vhclientx86_64` (Linux); Windows `vhui64.exe`,
|
// VH_CLIENT client binary. Default `vhclientx86_64` (Linux); Windows `vhui64.exe`,
|
||||||
// macOS `vhclientosx`, ARM Linux `vhclientarm64`.
|
// macOS `vhclientosx`, ARM Linux `vhclientarm64`.
|
||||||
|
|||||||
Reference in New Issue
Block a user