Files
Jackify/jackify/frontends/gui/mixins/main_window_dialogs.py
2026-02-25 20:54:28 +00:00

163 lines
5.7 KiB
Python

"""
Main window dialogs and cleanup mixin.
Settings, About, open URL, cleanup_processes, closeEvent.
"""
import os
import subprocess
from jackify.frontends.gui.dialogs.settings_dialog import SettingsDialog
class MainWindowDialogsMixin:
"""Mixin for settings/about dialogs, open URL, and cleanup."""
def _stop_qthread(self, thread, thread_name: str, cooperative_timeout_ms: int = 5000):
"""Stop a QThread robustly to avoid teardown crashes on app exit."""
if thread is None:
return None
try:
if not thread.isRunning():
return None
except RuntimeError:
return None
try:
thread.requestInterruption()
except Exception:
pass
try:
thread.quit()
except Exception:
pass
try:
if thread.wait(cooperative_timeout_ms):
return None
except Exception:
pass
try:
thread.terminate()
except Exception:
pass
try:
if not thread.wait(10000):
print(f"WARNING: {thread_name} still running during shutdown")
except Exception:
pass
return None
def open_settings_dialog(self):
try:
if self._settings_dialog is not None:
try:
if self._settings_dialog.isVisible():
self._settings_dialog.raise_()
self._settings_dialog.activateWindow()
return
else:
self._settings_dialog = None
except RuntimeError:
self._settings_dialog = None
dlg = SettingsDialog(self)
self._settings_dialog = dlg
def on_dialog_finished():
self._settings_dialog = None
dlg.finished.connect(on_dialog_finished)
dlg.exec()
except Exception as e:
print(f"[ERROR] Exception in open_settings_dialog: {e}")
import traceback
traceback.print_exc()
self._settings_dialog = None
def open_about_dialog(self):
try:
from jackify.frontends.gui.dialogs.about_dialog import AboutDialog
if self._about_dialog is not None:
try:
if self._about_dialog.isVisible():
self._about_dialog.raise_()
self._about_dialog.activateWindow()
return
else:
self._about_dialog = None
except RuntimeError:
self._about_dialog = None
dlg = AboutDialog(self.system_info, self)
self._about_dialog = dlg
def on_dialog_finished():
self._about_dialog = None
dlg.finished.connect(on_dialog_finished)
dlg.exec()
except Exception as e:
print(f"[ERROR] Exception in open_about_dialog: {e}")
import traceback
traceback.print_exc()
self._about_dialog = None
def _open_url(self, url: str):
env = os.environ.copy()
appimage_vars = [
'LD_LIBRARY_PATH', 'PYTHONPATH', 'PYTHONHOME',
'QT_PLUGIN_PATH', 'QML2_IMPORT_PATH',
]
if 'APPIMAGE' in env or 'APPDIR' in env:
for var in appimage_vars:
env.pop(var, None)
subprocess.Popen(
['xdg-open', url],
env=env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True
)
def cleanup_processes(self):
try:
if hasattr(self, '_update_thread') and self._update_thread is not None:
self._update_thread = self._stop_qthread(self._update_thread, "_update_thread")
if hasattr(self, '_gallery_cache_preload_thread') and self._gallery_cache_preload_thread is not None:
self._gallery_cache_preload_thread = self._stop_qthread(
self._gallery_cache_preload_thread,
"_gallery_cache_preload_thread",
)
for service in self.gui_services.values():
if hasattr(service, 'cleanup'):
service.cleanup()
screens = [
getattr(self, 'modlist_tasks_screen', None),
getattr(self, 'additional_tasks_screen', None),
getattr(self, 'install_modlist_screen', None),
getattr(self, 'install_ttw_screen', None),
getattr(self, 'configure_new_modlist_screen', None),
getattr(self, 'wabbajack_installer_screen', None),
getattr(self, 'configure_existing_modlist_screen', None),
getattr(self, 'install_mo2_screen', None),
]
for screen in screens:
if screen is None:
continue
if hasattr(screen, 'cleanup_processes'):
screen.cleanup_processes()
elif hasattr(screen, 'cleanup'):
screen.cleanup()
elif hasattr(screen, 'worker'):
worker = getattr(screen, 'worker', None)
setattr(screen, 'worker', self._stop_qthread(worker, f"{screen.__class__.__name__}.worker"))
try:
subprocess.run(['pkill', '-f', 'jackify-engine'], timeout=5, capture_output=True)
except Exception:
pass
except Exception as e:
print(f"Error during cleanup: {e}")
def closeEvent(self, event):
self._save_geometry_on_quit()
self.cleanup_processes()
event.accept()