2019-03-17 19:02:33 +01:00
|
|
|
package widgets
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os/exec"
|
2020-05-06 16:25:11 +02:00
|
|
|
"syscall"
|
2019-03-17 19:02:33 +01:00
|
|
|
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/lib/ui"
|
2022-03-22 09:52:27 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/logging"
|
2022-09-14 21:09:41 +02:00
|
|
|
tcellterm "git.sr.ht/~rockorager/tcell-term"
|
2019-03-17 19:02:33 +01:00
|
|
|
|
2020-11-30 23:07:03 +01:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2022-09-14 21:09:41 +02:00
|
|
|
"github.com/gdamore/tcell/v2/views"
|
2019-03-17 19:02:33 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Terminal struct {
|
2019-04-27 18:47:59 +02:00
|
|
|
ui.Invalidatable
|
|
|
|
closed bool
|
|
|
|
cmd *exec.Cmd
|
|
|
|
ctx *ui.Context
|
|
|
|
cursorShown bool
|
|
|
|
destroyed bool
|
|
|
|
focus bool
|
2022-09-14 21:09:41 +02:00
|
|
|
vterm *tcellterm.Terminal
|
|
|
|
running bool
|
2019-05-19 13:40:05 +02:00
|
|
|
|
2019-03-17 22:23:53 +01:00
|
|
|
OnClose func(err error)
|
2019-05-11 20:15:29 +02:00
|
|
|
OnEvent func(event tcell.Event) bool
|
2019-03-30 16:58:24 +01:00
|
|
|
OnStart func()
|
2019-03-17 22:08:54 +01:00
|
|
|
OnTitle func(title string)
|
2019-03-17 19:02:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewTerminal(cmd *exec.Cmd) (*Terminal, error) {
|
2019-03-22 02:00:03 +01:00
|
|
|
term := &Terminal{
|
|
|
|
cursorShown: true,
|
|
|
|
}
|
2019-03-17 19:02:33 +01:00
|
|
|
term.cmd = cmd
|
2022-09-14 21:09:41 +02:00
|
|
|
term.vterm = tcellterm.New()
|
2019-03-17 19:02:33 +01:00
|
|
|
return term, nil
|
|
|
|
}
|
|
|
|
|
2019-03-17 22:23:53 +01:00
|
|
|
func (term *Terminal) Close(err error) {
|
2019-03-30 16:58:24 +01:00
|
|
|
if term.closed {
|
|
|
|
return
|
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
// Stop receiving events
|
|
|
|
term.vterm.Unwatch(term)
|
2019-03-17 22:23:53 +01:00
|
|
|
if term.cmd != nil && term.cmd.Process != nil {
|
2022-07-29 22:31:54 +02:00
|
|
|
err := term.cmd.Process.Kill()
|
|
|
|
if err != nil {
|
|
|
|
logging.Warnf("failed to kill process: %v", err)
|
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
// Race condition here, check if cmd exists. If process exits
|
|
|
|
// fast, this could by nil and panic
|
|
|
|
if term.cmd != nil {
|
|
|
|
err = term.cmd.Wait()
|
|
|
|
}
|
2022-07-29 22:31:54 +02:00
|
|
|
if err != nil {
|
|
|
|
logging.Warnf("failed for wait for process to terminate: %v", err)
|
|
|
|
}
|
2019-03-17 22:23:53 +01:00
|
|
|
term.cmd = nil
|
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
if term.vterm != nil {
|
|
|
|
term.vterm.Close()
|
|
|
|
}
|
2019-03-17 22:23:53 +01:00
|
|
|
if !term.closed && term.OnClose != nil {
|
|
|
|
term.OnClose(err)
|
2019-03-17 19:02:33 +01:00
|
|
|
}
|
2022-09-15 20:16:31 +02:00
|
|
|
if term.ctx != nil {
|
|
|
|
term.ctx.HideCursor()
|
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
term.closed = true
|
2019-03-17 19:02:33 +01:00
|
|
|
}
|
|
|
|
|
2019-03-30 21:29:52 +01:00
|
|
|
func (term *Terminal) Destroy() {
|
|
|
|
if term.destroyed {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if term.ctx != nil {
|
|
|
|
term.ctx.HideCursor()
|
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
// If we destroy, we don't want to call the OnClose callback
|
|
|
|
term.OnClose = nil
|
|
|
|
term.Close(nil)
|
|
|
|
term.vterm = nil
|
2019-03-30 21:29:52 +01:00
|
|
|
term.destroyed = true
|
|
|
|
}
|
|
|
|
|
2019-03-17 19:02:33 +01:00
|
|
|
func (term *Terminal) Invalidate() {
|
2019-04-15 22:07:05 +02:00
|
|
|
term.invalidate()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (term *Terminal) invalidate() {
|
2019-04-27 18:47:59 +02:00
|
|
|
term.DoInvalidate(term)
|
2019-03-17 19:02:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (term *Terminal) Draw(ctx *ui.Context) {
|
2019-03-30 21:29:52 +01:00
|
|
|
if term.destroyed {
|
2019-03-17 22:23:53 +01:00
|
|
|
return
|
|
|
|
}
|
2019-03-30 21:29:52 +01:00
|
|
|
term.ctx = ctx // gross
|
2022-09-14 21:09:41 +02:00
|
|
|
term.vterm.SetView(ctx.View())
|
|
|
|
if !term.running && !term.closed && term.cmd != nil {
|
|
|
|
go func() {
|
|
|
|
defer logging.PanicHandler()
|
|
|
|
term.vterm.Watch(term)
|
|
|
|
attr := &syscall.SysProcAttr{Setsid: true, Setctty: true, Ctty: 1}
|
|
|
|
if err := term.vterm.RunWithAttrs(term.cmd, attr); err != nil {
|
|
|
|
logging.Errorf("error running terminal: %w", err)
|
2019-03-30 21:29:52 +01:00
|
|
|
term.Close(err)
|
2022-09-14 21:09:41 +02:00
|
|
|
term.running = false
|
2019-03-30 21:29:52 +01:00
|
|
|
return
|
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
term.running = false
|
|
|
|
term.Close(nil)
|
|
|
|
}()
|
|
|
|
for {
|
|
|
|
if term.cmd.Process != nil {
|
|
|
|
term.running = true
|
|
|
|
break
|
2022-07-29 22:31:54 +02:00
|
|
|
}
|
2019-03-30 16:58:24 +01:00
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
if term.OnStart != nil {
|
|
|
|
term.OnStart()
|
2019-03-17 19:02:33 +01:00
|
|
|
}
|
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
term.draw()
|
|
|
|
}
|
2019-03-22 00:56:47 +01:00
|
|
|
|
2022-09-14 21:09:41 +02:00
|
|
|
func (term *Terminal) draw() {
|
|
|
|
term.vterm.Draw()
|
2022-09-15 20:16:31 +02:00
|
|
|
if term.focus && !term.closed && term.ctx != nil {
|
2019-03-22 02:28:51 +01:00
|
|
|
if !term.cursorShown {
|
2022-09-14 21:09:41 +02:00
|
|
|
term.ctx.HideCursor()
|
2019-03-22 02:28:51 +01:00
|
|
|
} else {
|
2022-09-14 21:09:41 +02:00
|
|
|
_, x, y, style := term.vterm.GetCursor()
|
|
|
|
term.ctx.SetCursor(x, y)
|
|
|
|
term.ctx.SetCursorStyle(style)
|
2019-03-22 02:28:51 +01:00
|
|
|
}
|
2019-03-22 00:56:47 +01:00
|
|
|
}
|
2019-03-17 19:02:33 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 00:32:36 +02:00
|
|
|
func (term *Terminal) MouseEvent(localX int, localY int, event tcell.Event) {
|
2022-09-14 22:33:11 +02:00
|
|
|
ev, ok := event.(*tcell.EventMouse)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if term.OnEvent != nil {
|
|
|
|
term.OnEvent(ev)
|
|
|
|
}
|
|
|
|
if term.closed {
|
|
|
|
return
|
2019-09-06 00:32:36 +02:00
|
|
|
}
|
2022-09-14 22:33:11 +02:00
|
|
|
e := tcell.NewEventMouse(localX, localY, ev.Buttons(), ev.Modifiers())
|
|
|
|
term.vterm.HandleEvent(e)
|
2019-09-06 00:32:36 +02:00
|
|
|
}
|
|
|
|
|
2019-03-17 19:02:33 +01:00
|
|
|
func (term *Terminal) Focus(focus bool) {
|
2019-03-30 16:58:24 +01:00
|
|
|
if term.closed {
|
|
|
|
return
|
|
|
|
}
|
2019-03-17 19:02:33 +01:00
|
|
|
term.focus = focus
|
2019-03-22 02:34:12 +01:00
|
|
|
if term.ctx != nil {
|
|
|
|
if !term.focus {
|
|
|
|
term.ctx.HideCursor()
|
|
|
|
} else {
|
2022-09-14 21:09:41 +02:00
|
|
|
_, x, y, style := term.vterm.GetCursor()
|
|
|
|
term.ctx.SetCursor(x, y)
|
|
|
|
term.ctx.SetCursorStyle(style)
|
|
|
|
term.invalidate()
|
2019-03-22 02:34:12 +01:00
|
|
|
}
|
2019-03-22 02:28:51 +01:00
|
|
|
}
|
2019-03-17 19:02:33 +01:00
|
|
|
}
|
|
|
|
|
2022-09-14 21:09:41 +02:00
|
|
|
// HandleEvent is used to watch the underlying terminal events
|
|
|
|
func (term *Terminal) HandleEvent(ev tcell.Event) bool {
|
|
|
|
if term.closed || term.destroyed {
|
|
|
|
return false
|
2019-03-22 00:50:54 +01:00
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
switch ev := ev.(type) {
|
|
|
|
case *views.EventWidgetContent:
|
|
|
|
// Draw here for performance improvement. We call draw again in
|
|
|
|
// the main Draw, but tcell-term only draws dirty cells, so it
|
|
|
|
// won't be too much extra CPU there. Drawing there is needed
|
|
|
|
// for certain msgviews, particularly if the pager command
|
|
|
|
// exits.
|
|
|
|
term.draw()
|
|
|
|
// Perform a tcell screen.Show() to show our updates
|
|
|
|
// immediately
|
2022-09-15 20:16:31 +02:00
|
|
|
if term.ctx != nil {
|
|
|
|
term.ctx.Show()
|
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
term.invalidate()
|
|
|
|
return true
|
|
|
|
case *tcellterm.EventTitle:
|
|
|
|
if term.OnTitle != nil {
|
|
|
|
term.OnTitle(ev.Title())
|
|
|
|
}
|
2019-03-22 00:50:54 +01:00
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
return false
|
2019-03-22 00:50:54 +01:00
|
|
|
}
|
|
|
|
|
2019-03-17 19:02:33 +01:00
|
|
|
func (term *Terminal) Event(event tcell.Event) bool {
|
2019-05-11 20:15:29 +02:00
|
|
|
if term.OnEvent != nil {
|
|
|
|
if term.OnEvent(event) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2019-03-30 16:58:24 +01:00
|
|
|
if term.closed {
|
|
|
|
return false
|
|
|
|
}
|
2022-09-14 21:09:41 +02:00
|
|
|
return term.vterm.HandleEvent(event)
|
2019-03-17 22:08:54 +01:00
|
|
|
}
|