Skip to content

Commit

Permalink
Support for new Suspend and Resume API.
Browse files Browse the repository at this point in the history
fixes #194 Starting multiple screen sessions (lost key event)

You can test this by using the mouse demo, which now supports pressing
CTRL-Z.  This does not actually suspend the demo, but starts a subshell
which takes over.  After the subshell is exited, the demo takes control
of the screen back.  This can be done multiple times, and it is possible
to start multiple "nested" iterations of the demo this way.
  • Loading branch information
gdamore committed Feb 20, 2021
1 parent 19e1709 commit 1655637
Show file tree
Hide file tree
Showing 10 changed files with 406 additions and 110 deletions.
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

0 comments on commit 1655637

Please sign in to comment.