diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 210186e3..aba22e7b 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1059,6 +1059,8 @@ class AppWindow(object): self.status['text'] = str(e) def onexit(self, event=None): + config.set_shutdown() # Signal we're in shutdown now. + # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 if platform != 'darwin' or self.w.winfo_rooty() > 0: x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267' diff --git a/config.py b/config.py index 08d7ec89..76fa6fdf 100644 --- a/config.py +++ b/config.py @@ -117,6 +117,8 @@ class Config(object): if platform=='darwin': def __init__(self): + self.__in_shutdown = False # Is the application currently shutting down ? + self.app_dir = join(NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], appname) if not isdir(self.app_dir): mkdir(self.app_dir) @@ -177,9 +179,17 @@ class Config(object): self.save() self.defaults = None + def set_shutdown(self): + self.__in_shutdown = True + + @property + def shutting_down(self) -> bool: + return self.__in_shutdown + elif platform=='win32': def __init__(self): + self.__in_shutdown = False # Is the application currently shutting down ? self.app_dir = join(KnownFolderPath(FOLDERID_LocalAppData), appname) if not isdir(self.app_dir): @@ -268,11 +278,19 @@ class Config(object): RegCloseKey(self.hkey) self.hkey = None + def set_shutdown(self): + self.__in_shutdown = True + + @property + def shutting_down(self) -> bool: + return self.__in_shutdown + elif platform=='linux': SECTION = 'config' def __init__(self): + self.__in_shutdown = False # Is the application currently shutting down ? # http://standards.freedesktop.org/basedir-spec/latest/ar01s03.html self.app_dir = join(getenv('XDG_DATA_HOME', expanduser('~/.local/share')), appname) @@ -346,6 +364,13 @@ class Config(object): self.save() self.config = None + def set_shutdown(self): + self.__in_shutdown = True + + @property + def shutting_down(self) -> bool: + return self.__in_shutdown + def _escape(self, val): return str(val).replace(u'\\', u'\\\\').replace(u'\n', u'\\n').replace(u';', u'\\;') diff --git a/dashboard.py b/dashboard.py index 4e7f9ce6..4259dccb 100644 --- a/dashboard.py +++ b/dashboard.py @@ -115,6 +115,9 @@ class Dashboard(FileSystemEventHandler): # Can be called either in watchdog thread or, if polling, in main thread. def process(self, logfile=None): + if config.shutting_down: + return + try: with open(join(self.currentdir, 'Status.json'), 'rb') as h: data = h.read().strip() @@ -126,6 +129,7 @@ class Dashboard(FileSystemEventHandler): self.status != entry): self.status = entry self.root.event_generate('<>', when="tail") + except Exception: logger.exception('Reading Status.json') diff --git a/hotkey.py b/hotkey.py index 8ee8762d..9b261ccc 100644 --- a/hotkey.py +++ b/hotkey.py @@ -92,11 +92,15 @@ if platform == 'darwin': self.observer = NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, self._handler) def _poll(self): + if config.shutting_down: + return + # No way of signalling to Tkinter from within the callback handler block that doesn't # cause Python to crash, so poll. if self.activated: self.activated = False self.root.event_generate('<>', when="tail") + if self.keycode or self.modifiers: self.root.after(HotkeyMgr.POLL, self._poll) @@ -123,6 +127,9 @@ if platform == 'darwin': self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE def _acquire_poll(self): + if config.shutting_down: + return + # No way of signalling to Tkinter from within the monkey-patched event handler that doesn't # cause Python to crash, so poll. if self.acquire_state: @@ -316,7 +323,8 @@ elif platform == 'win32': while GetMessage(ctypes.byref(msg), None, 0, 0) != 0: if msg.message == WM_HOTKEY: if config.getint('hotkey_always') or WindowTitle(GetForegroundWindow()).startswith('Elite - Dangerous'): - self.root.event_generate('<>', when="tail") + if not config.shutting_down: + self.root.event_generate('<>', when="tail") else: # Pass the key on UnregisterHotKey(None, 1) diff --git a/monitor.py b/monitor.py index 9b57ef2d..f8d4a201 100644 --- a/monitor.py +++ b/monitor.py @@ -337,7 +337,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.event_queue.append(line) if self.event_queue: - self.root.event_generate('<>', when="tail") + if not config.shutting_down: + self.root.event_generate('<>', when="tail") log_pos = loghandle.tell() @@ -355,7 +356,9 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below '{{ "timestamp":"{}", "event":"ShutDown" }}'.format(strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())) ) - self.root.event_generate('<>', when="tail") + if not config.shutting_down: + self.root.event_generate('<>', when="tail") + self.game_was_running = False else: diff --git a/plug.py b/plug.py index 07d43dfb..755fec1c 100644 --- a/plug.py +++ b/plug.py @@ -366,9 +366,16 @@ def notify_newdata(data, is_beta): def show_error(err): """ Display an error message in the status line of the main window. + + Will be NOP during shutdown to avoid Tk hang. :param err: .. versionadded:: 2.3.7 """ + + if config.shutting_down: + logger.info(f'Called during shutdown: "{str(err)}"') + return + if err and last_error['root']: last_error['msg'] = str(err) last_error['root'].event_generate('<>', when="tail") diff --git a/plugins/edsm.py b/plugins/edsm.py index 80c0077a..e9fbd46c 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -598,8 +598,10 @@ def worker() -> None: if not closing and e['event'] in ('StartUp', 'Location', 'FSDJump', 'CarrierJump'): # Update main window's system status this.lastlookup = r - # calls update_status in main thread - this.system_link.event_generate('<>', when="tail") + + if not config.shutting_down: + # calls update_status in main thread + this.system_link.event_generate('<>', when="tail") if r['msgnum'] // 100 != 1: logger.warning(f'EDSM event with not-1xx status:\n{r["msgnum"]}\n{r["msg"]}\n' diff --git a/plugins/inara.py b/plugins/inara.py index 4b61322c..ac5d53f5 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1290,13 +1290,17 @@ def send_data(url: str, data: Mapping[str, Any]) -> bool: 'setCommanderTravelLocation' ): this.lastlocation = reply_event.get('eventData', {}) - # calls update_location in main thread - this.system_link.event_generate('<>', when="tail") + + if not config.shutting_down: + # calls update_location in main thread + this.system_link.event_generate('<>', when="tail") elif data_event['eventName'] in ['addCommanderShip', 'setCommanderShip']: this.lastship = reply_event.get('eventData', {}) - # calls update_ship in main thread - this.system_link.event_generate('<>', when="tail") + + if not config.shutting_down: + # calls update_ship in main thread + this.system_link.event_generate('<>', when="tail") return True # regardless of errors above, we DID manage to send it, therefore inform our caller as such diff --git a/prefs.py b/prefs.py index 95612e6a..e9c3afab 100644 --- a/prefs.py +++ b/prefs.py @@ -1157,4 +1157,5 @@ class PreferencesDialog(tk.Toplevel): except Exception: AXIsProcessTrustedWithOptions({kAXTrustedCheckOptionPrompt: True}) - self.parent.event_generate('<>', when="tail") + if not config.shutting_down: + self.parent.event_generate('<>', when="tail") diff --git a/protocol.py b/protocol.py index 49345643..7990b952 100644 --- a/protocol.py +++ b/protocol.py @@ -32,7 +32,9 @@ class GenericProtocolHandler(object): def event(self, url): self.lastpayload = url - self.master.event_generate('<>', when="tail") + + if not config.shutting_down: + self.master.event_generate('<>', when="tail") if sys.platform == 'darwin' and getattr(sys, 'frozen', False): diff --git a/update.py b/update.py index c234dfc9..aa2a1a8f 100644 --- a/update.py +++ b/update.py @@ -40,7 +40,8 @@ class Updater(object): Receive (Win)Sparkle shutdown request and send it to parent. :rtype: None """ - self.root.event_generate('<>', when="tail") + if not config.shutting_down: + self.root.event_generate('<>', when="tail") def use_internal(self) -> bool: """ @@ -208,4 +209,4 @@ class Updater(object): :return: None """ - pass \ No newline at end of file + pass