From 94a6fd8cbc3ae7d48499fb74482006e93f5ceac4 Mon Sep 17 00:00:00 2001 From: Matej Stastny Date: Fri, 15 May 2026 23:33:56 +0000 Subject: [PATCH 01/11] Added pypresence Co-authored-by: Copilot --- docker/Dockerfile | 2 ++ pixi.toml | 1 + 2 files changed, 3 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8b2a960..9604530 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -102,6 +102,8 @@ RUN printf '#include \nint drmCloseBufferHandle(int fd, unsigned int h # -------------------------------------- # Python packages +RUN pip3 install --break-system-packages \ + pypresence RUN rosdep init || true # -------------------------------------- diff --git a/pixi.toml b/pixi.toml index 31b5ee0..6760e11 100644 --- a/pixi.toml +++ b/pixi.toml @@ -20,3 +20,4 @@ scripts = [".devcontainer/pixi.sh"] [pypi-dependencies] sim = { path = ".", editable = true } filelock = "*" +pypresence = "*" From f92304f16f530d5218c46910c3f3f2ede912a465 Mon Sep 17 00:00:00 2001 From: Matej Stastny Date: Fri, 15 May 2026 23:34:15 +0000 Subject: [PATCH 02/11] Updated pixi lock --- pixi.lock | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pixi.lock b/pixi.lock index 12f74d4..31b074c 100644 --- a/pixi.lock +++ b/pixi.lock @@ -959,6 +959,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/0d/1c817c6769ce0b64cc0dcc20bc9d0edb4dc560e83a3b5f0d5cbda04c3d35/pypresence-4.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d0/07/86ad9e08fd22d5fbb19f8becfe14195c3388290cc98d58849ef4607f91b8/commentjson-0.8.2.tar.gz - pypi: https://files.pythonhosted.org/packages/d4/69/31c82567719b34d8f6b41077732589104883771d182a9f4ff3e71430999a/python_utils-3.9.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ea/2c/e17b8814050427929077639d35a42187a006922600d4840475bdc5f64ebb/numpy_stl-3.2.0-py3-none-any.whl @@ -1898,6 +1899,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/0d/1c817c6769ce0b64cc0dcc20bc9d0edb4dc560e83a3b5f0d5cbda04c3d35/pypresence-4.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d0/07/86ad9e08fd22d5fbb19f8becfe14195c3388290cc98d58849ef4607f91b8/commentjson-0.8.2.tar.gz - pypi: https://files.pythonhosted.org/packages/d4/69/31c82567719b34d8f6b41077732589104883771d182a9f4ff3e71430999a/python_utils-3.9.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ea/2c/e17b8814050427929077639d35a42187a006922600d4840475bdc5f64ebb/numpy_stl-3.2.0-py3-none-any.whl @@ -2767,6 +2769,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/0d/1c817c6769ce0b64cc0dcc20bc9d0edb4dc560e83a3b5f0d5cbda04c3d35/pypresence-4.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d0/07/86ad9e08fd22d5fbb19f8becfe14195c3388290cc98d58849ef4607f91b8/commentjson-0.8.2.tar.gz - pypi: https://files.pythonhosted.org/packages/d4/69/31c82567719b34d8f6b41077732589104883771d182a9f4ff3e71430999a/python_utils-3.9.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ea/2c/e17b8814050427929077639d35a42187a006922600d4840475bdc5f64ebb/numpy_stl-3.2.0-py3-none-any.whl @@ -3635,6 +3638,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/0d/1c817c6769ce0b64cc0dcc20bc9d0edb4dc560e83a3b5f0d5cbda04c3d35/pypresence-4.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d0/07/86ad9e08fd22d5fbb19f8becfe14195c3388290cc98d58849ef4607f91b8/commentjson-0.8.2.tar.gz - pypi: https://files.pythonhosted.org/packages/d4/69/31c82567719b34d8f6b41077732589104883771d182a9f4ff3e71430999a/python_utils-3.9.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ea/2c/e17b8814050427929077639d35a42187a006922600d4840475bdc5f64ebb/numpy_stl-3.2.0-py3-none-any.whl @@ -62131,6 +62135,22 @@ packages: - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' - chardet>=3.0.2,<8 ; extra == 'use-chardet-on-py3' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bd/0d/1c817c6769ce0b64cc0dcc20bc9d0edb4dc560e83a3b5f0d5cbda04c3d35/pypresence-4.6.1-py3-none-any.whl + name: pypresence + version: 4.6.1 + sha256: 33d4549fcdf4102f81935df4ba587bacb22193e0c50c541d1ab9329b21df33cd + requires_dist: + - pytest>=7.0.0 ; extra == 'dev' + - pytest-asyncio>=0.21.0 ; extra == 'dev' + - pytest-mock>=3.10.0 ; extra == 'dev' + - pytest-cov>=4.0.0 ; extra == 'dev' + - black>=23.0.0 ; extra == 'dev' + - flake8>=6.0.0 ; extra == 'dev' + - mypy>=1.0.0 ; extra == 'dev' + - isort>=5.12.0 ; extra == 'dev' + - sphinx>=5.0.0 ; extra == 'dev' + - sphinx-rtd-theme>=1.2.0 ; extra == 'dev' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/d0/07/86ad9e08fd22d5fbb19f8becfe14195c3388290cc98d58849ef4607f91b8/commentjson-0.8.2.tar.gz name: commentjson version: 0.8.2 From 7b741798e31ef742b9682121d5814d736723dca1 Mon Sep 17 00:00:00 2001 From: RadAlpaca11 Date: Sat, 16 May 2026 00:03:30 +0000 Subject: [PATCH 03/11] finishing rpc code prototype --- cli/cli.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cli/cli.py b/cli/cli.py index 7fe09f7..f06dc33 100644 --- a/cli/cli.py +++ b/cli/cli.py @@ -3,6 +3,7 @@ import argparse import shutil import sys +import threading from .docker import build_and_launch from .native import build_and_launch_native @@ -11,6 +12,7 @@ from .auth import auth as cmd_auth from .output import die, info from .paths import WORKSPACE_DIR +from .drpc import rpc_start def main() -> None: @@ -134,8 +136,10 @@ def main() -> None: try: if args.command == "docker": + threading.Thread(target=rpc_start, kwargs={"is_docker": True, "robot_name": args.robot}, daemon=True).start() build_and_launch(args.robot, build_only=args.build_only, no_build=args.no_build) elif args.command == "native": + threading.Thread(target=rpc_start, kwargs={"is_docker": False, "robot_name": args.robot}, daemon=True).start() build_and_launch_native(args.robot, build_only=args.build_only, no_build=args.no_build) elif args.command == "clean": for name in ("build", "install", "log"): From 4a1514d6b547edaa43e585075f1e8dfe78376210 Mon Sep 17 00:00:00 2001 From: RadAlpaca11 Date: Sat, 16 May 2026 00:04:21 +0000 Subject: [PATCH 04/11] actually adding the code this time --- cli/drpc.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 cli/drpc.py diff --git a/cli/drpc.py b/cli/drpc.py new file mode 100644 index 0000000..6acdd5e --- /dev/null +++ b/cli/drpc.py @@ -0,0 +1,17 @@ +from pypresence import Presence +import time + + +def rpc_start(is_docker: bool = False, robot_name: str = "Unknown Robot") -> None: + client_id = "1504990746527404063" + rpc = Presence(client_id) + rpc.connect() + rpc.update( + name="TrickFire Simulation (" + robot_name + ")", + details="Running TrickFire Simulation on " + robot_name, + state="Running in Docker" if is_docker else "Running locally", + ) + print("RPC started") + while True: + print("RPC thread alive") + time.sleep(15) From 30ae4d0ee069a794e53caaaa707160319ea9701f Mon Sep 17 00:00:00 2001 From: Matej Stastny Date: Sat, 16 May 2026 00:25:18 +0000 Subject: [PATCH 05/11] Fixed display checks on native --- cli/cli.py | 11 ++++++++--- cli/docker.py | 25 ++++++++++++++----------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/cli/cli.py b/cli/cli.py index f06dc33..9212213 100644 --- a/cli/cli.py +++ b/cli/cli.py @@ -5,7 +5,7 @@ import sys import threading -from .docker import build_and_launch +from .docker import build_and_launch, check_display from .native import build_and_launch_native from .create import create as robot_create, update as robot_update, PACKAGE_DIR, REPO_ROOT from .create.commands import cmd_local, cmd_raw @@ -136,10 +136,15 @@ def main() -> None: try: if args.command == "docker": - threading.Thread(target=rpc_start, kwargs={"is_docker": True, "robot_name": args.robot}, daemon=True).start() + threading.Thread( + target=rpc_start, kwargs={"is_docker": True, "robot_name": args.robot}, daemon=True + ).start() + check_display() build_and_launch(args.robot, build_only=args.build_only, no_build=args.no_build) elif args.command == "native": - threading.Thread(target=rpc_start, kwargs={"is_docker": False, "robot_name": args.robot}, daemon=True).start() + threading.Thread( + target=rpc_start, kwargs={"is_docker": False, "robot_name": args.robot}, daemon=True + ).start() build_and_launch_native(args.robot, build_only=args.build_only, no_build=args.no_build) elif args.command == "clean": for name in ("build", "install", "log"): diff --git a/cli/docker.py b/cli/docker.py index 88dfe75..179221a 100644 --- a/cli/docker.py +++ b/cli/docker.py @@ -114,6 +114,20 @@ def _run_logged_command( raise subprocess.CalledProcessError(return_code, command) +def check_display() -> None: + """Check that a display is available and can be connected to""" + info("Checking for display...") + display = os.environ.copy().get("DISPLAY") + if not display: + die("DISPLAY environment variable not set") + + display_running = subprocess.call( + ["xdpyinfo", "-display", display], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + if display_running != 0: + die("Cannot connect to display " + display) + + def build_and_launch(robot_name: str, *, build_only: bool = False, no_build: bool = False) -> None: """Build the workspace and launch a robot simulation.""" if build_only and no_build: @@ -171,17 +185,6 @@ def build_and_launch(robot_name: str, *, build_only: bool = False, no_build: boo info("Build-only requested; skipping launch") return - info("Checking for display...") - display = env.get("DISPLAY") - if not display: - die("DISPLAY environment variable not set") - - display_running = subprocess.call(["xdpyinfo", "-display", display], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) - if display_running != 0: - die("Cannot connect to display " + display) - info("Sourcing ROS2 environment and launching simulation...") launch_script = f""" set -e From 65bce44c06524e7da0f3ce516efb5595e5d31b57 Mon Sep 17 00:00:00 2001 From: Matej Stastny Date: Sat, 16 May 2026 00:41:44 +0000 Subject: [PATCH 06/11] Fixed --- cli/drpc.py | 56 +++++++++++++++++++++++++++-------- docker/docker-compose-dev.yml | 2 ++ 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/cli/drpc.py b/cli/drpc.py index 6acdd5e..54b2821 100644 --- a/cli/drpc.py +++ b/cli/drpc.py @@ -1,17 +1,47 @@ -from pypresence import Presence +import os +import sys import time +from pypresence import Presence + + +def _find_discord_socket_dir() -> str | None: + """Return the directory containing a discord-ipc-N socket, or None if not found.""" + candidates = [ + os.environ.get("XDG_RUNTIME_DIR"), # standard Linux user session + os.environ.get("TMPDIR"), + "/run/host-runtime", # devcontainer: host XDG_RUNTIME_DIR mounted here + "/tmp", + ] + for base in filter(None, candidates): + for i in range(10): + if os.path.exists(os.path.join(base, f"discord-ipc-{i}")): + return base + return None + def rpc_start(is_docker: bool = False, robot_name: str = "Unknown Robot") -> None: - client_id = "1504990746527404063" - rpc = Presence(client_id) - rpc.connect() - rpc.update( - name="TrickFire Simulation (" + robot_name + ")", - details="Running TrickFire Simulation on " + robot_name, - state="Running in Docker" if is_docker else "Running locally", - ) - print("RPC started") - while True: - print("RPC thread alive") - time.sleep(15) + try: + if sys.platform == "linux": + socket_dir = _find_discord_socket_dir() + if socket_dir is None: + print("Discord RPC unavailable: no Discord IPC socket found") + return + # pypresence checks TMPDIR early in its path search; point it at the socket dir + # so it works both natively and inside the devcontainer (/run/host-runtime). + os.environ["TMPDIR"] = socket_dir + + client_id = "1504990746527404063" + rpc = Presence(client_id) + rpc.connect() + rpc.update( + name="TrickFire Simulation (" + robot_name + ")", + details="Running TrickFire Simulation on " + robot_name, + state="Running in Docker" if is_docker else "Running locally", + ) + print("RPC started") + while True: + print("RPC thread alive") + time.sleep(15) + except Exception as e: # pylint: disable=broad-except + print(f"Discord RPC unavailable: {e}") diff --git a/docker/docker-compose-dev.yml b/docker/docker-compose-dev.yml index af0a469..b14ca58 100644 --- a/docker/docker-compose-dev.yml +++ b/docker/docker-compose-dev.yml @@ -4,3 +4,5 @@ services: target: dev args: USER_UID: "${UID:-1000}" + volumes: + - ${XDG_RUNTIME_DIR:-/tmp}:/run/host-runtime:ro From f2af618190bc5c4b3ee96e6335c0fa869d3a42bc Mon Sep 17 00:00:00 2001 From: RadAlpaca11 Date: Sat, 16 May 2026 01:05:38 +0000 Subject: [PATCH 07/11] removing prints --- cli/drpc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/drpc.py b/cli/drpc.py index 54b2821..15cbbeb 100644 --- a/cli/drpc.py +++ b/cli/drpc.py @@ -39,9 +39,7 @@ def rpc_start(is_docker: bool = False, robot_name: str = "Unknown Robot") -> Non details="Running TrickFire Simulation on " + robot_name, state="Running in Docker" if is_docker else "Running locally", ) - print("RPC started") while True: - print("RPC thread alive") time.sleep(15) except Exception as e: # pylint: disable=broad-except print(f"Discord RPC unavailable: {e}") From 39a2e617647b83d780707ccef4a26a83a5ac9afc Mon Sep 17 00:00:00 2001 From: RadAlpaca11 Date: Mon, 18 May 2026 19:29:18 +0000 Subject: [PATCH 08/11] adding directory to allow drpc in docker container --- cli/drpc.py | 3 ++- docker/docker-compose.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/drpc.py b/cli/drpc.py index 15cbbeb..ea21caf 100644 --- a/cli/drpc.py +++ b/cli/drpc.py @@ -10,7 +10,8 @@ def _find_discord_socket_dir() -> str | None: candidates = [ os.environ.get("XDG_RUNTIME_DIR"), # standard Linux user session os.environ.get("TMPDIR"), - "/run/host-runtime", # devcontainer: host XDG_RUNTIME_DIR mounted here + "/run/host-runtime", # devcontainer: host XDG_RUNTIME_DIR mounted here + "/run/user/1000", # fallback for some Linux distros "/tmp", ] for base in filter(None, candidates): diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2cb3ff5..e891707 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -25,6 +25,7 @@ services: volumes: - ..:/home/trickfire/gazebo-simulations + - /run/user/1000/discord-ipc-0:/run/user/1000/discord-ipc-0 working_dir: /home/trickfire/gazebo-simulations From 47d0b3614972250b779953b70a8bee8789e64051 Mon Sep 17 00:00:00 2001 From: Matej Stastny Date: Mon, 18 May 2026 19:53:10 +0000 Subject: [PATCH 09/11] Fixed socket finding issues --- .devcontainer/devcontainer.json | 8 ++------ docker/docker-compose-dev.yml | 11 +++++++++++ docker/docker-compose.yml | 6 +++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c30ae91..7a2ce31 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,12 +7,8 @@ "runServices": ["sim"], "workspaceFolder": "/home/trickfire/gazebo-simulations", "postCreateCommand": "[ -d .venv ] || (python3 -m venv --system-site-packages .venv && . .venv/bin/activate && pip install -e .)", - // "postStartCommand": "bash ./.devcontainer/x_server.sh", - "mounts": [ - "source=${localEnv:HOME}/.ssh,target=/home/trickfire/.ssh,type=bind,consistency=cached", - "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached" - ], - "forwardPorts": [6080, 5900], + "postStartCommand": "bash ./.devcontainer/x_server.sh", +"forwardPorts": [6080, 5900], "customizations": { "vscode": { "extensions": [ diff --git a/docker/docker-compose-dev.yml b/docker/docker-compose-dev.yml index b14ca58..d003f94 100644 --- a/docker/docker-compose-dev.yml +++ b/docker/docker-compose-dev.yml @@ -6,3 +6,14 @@ services: USER_UID: "${UID:-1000}" volumes: - ${XDG_RUNTIME_DIR:-/tmp}:/run/host-runtime:ro + - type: bind + source: ${HOME}/.ssh + target: /home/trickfire/.ssh + read_only: true + bind: + create_host_path: true + - type: bind + source: /tmp/.X11-unix + target: /tmp/.X11-unix + bind: + create_host_path: true diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e891707..4972d32 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -25,7 +25,11 @@ services: volumes: - ..:/home/trickfire/gazebo-simulations - - /run/user/1000/discord-ipc-0:/run/user/1000/discord-ipc-0 + - type: bind + source: ${XDG_RUNTIME_DIR:-/run/user/1000} + target: /run/user/1000 + bind: + create_host_path: true working_dir: /home/trickfire/gazebo-simulations From 3e3faf803864d28823129f1f49afb0eae87a0021 Mon Sep 17 00:00:00 2001 From: Matej Stastny Date: Mon, 18 May 2026 19:58:21 +0000 Subject: [PATCH 10/11] Fixed linux --- .devcontainer/devcontainer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7a2ce31..5106a87 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,8 +7,8 @@ "runServices": ["sim"], "workspaceFolder": "/home/trickfire/gazebo-simulations", "postCreateCommand": "[ -d .venv ] || (python3 -m venv --system-site-packages .venv && . .venv/bin/activate && pip install -e .)", - "postStartCommand": "bash ./.devcontainer/x_server.sh", -"forwardPorts": [6080, 5900], + "postStartCommand": "bash ./.devcontainer/x_server.sh || exit 0", + "forwardPorts": [6080, 5900], "customizations": { "vscode": { "extensions": [ From 8da492ba23cc3b9aac2ed2133b769498cc7618a5 Mon Sep 17 00:00:00 2001 From: Matej Stastny Date: Thu, 21 May 2026 18:18:38 +0000 Subject: [PATCH 11/11] Added a comment --- cli/drpc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/drpc.py b/cli/drpc.py index ea21caf..f2264b4 100644 --- a/cli/drpc.py +++ b/cli/drpc.py @@ -1,3 +1,5 @@ +"""Discord Rich Presence (DRPC) integration for TrickFire Simulation""" + import os import sys import time @@ -22,6 +24,7 @@ def _find_discord_socket_dir() -> str | None: def rpc_start(is_docker: bool = False, robot_name: str = "Unknown Robot") -> None: + """Start DRPC in a separate thread""" try: if sys.platform == "linux": socket_dir = _find_discord_socket_dir()