Files
StarPilot/openpilot/starpilot/galaxy/tmux_live.py
T
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

55 lines
2.6 KiB
Python

"""Read-only, bounded view of the normal launcher console over either HTTP transport."""
import re
import subprocess
import tempfile
import threading
import time
MAX_BYTES = 65536
MAX_LINES = 300
class TmuxLive:
def __init__(self, *, run=subprocess.run, clock=time.monotonic):
self.run, self.clock = run, clock
self.lock = threading.Lock()
self.cached, self.expiry = None, 0.0
def snapshot(self):
with self.lock:
if self.cached is not None and self.clock() < self.expiry:
return self.cached
result = {'available': False, 'reason': '', 'text': '', 'truncated': False, 'pane': ''}
try:
panes = self.run(['tmux', 'list-panes', '-t', '=comma:0', '-F', '#{pane_id}\t#{pane_index}\t#{pane_dead}'],
capture_output=True, timeout=1, check=True).stdout.decode()
pane = next((fields[0] for line in panes[:16384].splitlines()
if len(fields := line.split('\t')) == 3 and fields[1:] == ['0', '0'] and
re.fullmatch(r'%\d+', fields[0])), None)
if pane is None:
raise ValueError('No live launcher pane')
# A terminal can contain very long lines; bound memory as well as scrollback.
with tempfile.TemporaryFile() as output:
self.run(['tmux', 'capture-pane', '-p', '-t', pane, '-S', f'-{MAX_LINES}', '-E', '-'],
stdout=output, stderr=subprocess.DEVNULL, timeout=1, check=True)
size = output.tell()
output.seek(max(0, size - MAX_BYTES))
text = output.read(MAX_BYTES).decode('utf-8', errors='replace')
# Replacement decoding can expand malformed/cut UTF-8 beyond the raw
# byte limit. Bound the actual JSON text encoding as well.
encoded = text.encode('utf-8')
expanded = len(encoded) > MAX_BYTES
if expanded:
text = encoded[-MAX_BYTES:].decode('utf-8', errors='ignore')
lines = text.splitlines()
result.update(available=True, text='\n'.join(lines[-MAX_LINES:]), truncated=size > MAX_BYTES or expanded or len(lines) >= MAX_LINES,
pane='comma:0.0')
except FileNotFoundError:
result['reason'] = 'tmux is not installed in this build. Install a complete StarPilot build to use the live console.'
except (OSError, subprocess.SubprocessError, ValueError, UnicodeError):
result['reason'] = 'The StarPilot launcher console is not running in its normal tmux session. Crash Reports and System Monitor are still available.'
self.cached, self.expiry = result, self.clock() + 1.0
return result