Skip to content

Linux support: Unix socket transport (NEEDS TESTING on real Linux + Discord) - #1

Merged
KernelSpecter merged 1 commit into
mainfrom
linux-port
Aug 13, 2026
Merged

KernelSpecter merged 1 commit into
mainfrom
linux-port

Conversation

@KernelSpecter

Copy link
Copy Markdown
Owner

Status: needs a human with Linux and Discord

Everything here is written but has never run against a real Linux Discord client. It is deliberately not shipping as a release binary until someone confirms presence actually appears. If you are that person, testing this is the most useful thing you can do for the project right now — please comment with what you see.

Why Linux wasn't just a packaging job

The daemon's transport was Windows named pipes. On Linux both endpoints — mpv's --input-ipc-server and Discord's IPC socket — are Unix domain sockets, and open() on a socket path fails with ENXIO. The POSIX branches that were already in the file (lockfile singleton, select-based bytes_available) showed intent, but the transport itself was never written, so it could not have worked.

What changed

  • UnixSocketStream wraps AF_UNIX behind read/write/close/fileno. That is the entire surface PipeReader, pump() and _send() touch, so none of them needed a platform branch. bytes_available already had a select() branch that works on a socket through fileno().
  • open_ipc(path) picks open() on Windows, socket on POSIX.
  • discord_ipc_paths() also scans the Flatpak and Snap sandbox subdirectories (app/com.discordapp.Discord/, snap.discord/). Scanning only XDG_RUNTIME_DIR is the usual reason a Linux presence tool appears to do nothing on those installs.
  • The shim branches on uname for the only two things that differ: the mpv binary name, and pipe-vs-socket for the IPC argument.
  • install.sh writes ANI_CLI_PLAYER into the shell rc inside sentinel markers, so uninstall removes exactly what it added and re-running repoints rather than stacking duplicate exports. Same no-spaces-in-path check as Windows, for the same reason (ani-cli launches the player unquoted).

What is actually verified

tests/smoke_posix.py stands up a mock Discord IPC socket and a mock mpv IPC socket, runs a real daemon against both, and asserts a SET_ACTIVITY arrives with the correct title and episode. CI runs it on real Linux, and also builds the Linux binary — PyInstaller cannot cross-compile, so that job is the only way the artifact exists at all.

This proves the socket transport and framing. It does not prove presence renders in Discord. No mock can.

Windows is unaffected

discord_ipc_paths() still yields the same 10 named pipes, and the daemon was re-run after the refactor. v1.0.0 shipped from main and is untouched by this branch.

How to test

git clone -b linux-port https://github.com/KernelSpecter/anicli-rpc
cd anicli-rpc && ./install.sh
# new terminal, Discord desktop running
ani-cli
# then check:
cat run/anirpc.log      # want: "discord: connected via ..." and a presence: line

Please report your distro, how Discord is installed (native / Flatpak / Snap), and the log.

UNVERIFIED against a real Linux Discord client. Not released as a binary until
someone confirms presence actually appears. See tests/ for what IS checked.

The blocker was never packaging. Both endpoints this talks to are Unix domain
sockets on Linux, and open() on a socket path fails with ENXIO, so the previous
POSIX branches could not have worked - the transport was never written.

UnixSocketStream wraps AF_UNIX behind read/write/close/fileno, which is the
entire surface PipeReader, pump() and _send() use, so none of them needed a
platform branch. bytes_available already had a select() branch that works on a
socket via fileno(). open_ipc() picks the transport.

discord_ipc_paths() also scans the Flatpak and Snap sandbox subdirectories.
Scanning only XDG_RUNTIME_DIR is the usual reason a Linux presence tool looks
like it does nothing on those installs.

The shim now branches on uname for the two things that actually differ: the mpv
binary name, and pipe-vs-socket for --input-ipc-server. install.sh writes
ANI_CLI_PLAYER into the shell rc inside sentinel markers, so uninstall removes
exactly what was added, and re-running repoints instead of stacking exports.

tests/smoke_posix.py stands up a mock Discord and a mock mpv and asserts a real
SET_ACTIVITY arrives with the right title and episode. That is the only
automated evidence the socket transport works; CI runs it on real Linux and also
builds the Linux binary, which PyInstaller cannot cross-compile.

Windows is unaffected: discord_ipc_paths() still yields the same 10 named pipes,
and the daemon was re-checked after the refactor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@KernelSpecter

Copy link
Copy Markdown
Owner Author

CI is green on the first run — all four jobs. The socket transport is verified at the protocol level on real Linux:

daemon: start
mpv: connected /tmp/anirpc-smoke-.../mpv.sock
discord: connected via /tmp/anirpc-smoke-.../runtime/discord-ipc-0
presence: Frieren: Beyond Journey's End | Episode 4 of 12 · Sub
PASS: handshake, client_id=123456789012345678
PASS: SET_ACTIVITY {"type": 3, "details": "Frieren: Beyond Journey's End", "state": "Episode 4 of 12 · Sub", "timestamps": {"start": 1786632565, "end": 1786633985}}
RESULT: PASS

end - start is 1420s, exactly the duration the mock mpv reported, so the countdown arithmetic is correct as well. Both binaries built (Build linux-x64, Build windows-x64).

What this still does not cover, and why this PR stays open:

  • No real Discord client was involved. The handshake succeeded against a mock that replies READY to anything — it does not validate the client_id, so a rejected app ID would not show up here.
  • Socket discovery is untested. The test points XDG_RUNTIME_DIR at a temp dir, so the Flatpak and Snap sandbox paths in discord_ipc_paths() have never matched a real socket.
  • No real mpv. The mock replies success to every observe_property and pushes ideal events; real mpv ordering and timing during stream loading is messier.
  • install.sh never ran. Nothing here touches shell rc files.

So: transport and framing are sound, integration is unproven. Still needs a human on Linux with Discord.

@KernelSpecter
KernelSpecter merged commit 1e9b3fd into main Aug 13, 2026
4 checks passed
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.

1 participant