From 13758e96004129ac1af3d444d0965d17327fbfe3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 Aug 2022 21:14:37 +0530 Subject: [PATCH] Drop one dependency --- go.mod | 1 - go.sum | 3 -- tools/cmd/at/main.go | 33 ++++++------ tools/utils/tty.go | 109 +++++++++++++++++++++++---------------- tools/utils/tty_bsd.go | 26 ++++++++++ tools/utils/tty_linux.go | 36 +++++++++++++ 6 files changed, 143 insertions(+), 65 deletions(-) create mode 100644 tools/utils/tty_bsd.go create mode 100644 tools/utils/tty_linux.go diff --git a/go.mod b/go.mod index db7aca92b..4532d18ff 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.19 require ( github.com/mattn/go-isatty v0.0.14 github.com/mattn/go-runewidth v0.0.13 - github.com/pkg/term v1.1.0 github.com/seancfoley/ipaddress-go v1.2.1 github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index 74266c7c9..fe10686b8 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,6 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= -github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -20,7 +18,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/tools/cmd/at/main.go b/tools/cmd/at/main.go index 0a155b51b..4fd6f039b 100644 --- a/tools/cmd/at/main.go +++ b/tools/cmd/at/main.go @@ -92,26 +92,23 @@ func create_serializer(password string, encoded_pubkey string, response_timeout return simple_serializer, timeout, nil } -type TTYIO interface { +type IOAbstraction interface { WriteAllWithTimeout(b []byte, d time.Duration) (n int, err error) WriteFromReader(r utils.Reader, read_timeout time.Duration, write_timeout time.Duration) (n int, err error) ReadWithTimeout(b []byte, d time.Duration) (n int, err error) - - Restore() error - Close() error } -func do_tty_io(tty TTYIO, input utils.Reader, no_response bool, response_timeout time.Duration) (serialized_response []byte, err error) { +func do_io(device IOAbstraction, input utils.Reader, no_response bool, response_timeout time.Duration) (serialized_response []byte, err error) { - _, err = tty.WriteAllWithTimeout([]byte("\x1bP@kitty-cmd"), 2*time.Second) + _, err = device.WriteAllWithTimeout([]byte("\x1bP@kitty-cmd"), 2*time.Second) if err != nil { return } - _, err = tty.WriteFromReader(input, 2*time.Second, 2*time.Second) + _, err = device.WriteFromReader(input, 2*time.Second, 2*time.Second) if err != nil { return } - _, err = tty.WriteAllWithTimeout([]byte("\x1b\\"), 2*time.Second) + _, err = device.WriteAllWithTimeout([]byte("\x1b\\"), 2*time.Second) if err != nil { return } @@ -135,7 +132,7 @@ func do_tty_io(tty TTYIO, input utils.Reader, no_response bool, response_timeout for !response_received { buf = buf[:cap(buf)] var n int - n, err = tty.ReadWithTimeout(buf, response_timeout) + n, err = device.ReadWithTimeout(buf, response_timeout) if err != nil { if err == os.ErrDeadlineExceeded { err = fmt.Errorf("Timed out while waiting for a response from kitty") @@ -184,22 +181,24 @@ func get_response(rc *utils.RemoteControlCmd, timeout float64) (ans *Response, e if err != nil { return } - var tty TTYIO + var device IOAbstraction if global_options.to_network == "" { - tty, err = utils.OpenControllingTerm(true) + var term *utils.Term + term, err = utils.OpenControllingTerm(utils.SetRaw) if err != nil { return } + defer func() { + term.Restore() + term.Close() + }() + device = term } else { err = fmt.Errorf("TODO: Implement socket IO") return } - defer func() { - tty.Restore() - tty.Close() - }() r := utils.BytesReader{Data: d} - serialized_response, err := do_tty_io(tty, &r, rc.NoResponse, time.Duration(timeout*float64(time.Second))) + serialized_response, err := do_io(device, &r, rc.NoResponse, time.Duration(timeout*float64(time.Second))) if err != nil { if err == os.ErrDeadlineExceeded { rc.Payload = nil @@ -209,7 +208,7 @@ func get_response(rc *utils.RemoteControlCmd, timeout float64) (ans *Response, e if err != nil { return } - _, err = do_tty_io(tty, &r, rc.NoResponse, 0) + _, err = do_io(device, &r, rc.NoResponse, 0) } return } diff --git a/tools/utils/tty.go b/tools/utils/tty.go index 70a074663..05877b608 100644 --- a/tools/utils/tty.go +++ b/tools/utils/tty.go @@ -9,10 +9,15 @@ import ( "syscall" "time" - "github.com/pkg/term/termios" "golang.org/x/sys/unix" ) +const ( + TCSANOW = 0 + TCSADRAIN = 1 + TCSAFLUSH = 2 +) + type Term struct { name string fd int @@ -39,7 +44,46 @@ func eintr_retry_intret(f func() (int, error)) (int, error) { } } -func OpenTerm(name string, in_raw_mode bool) (self *Term, err error) { +type TermiosOperation func(t *unix.Termios) + +func get_vmin_and_vtime(d time.Duration) (uint8, uint8) { + if d > 0 { + // VTIME is expressed in terms of deciseconds + vtimeDeci := d.Milliseconds() / 100 + // ensure valid range + vtime := uint8(clamp(vtimeDeci, 1, 0xff)) + return 0, vtime + } + // block indefinitely until we receive at least 1 byte + return 1, 0 +} + +func SetReadTimeout(d time.Duration) TermiosOperation { + vmin, vtime := get_vmin_and_vtime(d) + return func(t *unix.Termios) { + t.Cc[unix.VMIN] = vmin + t.Cc[unix.VTIME] = vtime + } +} + +var SetBlockingRead TermiosOperation = SetReadTimeout(0) + +var SetRaw TermiosOperation = func(t *unix.Termios) { + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage, as Go doesnt wrap cfmakeraw probably because its not in POSIX + t.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON + t.Oflag &^= unix.OPOST + t.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN + t.Cflag &^= unix.CSIZE | unix.PARENB + t.Cflag |= unix.CS8 + t.Cc[unix.VMIN] = 1 + t.Cc[unix.VTIME] = 0 +} +var SetNoEcho TermiosOperation = func(t *unix.Termios) { + t.Lflag &^= unix.ECHO +} + +func OpenTerm(name string, operations ...TermiosOperation) (self *Term, err error) { fd, err := eintr_retry_intret(func() (int, error) { return unix.Open(name, unix.O_NOCTTY|unix.O_CLOEXEC|unix.O_NDELAY|unix.O_RDWR, 0666) }) @@ -49,20 +93,18 @@ func OpenTerm(name string, in_raw_mode bool) (self *Term, err error) { self = &Term{name: name, fd: fd} err = eintr_retry_noret(func() error { return unix.SetNonblock(self.fd, false) }) - if err != nil { - return + if err == nil { + err = self.ApplyOperations(TCSANOW, operations...) } - if in_raw_mode { - err = self.SetRaw() - if err != nil { - return - } + if err != nil { + self.Close() + self = nil } return } -func OpenControllingTerm(in_raw_mode bool) (self *Term, err error) { - return OpenTerm("/dev/tty", in_raw_mode) // go doesnt have a wrapper for ctermid() +func OpenControllingTerm(operations ...TermiosOperation) (self *Term, err error) { + return OpenTerm("/dev/tty", operations...) // go doesnt have a wrapper for ctermid() } func (self *Term) Fd() int { return self.fd } @@ -74,28 +116,32 @@ func (self *Term) Close() error { } func (self *Term) Tcgetattr(ans *unix.Termios) error { - return eintr_retry_noret(func() error { return termios.Tcgetattr(uintptr(self.fd), ans) }) + return eintr_retry_noret(func() error { return Tcgetattr(self.fd, ans) }) } func (self *Term) Tcsetattr(when uintptr, ans *unix.Termios) error { - return eintr_retry_noret(func() error { return termios.Tcsetattr(uintptr(self.fd), when, ans) }) + return eintr_retry_noret(func() error { return Tcsetattr(self.fd, when, ans) }) } -func (self *Term) SetRawWhen(when uintptr) (err error) { +func (self *Term) set_termios_attrs(when uintptr, modify func(*unix.Termios)) (err error) { var state unix.Termios if err = self.Tcgetattr(&state); err != nil { return } new_state := state - termios.Cfmakeraw(&new_state) + modify(&new_state) if err = self.Tcsetattr(when, &new_state); err == nil { self.states = append(self.states, state) } return } -func (self *Term) SetRaw() error { - return self.SetRawWhen(termios.TCSANOW) +func (self *Term) ApplyOperations(when uintptr, operations ...TermiosOperation) (err error) { + return self.set_termios_attrs(when, func(t *unix.Termios) { + for _, op := range operations { + op(t) + } + }) } func (self *Term) PopStateWhen(when uintptr) (err error) { @@ -110,7 +156,7 @@ func (self *Term) PopStateWhen(when uintptr) (err error) { } func (self *Term) PopState() error { - return self.PopStateWhen(termios.TCIOFLUSH) + return self.PopStateWhen(TCSAFLUSH) } func (self *Term) RestoreWhen(when uintptr) (err error) { @@ -122,7 +168,7 @@ func (self *Term) RestoreWhen(when uintptr) (err error) { } func (self *Term) Restore() error { - return self.RestoreWhen(termios.TCIOFLUSH) + return self.RestoreWhen(TCSAFLUSH) } func clamp(v, lo, hi int64) int64 { @@ -135,31 +181,6 @@ func clamp(v, lo, hi int64) int64 { return v } -func get_vmin_and_vtime(d time.Duration) (uint8, uint8) { - if d > 0 { - // VTIME is expressed in terms of deciseconds - vtimeDeci := d.Milliseconds() / 100 - // ensure valid range - vtime := uint8(clamp(vtimeDeci, 1, 0xff)) - return 0, vtime - } - // block indefinitely until we receive at least 1 byte - return 1, 0 -} - -func (self *Term) SetReadTimeout(d time.Duration) (err error) { - var a unix.Termios - if err := self.Tcgetattr(&a); err != nil { - return err - } - b := a - b.Cc[unix.VMIN], b.Cc[unix.VTIME] = get_vmin_and_vtime(d) - if err = self.Tcsetattr(termios.TCSANOW, &b); err == nil { - self.states = append(self.states, a) - } - return -} - func (self *Term) ReadWithTimeout(b []byte, d time.Duration) (n int, err error) { var read, write, in_err unix.FdSet pselect := func() (int, error) { diff --git a/tools/utils/tty_bsd.go b/tools/utils/tty_bsd.go new file mode 100644 index 000000000..3ec560cfd --- /dev/null +++ b/tools/utils/tty_bsd.go @@ -0,0 +1,26 @@ +//go:build darwin || freebsd || openbsd || netbsd || dragonfly +// +build darwin freebsd openbsd netbsd dragonfly + +package utils + +import ( + "golang.org/x/sys/unix" +) + +func Tcgetattr(fd uintptr, argp *unix.Termios) error { + return unix.IoctlSetTermios(int(fd), unix.TIOCGETA, argp) +} + +func Tcsetattr(fd, opt uintptr, argp *unix.Termios) error { + switch opt { + case TCSANOW: + opt = unix.TIOCSETA + case TCSADRAIN: + opt = unix.TIOCSETAW + case TCSAFLUSH: + opt = unix.TIOCSETAF + default: + return unix.EINVAL + } + return unix.IoctlSetTermios(int(fd), uint(opt), argp) +} diff --git a/tools/utils/tty_linux.go b/tools/utils/tty_linux.go new file mode 100644 index 000000000..4a5483e0b --- /dev/null +++ b/tools/utils/tty_linux.go @@ -0,0 +1,36 @@ +package utils + +import "golang.org/x/sys/unix" + +const ( + TCSETS = 0x5402 + TCSETSW = 0x5403 + TCSETSF = 0x5404 + TCFLSH = 0x540B + TCSBRK = 0x5409 + TCSBRKP = 0x5425 + + IXON = 0x00000400 + IXANY = 0x00000800 + IXOFF = 0x00001000 + CRTSCTS = 0x80000000 +) + +func Tcgetattr(fd int, argp *unix.Termios) error { + return unix.IoctlSetTermios(fd, unix.TCGETS, argp) +} + +func Tcsetattr(fd int, action uintptr, argp *unix.Termios) error { + var request uintptr + switch action { + case TCSANOW: + request = TCSETS + case TCSADRAIN: + request = TCSETSW + case TCSAFLUSH: + request = TCSETSF + default: + return unix.EINVAL + } + return unix.IoctlSetTermios(int(fd), uint(request), argp) +}