#!/usr/bin/env python3 """End-to-end check of Discord Rich Presence: real games talk to a fake Discord. Each game gets its own fake Discord IPC server (a Unix socket in a scratch folder, pointed to by HORIZON_DISCORD_IPC_DIR) that answers the handshake with READY and records every SET_ACTIVITY. The game goes title -> world -> Nether -> quit through horizon-ctl, and the activities must say "In the menus", "Singleplayer" in the Overworld, then the Nether, and be cleared on quit: single single player on its server thread (the default) in-frame single player ticked in the window's frame (--in-frame) lan a host opens its world to LAN and a guest joins: the guest shows "Multiplayer", the host "Hosting a world" with a party of 2 of 8 (lan-in-frame: the host in-frame) setting off in horizon-extras.txt: nothing reaches Discord; the Horizon Settings switch turns it on (the activity appears) and off (it is cleared) late Discord starts after the game: found within the 15 s retry python3 tools/discord_presence_e2e.py # all of them python3 tools/discord_presence_e2e.py --check lan --bin-dir target/fast macOS and Linux (Discord's Windows pipes aren't faked here). Games render offscreen, have no audio and use scratch homes (lab.game_env). Needs the prepared resources in .local/horizon-client. """ import argparse import json import os import shutil import socket import struct import subprocess import sys import tempfile import threading import time from pathlib import Path try: from . import lab except ImportError: import lab ROOT = Path(__file__).resolve().parents[1] HANDSHAKE, FRAME, CLOSE = 0, 1, 2 class FakeDiscord: """discord-ipc-0 in a scratch folder: READY on handshake, a reply to each command.""" def __init__(self): # Short: Unix socket paths are limited to ~100 bytes. self.dir = Path(tempfile.mkdtemp(prefix='hzdc-', dir='/tmp')) self.server = None self.activities = [] # (seconds since start, activity or None) self.handshakes = [] self.started = time.time() self.lock = threading.Lock() def start(self): """Discord starts: its socket appears.""" self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.server.bind(str(self.dir / 'discord-ipc-0')) self.server.listen() threading.Thread(target=self.accept, daemon=True).start() def accept(self): while True: try: conn, _ = self.server.accept() except OSError: return threading.Thread(target=self.serve, args=(conn,), daemon=True).start() def serve(self, conn): def read(): header = conn.recv(8, socket.MSG_WAITALL) if len(header) < 8: raise EOFError op, length = struct.unpack('