summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/gdamore/tcell/v2/tty_unix.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/gdamore/tcell/v2/tty_unix.go')
-rw-r--r--vendor/github.com/gdamore/tcell/v2/tty_unix.go173
1 files changed, 173 insertions, 0 deletions
diff --git a/vendor/github.com/gdamore/tcell/v2/tty_unix.go b/vendor/github.com/gdamore/tcell/v2/tty_unix.go
new file mode 100644
index 000000000..a48b2e6da
--- /dev/null
+++ b/vendor/github.com/gdamore/tcell/v2/tty_unix.go
@@ -0,0 +1,173 @@
+// 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 aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
+
+package tcell
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "os/signal"
+ "sync"
+ "syscall"
+ "time"
+
+ "golang.org/x/term"
+)
+
+// devTty is an implementation of the Tty API based upon /dev/tty.
+type devTty struct {
+ fd int
+ f *os.File
+ of *os.File // the first open of /dev/tty
+ saved *term.State
+ sig chan os.Signal
+ cb func()
+ stopQ chan struct{}
+ dev string
+ wg sync.WaitGroup
+ l sync.Mutex
+}
+
+func (tty *devTty) Read(b []byte) (int, error) {
+ return tty.f.Read(b)
+}
+
+func (tty *devTty) Write(b []byte) (int, error) {
+ return tty.f.Write(b)
+}
+
+func (tty *devTty) Close() error {
+ return nil
+}
+
+func (tty *devTty) Start() error {
+ tty.l.Lock()
+ defer tty.l.Unlock()
+
+ // We open another copy of /dev/tty. This is a workaround for unusual behavior
+ // observed in macOS, apparently caused when a subshell (for example) closes our
+ // own tty device (when it exits for example). Getting a fresh new one seems to
+ // resolve the problem. (We believe this is a bug in the macOS tty driver that
+ // fails to account for dup() references to the same file before applying close()
+ // related behaviors to the tty.) We're also holding the original copy we opened
+ // since closing that might have deleterious effects as well. The upshot is that
+ // we will have up to two separate file handles open on /dev/tty. (Note that when
+ // using stdin/stdout instead of /dev/tty this problem is not observed.)
+ var err error
+ if tty.f, err = os.OpenFile(tty.dev, os.O_RDWR, 0); err != nil {
+ return err
+ }
+ tty.fd = int(tty.of.Fd())
+
+ if !term.IsTerminal(tty.fd) {
+ return errors.New("device is not a terminal")
+ }
+
+ _ = tty.f.SetReadDeadline(time.Time{})
+ saved, err := term.MakeRaw(tty.fd) // also sets vMin and vTime
+ if err != nil {
+ return err
+ }
+ tty.saved = saved
+
+ tty.stopQ = make(chan struct{})
+ tty.wg.Add(1)
+ go func(stopQ chan struct{}) {
+ defer tty.wg.Done()
+ for {
+ select {
+ case <-tty.sig:
+ tty.l.Lock()
+ cb := tty.cb
+ tty.l.Unlock()
+ if cb != nil {
+ cb()
+ }
+ case <-stopQ:
+ return
+ }
+ }
+ }(tty.stopQ)
+
+ signal.Notify(tty.sig, syscall.SIGWINCH)
+ return nil
+}
+
+func (tty *devTty) Drain() error {
+ _ = tty.f.SetReadDeadline(time.Now())
+ if err := tcSetBufParams(tty.fd, 0, 0); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (tty *devTty) Stop() error {
+ tty.l.Lock()
+ if err := term.Restore(tty.fd, tty.saved); err != nil {
+ tty.l.Unlock()
+ return err
+ }
+ _ = tty.f.SetReadDeadline(time.Now())
+
+ signal.Stop(tty.sig)
+ close(tty.stopQ)
+ tty.l.Unlock()
+
+ tty.wg.Wait()
+
+ // close our tty device -- we'll get another one if we Start again later.
+ _ = tty.f.Close()
+
+ return nil
+}
+
+func (tty *devTty) WindowSize() (int, int, error) {
+ return term.GetSize(tty.fd)
+}
+
+func (tty *devTty) NotifyResize(cb func()) {
+ tty.l.Lock()
+ tty.cb = cb
+ tty.l.Unlock()
+}
+
+// NewDevTty opens a /dev/tty based Tty.
+func NewDevTty() (Tty, error) {
+ return NewDevTtyFromDev("/dev/tty")
+}
+
+// NewDevTtyFromDev opens a tty device given a path. This can be useful to bind to other nodes.
+func NewDevTtyFromDev(dev string) (Tty, error) {
+ tty := &devTty{
+ dev: dev,
+ sig: make(chan os.Signal),
+ }
+ var err error
+ if tty.of, err = os.OpenFile(dev, os.O_RDWR, 0); err != nil {
+ return nil, err
+ }
+ tty.fd = int(tty.of.Fd())
+ if !term.IsTerminal(tty.fd) {
+ _ = tty.f.Close()
+ return nil, errors.New("not a terminal")
+ }
+ if tty.saved, err = term.GetState(tty.fd); err != nil {
+ _ = tty.f.Close()
+ return nil, fmt.Errorf("failed to get state: %w", err)
+ }
+ return tty, nil
+}