From aaa8e2f9e5bad96e0cc893b5058eb1faef112174 Mon Sep 17 00:00:00 2001 From: stk Date: Sat, 11 Mar 2023 18:05:12 +0100 Subject: [PATCH 1/4] Implement focus events --- _demos/mouse.go | 12 +++++++++++- focus.go | 28 ++++++++++++++++++++++++++++ tscreen.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 focus.go diff --git a/_demos/mouse.go b/_demos/mouse.go index 394808f4..7b41238a 100644 --- a/_demos/mouse.go +++ b/_demos/mouse.go @@ -135,6 +135,7 @@ func main() { btnfmt := "Buttons: %s" keyfmt := "Keys: %s" pastefmt := "Paste: [%d] %s" + focusfmt := "Focus: %s" white := tcell.StyleDefault. Foreground(tcell.ColorMidnightBlue).Background(tcell.ColorLightCoral) @@ -148,9 +149,10 @@ func main() { pstr := "" ecnt := 0 pasting := false + focus := true // assume we are focused when we start for { - drawBox(s, 1, 1, 42, 7, white, ' ') + drawBox(s, 1, 1, 42, 8, white, ' ') emitStr(s, 2, 2, white, "Press ESC twice to exit, C to clear.") emitStr(s, 2, 3, white, fmt.Sprintf(posfmt, mx, my)) emitStr(s, 2, 4, white, fmt.Sprintf(btnfmt, bstr)) @@ -162,6 +164,12 @@ func main() { } emitStr(s, 2, 6, white, fmt.Sprintf(pastefmt, len(pstr), ps)) + fstr := "false" + if focus { + fstr = "true" + } + emitStr(s, 2, 7, white, fmt.Sprintf(focusfmt, fstr)) + s.Show() bstr = "" ev := s.PollEvent() @@ -315,6 +323,8 @@ func main() { lchar = ch s.SetContent(w-1, h-1, 'M', nil, st) mx, my = x, y + case *tcell.EventFocus: + focus = ev.Focused default: s.SetContent(w-1, h-1, 'X', nil, st) } diff --git a/focus.go b/focus.go new file mode 100644 index 00000000..e9b93ef6 --- /dev/null +++ b/focus.go @@ -0,0 +1,28 @@ +// Copyright 2023 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +// EventFocus is a focus event. It is sent when the terminal window (or tab) +// gets or loses focus. +type EventFocus struct { + *EventTime + + // True if the window received focus, false if it lost focus + Focused bool +} + +func NewEventFocus(focused bool) *EventFocus { + return &EventFocus{Focused: focused} +} diff --git a/tscreen.go b/tscreen.go index e36e6e41..82880def 100644 --- a/tscreen.go +++ b/tscreen.go @@ -1043,6 +1043,14 @@ func (t *tScreen) enablePasting(on bool) { } } +func (t *tScreen) enableFocusReporting() { + t.TPuts("\x1b[?1004h") +} + +func (t *tScreen) disableFocusReporting() { + t.TPuts("\x1b[?1004l") +} + func (t *tScreen) Size() (int, int) { t.Lock() w, h := t.w, t.h @@ -1380,6 +1388,35 @@ func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) { return true, false } +func (t *tScreen) parseFocus(buf *bytes.Buffer, evs *[]Event) (bool, bool) { + state := 0 + b := buf.Bytes() + for i := range b { + switch state { + case 0: + if b[i] != '\x1b' { + return false, false + } + state = 1 + case 1: + if b[i] != '[' { + return false, false + } + state = 2 + case 2: + if b[i] != 'I' && b[i] != 'O' { + return false, false + } + *evs = append(*evs, NewEventFocus(b[i] == 'I')) + _, _ = buf.ReadByte() + _, _ = buf.ReadByte() + _, _ = buf.ReadByte() + return true, true + } + } + return true, false +} + // parseXtermMouse is like parseSgrMouse, but it parses a legacy // X11 mouse record. func (t *tScreen) parseXtermMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) { @@ -1556,6 +1593,12 @@ func (t *tScreen) collectEventsFromInput(buf *bytes.Buffer, expire bool) []Event partials++ } + if part, comp := t.parseFocus(buf, &res); comp { + continue + } else if part { + partials++ + } + // Only parse mouse records if this term claims to have // mouse support @@ -1804,6 +1847,7 @@ func (t *tScreen) engage() error { t.stopQ = stopQ t.enableMouse(t.mouseFlags) t.enablePasting(t.pasteEnabled) + t.enableFocusReporting() ti := t.ti t.TPuts(ti.EnterCA) @@ -1853,6 +1897,7 @@ func (t *tScreen) disengage() { t.TPuts(ti.ExitKeypad) t.enableMouse(0) t.enablePasting(false) + t.disableFocusReporting() _ = t.tty.Stop() } From 37ad113fdb38493bdc70ee22455b778e0372ab41 Mon Sep 17 00:00:00 2001 From: stk Date: Sat, 11 Mar 2023 20:08:55 +0100 Subject: [PATCH 2/4] Enable focus reporting only for terminals that support it We assume that any terminal that supports mouse reporting will also support focus reporting; but we also add an entry to Terminfo to let specific terminals override it if needed. --- terminfo/terminfo.go | 2 ++ tscreen.go | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/terminfo/terminfo.go b/terminfo/terminfo.go index cf157864..c248dd49 100644 --- a/terminfo/terminfo.go +++ b/terminfo/terminfo.go @@ -230,6 +230,8 @@ type Terminfo struct { EnterUrl string ExitUrl string SetWindowSize string + EnableFocusReporting string + DisableFocusReporting string } const ( diff --git a/tscreen.go b/tscreen.go index 82880def..dac6e62b 100644 --- a/tscreen.go +++ b/tscreen.go @@ -153,6 +153,8 @@ type tScreen struct { enterUrl string exitUrl string setWinSize string + enableFocus string + disableFocus string cursorStyles map[CursorStyle]string cursorStyle CursorStyle saved *term.State @@ -366,6 +368,17 @@ func (t *tScreen) prepareExtendedOSC() { } else if t.ti.Mouse != "" { t.setWinSize = "\x1b[8;%p1%p2%d;%dt" } + + if t.ti.EnableFocusReporting != "" { + t.enableFocus = t.ti.EnableFocusReporting + } else if t.ti.Mouse != "" { + t.enableFocus = "\x1b[?1004h" + } + if t.ti.DisableFocusReporting != "" { + t.disableFocus = t.ti.DisableFocusReporting + } else if t.ti.Mouse != "" { + t.disableFocus = "\x1b[?1004l" + } } func (t *tScreen) prepareCursorStyles() { @@ -1044,11 +1057,15 @@ func (t *tScreen) enablePasting(on bool) { } func (t *tScreen) enableFocusReporting() { - t.TPuts("\x1b[?1004h") + if t.enableFocus != "" { + t.TPuts(t.enableFocus) + } } func (t *tScreen) disableFocusReporting() { - t.TPuts("\x1b[?1004l") + if t.disableFocus != "" { + t.TPuts(t.disableFocus) + } } func (t *tScreen) Size() (int, int) { From 8fc93f2f504294b146348242349f7771096a9ccb Mon Sep 17 00:00:00 2001 From: stk Date: Sun, 12 Mar 2023 09:49:34 +0100 Subject: [PATCH 3/4] Make focus reporting an opt-in feature, like mouse reporting --- _demos/mouse.go | 1 + screen.go | 6 ++++++ simulation.go | 6 ++++++ tscreen.go | 19 ++++++++++++++++++- 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/_demos/mouse.go b/_demos/mouse.go index 7b41238a..1047678e 100644 --- a/_demos/mouse.go +++ b/_demos/mouse.go @@ -129,6 +129,7 @@ func main() { s.SetStyle(defStyle) s.EnableMouse() s.EnablePaste() + s.EnableFocus() s.Clear() posfmt := "Mouse: %d, %d " diff --git a/screen.go b/screen.go index 5598a8c7..bd35a89d 100644 --- a/screen.go +++ b/screen.go @@ -139,6 +139,12 @@ type Screen interface { // DisablePaste disables bracketed paste mode. DisablePaste() + // EnableFocus enables reporting of focus events, if your terminal supports it. + EnableFocus() + + // DisableFocus disables reporting of focus events. + DisableFocus() + // HasMouse returns true if the terminal (apparently) supports a // mouse. Note that the return value of true doesn't guarantee that // a mouse/pointing device is present; a false return definitely diff --git a/simulation.go b/simulation.go index 9ad6131e..b18b6648 100644 --- a/simulation.go +++ b/simulation.go @@ -325,6 +325,12 @@ func (s *simscreen) DisablePaste() { s.paste = false } +func (s *simscreen) EnableFocus() { +} + +func (s *simscreen) DisableFocus() { +} + func (s *simscreen) Size() (int, int) { s.Lock() w, h := s.back.Size() diff --git a/tscreen.go b/tscreen.go index dac6e62b..27635d38 100644 --- a/tscreen.go +++ b/tscreen.go @@ -163,6 +163,7 @@ type tScreen struct { wg sync.WaitGroup mouseFlags MouseFlags pasteEnabled bool + focusEnabled bool sync.Mutex } @@ -1056,6 +1057,20 @@ func (t *tScreen) enablePasting(on bool) { } } +func (t *tScreen) EnableFocus() { + t.Lock() + t.focusEnabled = true + t.enableFocusReporting() + t.Unlock() +} + +func (t *tScreen) DisableFocus() { + t.Lock() + t.focusEnabled = false + t.disableFocusReporting() + t.Unlock() +} + func (t *tScreen) enableFocusReporting() { if t.enableFocus != "" { t.TPuts(t.enableFocus) @@ -1864,7 +1879,9 @@ func (t *tScreen) engage() error { t.stopQ = stopQ t.enableMouse(t.mouseFlags) t.enablePasting(t.pasteEnabled) - t.enableFocusReporting() + if t.focusEnabled { + t.enableFocusReporting() + } ti := t.ti t.TPuts(ti.EnterCA) From 40a241393d8e1518c5070ec4ac537a3c365c2999 Mon Sep 17 00:00:00 2001 From: stk Date: Fri, 21 Jul 2023 17:36:15 +0200 Subject: [PATCH 4/4] fixup! Make focus reporting an opt-in feature, like mouse reporting --- console_win.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/console_win.go b/console_win.go index 5f0063e2..01fa1f7b 100644 --- a/console_win.go +++ b/console_win.go @@ -282,6 +282,10 @@ func (s *cScreen) EnablePaste() {} func (s *cScreen) DisablePaste() {} +func (s *cScreen) EnableFocus() {} + +func (s *cScreen) DisableFocus() {} + func (s *cScreen) Fini() { s.disengage() }