Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for new Suspend and Resume API. #431

Merged
merged 1 commit into from
Feb 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions _demos/mouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ package main
import (
"fmt"
"os"
"os/exec"
"runtime"

"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
Expand Down Expand Up @@ -100,6 +102,15 @@ func drawSelect(s tcell.Screen, x1, y1, x2, y2 int, sel bool) {
// exit.
func main() {

shell := os.Getenv("SHELL")
if shell == "" {
if runtime.GOOS == "windows" {
shell = "CMD.EXE"
} else {
shell = "/bin/sh"
}
}

encoding.Register()

s, e := tcell.NewScreen()
Expand Down Expand Up @@ -190,6 +201,20 @@ func main() {
}
} else if ev.Key() == tcell.KeyCtrlL {
s.Sync()
} else if ev.Key() == tcell.KeyCtrlZ {
// CtrlZ doesn't really suspend the process, but we use it to execute a subshell.
if err := s.Suspend(); err == nil {
fmt.Printf("Executing shell (%s -l)...\n", shell)
fmt.Printf("Exit the shell to return to the demo.\n")
c := exec.Command(shell, "-l" ) // NB: -l works for cmd.exe too (ignored)
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.Run()
if err := s.Resume(); err != nil {
panic("failed to resume: " + err.Error())
}
}
} else {
ecnt = 0
if ev.Rune() == 'C' || ev.Rune() == 'c' {
Expand Down
145 changes: 114 additions & 31 deletions console_win.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ type cScreen struct {

finiOnce sync.Once

mouseEnabled bool
wg sync.WaitGroup
stopQ chan struct{}

sync.Mutex
}

Expand Down Expand Up @@ -176,7 +180,7 @@ func (s *cScreen) Init() error {
// ConEmu handling of colors and scrolling when in terminal
// mode is extremely problematic at the best. The color
// palette will scroll even though characters do not, when
// emiting stuff for the last character. In the future we
// emitting stuff for the last character. In the future we
// might change this to look at specific versions of ConEmu
// if they fix the bug.
if os.Getenv("ConEmuPID") != "" {
Expand All @@ -189,16 +193,6 @@ func (s *cScreen) Init() error {
s.truecolor = true
}

cf, _, e := procCreateEvent.Call(
uintptr(0),
uintptr(1),
uintptr(0),
uintptr(0))
if cf == uintptr(0) {
return e
}
s.cancelflag = syscall.Handle(cf)

s.Lock()

s.curx = -1
Expand Down Expand Up @@ -229,12 +223,9 @@ func (s *cScreen) Init() error {
s.setOutMode(0)
}

s.clearScreen(s.style)
s.hideCursor()
s.Unlock()
go s.scanInput()

return nil
return s.engage()
}

func (s *cScreen) CharacterSet() string {
Expand All @@ -243,19 +234,35 @@ func (s *cScreen) CharacterSet() string {
}

func (s *cScreen) EnableMouse(...MouseFlags) {
s.setInMode(modeResizeEn | modeMouseEn | modeExtndFlg)
s.Lock()
s.mouseEnabled = true
s.enableMouse(true)
s.Unlock()
}

func (s *cScreen) DisableMouse() {
s.setInMode(modeResizeEn | modeExtndFlg)
s.Lock()
s.mouseEnabled = false
s.enableMouse(false)
s.Unlock()
}

func (s *cScreen) enableMouse(on bool) {
if on {
s.setInMode(modeResizeEn | modeMouseEn | modeExtndFlg)
} else {
s.setInMode(modeResizeEn | modeExtndFlg)
}
}

// Windows lacks bracketed paste (for now)

func (s *cScreen) EnablePaste() {}

func (s *cScreen) DisablePaste() {}

func (s *cScreen) Fini() {
s.finiOnce.Do(s.finish)
s.disengage()
}

func (s *cScreen) finish() {
Expand All @@ -271,8 +278,8 @@ func (s *cScreen) finish() {
s.setInMode(s.oimode)
s.setOutMode(s.oomode)
s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
s.clearScreen(StyleDefault)
s.setCursorPos(0, 0)
s.clearScreen(StyleDefault, false)
s.setCursorPos(0, 0, false)
procSetConsoleTextAttribute.Call(
uintptr(s.out),
uintptr(s.mapStyle(StyleDefault)))
Expand All @@ -286,6 +293,68 @@ func (s *cScreen) finish() {
syscall.Close(s.out)
}

func (s *cScreen) disengage() {
s.Lock()
stopQ := s.stopQ
if stopQ == nil {
s.Unlock()
return
}
s.stopQ = nil
procSetEvent.Call(uintptr(s.cancelflag))
close(stopQ)
s.Unlock()

s.wg.Wait()

s.setInMode(s.oimode)
s.setOutMode(s.oomode)
s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
s.clearScreen(StyleDefault, false)
s.setCursorPos(0, 0, false)
procSetConsoleTextAttribute.Call(
uintptr(s.out),
uintptr(s.mapStyle(StyleDefault)))
}

func (s *cScreen) engage() error {
s.Lock()
defer s.Unlock()
if s.stopQ != nil {
return errors.New("already engaged")
}
s.stopQ = make(chan struct{})
cf, _, e := procCreateEvent.Call(
uintptr(0),
uintptr(1),
uintptr(0),
uintptr(0))
if cf == uintptr(0) {
return e
}
s.cancelflag = syscall.Handle(cf)
s.enableMouse(s.mouseEnabled)

if s.vten {
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut)
} else {
s.setOutMode(0)
}

s.clearScreen(s.style, s.vten)
s.hideCursor()

s.cells.Invalidate()
s.hideCursor()
s.resize()
s.draw()
s.doCursor()

s.wg.Add(1)
go s.scanInput(s.stopQ)
return nil
}

func (s *cScreen) PostEventWait(ev Event) {
s.evch <- ev
}
Expand Down Expand Up @@ -367,7 +436,7 @@ func (s *cScreen) doCursor() {
if x < 0 || y < 0 || x >= s.w || y >= s.h {
s.hideCursor()
} else {
s.setCursorPos(x, y)
s.setCursorPos(x, y, s.vten)
s.showCursor()
}
}
Expand Down Expand Up @@ -691,10 +760,15 @@ func (s *cScreen) getConsoleInput() error {
return nil
}

func (s *cScreen) scanInput() {
func (s *cScreen) scanInput(stopQ chan struct{}) {
defer s.wg.Done()
for {
select {
case <-stopQ:
return
default:
}
if e := s.getConsoleInput(); e != nil {
close(s.scandone)
return
}
}
Expand Down Expand Up @@ -843,7 +917,7 @@ func (s *cScreen) writeString(x, y int, style Style, ch []uint16) {
if len(ch) == 0 {
return
}
s.setCursorPos(x, y)
s.setCursorPos(x, y, s.vten)

if s.vten {
s.sendVtStyle(style)
Expand All @@ -859,7 +933,7 @@ func (s *cScreen) draw() {
// allocate a scratch line bit enough for no combining chars.
// if you have combining characters, you may pay for extra allocs.
if s.clear {
s.clearScreen(s.style)
s.clearScreen(s.style, s.vten)
s.clear = false
s.cells.Invalidate()
}
Expand Down Expand Up @@ -965,8 +1039,8 @@ func (s *cScreen) setCursorInfo(info *cursorInfo) {

}

func (s *cScreen) setCursorPos(x, y int) {
if s.vten {
func (s *cScreen) setCursorPos(x, y int, vtEnable bool) {
if vtEnable {
// Note that the string is Y first. Origin is 1,1.
s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1))
} else {
Expand Down Expand Up @@ -1028,15 +1102,15 @@ func (s *cScreen) Fill(r rune, style Style) {
s.Unlock()
}

func (s *cScreen) clearScreen(style Style) {
if s.vten {
func (s *cScreen) clearScreen(style Style, vtEnable bool) {
if vtEnable {
s.sendVtStyle(style)
row := strings.Repeat(" ", s.w)
for y := 0; y < s.h; y++ {
s.setCursorPos(0, y)
s.setCursorPos(0, y, vtEnable)
s.emitVtString(row)
}
s.setCursorPos(0, 0)
s.setCursorPos(0, 0, vtEnable)

} else {
pos := coord{0, 0}
Expand Down Expand Up @@ -1185,3 +1259,12 @@ func (s *cScreen) Beep() error {
}
return nil
}

func (s *cScreen) Suspend() error {
s.disengage()
return nil
}

func (s *cScreen) Resume() error {
return s.engage()
}
50 changes: 50 additions & 0 deletions nonblock_bsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.

// +build darwin dragonfly freebsd netbsd openbsd

package tcell

import (
"os"
"syscall"

"golang.org/x/sys/unix"
)

// BSD systems use TIOC style ioctls.

// nonBlocking changes VMIN to 0, and VTIME to 1. This basically ensures that
// we can wake up the input loop. We only want to do this if we are going to interrupt
// that loop. Normally we use VMIN 1 and VTIME 0, which ensures we pick up bytes when
// they come but don't spin burning cycles.
func (t *tScreen) nonBlocking(on bool) {
fd := int(os.Stdin.Fd())
tio, err := unix.IoctlGetTermios(fd, unix.TIOCGETA)
if err != nil {
return
}
if on {
tio.Cc[unix.VMIN] = 0
tio.Cc[unix.VTIME] = 0
} else {
// block for any output
tio.Cc[unix.VTIME] = 0
tio.Cc[unix.VMIN] = 1
}

_ = syscall.SetNonblock(fd, on)
// We want to set this *right now*.
_ = unix.IoctlSetTermios(fd, unix.TIOCSETA, tio)
}
21 changes: 21 additions & 0 deletions nonblock_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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.

// +build plan9 windows js

package tcell

func (t *tScreen) nonBlocking(on bool) error {
return nil
}
Loading