Skip to content

fix: find seats by probing the seat port block, not by asking the service - #2

Merged
vibesoftwarecoder merged 1 commit into
masterfrom
fix/seat-discovery-probe
Sep 10, 2026
Merged

vibesoftwarecoder merged 1 commit into
masterfrom
fix/seat-discovery-probe

Conversation

@vibesoftwarecoder

Copy link
Copy Markdown
Owner

Closes #1.

The bug

Discovery polled http://127.0.0.1:9550/api/seats and emitted every seat at 127.0.0.1. On any machine that is not the host, that address is the client, which runs no MultiSeat service — so the request failed and was swallowed on purpose:

// MultiSeat service not running — silent failure, will retry
return;

Seats never appeared, no error was shown, nothing was logged. It could only ever have worked with Moonlight running on the host itself.

The fix

Seats are found by probing the seat port block on hosts the user already has. A seat's Apollo answers /serverinfo on its own port — exactly what adding it by hand does, and that path is known to work: verified end to end on 2026-09-10 by adding 192.168.1.46:48100, pairing, and streaming.

Ports come from MultiSeat's own constants (PortBase 48100, PortsPerSeat 30 → 48100/48130/48160/48190). A seat identifies itself by the hostname ApolloConfigBuilder gives it, MultiSeat-{Account}-{N}. Anything answering on those ports without that prefix is left alone.

Why probing rather than fixing the address

  • No API key on the client, and no secret to distribute
  • MultiSeat's ApiBindLoopbackOnly stays true — its dashboard API never has to be reachable from the LAN
  • No dependency on mDNS, which does not work for seats

That last point was measured, not assumed. A seat's Apollo logs Registered Apollo mDNS service, but the registration never reaches the network — Apollo registers through Windows' responder rather than binding 5353 itself, and a registration made inside an RDP session does not escape it. Browsing _nvstream._tcp with a seat running returns the console Apollo and nothing else, with that same browse finding the console Apollo every time as the control.

Implementation notes

Addresses are pulled, not pushed. ComputerManager's host add/remove paths run on a thread pool under its write lock; reaching back into it from there invites a deadlock. Discovery raises aboutToPoll and ComputerManager fills the list in, on the timer's own thread.

Local and manual addresses only, deduplicated. A seat and the console Apollo beside it share one address, and seat ports are never port-forwarded — probing a remote address would be four guaranteed failures per tick against someone else's network.

Reuses NvHTTP::getXmlString rather than opening a second QXmlStreamReader on the same shape of document.

In-flight tracking is per endpoint (host:port), replacing a single m_RequestPending flag that cannot express parallel probes.

⛔ Not compiled or run

The machine this was written on has no Qt toolchain — no qmake, no cmake — so CI is the only verification. The probe's server side is proven (a seat answers /serverinfo with the expected hostname), but the client code itself has not executed anywhere.

Realistic risks are compile errors and Qt-idiom mismatches. QReadLocker against CopySafeReadWriteLock was checked by inheritance (: public QReadWriteLock) and the XML comparison follows the existing pattern in nvhttp.cpp, but that is reasoning rather than a compiler.

Known limitation

PortBase and MaxSeats are configurable host-side and nothing advertises them, so a host that has changed them still needs its seats added manually. Probing the default block covers the normal case with no host-side cooperation at all.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SQvL62WkT8xDWXqyjFCGDw

…vice

Closes #1.

Discovery polled http://127.0.0.1:9550/api/seats and emitted every seat at
127.0.0.1. On any machine that is not the host, that address is the CLIENT,
which runs no MultiSeat service -- so the request failed and was swallowed on
purpose ("silent failure, will retry"). Seats never appeared, no error was
shown and nothing was logged. It could only ever have worked with Moonlight
running on the host itself.

Seats are now found by probing the seat port block on hosts the user already
has. A seat's Apollo answers /serverinfo on its own port -- exactly what adding
it by hand does, and that path is known to work: verified end to end on
2026-09-10 by adding 192.168.1.46:48100, pairing and streaming.

Ports come from MultiSeat's own constants (PortBase 48100, PortsPerSeat 30 ->
48100/48130/48160/48190) and a seat identifies itself by the hostname
ApolloConfigBuilder gives it, MultiSeat-{Account}-{N}. Anything answering on
those ports without that prefix is left alone.

Why probing rather than fixing the address:

- no API key on the client, and no secret to distribute
- MultiSeat's ApiBindLoopbackOnly stays true; its dashboard API never has to
  be reachable from the LAN
- no dependency on mDNS, which does NOT work for seats. A seat's Apollo logs
  "Registered Apollo mDNS service" but the registration never reaches the
  network: Apollo registers through Windows' responder instead of binding 5353
  itself, and a registration made inside an RDP session does not escape it.
  Measured -- browsing _nvstream._tcp with a seat running returns the console
  Apollo and nothing else, with that same browse finding the console Apollo
  every time as the control.

Addresses are pulled on an aboutToPoll signal rather than pushed from the host
add/remove paths, which run on a thread pool under ComputerManager's write
lock -- reaching back into it from there invites a deadlock. Local and manual
addresses only, deduplicated: a seat and the console Apollo beside it share one
address, and seat ports are never port-forwarded.

Parsing reuses NvHTTP::getXmlString rather than opening a second
QXmlStreamReader on the same shape of document.

⛔ NOT compiled or run: this host has no Qt toolchain (no qmake, no cmake), so
CI is the only verification. The probe's server side is proven -- the seat
answers /serverinfo with the expected hostname -- but the client code itself
has not executed anywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQvL62WkT8xDWXqyjFCGDw
@vibesoftwarecoder
vibesoftwarecoder merged commit 00e256a into master Sep 10, 2026
3 of 5 checks passed
@vibesoftwarecoder
vibesoftwarecoder deleted the fix/seat-discovery-probe branch September 10, 2026 14:57
@vibesoftwarecoder vibesoftwarecoder mentioned this pull request Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Seat auto-discovery only works when the client runs on the host

1 participant