ui: switch spinner and text window back to standalone process (#35470)
switch spinner and text window back to standalone processpull/35483/head
parent
6767bfce44
commit
541bd4d4d9
8 changed files with 165 additions and 125 deletions
@ -0,0 +1,52 @@ |
|||||||
|
import os |
||||||
|
import subprocess |
||||||
|
from openpilot.common.basedir import BASEDIR |
||||||
|
|
||||||
|
|
||||||
|
class Spinner: |
||||||
|
def __init__(self): |
||||||
|
try: |
||||||
|
self.spinner_proc = subprocess.Popen(["./spinner.py"], |
||||||
|
stdin=subprocess.PIPE, |
||||||
|
cwd=os.path.join(BASEDIR, "system", "ui"), |
||||||
|
close_fds=True) |
||||||
|
except OSError: |
||||||
|
self.spinner_proc = None |
||||||
|
|
||||||
|
def __enter__(self): |
||||||
|
return self |
||||||
|
|
||||||
|
def update(self, spinner_text: str): |
||||||
|
if self.spinner_proc is not None: |
||||||
|
self.spinner_proc.stdin.write(spinner_text.encode('utf8') + b"\n") |
||||||
|
try: |
||||||
|
self.spinner_proc.stdin.flush() |
||||||
|
except BrokenPipeError: |
||||||
|
pass |
||||||
|
|
||||||
|
def update_progress(self, cur: float, total: float): |
||||||
|
self.update(str(round(100 * cur / total))) |
||||||
|
|
||||||
|
def close(self): |
||||||
|
if self.spinner_proc is not None: |
||||||
|
self.spinner_proc.kill() |
||||||
|
try: |
||||||
|
self.spinner_proc.communicate(timeout=2.) |
||||||
|
except subprocess.TimeoutExpired: |
||||||
|
print("WARNING: failed to kill spinner") |
||||||
|
self.spinner_proc = None |
||||||
|
|
||||||
|
def __del__(self): |
||||||
|
self.close() |
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback): |
||||||
|
self.close() |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
import time |
||||||
|
with Spinner() as s: |
||||||
|
s.update("Spinner text") |
||||||
|
time.sleep(5.0) |
||||||
|
print("gone") |
||||||
|
time.sleep(5.0) |
@ -0,0 +1,63 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import os |
||||||
|
import time |
||||||
|
import subprocess |
||||||
|
from openpilot.common.basedir import BASEDIR |
||||||
|
|
||||||
|
|
||||||
|
class TextWindow: |
||||||
|
def __init__(self, text): |
||||||
|
try: |
||||||
|
self.text_proc = subprocess.Popen(["./text.py", text], |
||||||
|
stdin=subprocess.PIPE, |
||||||
|
cwd=os.path.join(BASEDIR, "system", "ui"), |
||||||
|
close_fds=True) |
||||||
|
except OSError: |
||||||
|
self.text_proc = None |
||||||
|
|
||||||
|
def get_status(self): |
||||||
|
if self.text_proc is not None: |
||||||
|
self.text_proc.poll() |
||||||
|
return self.text_proc.returncode |
||||||
|
return None |
||||||
|
|
||||||
|
def __enter__(self): |
||||||
|
return self |
||||||
|
|
||||||
|
def close(self): |
||||||
|
if self.text_proc is not None: |
||||||
|
self.text_proc.terminate() |
||||||
|
self.text_proc = None |
||||||
|
|
||||||
|
def wait_for_exit(self): |
||||||
|
if self.text_proc is not None: |
||||||
|
while True: |
||||||
|
if self.get_status() == 1: |
||||||
|
return |
||||||
|
time.sleep(0.1) |
||||||
|
|
||||||
|
def __del__(self): |
||||||
|
self.close() |
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback): |
||||||
|
self.close() |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
text = """Traceback (most recent call last): |
||||||
|
File "./controlsd.py", line 608, in <module> |
||||||
|
main() |
||||||
|
File "./controlsd.py", line 604, in main |
||||||
|
controlsd_thread(sm, pm, logcan) |
||||||
|
File "./controlsd.py", line 455, in controlsd_thread |
||||||
|
1/0 |
||||||
|
ZeroDivisionError: division by zero""" |
||||||
|
print(text) |
||||||
|
|
||||||
|
with TextWindow(text) as s: |
||||||
|
for _ in range(100): |
||||||
|
if s.get_status() == 1: |
||||||
|
print("Got exit button") |
||||||
|
break |
||||||
|
time.sleep(0.1) |
||||||
|
print("gone") |
@ -1,58 +0,0 @@ |
|||||||
import threading |
|
||||||
import time |
|
||||||
import os |
|
||||||
from typing import Generic, Protocol, TypeVar |
|
||||||
from openpilot.common.swaglog import cloudlog |
|
||||||
from openpilot.system.ui.lib.application import gui_app |
|
||||||
|
|
||||||
|
|
||||||
class RendererProtocol(Protocol): |
|
||||||
def render(self): ... |
|
||||||
|
|
||||||
|
|
||||||
R = TypeVar("R", bound=RendererProtocol) |
|
||||||
|
|
||||||
|
|
||||||
class BaseWindow(Generic[R]): |
|
||||||
def __init__(self, title: str): |
|
||||||
self._title = title |
|
||||||
self._renderer: R | None = None |
|
||||||
self._stop_event = threading.Event() |
|
||||||
self._thread = threading.Thread(target=self._run) |
|
||||||
self._thread.start() |
|
||||||
|
|
||||||
# wait for the renderer to be initialized |
|
||||||
while self._renderer is None and self._thread.is_alive(): |
|
||||||
time.sleep(0.01) |
|
||||||
|
|
||||||
def _create_renderer(self) -> R: |
|
||||||
raise NotImplementedError() |
|
||||||
|
|
||||||
def _run(self): |
|
||||||
if os.getenv("CI") is not None: |
|
||||||
return |
|
||||||
gui_app.init_window(self._title) |
|
||||||
self._renderer = self._create_renderer() |
|
||||||
try: |
|
||||||
for _ in gui_app.render(): |
|
||||||
if self._stop_event.is_set(): |
|
||||||
break |
|
||||||
self._renderer.render() |
|
||||||
finally: |
|
||||||
gui_app.close() |
|
||||||
|
|
||||||
def __enter__(self): |
|
||||||
return self |
|
||||||
|
|
||||||
def close(self): |
|
||||||
if self._thread.is_alive(): |
|
||||||
self._stop_event.set() |
|
||||||
self._thread.join(timeout=2.0) |
|
||||||
if self._thread.is_alive(): |
|
||||||
cloudlog.warning(f"Failed to join {self._title} thread") |
|
||||||
|
|
||||||
def __del__(self): |
|
||||||
self.close() |
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb): |
|
||||||
self.close() |
|
Loading…
Reference in new issue