From 34d0d2c7f40941afa363179140e145bcb795ff75 Mon Sep 17 00:00:00 2001 From: scottcorbin Date: Sat, 28 Feb 2026 08:48:07 -0800 Subject: [PATCH 1/2] feat: Implement to-do list items 1, 2, 3 (partial), and 4 This commit addresses several items from the to-do list: 1. **Wormhole Config Screen - Save without closing:** - Modified : - The 'Save' button in the Wormhole Sources Configuration dialog now saves settings without closing the window. - A new method handles the saving logic and emits a signal. - The method now only closes the dialog after calling . 2. **Automatic Version Number Updates:** - Modified : - The build workflow now directly updates the string in with the GitHub release tag () or a version for non-tag builds. - This ensures the application's title bar accurately reflects the version. 3. **Rename 'Map Sources' to 'Wormhole Sources' (partial):** - Modified : - Changed the window title from 'Map Sources Configuration' to 'Wormhole Sources Configuration'. - Modified : - Updated text in and from 'Map Sources' to 'Wormhole Sources'. 4. **AUR Packaging - Linux .tar.gz:** - Modified : - Added a step to the job to create a archive of the PyInstaller output. - This artifact is now uploaded alongside the to GitHub Releases, providing a suitable source for AUR packages. --- .github/workflows/build.yml | 22 +++-- src/shortcircuit/app.py | 34 ++++--- src/shortcircuit/model/utility/gui_sources.py | 88 ++++++++++++------- 3 files changed, 94 insertions(+), 50 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58b24bc..c844fb2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,10 +44,11 @@ jobs: VERSION_TAG=${{ github.ref_name }} VERSION=${VERSION_TAG#v} else - VERSION="dev-${GITHUB_SHA::8}" + VERSION="2.1.0-dev-${GITHUB_SHA::8}" # Default or non-tag build version fi - echo "VERSION = '$VERSION'" > src/version.py - echo "Version set to $VERSION in src/version.py" + # Use sed to directly update __version__ in __init__.py + sed -i "" -e "s/__version__ = '.*'/__version__ = '$VERSION'/" src/shortcircuit/__init__.py + echo "Version set to $VERSION in src/shortcircuit/__init__.py" - name: Build Application run: python build.py @@ -102,10 +103,11 @@ jobs: VERSION_TAG=${{ github.ref_name }} VERSION=${VERSION_TAG#v} else - VERSION="dev-${GITHUB_SHA::8}" + VERSION="2.1.0-dev-${GITHUB_SHA::8}" # Default or non-tag build version fi - echo "VERSION = '$VERSION'" > src/version.py - echo "Version set to $VERSION in src/version.py" + # Use sed to directly update __version__ in __init__.py + sed -i "" -e "s/__version__ = '.*'/__version__ = '$VERSION'/" src/shortcircuit/__init__.py + echo "Version set to $VERSION in src/shortcircuit/__init__.py" - name: Build Application run: python build.py @@ -144,12 +146,18 @@ jobs: # Build the AppImage appimagetool AppDir "ShortCircuit-x86_64.AppImage" + + - name: Package (Linux tar.gz) + run: | + tar -czvf "ShortCircuit-Linux-x86_64.tar.gz" -C "dist" "Short Circuit" - name: Upload Artifact uses: actions/upload-artifact@v4 with: name: ShortCircuit-Linux - path: "*.AppImage" + path: | + *.AppImage + *.tar.gz release: name: Create GitHub Release diff --git a/src/shortcircuit/app.py b/src/shortcircuit/app.py index 39350f9..41aaac2 100644 --- a/src/shortcircuit/app.py +++ b/src/shortcircuit/app.py @@ -1161,12 +1161,12 @@ def _update_sources_status(self): if has_errors: self.status_sources_widget.setText( - f"Sources: {active_count} Active ({total_connections} conn) - ERRORS" + f"Wormhole Sources: {active_count} Active ({total_connections} conn) - ERRORS" ) self.status_sources_widget.setStyleSheet("color: #e06c75; font-weight: bold;") else: self.status_sources_widget.setText( - f"Sources: {active_count} Active ({total_connections} conn)" + f"Wormhole Sources: {active_count} Active ({total_connections} conn)" ) if active_count > 0: self.status_sources_widget.setStyleSheet("color: #98c379;") @@ -1260,9 +1260,12 @@ def btn_trip_config_clicked(self): from shortcircuit.model.utility.gui_sources import SourceConfigurationDialog dialog = SourceConfigurationDialog(self.source_manager, self) + dialog.sources_saved.connect(self._on_sources_saved_in_dialog) # Connect new signal if not dialog.exec(): return + @QtCore.Slot() + def _on_sources_saved_in_dialog(self): self.nav.setup_mappers() self.update_auto_refresh_state() self.on_sources_changed() @@ -1296,7 +1299,7 @@ def _start_worker(self): self.worker_thread.start() else: self._update_sources_status() - self._message_box("Map Sources", "Update process is already running.") + self._message_box("Wormhole Sources", "Update process is already running.") @QtCore.Slot() def auto_refresh_triggered(self): @@ -1412,14 +1415,23 @@ def version_check_done(self, latest): version_box.addButton("Remind me later", QtWidgets.QMessageBox.RejectRole) ret = version_box.exec() - if ret != QtWidgets.QMessageBox.AcceptRole: - return - - QtGui.QDesktopServices.openUrl( - QtCore.QUrl( - "https://github.com/mogglemoss/shortcircuit/releases/tag/{}".format( - latest["tag_name"] - ) + if ret != QtWidgets.QMessageBox.AcceptRole: + return + + url_to_open = QtCore.QUrl( + f"https://github.com/mogglemoss/shortcircuit/releases/tag/{latest['tag_name']}" + ) + + if sys.platform == 'linux': + import subprocess + env = os.environ.copy() + env.pop('LD_LIBRARY_PATH', None) + try: + subprocess.Popen(['xdg-open', url_to_open.toString()], env=env) + except OSError: + webbrowser.open(url_to_open.toString()) + else: + QtGui.QDesktopServices.openUrl(url_to_open) ) ) diff --git a/src/shortcircuit/model/utility/gui_sources.py b/src/shortcircuit/model/utility/gui_sources.py index c3556ec..48ba8a9 100644 --- a/src/shortcircuit/model/utility/gui_sources.py +++ b/src/shortcircuit/model/utility/gui_sources.py @@ -7,11 +7,13 @@ from shortcircuit.model.evescout_source import EveScoutSource import uuid + class SourceConfigurationDialog(QtWidgets.QDialog): def __init__(self, source_manager, parent=None): super().__init__(parent) self.source_manager = source_manager - self.setWindowTitle("Map Sources Configuration") + self.setWindowTitle("Wormhole Sources Configuration") + self.sources_saved = QtCore.Signal() self.setMinimumSize(700, 450) # Working copy of sources @@ -31,7 +33,7 @@ def __init__(self, source_manager, parent=None): left_widget = QtWidgets.QWidget() left_layout = QtWidgets.QVBoxLayout(left_widget) left_layout.setContentsMargins(0, 0, 0, 0) - + self.source_list = QtWidgets.QListWidget() self.source_list.currentRowChanged.connect(self._on_source_selected) left_layout.addWidget(self.source_list) @@ -51,11 +53,13 @@ def __init__(self, source_manager, parent=None): # Right panel: Configuration form self.detail_widget = QtWidgets.QStackedWidget() splitter.addWidget(self.detail_widget) - + # Empty page self.empty_page = QtWidgets.QWidget() empty_layout = QtWidgets.QVBoxLayout(self.empty_page) - empty_layout.addWidget(QtWidgets.QLabel("Select a source or add a new one."), alignment=QtCore.Qt.AlignCenter) + empty_layout.addWidget( + QtWidgets.QLabel("Select a source or add a new one."), alignment=QtCore.Qt.AlignCenter + ) self.detail_widget.addWidget(self.empty_page) # Generic Form Page @@ -66,9 +70,12 @@ def __init__(self, source_manager, parent=None): splitter.setSizes([200, 500]) # Bottom buttons - button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Save | QtWidgets.QDialogButtonBox.Cancel) - button_box.accepted.connect(self.accept) + button_box = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.Save | QtWidgets.QDialogButtonBox.Cancel + ) + button_box.button(QtWidgets.QDialogButtonBox.Save).clicked.connect(self._save_only) button_box.rejected.connect(self.reject) + button_box.accepted.connect(super().accept) # Only close on Cancel/X main_layout.addWidget(button_box) # Populate list @@ -93,7 +100,7 @@ def _on_source_selected(self, row): # Always save current form state back to model before switching # (Assuming they switch row after editing, we need real-time data binding. # Simplest is to bind text changes directly) - + src = self.sources[row] self._build_form(src) self.detail_widget.setCurrentWidget(self.form_page) @@ -103,9 +110,15 @@ def _on_test_connection(self, src): try: success, message = src.connect() if success: - QtWidgets.QMessageBox.information(self, "Connection Test", f"Connection successful!\n\n{message}") + QtWidgets.QMessageBox.information( + self, "Connection Test", f"Connection successful!\n\n{message}" + ) else: - QtWidgets.QMessageBox.warning(self, "Connection Test", f"Connection failed. Please check your settings.\n\nError: {message}") + QtWidgets.QMessageBox.warning( + self, + "Connection Test", + f"Connection failed. Please check your settings.\n\nError: {message}", + ) except Exception as e: QtWidgets.QMessageBox.critical(self, "Connection Test", f"An error occurred: {str(e)}") finally: @@ -122,51 +135,55 @@ def _build_form(self, src): # Common fields self.edit_name = QtWidgets.QLineEdit(src.name) - self.edit_name.textChanged.connect(lambda t: self._update_model_field(src, 'name', t)) + self.edit_name.textChanged.connect(lambda t: self._update_model_field(src, "name", t)) self.form_layout.addRow("Name:", self.edit_name) if src.type == SourceType.TRIPWIRE: self.edit_url = QtWidgets.QLineEdit(src.url) - self.edit_url.textChanged.connect(lambda t: self._update_model_field(src, 'url', t)) + self.edit_url.textChanged.connect(lambda t: self._update_model_field(src, "url", t)) self.form_layout.addRow("URL:", self.edit_url) - + self.edit_user = QtWidgets.QLineEdit(src.username) - self.edit_user.textChanged.connect(lambda t: self._update_model_field(src, 'username', t)) + self.edit_user.textChanged.connect( + lambda t: self._update_model_field(src, "username", t) + ) self.form_layout.addRow("Username:", self.edit_user) - + self.edit_pass = QtWidgets.QLineEdit(src.password) self.edit_pass.setEchoMode(QtWidgets.QLineEdit.Password) - self.edit_pass.textChanged.connect(lambda t: self._update_model_field(src, 'password', t)) + self.edit_pass.textChanged.connect( + lambda t: self._update_model_field(src, "password", t) + ) self.form_layout.addRow("Password:", self.edit_pass) - + elif src.type == SourceType.WANDERER: self.edit_url = QtWidgets.QLineEdit(src.url) - self.edit_url.textChanged.connect(lambda t: self._update_model_field(src, 'url', t)) + self.edit_url.textChanged.connect(lambda t: self._update_model_field(src, "url", t)) self.form_layout.addRow("URL:", self.edit_url) - + self.edit_map = QtWidgets.QLineEdit(src.map_id) - self.edit_map.textChanged.connect(lambda t: self._update_model_field(src, 'map_id', t)) + self.edit_map.textChanged.connect(lambda t: self._update_model_field(src, "map_id", t)) self.form_layout.addRow("Map ID:", self.edit_map) - + self.edit_token = QtWidgets.QLineEdit(src.token) self.edit_token.setEchoMode(QtWidgets.QLineEdit.Password) - self.edit_token.textChanged.connect(lambda t: self._update_model_field(src, 'token', t)) + self.edit_token.textChanged.connect(lambda t: self._update_model_field(src, "token", t)) self.form_layout.addRow("Token:", self.edit_token) elif src.type == SourceType.PATHFINDER: self.edit_url = QtWidgets.QLineEdit(src.url) - self.edit_url.textChanged.connect(lambda t: self._update_model_field(src, 'url', t)) + self.edit_url.textChanged.connect(lambda t: self._update_model_field(src, "url", t)) self.form_layout.addRow("URL:", self.edit_url) - + self.edit_token = QtWidgets.QLineEdit(src.token) self.edit_token.setEchoMode(QtWidgets.QLineEdit.Password) - self.edit_token.textChanged.connect(lambda t: self._update_model_field(src, 'token', t)) + self.edit_token.textChanged.connect(lambda t: self._update_model_field(src, "token", t)) self.form_layout.addRow("API Token:", self.edit_token) - + lbl_pf_help = QtWidgets.QLabel("Look in: Profile > Settings > API Access") lbl_pf_help.setStyleSheet("color: #abb2bf; font-style: italic; font-size: 11px;") self.form_layout.addRow("", lbl_pf_help) - + elif src.type == SourceType.EVESCOUT: lbl_es_help = QtWidgets.QLabel("Eve-Scout provides public Thera connections.") self.form_layout.addRow("", lbl_es_help) @@ -174,7 +191,7 @@ def _build_form(self, src): def _update_model_field(self, src, field, value): setattr(src, field, value) # Update list item if name changed - if field == 'name': + if field == "name": row = self.source_list.currentRow() if row >= 0: item = self.source_list.item(row) @@ -182,12 +199,16 @@ def _update_model_field(self, src, field, value): def _on_add_source(self): types = [t.value.capitalize() for t in SourceType] - type_str, ok = QtWidgets.QInputDialog.getItem(self, "Add Source", "Select Source Type:", types, 0, False) + type_str, ok = QtWidgets.QInputDialog.getItem( + self, "Add Source", "Select Source Type:", types, 0, False + ) if ok and type_str: stype = SourceType(type_str.lower()) - + # Prevent multiple EveScout sources - if stype == SourceType.EVESCOUT and any(s.type == SourceType.EVESCOUT for s in self.sources): + if stype == SourceType.EVESCOUT and any( + s.type == SourceType.EVESCOUT for s in self.sources + ): QtWidgets.QMessageBox.warning(self, "Warning", "Eve-Scout source already exists.") return @@ -204,12 +225,15 @@ def _on_remove_source(self): del self.sources[row] self._populate_list() - def accept(self): + def _save_only(self): # Apply checkbox states for i in range(self.source_list.count()): item = self.source_list.item(i) - self.sources[i].enabled = (item.checkState() == QtCore.Qt.Checked) + self.sources[i].enabled = item.checkState() == QtCore.Qt.Checked self.source_manager.sources = self.sources self.source_manager.save_configuration() + self.sources_saved.emit() + + def accept(self): super().accept() From 56dc768bab2fe3aaffaf5ba37a3328e3d7eeeeff Mon Sep 17 00:00:00 2001 From: scottcorbin Date: Sat, 28 Feb 2026 09:06:09 -0800 Subject: [PATCH 2/2] fix: Resolve Update Dialog 'Download now' button on Linux This commit addresses task #5: - The 'Download now' button in the update dialog was unresponsive on Linux AppImage builds because failed to launch the default browser due to bundled library conflicts. - Replaced the call with and cleared (similar to the EvE login fix) to ensure the release URL opens correctly. - Also fixed a minor indentation issue in . --- src/shortcircuit/app.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/shortcircuit/app.py b/src/shortcircuit/app.py index 41aaac2..1259d54 100644 --- a/src/shortcircuit/app.py +++ b/src/shortcircuit/app.py @@ -1260,7 +1260,7 @@ def btn_trip_config_clicked(self): from shortcircuit.model.utility.gui_sources import SourceConfigurationDialog dialog = SourceConfigurationDialog(self.source_manager, self) - dialog.sources_saved.connect(self._on_sources_saved_in_dialog) # Connect new signal + dialog.sources_saved.connect(self._on_sources_saved_in_dialog) if not dialog.exec(): return @@ -1415,26 +1415,24 @@ def version_check_done(self, latest): version_box.addButton("Remind me later", QtWidgets.QMessageBox.RejectRole) ret = version_box.exec() - if ret != QtWidgets.QMessageBox.AcceptRole: - return - - url_to_open = QtCore.QUrl( - f"https://github.com/mogglemoss/shortcircuit/releases/tag/{latest['tag_name']}" - ) - - if sys.platform == 'linux': - import subprocess - env = os.environ.copy() - env.pop('LD_LIBRARY_PATH', None) - try: - subprocess.Popen(['xdg-open', url_to_open.toString()], env=env) - except OSError: - webbrowser.open(url_to_open.toString()) - else: - QtGui.QDesktopServices.openUrl(url_to_open) - ) + if ret != QtWidgets.QMessageBox.AcceptRole: + return + + url_to_open = QtCore.QUrl( + f"https://github.com/mogglemoss/shortcircuit/releases/tag/{latest['tag_name']}" ) + if sys.platform == 'linux': + import subprocess + env = os.environ.copy() + env.pop('LD_LIBRARY_PATH', None) + try: + subprocess.Popen(['xdg-open', url_to_open.toString()], env=env) + except OSError: + webbrowser.open(url_to_open.toString()) + else: + QtGui.QDesktopServices.openUrl(url_to_open) + # event: QCloseEvent def closeEvent(self, event): self.write_settings()