SerenaDashboardTrayManager: Make pywebview mode optional (open browser as new default)

This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-04-18 01:47:33 +02:00
1 parent a386ec41c4
commit f6da084ab2
2 files changed
+43 -30

No files matched your search

+4 -16
View File
@@ -7,8 +7,6 @@ import multiprocessing
import os
import platform
import signal
import subprocess
import sys
from collections.abc import Callable, Iterator, Sequence
from contextlib import contextmanager
from datetime import datetime
@@ -35,7 +33,7 @@ from serena.config.serena_config import (
SerenaPaths,
ToolInclusionDefinition,
)
from serena.dashboard import SerenaDashboardAPI, SerenaDashboardTrayManager, SerenaDashboardViewer
from serena.dashboard import SerenaDashboardAPI, SerenaDashboardTrayManager, SerenaDashboardViewer, open_url_in_browser
from serena.ls_manager import LanguageServerManager
from serena.project import MemoriesManager, Project
from serena.prompt_factory import SerenaPromptFactory
@@ -276,7 +274,8 @@ class DashboardManager:
def __init__(self, port: int, host_listen_address: str, open_dashboard_on_launch: bool, active_project: Project | None = None):
self._port = port
self._mode = self.Mode.from_platform()
# self._mode = self.Mode.from_platform()
self._mode = self.Mode.TRAY_MANAGER
self._dashboard_viewer_process: multiprocessing.Process | None = None
dashboard_host = host_listen_address
@@ -297,19 +296,8 @@ class DashboardManager:
else:
self.open_dashboard_in_browser()
@staticmethod
def _open_url_in_browser(url: str) -> None:
# Use a subprocess to avoid any output from webbrowser.open being written to stdout
subprocess.Popen(
[sys.executable, "-c", f"import webbrowser; webbrowser.open({url!r})"],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=False,
)
def open_dashboard_in_browser(self) -> None:
return self._open_url_in_browser(self.url)
open_url_in_browser(self.url)
@staticmethod
def _start_dashboard_viewer_process_function(url: str, minimized: bool, parent_process_id: int) -> None:
+39 -14
View File
@@ -1,4 +1,5 @@
import json
import multiprocessing
import os
import socket
import subprocess
@@ -13,6 +14,7 @@ from html import escape
from pathlib import Path
from typing import TYPE_CHECKING, Any, Self
import pystray
from flask import Flask, Response, redirect, request, send_from_directory
from PIL import Image
from pydantic import BaseModel
@@ -787,6 +789,22 @@ class SerenaDashboardAPI:
return thread, port
def open_url_in_browser(url: str) -> None:
"""
Opens the given URL in the user's default web browser without blocking the main process or printing any output.
:param url: the URL to open
"""
# Use a subprocess to avoid any output from webbrowser.open being written to stdout
subprocess.Popen(
[sys.executable, "-c", f"import webbrowser; webbrowser.open({url!r})"],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=False,
)
class SerenaDashboardViewer(WebViewWithTray):
"""
Minimal pywebview wrapper that opens a dashboard in a native window.
@@ -887,12 +905,17 @@ class SerenaDashboardTrayManager:
ALIVE_CHECK_INTERVAL_SECONDS = 15
"""interval in seconds between alive checks of registered instances"""
def __init__(self) -> None:
def __init__(self, use_pywebview: bool = False) -> None:
"""
:param use_pywebview: whether to use pywebview-based viewer applications (separate child processes)
for opening dashboards; if False, open them directly in the user's default web browser.
"""
self._instances: dict[int, TrayManagedInstance] = {}
self._lock = threading.Lock()
self._tray_icon: Any = None
self._tray_icon: pystray.Icon | None = None
self._app = Flask(__name__)
self._setup_routes()
self._use_pywebview = use_pywebview
def _setup_routes(self) -> None:
@self._app.route("/health", methods=["GET"])
@@ -914,7 +937,7 @@ class SerenaDashboardTrayManager:
# open a viewer immediately if requested
if data.get("open_viewer", False):
self._open_viewer(instance)
self._open_dashboard(instance)
return {"status": "registered"}
@@ -976,7 +999,7 @@ class SerenaDashboardTrayManager:
# closure to capture the current instance
def _make_callback(instance: TrayManagedInstance) -> Callable:
def _callback(_icon: Any, _item: Any) -> None:
self._open_viewer(instance)
self._open_dashboard(instance)
return _callback
@@ -984,16 +1007,18 @@ class SerenaDashboardTrayManager:
return tuple(items)
def _open_viewer(self, instance: TrayManagedInstance) -> None:
"""Spawn a dashboard viewer window for the given instance in a new process."""
import multiprocessing
process = multiprocessing.Process(
target=self._run_viewer,
args=(instance.dashboard_url,),
daemon=True,
)
process.start()
def _open_dashboard(self, instance: TrayManagedInstance) -> None:
"""Opens the dashboard of the given instance."""
if self._use_pywebview:
# spawn pywebview process showing the dashboard
process = multiprocessing.Process(
target=self._run_viewer,
args=(instance.dashboard_url,),
daemon=True,
)
process.start()
else:
open_url_in_browser(instance.dashboard_url)
@staticmethod
def _run_viewer(url: str) -> None: