Fix jump-to-end firing on every line: debounce sustained unfocus, not recency

Debug logging disproved the previous "recent append perturbs GetFocus"
theory - one spurious jump fired 7.67s after the last append. The real
pattern: GetFocus() reports "not focused" for a single poll tick now
and then even while the user never left the window, and that lone blip
was enough for the very next tick to be read as "just switched to the
window," triggering a jump on practically every incoming line.

Now requires 3 consecutive unfocused poll ticks (~0.6s) before the next
focused tick counts as a genuine switch, filtering out single-tick
noise from MUSHclient's own UI thread. Removed the now-unused
last_append_clock tracking from the previous (wrong) fix attempt.
This commit is contained in:
OlegTheSnowman
2026-07-11 00:39:44 +03:00
parent b95f743bb2
commit 466be8a544

View File

@@ -407,11 +407,7 @@ end
-- Note: text should use "\r\n" line endings, not bare "\n" - the plain
-- Win32 Edit control that backs the notepad doesn't recognise bare "\n"
-- as a line break for scrolling/line-count purposes.
last_append_clock = 0
function SmartAppendToNotepad(title, text)
last_append_clock = os.clock()
local edit = find_notepad_edit(title)
local was_at_end = true
local sel_start, sel_end, first_visible
@@ -459,36 +455,49 @@ end
-- while a separate notepad window has focus (confirmed by Nick Gammon on
-- the MUSHclient forum), so this can't be event-driven - polling every
-- 0.2s is the safe, non-hooking way to catch it.
--
-- GetFocus() genuinely reports "not focused" for a single poll tick now
-- and then, even while the user never left the window and nothing was
-- just appended (confirmed via logging - one case was 7+ seconds after
-- the last append) - just noise from MUSHclient's own UI thread being
-- momentarily busy. A single such blip was enough to make this treat
-- the very next tick as "just switched to the window" and jump, on
-- practically every incoming line. Requiring several *consecutive*
-- unfocused ticks before the next focused tick counts as a genuine
-- switch filters that out.
was_output_focused = false
unfocused_streak = 0
FOCUS_DEBOUNCE_TICKS = 3 -- ~0.6s of sustained unfocus at the 0.2s poll rate
function poll_output_focus()
if GetVariable("jump_to_end_on_focus") == "0" then
was_output_focused = false
unfocused_streak = 0
return
end
local edit = find_notepad_edit("output")
if not edit then
was_output_focused = false
unfocused_streak = 0
return
end
local focused = GetFocus()
-- Compare by string representation, not raw "==": Alien's pointer
-- returns are full userdata objects, so two separate calls returning
-- the same real HWND don't reliably compare equal with "==" (which
-- fell back to identity comparison), causing spurious "just gained
-- focus" edges and repeated jump-to-end even while focus never moved.
-- falls back to identity comparison).
local is_focused = (tostring(focused) == tostring(edit))
-- Skip the jump if a SmartAppendToNotepad call happened very recently:
-- AppendToNotepad's own SetSel/ReplaceSel appears to momentarily
-- perturb GetFocus() around each append, which was making this poll
-- see a spurious "just gained focus" edge - and jump to the end - on
-- practically every incoming line, even though the user never left
-- the window.
local recent_append = (os.clock() - last_append_clock) < 0.5
if is_focused and not was_output_focused and not recent_append then
if is_focused then
if not was_output_focused and unfocused_streak >= FOCUS_DEBOUNCE_TICKS then
jump_notepad_to_end(edit)
end
was_output_focused = is_focused
unfocused_streak = 0
was_output_focused = true
else
unfocused_streak = unfocused_streak + 1
was_output_focused = false
end
end
------------------------------------------------------------------