diff --git a/_demos/cursors.go b/_demos/cursors.go new file mode 100644 index 00000000..ba72a97e --- /dev/null +++ b/_demos/cursors.go @@ -0,0 +1,91 @@ +// +build ignore + +// Copyright 2021 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. + +// beep makes a beep every second until you press ESC +package main + +import ( + "fmt" + "github.com/gdamore/tcell/v2" + "os" +) + +func main() { + tcell.SetEncodingFallback(tcell.EncodingFallbackASCII) + s, e := tcell.NewScreen() + if e != nil { + fmt.Fprintf(os.Stderr, "%v\n", e) + os.Exit(1) + } + if e = s.Init(); e != nil { + fmt.Fprintf(os.Stderr, "%v\n", e) + os.Exit(1) + } + + s.SetStyle(tcell.StyleDefault) + s.Clear() + + s.SetCell(2, 2, tcell.StyleDefault, '0') + s.SetCursorStyle(tcell.CursorStyleDefault) + s.ShowCursor(3, 2) + quit := make(chan struct{}) + style := tcell.StyleDefault + go func() { + for { + ev := s.PollEvent() + switch ev := ev.(type) { + case *tcell.EventKey: + switch ev.Key() { + case tcell.KeyRune: + switch ev.Rune() { + case '0': + s.SetContent(2, 2, '0', nil, style) + s.SetCursorStyle(tcell.CursorStyleDefault) + case '1': + s.SetContent(2, 2, '1', nil, style) + s.SetCursorStyle(tcell.CursorStyleBlinkingBlock) + case '2': + s.SetCell(2, 2, tcell.StyleDefault, '2') + s.SetCursorStyle(tcell.CursorStyleSteadyBlock) + case '3': + s.SetCell(2, 2, tcell.StyleDefault, '3') + s.SetCursorStyle(tcell.CursorStyleBlinkingUnderline) + case '4': + s.SetCell(2, 2, tcell.StyleDefault, '4') + s.SetCursorStyle(tcell.CursorStyleSteadyUnderline) + case '5': + s.SetCell(2, 2, tcell.StyleDefault, '5') + s.SetCursorStyle(tcell.CursorStyleBlinkingBar) + case '6': + s.SetCell(2, 2, tcell.StyleDefault, '6') + s.SetCursorStyle(tcell.CursorStyleSteadyBar) + } + s.Show() + + case tcell.KeyEscape, tcell.KeyEnter, tcell.KeyCtrlC: + close(quit) + return + case tcell.KeyCtrlL: + s.Sync() + } + case *tcell.EventResize: + s.Sync() + } + } + }() + <-quit + s.Fini() +} diff --git a/console_win.go b/console_win.go index a901a125..16be4e2f 100644 --- a/console_win.go +++ b/console_win.go @@ -46,11 +46,12 @@ type cScreen struct { w int h int - oscreen consoleInfo - ocursor cursorInfo - oimode uint32 - oomode uint32 - cells CellBuffer + oscreen consoleInfo + ocursor cursorInfo + cursorStyle CursorStyle + oimode uint32 + oomode uint32 + cells CellBuffer finiOnce sync.Once @@ -138,20 +139,37 @@ const ( const ( // VT100/XTerm escapes understood by the console - vtShowCursor = "\x1b[?25h" - vtHideCursor = "\x1b[?25l" - vtCursorPos = "\x1b[%d;%dH" // Note that it is Y then X - vtSgr0 = "\x1b[0m" - vtBold = "\x1b[1m" - vtUnderline = "\x1b[4m" - vtBlink = "\x1b[5m" // Not sure this is processed - vtReverse = "\x1b[7m" - vtSetFg = "\x1b[38;5;%dm" - vtSetBg = "\x1b[48;5;%dm" - vtSetFgRGB = "\x1b[38;2;%d;%d;%dm" // RGB - vtSetBgRGB = "\x1b[48;2;%d;%d;%dm" // RGB + vtShowCursor = "\x1b[?25h" + vtHideCursor = "\x1b[?25l" + vtCursorPos = "\x1b[%d;%dH" // Note that it is Y then X + vtSgr0 = "\x1b[0m" + vtBold = "\x1b[1m" + vtUnderline = "\x1b[4m" + vtBlink = "\x1b[5m" // Not sure this is processed + vtReverse = "\x1b[7m" + vtSetFg = "\x1b[38;5;%dm" + vtSetBg = "\x1b[48;5;%dm" + vtSetFgRGB = "\x1b[38;2;%d;%d;%dm" // RGB + vtSetBgRGB = "\x1b[48;2;%d;%d;%dm" // RGB + vtCursorDefault = "\x1b[0 q" + vtCursorBlinkingBlock = "\x1b[1 q" + vtCursorSteadyBlock = "\x1b[2 q" + vtCursorBlinkingUnderline = "\x1b[3 q" + vtCursorSteadyUnderline = "\x1b[4 q" + vtCursorBlinkingBar = "\x1b[5 q" + vtCursorSteadyBar = "\x1b[6 q" ) +var vtCursorStyles = map[CursorStyle]string{ + CursorStyleDefault: vtCursorDefault, + CursorStyleBlinkingBlock: vtCursorBlinkingBlock, + CursorStyleSteadyBlock: vtCursorSteadyBlock, + CursorStyleBlinkingUnderline: vtCursorBlinkingUnderline, + CursorStyleSteadyUnderline: vtCursorSteadyUnderline, + CursorStyleBlinkingBar: vtCursorBlinkingBar, + CursorStyleSteadyBar: vtCursorSteadyBar, +} + // NewConsoleScreen returns a Screen for the Windows console associated // with the current process. The Screen makes use of the Windows Console // API to display content and read events. @@ -280,6 +298,9 @@ func (s *cScreen) disengage() { s.wg.Wait() + if s.vten { + s.emitVtString(vtCursorStyles[CursorStyleDefault]) + } s.setInMode(s.oimode) s.setOutMode(s.oomode) s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y)) @@ -406,6 +427,7 @@ func (s *cScreen) emitVtString(vs string) { func (s *cScreen) showCursor() { if s.vten { s.emitVtString(vtShowCursor) + s.emitVtString(vtCursorStyles[s.cursorStyle]) } else { s.setCursorInfo(&cursorInfo{size: 100, visible: 1}) } @@ -429,6 +451,17 @@ func (s *cScreen) ShowCursor(x, y int) { s.Unlock() } +func (s *cScreen) SetCursorStyle(cs CursorStyle) { + s.Lock() + if !s.fini { + if _, ok := vtCursorStyles[cs]; ok { + s.cursorStyle = cs + s.doCursor() + } + } + s.Unlock() +} + func (s *cScreen) doCursor() { x, y := s.curx, s.cury diff --git a/screen.go b/screen.go index 15cfbf02..272c8fdf 100644 --- a/screen.go +++ b/screen.go @@ -71,9 +71,14 @@ type Screen interface { ShowCursor(x int, y int) // HideCursor is used to hide the cursor. Its an alias for - // ShowCursor(-1, -1). + // ShowCursor(-1, -1).sim HideCursor() + // SetCursorStyle is used to set the cursor style. If the style + // is not supported (or cursor styles are not supported at all), + // then this will have no effect. + SetCursorStyle(CursorStyle) + // Size returns the screen size as width, height. This changes in // response to a call to Clear or Flush. Size() (width, height int) @@ -258,3 +263,17 @@ const ( MouseDragEvents = MouseFlags(2) // Click-drag events (includes button events) MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events) ) + +// CursorStyle represents a given cursor style, which can include the shape and +// whether the cursor blinks or is solid. Support for changing these is not universal. +type CursorStyle int + +const ( + CursorStyleDefault = CursorStyle(iota) // The default + CursorStyleBlinkingBlock + CursorStyleSteadyBlock + CursorStyleBlinkingUnderline + CursorStyleSteadyUnderline + CursorStyleBlinkingBar + CursorStyleSteadyBar +) \ No newline at end of file diff --git a/simulation.go b/simulation.go index 451460be..f8b1c0a2 100644 --- a/simulation.go +++ b/simulation.go @@ -281,6 +281,8 @@ func (s *simscreen) hideCursor() { s.cursorvis = false } +func (s *simscreen) SetCursorStyle(CursorStyle) {} + func (s *simscreen) Show() { s.Lock() s.resize() diff --git a/terminfo/mkinfo.go b/terminfo/mkinfo.go index d06110ff..188183ca 100644 --- a/terminfo/mkinfo.go +++ b/terminfo/mkinfo.go @@ -609,6 +609,13 @@ func dotGoInfo(w io.Writer, terms []*TData) { dotGoAddFlag(w, "TrueColor", t.TrueColor) dotGoAddFlag(w, "AutoMargin", t.AutoMargin) dotGoAddStr(w, "InsertChar", t.InsertChar) + dotGoAddStr(w, "CursorDefault", t.CursorDefault) + dotGoAddStr(w, "CursorBlinkingBlock", t.CursorBlinkingBlock) + dotGoAddStr(w, "CursorSteadyBlock", t.CursorSteadyBlock) + dotGoAddStr(w, "CursorBlinkingUnderline", t.CursorBlinkingUnderline) + dotGoAddStr(w, "CursorSteadyUnderline", t.CursorSteadyUnderline) + dotGoAddStr(w, "CursorBlinkingBar", t.CursorBlinkingBar) + dotGoAddStr(w, "CursorSteadyBar", t.CursorSteadyBar) fmt.Fprintln(w, "\t})") } fmt.Fprintln(w, "}") diff --git a/terminfo/terminfo.go b/terminfo/terminfo.go index 7e17352c..193e06e3 100644 --- a/terminfo/terminfo.go +++ b/terminfo/terminfo.go @@ -167,59 +167,66 @@ type Terminfo struct { // Terminal support for these are going to vary amongst XTerm // emulations, so don't depend too much on them in your application. - StrikeThrough string // smxx - SetFgBg string // setfgbg - SetFgBgRGB string // setfgbgrgb - SetFgRGB string // setfrgb - SetBgRGB string // setbrgb - KeyShfUp string // shift-up - KeyShfDown string // shift-down - KeyShfPgUp string // shift-kpp - KeyShfPgDn string // shift-knp - KeyCtrlUp string // ctrl-up - KeyCtrlDown string // ctrl-left - KeyCtrlRight string // ctrl-right - KeyCtrlLeft string // ctrl-left - KeyMetaUp string // meta-up - KeyMetaDown string // meta-left - KeyMetaRight string // meta-right - KeyMetaLeft string // meta-left - KeyAltUp string // alt-up - KeyAltDown string // alt-left - KeyAltRight string // alt-right - KeyAltLeft string // alt-left - KeyCtrlHome string - KeyCtrlEnd string - KeyMetaHome string - KeyMetaEnd string - KeyAltHome string - KeyAltEnd string - KeyAltShfUp string - KeyAltShfDown string - KeyAltShfLeft string - KeyAltShfRight string - KeyMetaShfUp string - KeyMetaShfDown string - KeyMetaShfLeft string - KeyMetaShfRight string - KeyCtrlShfUp string - KeyCtrlShfDown string - KeyCtrlShfLeft string - KeyCtrlShfRight string - KeyCtrlShfHome string - KeyCtrlShfEnd string - KeyAltShfHome string - KeyAltShfEnd string - KeyMetaShfHome string - KeyMetaShfEnd string - EnablePaste string // bracketed paste mode - DisablePaste string - PasteStart string - PasteEnd string - Modifiers int - InsertChar string // string to insert a character (ich1) - AutoMargin bool // true if writing to last cell in line advances - TrueColor bool // true if the terminal supports direct color + StrikeThrough string // smxx + SetFgBg string // setfgbg + SetFgBgRGB string // setfgbgrgb + SetFgRGB string // setfrgb + SetBgRGB string // setbrgb + KeyShfUp string // shift-up + KeyShfDown string // shift-down + KeyShfPgUp string // shift-kpp + KeyShfPgDn string // shift-knp + KeyCtrlUp string // ctrl-up + KeyCtrlDown string // ctrl-left + KeyCtrlRight string // ctrl-right + KeyCtrlLeft string // ctrl-left + KeyMetaUp string // meta-up + KeyMetaDown string // meta-left + KeyMetaRight string // meta-right + KeyMetaLeft string // meta-left + KeyAltUp string // alt-up + KeyAltDown string // alt-left + KeyAltRight string // alt-right + KeyAltLeft string // alt-left + KeyCtrlHome string + KeyCtrlEnd string + KeyMetaHome string + KeyMetaEnd string + KeyAltHome string + KeyAltEnd string + KeyAltShfUp string + KeyAltShfDown string + KeyAltShfLeft string + KeyAltShfRight string + KeyMetaShfUp string + KeyMetaShfDown string + KeyMetaShfLeft string + KeyMetaShfRight string + KeyCtrlShfUp string + KeyCtrlShfDown string + KeyCtrlShfLeft string + KeyCtrlShfRight string + KeyCtrlShfHome string + KeyCtrlShfEnd string + KeyAltShfHome string + KeyAltShfEnd string + KeyMetaShfHome string + KeyMetaShfEnd string + EnablePaste string // bracketed paste mode + DisablePaste string + PasteStart string + PasteEnd string + Modifiers int + InsertChar string // string to insert a character (ich1) + AutoMargin bool // true if writing to last cell in line advances + TrueColor bool // true if the terminal supports direct color + CursorDefault string + CursorBlinkingBlock string + CursorSteadyBlock string + CursorBlinkingUnderline string + CursorSteadyUnderline string + CursorBlinkingBar string + CursorSteadyBar string } const ( diff --git a/tscreen.go b/tscreen.go index d0f73e54..1cffedfd 100644 --- a/tscreen.go +++ b/tscreen.go @@ -148,6 +148,8 @@ type tScreen struct { finiOnce sync.Once enablePaste string disablePaste string + cursorStyles map[CursorStyle]string + cursorStyle CursorStyle saved *term.State stopQ chan struct{} running bool @@ -332,6 +334,35 @@ func (t *tScreen) prepareBracketedPaste() { } } +func (t *tScreen) prepareCursorStyles() { + // Another workaround for lack of reporting in terminfo. + // We assume if the terminal has a mouse entry, that it + // offers bracketed paste. But we allow specific overrides + // via our terminal database. + if t.ti.CursorDefault != "" { + t.cursorStyles = map[CursorStyle]string{ + CursorStyleDefault: t.ti.CursorDefault, + CursorStyleBlinkingBlock: t.ti.CursorBlinkingBlock, + CursorStyleSteadyBlock: t.ti.CursorSteadyBlock, + CursorStyleBlinkingUnderline: t.ti.CursorBlinkingUnderline, + CursorStyleSteadyUnderline: t.ti.CursorSteadyUnderline, + CursorStyleBlinkingBar: t.ti.CursorBlinkingBar, + CursorStyleSteadyBar: t.ti.CursorSteadyBar, + } + } else if t.ti.Mouse != "" { + t.cursorStyles = map[CursorStyle]string{ + CursorStyleDefault: "\x1b[0 q", + CursorStyleBlinkingBlock: "\x1b[1 q", + CursorStyleSteadyBlock: "\x1b[2 q", + CursorStyleBlinkingUnderline: "\x1b[3 q", + CursorStyleSteadyUnderline: "\x1b[4 q", + CursorStyleBlinkingBar: "\x1b[5 q", + CursorStyleSteadyBar: "\x1b[6 q", + + } + } +} + func (t *tScreen) prepareKey(key Key, val string) { t.prepareKeyMod(key, ModNone, val) } @@ -471,6 +502,7 @@ func (t *tScreen) prepareKeys() { t.prepareKey(keyPasteEnd, ti.PasteEnd) t.prepareXtermModifiers() t.prepareBracketedPaste() + t.prepareCursorStyles() outer: // Add key mappings for control keys. @@ -754,6 +786,12 @@ func (t *tScreen) ShowCursor(x, y int) { t.Unlock() } +func (t *tScreen) SetCursorStyle(cs CursorStyle) { + t.Lock() + t.cursorStyle = cs + t.Unlock() +} + func (t *tScreen) HideCursor() { t.ShowCursor(-1, -1) } @@ -768,6 +806,11 @@ func (t *tScreen) showCursor() { } t.TPuts(t.ti.TGoto(x, y)) t.TPuts(t.ti.ShowCursor) + if t.cursorStyles != nil { + if esc, ok := t.cursorStyles[t.cursorStyle]; ok { + t.TPuts(esc) + } + } t.cx = x t.cy = y } @@ -1737,6 +1780,9 @@ func (t *tScreen) disengage() { ti := t.ti t.cells.Resize(0, 0) t.TPuts(ti.ShowCursor) + if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault { + t.TPuts(t.cursorStyles[t.cursorStyle]) + } t.TPuts(ti.ResetFgBg) t.TPuts(ti.AttrOff) t.TPuts(ti.Clear)