From 16f99be822b6bd377edd19b10f87ccba0a918588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 20 Aug 2026 16:11:57 -0400 Subject: [PATCH 1/5] StandardStreamEmitter: implement 'flush' method --- qtapputils/managers/capture.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qtapputils/managers/capture.py b/qtapputils/managers/capture.py index 3455736..375b1e9 100644 --- a/qtapputils/managers/capture.py +++ b/qtapputils/managers/capture.py @@ -48,6 +48,12 @@ def write(self, text): pass self.sig_new_text.emit(str(text)) + def flush(self): + try: + sys.__stdout__.flush() + except Exception: + pass + class SysCaptureManager(QObject): """ From 75cfcea59b28b09b0ed61ab000d3b65c8eeff3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 20 Aug 2026 16:12:47 -0400 Subject: [PATCH 2/5] Fix show_detailed_log in ExceptDialog --- qtapputils/widgets/exceptions.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/qtapputils/widgets/exceptions.py b/qtapputils/widgets/exceptions.py index a78bf6a..f51a548 100644 --- a/qtapputils/widgets/exceptions.py +++ b/qtapputils/widgets/exceptions.py @@ -7,29 +7,24 @@ # Licensed under the terms of the GNU General Public License. # ----------------------------------------------------------------------------- from __future__ import annotations -from typing import TYPE_CHECKING, Callable # ---- Standard library imports import os -import os.path as osp import sys import datetime import tempfile +from pathlib import Path # ---- Third party imports from qtapputils.icons import get_standard_icon, get_standard_iconsize -from qtpy.QtCore import Qt -from qtpy.QtGui import QIcon +from qtpy.QtCore import Qt, QUrl +from qtpy.QtGui import QIcon, QDesktopServices from qtpy.QtWidgets import ( QApplication, QDialog, QDialogButtonBox, QGridLayout, QLabel, QPushButton, QTextEdit, QWidget) -# ---- Local imports -from hydrogeolab.config.main import TEMP_DIR - - class ExceptDialog(QDialog): """ A dialog to report internal errors encountered by the application during @@ -38,7 +33,8 @@ class ExceptDialog(QDialog): def __init__(self, appname: str, appver: str, system_info: str = None, icon: QIcon = None, issue_tracker: str = None, - issue_email: str = None, parent: QWidget = None): + issue_email: str = None, parent: QWidget = None, + temp_dir: str | Path = None): super().__init__(parent) self.setWindowTitle(f"{appname} Internal Error") self.setWindowFlags( @@ -46,6 +42,13 @@ def __init__(self, appname: str, appver: str, system_info: str = None, if icon is not None: self.setWindowIcon(icon) + self.temp_dir = None + if temp_dir is not None: + temp_dir = Path(temp_dir) + if temp_dir.is_dir() and os.access(temp_dir, os.W_OK): + self.temp_dir = temp_dir + + self.temp_dir = temp_dir self.log_msg = None self.detailed_log = None @@ -176,11 +179,14 @@ def show_detailed_log(self): chosen by the OS. """ name = '{}Log_{}.txt'.format(self.appname, self.log_datetime) - temp_path = tempfile.mkdtemp(dir=TEMP_DIR) - temp_filename = osp.join(temp_path, name) - with open(temp_filename, 'w') as txtfile: + + temp_path = Path(tempfile.mkdtemp(dir=self.temp_dir)) + temp_filename = temp_path / name + with open(temp_filename, 'w', encoding='utf-8') as txtfile: txtfile.write(self.detailed_log) - os.startfile(temp_filename) + + # Cross-platform file opening (Windows, macOS, Linux) via Qt + QDesktopServices.openUrl(QUrl.fromLocalFile(str(temp_filename))) def copy(self): """ From 807eb51b4cbd7a90689e3bc4fea294e5f13bb19f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 20 Aug 2026 16:16:05 -0400 Subject: [PATCH 3/5] Add print when provided temp_dir is not valid --- qtapputils/widgets/exceptions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qtapputils/widgets/exceptions.py b/qtapputils/widgets/exceptions.py index f51a548..6c94f81 100644 --- a/qtapputils/widgets/exceptions.py +++ b/qtapputils/widgets/exceptions.py @@ -47,6 +47,9 @@ def __init__(self, appname: str, appver: str, system_info: str = None, temp_dir = Path(temp_dir) if temp_dir.is_dir() and os.access(temp_dir, os.W_OK): self.temp_dir = temp_dir + else: + print(f"Provided temp_dir {temp_dir} is not a valid or " + "writable directory. Falling back to system default.") self.temp_dir = temp_dir self.log_msg = None From bd312bd510145d18fd421adf124b143c4f5e107d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 20 Aug 2026 16:27:44 -0400 Subject: [PATCH 4/5] Remove temp_dir wrong assignment after validation block --- qtapputils/widgets/exceptions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qtapputils/widgets/exceptions.py b/qtapputils/widgets/exceptions.py index 6c94f81..ef2db1c 100644 --- a/qtapputils/widgets/exceptions.py +++ b/qtapputils/widgets/exceptions.py @@ -51,7 +51,6 @@ def __init__(self, appname: str, appver: str, system_info: str = None, print(f"Provided temp_dir {temp_dir} is not a valid or " "writable directory. Falling back to system default.") - self.temp_dir = temp_dir self.log_msg = None self.detailed_log = None From c2f32464a8622205623e304310f8fc5677604912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 20 Aug 2026 16:34:39 -0400 Subject: [PATCH 5/5] StandardStreamEmitter: add parameter indicating which underlying stream to mirror --- qtapputils/managers/capture.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qtapputils/managers/capture.py b/qtapputils/managers/capture.py index 375b1e9..26cfb8f 100644 --- a/qtapputils/managers/capture.py +++ b/qtapputils/managers/capture.py @@ -41,16 +41,20 @@ class StandardStreamEmitter(QObject): """ sig_new_text = Signal(str) + def __init__(self, stream='stdout'): + super().__init__() + self._stream = sys.__stderr__ if stream == 'stderr' else sys.__stdout__ + def write(self, text): try: - sys.__stdout__.write(text) + self._stream.write(text) except Exception: pass self.sig_new_text.emit(str(text)) def flush(self): try: - sys.__stdout__.flush() + self._stream.flush() except Exception: pass @@ -82,10 +86,10 @@ def __init__(self, start_capture=False): self.except_hook.sig_except_caught.connect(self._handle_except) # Setup the standard stream emitter. - self.stdout_emitter = StandardStreamEmitter() + self.stdout_emitter = StandardStreamEmitter('stdout') self.stdout_emitter.sig_new_text.connect(self.__handle_stdout) - self.stderr_emitter = StandardStreamEmitter() + self.stderr_emitter = StandardStreamEmitter('stderr') self.stderr_emitter.sig_new_text.connect(self.handle_stderr) if start_capture: