Release v0.6.0

This commit is contained in:
Omni
2026-04-20 20:57:23 +01:00
parent 69fabb32e6
commit 2ff09a1448
144 changed files with 4841 additions and 1306 deletions

View File

@@ -22,6 +22,7 @@ from PySide6.QtGui import QFont, QClipboard
from ....backend.services.update_service import UpdateService
from ....backend.models.configuration import SystemInfo
from .... import __version__
from jackify.frontends.gui.mixins.thread_lifecycle_mixin import ThreadLifecycleMixin
logger = logging.getLogger(__name__)
@@ -45,7 +46,7 @@ class UpdateCheckThread(QThread):
self.update_check_finished.emit(None)
class AboutDialog(QDialog):
class AboutDialog(ThreadLifecycleMixin, QDialog):
"""About dialog showing system info and app details."""
def __init__(self, system_info: SystemInfo, parent=None):
@@ -420,8 +421,7 @@ Python: {platform.python_version()}"""
def closeEvent(self, event):
"""Handle dialog close event."""
if self.update_check_thread and self.update_check_thread.isRunning():
self.update_check_thread.terminate()
self.update_check_thread.wait()
self.update_check_thread = self._park_thread(
self.update_check_thread, ["update_available", "no_update", "check_failed"]
)
event.accept()

View File

@@ -12,7 +12,7 @@ from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QWidget,
QSpacerItem, QSizePolicy, QFrame, QApplication
)
from PySide6.QtCore import Qt
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QIcon, QFont
logger = logging.getLogger(__name__)
@@ -145,7 +145,8 @@ class ENBProtonDialog(QDialog):
# OK button
btn_row = QHBoxLayout()
btn_row.addStretch()
self.ok_btn = QPushButton("I Understand")
self.ok_btn = QPushButton("I Understand (3s)")
self.ok_btn.setEnabled(False)
self.ok_btn.setStyleSheet(
"QPushButton { "
" background: #3fb7d6; "
@@ -162,9 +163,19 @@ class ENBProtonDialog(QDialog):
"QPushButton:pressed { "
" background: #2d8fa8; "
"}"
"QPushButton:disabled { "
" background: #555; "
" color: #aaa; "
"}"
)
self.ok_btn.clicked.connect(self.accept)
btn_row.addWidget(self.ok_btn)
self._protect_countdown = 3
self._protect_timer = QTimer(self)
self._protect_timer.setInterval(1000)
self._protect_timer.timeout.connect(self._on_protect_tick)
self._protect_timer.start()
btn_row.addStretch()
layout.addLayout(btn_row)
@@ -173,6 +184,15 @@ class ENBProtonDialog(QDialog):
logger.info(f"ENBProtonDialog created for modlist: {modlist_name}")
def _on_protect_tick(self):
self._protect_countdown -= 1
if self._protect_countdown > 0:
self.ok_btn.setText(f"I Understand ({self._protect_countdown}s)")
else:
self._protect_timer.stop()
self.ok_btn.setText("I Understand")
self.ok_btn.setEnabled(True)
def _set_dialog_icon(self):
"""Set the dialog icon to Wabbajack icon if available"""
try:

View File

@@ -361,7 +361,8 @@ class ManualDownloadDialog(QDialog):
logger.debug("Could not persist manual_download_concurrent_limit", exc_info=True)
def _on_pick_folder(self) -> None:
chosen = QFileDialog.getExistingDirectory(self, "Select watch folder", str(self._watch_dir))
from jackify.frontends.gui.utils import browse_directory
chosen = browse_directory(self, "Select watch folder", str(self._watch_dir))
if chosen:
from jackify.backend.services.download_watcher_service import WatcherConfig
self._watch_dir = Path(chosen)
@@ -441,7 +442,7 @@ class ManualDownloadDialog(QDialog):
def _on_all_done_slot(self, completed: int, skipped: int) -> None:
from PySide6.QtCore import QTimer
self._progress_label.setText(
f"All downloads complete ({completed} accepted, {skipped} deferred) closing..."
f"All downloads complete ({completed} accepted, {skipped} deferred) - closing..."
)
# Raise now while the dialog is still visible so the user sees the completion state
self._raise_main_window()

View File

@@ -11,6 +11,7 @@ from PySide6.QtWidgets import (
from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QPixmap, QIcon, QFont
from .. import shared_theme
from jackify.frontends.gui.mixins.thread_lifecycle_mixin import ThreadLifecycleMixin
class FlatpakInstallThread(QThread):
@@ -26,7 +27,7 @@ class FlatpakInstallThread(QThread):
self.finished.emit(success, message)
class ProtontricksErrorDialog(QDialog):
class ProtontricksErrorDialog(ThreadLifecycleMixin, QDialog):
"""
Dialog shown when protontricks is not found
Provides options to install via Flatpak or get native installation guidance
@@ -322,7 +323,7 @@ class ProtontricksErrorDialog(QDialog):
def closeEvent(self, event):
"""Handle dialog close event"""
if self.install_thread and self.install_thread.isRunning():
self.install_thread.terminate()
self.install_thread.wait()
self.install_thread = self._park_thread(
self.install_thread, ["install_complete", "install_failed", "progress_update"]
)
event.accept()

View File

@@ -92,9 +92,10 @@ class SettingsDialog(SettingsDialogTabsMixin, SettingsDialogProtonMixin, QDialog
self.api_show_btn.setStyleSheet("")
def _pick_directory(self, line_edit):
dir_path = QFileDialog.getExistingDirectory(self, "Select Directory", line_edit.text() or os.path.expanduser("~"))
from jackify.frontends.gui.utils import browse_directory
dir_path = browse_directory(self, "Select Directory", line_edit.text())
if dir_path:
line_edit.setText(os.path.realpath(dir_path))
line_edit.setText(dir_path)
def _show_help(self):
MessageService.information(self, "Help", "Help/documentation coming soon!", safety_level="low")
@@ -125,6 +126,7 @@ class SettingsDialog(SettingsDialogTabsMixin, SettingsDialogProtonMixin, QDialog
api_key = text.strip()
self.config_handler.save_api_key(api_key)
def _update_oauth_status(self):
from jackify.backend.services.nexus_auth_service import NexusAuthService
auth_service = NexusAuthService()
@@ -309,6 +311,10 @@ class SettingsDialog(SettingsDialogTabsMixin, SettingsDialogProtonMixin, QDialog
self.config_handler.set("game_proton_path", resolved_game_path)
self.config_handler.set("game_proton_version", resolved_game_version)
# Save auto tool compat preference
self.config_handler.set('auto_tool_compat', self.auto_tool_compat_checkbox.isChecked())
self.config_handler.set('force_github_updates', self.force_github_updates_checkbox.isChecked())
# Save component installation method preference
if self.winetricks_radio.isChecked():
method = 'winetricks'

View File

@@ -170,6 +170,7 @@ class SettingsDialogTabsMixin:
advanced_layout.addWidget(auth_group)
advanced_layout.addSpacing(12)
self.resource_settings_path = os.path.expanduser("~/.config/jackify/resource_settings.json")
self.resource_settings = self._load_json(self.resource_settings_path)
self.resource_edits = {}
@@ -275,6 +276,27 @@ class SettingsDialogTabsMixin:
self.component_method_group.addButton(self.protontricks_radio, 1)
component_method_layout.addWidget(self.protontricks_radio)
component_layout.addLayout(component_method_layout)
self.auto_tool_compat_checkbox = QCheckBox("Apply tool compatibility settings during install/configure")
self.auto_tool_compat_checkbox.setChecked(self.config_handler.get('auto_tool_compat', True))
self.auto_tool_compat_checkbox.setToolTip(
"Automatically apply Wine registry fixes for xEdit, Pandora, and DLL overrides "
"at the end of every install or configure workflow. Disable if you find it adds "
"noticeable delay."
)
self.auto_tool_compat_checkbox.setStyleSheet("color: #fff;")
component_layout.addWidget(self.auto_tool_compat_checkbox)
self.force_github_updates_checkbox = QCheckBox("Use GitHub as update source (bypass Nexus CDN)")
self.force_github_updates_checkbox.setChecked(self.config_handler.get('force_github_updates', False))
self.force_github_updates_checkbox.setToolTip(
"Always download Jackify updates directly from GitHub Releases instead of Nexus CDN. "
"Enable this if self-updates fail or stall. GitHub delivers the AppImage directly; "
"Nexus delivers a .7z archive that Jackify must extract."
)
self.force_github_updates_checkbox.setStyleSheet("color: #fff;")
component_layout.addWidget(self.force_github_updates_checkbox)
advanced_layout.addWidget(component_group)
advanced_layout.addStretch()
self.tab_widget.addTab(advanced_tab, "Advanced")

View File

@@ -92,6 +92,8 @@ class SuccessDialog(QDialog):
suffix_text = "configured successfully!"
elif self.workflow_type == "configure_existing":
suffix_text = "configuration updated successfully!"
elif self.workflow_type == "tool_config":
suffix_text = "tool compatibility configured successfully!"
else:
# Fallback for other workflow types
message_text = self._build_success_message()
@@ -118,18 +120,19 @@ class SuccessDialog(QDialog):
# Ensure the label uses full width of the card before wrapping
card_layout.addWidget(message_label)
# Time taken
time_label = QLabel(f"Completed in {self.time_taken}")
time_label.setAlignment(Qt.AlignCenter)
time_label.setStyleSheet(
"QLabel { "
" font-size: 12px; "
" color: #b0b0b0; "
" font-style: italic; "
" margin-bottom: 10px; "
"}"
)
card_layout.addWidget(time_label)
# Time taken (omit label if time is not available)
if self.time_taken:
time_label = QLabel(f"Completed in {self.time_taken}")
time_label.setAlignment(Qt.AlignCenter)
time_label.setStyleSheet(
"QLabel { "
" font-size: 12px; "
" color: #b0b0b0; "
" font-style: italic; "
" margin-bottom: 10px; "
"}"
)
card_layout.addWidget(time_label)
# Next steps guidance
next_steps_text = self._build_next_steps()
@@ -240,15 +243,37 @@ class SuccessDialog(QDialog):
game_display = self.game_name or self.modlist_name
base_message = ""
if self.workflow_type == "tuxborn":
if self.workflow_type == "tool_config":
base_message = (
f"Modding tools for {self.modlist_name} are now configured. "
"xEdit, Synthesis, Pandora, and DLL overrides are ready to use from within Mod Organizer 2."
)
elif self.workflow_type == "tuxborn":
base_message = f"You can now launch Tuxborn from Steam and enjoy your modded {game_display} experience!"
elif self.workflow_type == "install" and self.modlist_name == "Wabbajack":
base_message = "You can now launch Wabbajack from Steam and install modlists. Once the modlist install is complete, you can run \"Configure New Modlist\" in Jackify to complete the configuration for running the modlist on Linux."
else:
base_message = f"You can now launch {self.modlist_name} from Steam and enjoy your modded {game_display} experience!"
try:
from jackify.backend.handlers.config_handler import ConfigHandler
auto_tool_compat = ConfigHandler().get('auto_tool_compat', True)
except Exception:
auto_tool_compat = True
tool_hint = (
"<br><br>"
"<span style=\"color:#b0b0b0; font-size:12px;\">"
"If you use modding tools such as xEdit, Synthesis, or Pandora, "
"run <b>Configure Tool Compatibility</b> from the Additional Tasks menu."
"</span>"
) if not auto_tool_compat else ""
base_message = (
f"You can now launch {self.modlist_name} from Steam and enjoy your modded {game_display} experience!"
f"{tool_hint}"
)
# ENB Proton warning shown in separate dialog
return base_message
return base_message
def _update_countdown(self):
if self._countdown > 0:

View File

@@ -17,6 +17,7 @@ from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QPixmap, QFont
from ....backend.services.update_service import UpdateService, UpdateInfo
from jackify.frontends.gui.mixins.thread_lifecycle_mixin import ThreadLifecycleMixin
logger = logging.getLogger(__name__)
@@ -51,7 +52,7 @@ class UpdateDownloadThread(QThread):
self.download_finished.emit(None)
class UpdateDialog(QDialog):
class UpdateDialog(ThreadLifecycleMixin, QDialog):
"""Dialog for notifying users about updates and handling downloads."""
def __init__(self, update_info: UpdateInfo, update_service: UpdateService, parent=None):
@@ -335,9 +336,7 @@ class UpdateDialog(QDialog):
def closeEvent(self, event):
"""Handle dialog close event."""
if self.download_thread and self.download_thread.isRunning():
# Cancel download if in progress
self.download_thread.terminate()
self.download_thread.wait()
self.download_thread = self._park_thread(
self.download_thread, ["progress_updated", "download_finished"]
)
event.accept()