From 42bc097e92cb67ea135d7db8280292e63b1955b1 Mon Sep 17 00:00:00 2001 From: Inutt <1228850-inutt@users.noreply.gitlab.com> Date: Fri, 28 Nov 2025 03:37:14 +0000 Subject: [PATCH 1/2] Pass events prior to initial LoadGame event to plugins Done by temporarily buffering events before monitor.mode is set, then notifying plugins about them before sending the first event where monitor.mode has a value. LoadGame is currently the only event that sets monitor.mode. Fixes #2286 diff --git a/EDMarketConnector.py b/EDMarketConnector.py index e7bdf490..d53b625d 100644 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -22,7 +22,7 @@ import threading import webbrowser from os import chdir, environ from time import localtime, strftime, time -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any, Literal, MutableMapping from constants import applongname, appname, protocolhandler_redirect # Have this as early as possible for people running EDMarketConnector.exe @@ -458,6 +458,8 @@ class AppWindow: def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out + self.early_journal_events: list[MutableMapping[str, Any]] = [] + self.capi_query_holdoff_time = config.get_int('querytime', default=0) + companion.capi_query_cooldown self.capi_fleetcarrier_query_holdoff_time = config.get_int('fleetcarrierquerytime', default=0) \ + companion.capi_fleetcarrier_query_cooldown @@ -1400,6 +1402,19 @@ class AppWindow: self.cooldown() logger.trace_if('capi.worker', '...done') + def handle_plugin_event_error(self, err: str | None) -> None: + """ + Display a plugin's error message at the bottom of the main window. + + If there was no error message passed in, nothing will be done. + + :param err: Message to display, or None for no error. + """ + if err: + self.status['text'] = err + if not config.get_int('hotkey_mute'): + hotkeymgr.play_bad() + def journal_event(self, event: str): # noqa: C901, CCR001 # Currently not easily broken up. """ Handle a Journal event passed through event queue from monitor.py. @@ -1513,15 +1528,21 @@ class AppWindow: if monitor.cmdr and monitor.mode == 'CQC' and entry['event']: err = plug.notify_journal_entry_cqc(monitor.cmdr, monitor.is_beta, entry, monitor.state) - if err: - self.status['text'] = err - if not config.get_int('hotkey_mute'): - hotkeymgr.play_bad() + self.handle_plugin_event_error(err) return # in CQC if not entry['event'] or not monitor.mode: + # Game session has not fully loaded yet. We may be receiving events from the + # journal, but don't know which game mode (Open/CQC/solo etc) we're running in. logger.trace_if('journal.queue', 'Startup, returning') + + if entry['event']: + # Since we don't know which mode the game is in (and therefore whether to send + # the event to a plugin's `journal_event` or `journal_event_cqc` function), + # temporarily queue it so we can pass it on once the mode is known. + self.early_journal_events.append(entry) + return # Startup if entry['event'] in ('StartUp', 'LoadGame') and monitor.started: @@ -1538,6 +1559,25 @@ class AppWindow: if not dashboard.start(self.w, monitor.started): logger.info("Can't start Status monitoring") + # monitor.cmdr should always be set if monitor.mode is set (and monitor.mode must be set + # if we reach this point in the function), but we check monitor.cmdr here so the mypy + # doesn't complain about passing str|None where str is required. + if self.early_journal_events and monitor.cmdr: + for early_entry in self.early_journal_events: + if monitor.mode == 'CQC': + err = plug.notify_journal_entry_cqc(monitor.cmdr, monitor.is_beta, early_entry, monitor.state) + else: + err = plug.notify_journal_entry( + monitor.cmdr, + monitor.is_beta, + monitor.state['SystemName'], + monitor.state['StationName'], + early_entry, + monitor.state + ) + self.handle_plugin_event_error(err) + self.early_journal_events.clear() + # Export loadout if entry['event'] == 'Loadout' and not monitor.state['Captain'] \ and config.get_int('output') & config.OUT_SHIP: @@ -1553,10 +1593,7 @@ class AppWindow: monitor.state ) - if err: - self.status['text'] = err - if not config.get_int('hotkey_mute'): - hotkeymgr.play_bad() + self.handle_plugin_event_error(err) auto_update = False # Only if auth callback is not pending @@ -1638,11 +1675,7 @@ class AppWindow: # Currently we don't do anything with these events if monitor.cmdr: err = plug.notify_dashboard_entry(monitor.cmdr, monitor.is_beta, entry) - - if err: - self.status['text'] = err - if not config.get_int('hotkey_mute'): - hotkeymgr.play_bad() + self.handle_plugin_event_error(err) def plugin_error(self, event=None) -> None: """Display asynchronous error from plugin.""" --- EDMarketConnector.py | 61 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index e7bdf490..d53b625d 100644 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -22,7 +22,7 @@ import threading import webbrowser from os import chdir, environ from time import localtime, strftime, time -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any, Literal, MutableMapping from constants import applongname, appname, protocolhandler_redirect # Have this as early as possible for people running EDMarketConnector.exe @@ -458,6 +458,8 @@ class AppWindow: def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out + self.early_journal_events: list[MutableMapping[str, Any]] = [] + self.capi_query_holdoff_time = config.get_int('querytime', default=0) + companion.capi_query_cooldown self.capi_fleetcarrier_query_holdoff_time = config.get_int('fleetcarrierquerytime', default=0) \ + companion.capi_fleetcarrier_query_cooldown @@ -1400,6 +1402,19 @@ class AppWindow: self.cooldown() logger.trace_if('capi.worker', '...done') + def handle_plugin_event_error(self, err: str | None) -> None: + """ + Display a plugin's error message at the bottom of the main window. + + If there was no error message passed in, nothing will be done. + + :param err: Message to display, or None for no error. + """ + if err: + self.status['text'] = err + if not config.get_int('hotkey_mute'): + hotkeymgr.play_bad() + def journal_event(self, event: str): # noqa: C901, CCR001 # Currently not easily broken up. """ Handle a Journal event passed through event queue from monitor.py. @@ -1513,15 +1528,21 @@ class AppWindow: if monitor.cmdr and monitor.mode == 'CQC' and entry['event']: err = plug.notify_journal_entry_cqc(monitor.cmdr, monitor.is_beta, entry, monitor.state) - if err: - self.status['text'] = err - if not config.get_int('hotkey_mute'): - hotkeymgr.play_bad() + self.handle_plugin_event_error(err) return # in CQC if not entry['event'] or not monitor.mode: + # Game session has not fully loaded yet. We may be receiving events from the + # journal, but don't know which game mode (Open/CQC/solo etc) we're running in. logger.trace_if('journal.queue', 'Startup, returning') + + if entry['event']: + # Since we don't know which mode the game is in (and therefore whether to send + # the event to a plugin's `journal_event` or `journal_event_cqc` function), + # temporarily queue it so we can pass it on once the mode is known. + self.early_journal_events.append(entry) + return # Startup if entry['event'] in ('StartUp', 'LoadGame') and monitor.started: @@ -1538,6 +1559,25 @@ class AppWindow: if not dashboard.start(self.w, monitor.started): logger.info("Can't start Status monitoring") + # monitor.cmdr should always be set if monitor.mode is set (and monitor.mode must be set + # if we reach this point in the function), but we check monitor.cmdr here so the mypy + # doesn't complain about passing str|None where str is required. + if self.early_journal_events and monitor.cmdr: + for early_entry in self.early_journal_events: + if monitor.mode == 'CQC': + err = plug.notify_journal_entry_cqc(monitor.cmdr, monitor.is_beta, early_entry, monitor.state) + else: + err = plug.notify_journal_entry( + monitor.cmdr, + monitor.is_beta, + monitor.state['SystemName'], + monitor.state['StationName'], + early_entry, + monitor.state + ) + self.handle_plugin_event_error(err) + self.early_journal_events.clear() + # Export loadout if entry['event'] == 'Loadout' and not monitor.state['Captain'] \ and config.get_int('output') & config.OUT_SHIP: @@ -1553,10 +1593,7 @@ class AppWindow: monitor.state ) - if err: - self.status['text'] = err - if not config.get_int('hotkey_mute'): - hotkeymgr.play_bad() + self.handle_plugin_event_error(err) auto_update = False # Only if auth callback is not pending @@ -1638,11 +1675,7 @@ class AppWindow: # Currently we don't do anything with these events if monitor.cmdr: err = plug.notify_dashboard_entry(monitor.cmdr, monitor.is_beta, entry) - - if err: - self.status['text'] = err - if not config.get_int('hotkey_mute'): - hotkeymgr.play_bad() + self.handle_plugin_event_error(err) def plugin_error(self, event=None) -> None: """Display asynchronous error from plugin.""" From e41317245676936f02da1140450f2148742fcce1 Mon Sep 17 00:00:00 2001 From: Inutt <1228850-inutt@users.noreply.gitlab.com> Date: Fri, 28 Nov 2025 16:59:42 +0000 Subject: [PATCH 2/2] Unset monitor.mode on Commander journal event to avoid reusing previous mode Changing game mode would otherwise re-use the previously used game mode for events before the new LoadGame event. This resets the mode on the first available event so they are buffered again until the new game mode is determined. --- monitor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monitor.py b/monitor.py index 3df0f85c..ee94d2f1 100644 --- a/monitor.py +++ b/monitor.py @@ -588,6 +588,7 @@ class EDLogs(FileSystemEventHandler): elif event_type == 'commander': self.live = True # First event in 3.0 self.cmdr = entry['Name'] + self.mode = None self.state['FID'] = entry['FID'] logger.trace_if(STARTUP, f'"Commander" event, {monitor.cmdr=}, {monitor.state["FID"]=}')