diff --git a/src/gui_2.py b/src/gui_2.py index 0a537782..9b59e2db 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -1185,15 +1185,12 @@ class App: # Draw right-aligned window controls directly in the menu bar (Win32 only) if sys.platform == "win32": - try: - import ctypes - ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p - ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p] - hwnd_capsule = imgui.get_main_viewport().platform_handle_raw - hwnd = ctypes.pythonapi.PyCapsule_GetPointer(hwnd_capsule, b"nb_handle") - except Exception: - hwnd = 0 - + result = _show_menus_hwnd_result(self) + if not result.ok: + if not hasattr(self, '_last_request_errors'): self._last_request_errors = [] + self._last_request_errors.append(("_show_menus.hwnd", result.errors[0])) + hwnd = result.data + if hwnd: btn_w = 40 # Use window width (points) instead of display_size (pixels) for correct scaling @@ -7528,6 +7525,35 @@ def _show_menus_do_generate_result(app: "App") -> Result[bool]: original=e, )]) +def _show_menus_hwnd_result(app: "App") -> Result[int]: + """Drain-aware variant of L1197 _show_menus hwnd capsule try/except. + + Extracts the ctypes PyCapsule_GetPointer try/except from App._show_menus + into a Result-returning helper. On success, returns Result(data=hwnd) + where hwnd is the resolved window handle. On failure (e.g., on a + non-Windows platform), returns Result(data=0, errors=[ErrorInfo]). + + The data field is the resolved hwnd (int) so the legacy wrapper can + pass it to subsequent win32gui calls without an additional app.hwnd + instance attribute. + + [C: src/gui_2.py:App._show_menus (L1197 legacy wrapper)] + """ + try: + import ctypes + ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p + ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p] + hwnd_capsule = imgui.get_main_viewport().platform_handle_raw + hwnd = ctypes.pythonapi.PyCapsule_GetPointer(hwnd_capsule, b"nb_handle") + return Result(data=int(hwnd) if hwnd else 0) + except Exception as e: + return Result(data=0, errors=[ErrorInfo( + kind=ErrorKind.INTERNAL, + message=f"Failed to resolve hwnd via PyCapsule_GetPointer: {e}", + source="gui_2._show_menus_hwnd_result", + original=e, + )]) + #endregion: Phase 3 Render-Loop Result Helpers #endregion: MMA diff --git a/tests/test_gui_2_result.py b/tests/test_gui_2_result.py index eec0a43f..30946753 100644 --- a/tests/test_gui_2_result.py +++ b/tests/test_gui_2_result.py @@ -378,4 +378,45 @@ def test_phase_3_l1171_show_menus_do_generate_result_failure(): err = result.errors[0] assert err.source == "gui_2._show_menus_do_generate_result" assert "generate blew up" in err.message - assert "error" in app.ai_status \ No newline at end of file + assert "error" in app.ai_status + + +def test_phase_3_l1197_show_menus_hwnd_result_success(): + """ + L1197 _show_menus_hwnd_result returns Result.ok=True on success. + + The helper wraps the ctypes PyCapsule_GetPointer try/except in + App._show_menus. On success, returns Result(data=hwnd) with the + resolved window handle. + """ + from src import gui_2 + from unittest.mock import MagicMock, patch + app = MagicMock() + mock_viewport = MagicMock() + mock_viewport.platform_handle_raw = "mock_capsule" + with patch.object(gui_2.imgui, "get_main_viewport", return_value=mock_viewport), \ + patch("ctypes.pythonapi.PyCapsule_GetPointer", return_value=12345): + result = gui_2._show_menus_hwnd_result(app) + assert result.ok, f"Expected ok=True on success, got errors: {result.errors}" + assert result.data == 12345 + + +def test_phase_3_l1197_show_menus_hwnd_result_failure(): + """ + L1197 _show_menus_hwnd_result returns Result.ok=False with ErrorInfo on failure. + + When the ctypes call raises (e.g., on a non-Windows platform or when + imgui.get_main_viewport returns None), the helper returns + Result(data=0, errors=[ErrorInfo]). + """ + from src import gui_2 + from unittest.mock import MagicMock, patch + app = MagicMock() + # Force the except branch by raising inside the try block + with patch.object(gui_2.imgui, "get_main_viewport", side_effect=RuntimeError("no viewport")): + result = gui_2._show_menus_hwnd_result(app) + assert not result.ok, f"Expected ok=False on failure, got data: {result.data}" + assert result.errors, "Expected at least one error on failure" + err = result.errors[0] + assert err.source == "gui_2._show_menus_hwnd_result" + assert result.data == 0 \ No newline at end of file