Disconnect from the device when opening a file or closing the app

Opening a file now drops the live connection once a file is chosen, without
asking. File > Exit and the window close box go through quit_app, which
closes the Sox session properly instead of leaving it for the device to
time out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 23:45:12 +03:00
co-authored by Claude Opus 5.5
parent 85b6fa62f3
commit dbeeb78916
3 changed files with 42 additions and 15 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it.
import configparser import configparser
import os import os
VERSION = "0.0.0.139" VERSION = "0.0.0.140"
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties") PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
DEFAULT_GRID_SIZE = 10 DEFAULT_GRID_SIZE = 10
DEFAULT_ADD_TIMESTAMP = True DEFAULT_ADD_TIMESTAMP = True
+27 -1
View File
@@ -2435,6 +2435,30 @@ def do_device_disconnect():
wiresheet.render_components([]) wiresheet.render_components([])
def quit_app():
"""File > Exit and the window's close box: let go of the device before going.
Leaving without a proper close keeps the device's Sox session and subscriptions
alive until it times them out, so the next connect may be turned away.
"""
global _cov_poll_id
if _cov_poll_id is not None:
app_state.window.after_cancel(_cov_poll_id)
_cov_poll_id = None
ds = app_state.device_session
app_state.device_session = None
app_state.live_mode = False
if ds is not None:
try:
ds.disconnect()
except Exception:
pass
app_state.window.destroy()
def _start_cov_polling(): def _start_cov_polling():
"""Starts the 100ms COV event polling loop on the main thread.""" """Starts the 100ms COV event polling loop on the main thread."""
global _cov_poll_id global _cov_poll_id
@@ -2616,7 +2640,7 @@ menu_file.add_command(label="Save As...", command=sax_file.save_file_as)
menu_file.add_separator() menu_file.add_separator()
menu_file.add_command(label="Print...", command=wiresheet.print_sheet, accelerator=f"{accel_mod}+P") menu_file.add_command(label="Print...", command=wiresheet.print_sheet, accelerator=f"{accel_mod}+P")
menu_file.add_separator() menu_file.add_separator()
menu_file.add_command(label="Exit", command=window.quit) menu_file.add_command(label="Exit", command=quit_app)
menu_bar.add_cascade(label="File", menu=menu_file) menu_bar.add_cascade(label="File", menu=menu_file)
menu_edit = tk.Menu(menu_bar, tearoff=0) menu_edit = tk.Menu(menu_bar, tearoff=0)
@@ -3000,6 +3024,8 @@ paned_window.add(frame_editor, weight=3)
app_state.dirty_display_callback = refresh_dirty_display app_state.dirty_display_callback = refresh_dirty_display
undo.set_refresh_callback(refresh_all_views) undo.set_refresh_callback(refresh_all_views)
sax_file.set_tree_filled_callback(on_tree_filled) sax_file.set_tree_filled_callback(on_tree_filled)
sax_file.set_disconnect_device_callback(do_device_disconnect)
window.protocol("WM_DELETE_WINDOW", quit_app)
refresh_palette_kits() refresh_palette_kits()
apply_left_pane_width() apply_left_pane_width()
+14 -13
View File
@@ -31,6 +31,15 @@ def set_tree_filled_callback(callback):
_tree_filled = callback _tree_filled = callback
_disconnect_device = None
def set_disconnect_device_callback(callback):
"""Registers how to drop the live device session before a file takes its place."""
global _disconnect_device
_disconnect_device = callback
def parse_xml_to_tree(treeview, parent_node, xml_element): def parse_xml_to_tree(treeview, parent_node, xml_element):
"""Recursively traverses XML elements and adds <comp> tags to the Treeview.""" """Recursively traverses XML elements and adds <comp> tags to the Treeview."""
for child in xml_element: for child in xml_element:
@@ -132,19 +141,6 @@ def open_file():
messagebox.showwarning("Action Blocked", "Cannot load SAX profiles because 'Sedona home folder' configuration is missing or empty.") messagebox.showwarning("Action Blocked", "Cannot load SAX profiles because 'Sedona home folder' configuration is missing or empty.")
return return
if app_state.live_mode:
answer = messagebox.askyesnocancel(
"Device Connected",
"A live device connection is active.\n\n"
"Disconnect from the device and open a file?\n\n"
"Choose Yes to disconnect and open, No to cancel.",
)
if not answer:
return
# Import here to avoid circular import at module level
from main import do_device_disconnect
do_device_disconnect()
file_path = filedialog.askopenfilename( file_path = filedialog.askopenfilename(
title="Select SAX File", title="Select SAX File",
filetypes=[("SAX Files", "*.sax"), ("XML Files", "*.xml"), ("All Files", "*.*")], filetypes=[("SAX Files", "*.sax"), ("XML Files", "*.xml"), ("All Files", "*.*")],
@@ -152,6 +148,11 @@ def open_file():
if not file_path: if not file_path:
return return
# A session is a file or a device, never both. The device goes only once a file has
# actually been chosen, so cancelling the dialog leaves the connection as it was.
if app_state.device_session is not None and _disconnect_device is not None:
_disconnect_device()
canvas = app_state.canvas canvas = app_state.canvas
tree = app_state.tree tree = app_state.tree