Hooks: dispatch HOOK messages directly, bypassing state.messages
The previous fix routed (MSG,HOOK:...) lines through state.messages and then immediately ack'd them to suppress the user-visible popup. But state changes are debounced 0.25s before listeners fire, so the HOOK message was already ack'd (removed from the list) by the time Hooks._on_state_change saw the update - and the hook never ran. Add Hooks.dispatch_hook_message() as a direct entry point and call it from Planner._add_message. HOOK lines are dispatched synchronously from the planner thread; the user message list is left untouched, so no popup leaks and no debounce race.
This commit is contained in:
@@ -249,6 +249,23 @@ class Hooks:
|
||||
|
||||
# -- Hook execution --
|
||||
|
||||
def dispatch_hook_message(self, text):
|
||||
"""Direct entry point for HOOK:<event>:<data> messages emitted
|
||||
by the planner via (MSG,HOOK:...) comments. Bypasses the
|
||||
state.messages list (which the UI also reads), so callers can
|
||||
suppress popup display without losing the hook dispatch.
|
||||
|
||||
Returns True if the text matched a HOOK: line and was
|
||||
dispatched, False otherwise."""
|
||||
if not isinstance(text, str) or not text.startswith('HOOK:'):
|
||||
return False
|
||||
parts = text[5:].split(':', 1)
|
||||
event = parts[0]
|
||||
data = parts[1] if len(parts) > 1 else ''
|
||||
self._fire('custom', {'event': event, 'data': data},
|
||||
custom_name=event)
|
||||
return True
|
||||
|
||||
def register_internal(self, name, fn, block_unpause=True,
|
||||
auto_resume=True, timeout=120):
|
||||
"""Register an in-process handler for HOOK:<name> events.
|
||||
|
||||
@@ -196,25 +196,19 @@ class Planner():
|
||||
|
||||
|
||||
def _add_message(self, text):
|
||||
# HOOK:<event>:<data> messages are an internal IPC channel
|
||||
# between the gcode preprocessor and Hooks; they should not
|
||||
# surface as user-visible message popups. Hooks._on_state_change
|
||||
# will still see them via the messages list before we filter.
|
||||
line = self.ctrl.state.get('line', 0)
|
||||
if 0 <= line: where = '%s:%d' % (self.where, line)
|
||||
else: where = self.where
|
||||
|
||||
if isinstance(text, str) and text.startswith('HOOK:'):
|
||||
# Push it through state.messages so Hooks._on_state_change
|
||||
# can see and dispatch it, then immediately ack it so the UI
|
||||
# doesn't render a popup.
|
||||
self.ctrl.state.add_message(text)
|
||||
try:
|
||||
msgs = self.ctrl.state.get('messages', []) or []
|
||||
if msgs:
|
||||
self.ctrl.state.ack_message(msgs[-1].get('id', -1))
|
||||
except Exception:
|
||||
pass
|
||||
# HOOK:<event>:<data> messages are an internal IPC channel
|
||||
# between the gcode preprocessor and Hooks; bypass the user
|
||||
# message list so they don't surface as popups, and dispatch
|
||||
# the hook directly. Routing through state.messages would
|
||||
# only deliver it after the 0.25s state-change debounce, by
|
||||
# which point we'd have to keep it visible to ensure Hooks
|
||||
# could see it.
|
||||
hooks = getattr(self.ctrl, 'hooks', None)
|
||||
if hooks is not None and hooks.dispatch_hook_message(text):
|
||||
self.log.info('HOOK msg: %s' % text, where = where)
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user