Skip to content

Commit 7a2732b

Browse files
feat: fire OnRequestSendFocus event when DECSET 1004 is enabled
When focus reporting is enabled via DECSET 1004, fire an OnRequestSendFocus event so consumers can immediately report the current focus state. This matches the upstream xterm.js behavior. Fixes #39 Co-authored-by: Ona <no-reply@ona.com>
1 parent 6c3e68a commit 7a2732b

4 files changed

Lines changed: 90 additions & 0 deletions

File tree

inputhandler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ type InputHandler struct {
9999
OnRequestResetEmitter EventEmitter[struct{}]
100100
OnRequestRefreshRowsEmitter EventEmitter[RowRange]
101101
OnColorEmitter EventEmitter[[]ColorEvent]
102+
OnRequestSendFocusEmitter EventEmitter[struct{}]
102103
OnRequestSyncScrollBarEmitter EventEmitter[struct{}]
103104
OnRequestColorSchemeQueryEmitter EventEmitter[struct{}]
104105
OnRequestWindowsOptionsReportEmitter EventEmitter[WindowsOptionsReportType]
@@ -563,6 +564,7 @@ func (h *InputHandler) Dispose() {
563564
h.OnRequestResetEmitter.Dispose()
564565
h.OnRequestRefreshRowsEmitter.Dispose()
565566
h.OnColorEmitter.Dispose()
567+
h.OnRequestSendFocusEmitter.Dispose()
566568
h.OnRequestSyncScrollBarEmitter.Dispose()
567569
h.OnRequestColorSchemeQueryEmitter.Dispose()
568570
h.OnRequestWindowsOptionsReportEmitter.Dispose()

inputhandler_csi.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,7 @@ func (h *InputHandler) setModePrivate(params *Params) bool {
658658
h.coreService.DecPrivateModes.MouseTrackingMode = "ANY"
659659
case 1004:
660660
h.coreService.DecPrivateModes.SendFocus = true
661+
h.OnRequestSendFocusEmitter.Fire(struct{}{})
661662
case 1006:
662663
h.coreService.DecPrivateModes.MouseEncoding = "SGR"
663664
case 1016:

inputhandler_csi_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,83 @@ func TestWindowOptionsReportTerminalLevel(t *testing.T) {
18561856
})
18571857
}
18581858

1859+
// --- OnRequestSendFocus (DECSET 1004) tests ---
1860+
1861+
func TestOnRequestSendFocus(t *testing.T) {
1862+
t.Parallel()
1863+
1864+
t.Run("fires_when_focus_reporting_enabled", func(t *testing.T) {
1865+
t.Parallel()
1866+
h := newTestInputHandler(80, 24)
1867+
fired := 0
1868+
h.OnRequestSendFocusEmitter.Event(func(struct{}) {
1869+
fired++
1870+
})
1871+
1872+
// Enable focus reporting (DECSET 1004).
1873+
h.ParseString("\x1b[?1004h")
1874+
1875+
if fired != 1 {
1876+
t.Errorf("expected OnRequestSendFocus to fire once, got %d", fired)
1877+
}
1878+
})
1879+
1880+
t.Run("does_not_fire_when_focus_reporting_disabled", func(t *testing.T) {
1881+
t.Parallel()
1882+
h := newTestInputHandler(80, 24)
1883+
fired := 0
1884+
h.OnRequestSendFocusEmitter.Event(func(struct{}) {
1885+
fired++
1886+
})
1887+
1888+
// Disable focus reporting (DECRST 1004) without enabling first.
1889+
h.ParseString("\x1b[?1004l")
1890+
1891+
if fired != 0 {
1892+
t.Errorf("expected OnRequestSendFocus not to fire, got %d", fired)
1893+
}
1894+
})
1895+
1896+
t.Run("fires_each_time_focus_reporting_enabled", func(t *testing.T) {
1897+
t.Parallel()
1898+
h := newTestInputHandler(80, 24)
1899+
fired := 0
1900+
h.OnRequestSendFocusEmitter.Event(func(struct{}) {
1901+
fired++
1902+
})
1903+
1904+
// Enable, disable, enable again.
1905+
h.ParseString("\x1b[?1004h")
1906+
h.ParseString("\x1b[?1004l")
1907+
h.ParseString("\x1b[?1004h")
1908+
1909+
if fired != 2 {
1910+
t.Errorf("expected OnRequestSendFocus to fire twice, got %d", fired)
1911+
}
1912+
})
1913+
}
1914+
1915+
func TestOnRequestSendFocusTerminalLevel(t *testing.T) {
1916+
t.Parallel()
1917+
1918+
t.Run("DECSET_1004_forwarded_to_terminal", func(t *testing.T) {
1919+
t.Parallel()
1920+
term := newTestTerminal(80, 24)
1921+
defer term.Dispose()
1922+
1923+
fired := false
1924+
d := term.OnRequestSendFocus(func() {
1925+
fired = true
1926+
})
1927+
defer d.Dispose()
1928+
1929+
term.WriteString("\x1b[?1004h")
1930+
if !fired {
1931+
t.Fatal("Terminal.OnRequestSendFocus not fired for DECSET 1004")
1932+
}
1933+
})
1934+
}
1935+
18591936
// --- VtExtensions gating tests ---
18601937

18611938
func TestVtExtensions_Win32InputModeGating(t *testing.T) {

terminal.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type Terminal struct {
5959
OnScrollEmitter EventEmitter[int]
6060
OnRenderEmitter EventEmitter[RowRange]
6161
OnWriteParsedEmitter EventEmitter[struct{}]
62+
OnRequestSendFocusEmitter EventEmitter[struct{}]
6263
OnRequestColorSchemeQueryEmitter EventEmitter[struct{}]
6364
OnRequestWindowsOptionsReportEmitter EventEmitter[WindowsOptionsReportType]
6465
}
@@ -97,6 +98,7 @@ func New(opts ...Option) *Terminal {
9798
ih.OnLineFeedEmitter.Event(func(struct{}) { t.OnLineFeedEmitter.Fire(struct{}{}) })
9899
ih.OnCursorMoveEmitter.Event(func(struct{}) { t.OnCursorMoveEmitter.Fire(struct{}{}) })
99100
ih.OnRequestRefreshRowsEmitter.Event(func(r RowRange) { t.OnRenderEmitter.Fire(r) })
101+
ih.OnRequestSendFocusEmitter.Event(func(struct{}) { t.OnRequestSendFocusEmitter.Fire(struct{}{}) })
100102
ih.OnRequestColorSchemeQueryEmitter.Event(func(struct{}) { t.OnRequestColorSchemeQueryEmitter.Fire(struct{}{}) })
101103
ih.OnRequestWindowsOptionsReportEmitter.Event(func(rt WindowsOptionsReportType) { t.OnRequestWindowsOptionsReportEmitter.Fire(rt) })
102104

@@ -280,6 +282,13 @@ func (t *Terminal) OnRender(fn func(RowRange)) Disposable {
280282
return t.OnRenderEmitter.Event(fn)
281283
}
282284

285+
// OnRequestSendFocus subscribes to focus-reporting enable events (DECSET 1004).
286+
// Fired when the application enables focus tracking so the host can immediately
287+
// report the current focus state.
288+
func (t *Terminal) OnRequestSendFocus(fn func()) Disposable {
289+
return t.OnRequestSendFocusEmitter.Event(func(struct{}) { fn() })
290+
}
291+
283292
// OnRequestColorSchemeQuery subscribes to DSR 996 color scheme query events.
284293
// Fired when the client sends CSI ? 996 n while color scheme updates (DECSET 2031) are enabled.
285294
func (t *Terminal) OnRequestColorSchemeQuery(fn func()) Disposable {
@@ -447,6 +456,7 @@ func (t *Terminal) Dispose() {
447456
t.OnScrollEmitter.Dispose()
448457
t.OnRenderEmitter.Dispose()
449458
t.OnWriteParsedEmitter.Dispose()
459+
t.OnRequestSendFocusEmitter.Dispose()
450460
t.OnRequestColorSchemeQueryEmitter.Dispose()
451461
t.OnRequestWindowsOptionsReportEmitter.Dispose()
452462
}

0 commit comments

Comments
 (0)