From c3a9bba4b86445b26687a89289c3584e7a674ef0 Mon Sep 17 00:00:00 2001 From: Oliver Meyer Date: Fri, 5 Dec 2025 12:01:25 +0100 Subject: [PATCH 1/3] fix: bring Launchpad to front after successful login --- src/aignostics/gui/_frame.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/aignostics/gui/_frame.py b/src/aignostics/gui/_frame.py index 495b2bd4..98db7c58 100644 --- a/src/aignostics/gui/_frame.py +++ b/src/aignostics/gui/_frame.py @@ -84,6 +84,33 @@ def _render_nav_groups() -> None: for item in group.items: _render_nav_item(item) + def _bring_window_to_front() -> None: + """Bring the native window to front after authentication completes. + + Uses platform-specific approaches: + - Windows: Uses ctypes to find window by title and call SetForegroundWindow, + as pywebview's set_always_on_top/show methods don't reliably bring windows + to front and the window handle isn't directly exposed. + - macOS/Linux: Uses pywebview's built-in methods. + """ + if not app.native.main_window: + return + try: + if platform.system() == "Windows": + import ctypes # noqa: PLC0415 + + # Find window by title since pywebview doesn't expose hwnd directly + # FindWindowW(lpClassName, lpWindowName) - use None for class to match any + hwnd = ctypes.windll.user32.FindWindowW(None, "Aignostics Launchpad") # type: ignore + if hwnd: + ctypes.windll.user32.SetForegroundWindow(hwnd) # type: ignore + else: + app.native.main_window.set_always_on_top(True) + app.native.main_window.show() + app.native.main_window.set_always_on_top(False) + except Exception: # noqa: S110 + pass # Window operations can fail on some platforms + user_info: UserInfo | None = None launchpad_healthy: bool | None = None @@ -127,6 +154,8 @@ async def _user_info_ui_load() -> None: await ui.context.client.connected() app.storage.tab["user_info"] = user_info _user_info_ui.refresh() + if user_info: + _bring_window_to_front() ui.timer(interval=USERINFO_UPDATE_INTERVAL, callback=_user_info_ui_load, immediate=True) From 087066abf6e0f3483776ec2a23f732d81a5d51b7 Mon Sep 17 00:00:00 2001 From: Oliver Meyer Date: Wed, 10 Dec 2025 17:36:36 +0100 Subject: [PATCH 2/3] fix: address review comments --- src/aignostics.py | 4 ++-- src/aignostics/cli.py | 4 ++-- src/aignostics/constants.py | 2 ++ src/aignostics/gui/_frame.py | 11 ++++++++--- tests/aignostics/cli_test.py | 3 ++- tests/main.py | 3 ++- 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/aignostics.py b/src/aignostics.py index f591a5a2..caf2cd10 100644 --- a/src/aignostics.py +++ b/src/aignostics.py @@ -23,7 +23,7 @@ os.environ["LOGFIRE_PYDANTIC_RECORD"] = "off" -from aignostics.constants import SENTRY_INTEGRATIONS # noqa: E402 +from aignostics.constants import SENTRY_INTEGRATIONS, WINDOW_TITLE # noqa: E402 from aignostics.utils import boot, gui_run # noqa: E402 boot(SENTRY_INTEGRATIONS) @@ -80,4 +80,4 @@ else: if pyi_splash and pyi_splash.is_alive(): pyi_splash.update_text("Opening user interface ...") - gui_run(native=True, with_api=False, title="Aignostics Launchpad", icon="🔬") + gui_run(native=True, with_api=False, title=WINDOW_TITLE, icon="🔬") diff --git a/src/aignostics/cli.py b/src/aignostics/cli.py index f2cff89b..12ace588 100644 --- a/src/aignostics/cli.py +++ b/src/aignostics/cli.py @@ -7,7 +7,7 @@ import typer from loguru import logger -from .constants import NOTEBOOK_DEFAULT +from .constants import NOTEBOOK_DEFAULT, WINDOW_TITLE from .utils import ( __is_running_in_container__, __python_version__, @@ -27,7 +27,7 @@ def launchpad() -> None: """Open Aignostics Launchpad, the graphical user interface of the Aignostics Platform.""" from .utils import gui_run # noqa: PLC0415 - gui_run(native=True, with_api=False, title="Aignostics Launchpad", icon="🔬") + gui_run(native=True, with_api=False, title=WINDOW_TITLE, icon="🔬") if find_spec("marimo"): diff --git a/src/aignostics/constants.py b/src/aignostics/constants.py index c8bde088..0a5d4b0a 100644 --- a/src/aignostics/constants.py +++ b/src/aignostics/constants.py @@ -37,3 +37,5 @@ TEST_APP_APPLICATION_ID = "test-app" WSI_SUPPORTED_FILE_EXTENSIONS = {".dcm", ".tiff", ".tif", ".svs"} WSI_SUPPORTED_FILE_EXTENSIONS_TEST_APP = {".tiff"} + +WINDOW_TITLE = "Aignostics Launchpad" diff --git a/src/aignostics/gui/_frame.py b/src/aignostics/gui/_frame.py index 98db7c58..f4470f5a 100644 --- a/src/aignostics/gui/_frame.py +++ b/src/aignostics/gui/_frame.py @@ -11,7 +11,9 @@ from html_sanitizer import Sanitizer from humanize import naturaldelta +from loguru import logger +from aignostics.constants import WINDOW_TITLE from aignostics.utils import __version__, open_user_data_directory from ._theme import theme @@ -101,15 +103,16 @@ def _bring_window_to_front() -> None: # Find window by title since pywebview doesn't expose hwnd directly # FindWindowW(lpClassName, lpWindowName) - use None for class to match any - hwnd = ctypes.windll.user32.FindWindowW(None, "Aignostics Launchpad") # type: ignore + hwnd = ctypes.windll.user32.FindWindowW(None, WINDOW_TITLE) # type: ignore if hwnd: ctypes.windll.user32.SetForegroundWindow(hwnd) # type: ignore else: app.native.main_window.set_always_on_top(True) app.native.main_window.show() app.native.main_window.set_always_on_top(False) - except Exception: # noqa: S110 - pass # Window operations can fail on some platforms + except Exception as e: + logger.exception(f"Failed to bring window to front: {e}") + # Window operations can fail on some platforms user_info: UserInfo | None = None launchpad_healthy: bool | None = None @@ -168,6 +171,8 @@ async def _user_info_ui_relogin() -> None: _user_info_ui.refresh() with contextlib.suppress(Exception): user_info = await run.io_bound(PlatformService.get_user_info, relogin=True) + if user_info: + _bring_window_to_front() app.storage.tab["user_info"] = user_info ui.navigate.reload() diff --git a/tests/aignostics/cli_test.py b/tests/aignostics/cli_test.py index 9c2eda66..932d3931 100644 --- a/tests/aignostics/cli_test.py +++ b/tests/aignostics/cli_test.py @@ -10,6 +10,7 @@ from typer.testing import CliRunner from aignostics.cli import cli +from aignostics.constants import WINDOW_TITLE from aignostics.utils import ( __python_version__, __version__, @@ -205,7 +206,7 @@ def mock_app_mount(path, app_instance): # Check that ui.run was called with the expected parameters assert mock_ui_run_called, "ui.run was not called" - assert mock_ui_run_args["title"] == "Aignostics Launchpad", "title parameter is incorrect" + assert mock_ui_run_args["title"] == WINDOW_TITLE, "title parameter is incorrect" assert mock_ui_run_args["favicon"] == "🔬", "favicon parameter is incorrect" if platform.system() == "Linux": assert mock_ui_run_args["native"] is False, "native parameter should be False on Linux" diff --git a/tests/main.py b/tests/main.py index 02063948..c607dea1 100644 --- a/tests/main.py +++ b/tests/main.py @@ -1,7 +1,8 @@ """Start script for pytest.""" +from aignostics.constants import WINDOW_TITLE from aignostics.utils import ( gui_run, ) -gui_run(native=False, with_api=False, title="Aignostics Launchpad", icon="🔬") +gui_run(native=False, with_api=False, title=WINDOW_TITLE, icon="🔬") From 6f1553128e4ffb5c9b5dd1d83d68eb7e8fcf12b8 Mon Sep 17 00:00:00 2001 From: Oliver Meyer Date: Thu, 11 Dec 2025 08:33:11 +0100 Subject: [PATCH 3/3] style: linter --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c22469f9..502deaea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1102,5 +1102,3 @@ * @omid-aignostics made their first contribution * @idelsink made their first contribution * @dependabot[bot] made their first contribution - -