...

Source file src/github.com/gdamore/tcell/v2/tty_unix.go

Documentation: github.com/gdamore/tcell/v2

     1  // Copyright 2021 The TCell Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use file except in compliance with the License.
     5  // You may obtain a copy of the license at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
    16  // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
    17  
    18  package tcell
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"os"
    24  	"os/signal"
    25  	"strconv"
    26  	"sync"
    27  	"syscall"
    28  	"time"
    29  
    30  	"golang.org/x/term"
    31  )
    32  
    33  // devTty is an implementation of the Tty API based upon /dev/tty.
    34  type devTty struct {
    35  	fd    int
    36  	f     *os.File
    37  	of    *os.File // the first open of /dev/tty
    38  	saved *term.State
    39  	sig   chan os.Signal
    40  	cb    func()
    41  	stopQ chan struct{}
    42  	dev   string
    43  	wg    sync.WaitGroup
    44  	l     sync.Mutex
    45  }
    46  
    47  func (tty *devTty) Read(b []byte) (int, error) {
    48  	return tty.f.Read(b)
    49  }
    50  
    51  func (tty *devTty) Write(b []byte) (int, error) {
    52  	return tty.f.Write(b)
    53  }
    54  
    55  func (tty *devTty) Close() error {
    56  	return tty.f.Close()
    57  }
    58  
    59  func (tty *devTty) Start() error {
    60  	tty.l.Lock()
    61  	defer tty.l.Unlock()
    62  
    63  	// We open another copy of /dev/tty.  This is a workaround for unusual behavior
    64  	// observed in macOS, apparently caused when a subshell (for example) closes our
    65  	// own tty device (when it exits for example).  Getting a fresh new one seems to
    66  	// resolve the problem.  (We believe this is a bug in the macOS tty driver that
    67  	// fails to account for dup() references to the same file before applying close()
    68  	// related behaviors to the tty.)  We're also holding the original copy we opened
    69  	// since closing that might have deleterious effects as well.  The upshot is that
    70  	// we will have up to two separate file handles open on /dev/tty.  (Note that when
    71  	// using stdin/stdout instead of /dev/tty this problem is not observed.)
    72  	var err error
    73  	if tty.f, err = os.OpenFile(tty.dev, os.O_RDWR, 0); err != nil {
    74  		return err
    75  	}
    76  
    77  	if !term.IsTerminal(tty.fd) {
    78  		return errors.New("device is not a terminal")
    79  	}
    80  
    81  	_ = tty.f.SetReadDeadline(time.Time{})
    82  	saved, err := term.MakeRaw(tty.fd) // also sets vMin and vTime
    83  	if err != nil {
    84  		return err
    85  	}
    86  	tty.saved = saved
    87  
    88  	tty.stopQ = make(chan struct{})
    89  	tty.wg.Add(1)
    90  	go func(stopQ chan struct{}) {
    91  		defer tty.wg.Done()
    92  		for {
    93  			select {
    94  			case <-tty.sig:
    95  				tty.l.Lock()
    96  				cb := tty.cb
    97  				tty.l.Unlock()
    98  				if cb != nil {
    99  					cb()
   100  				}
   101  			case <-stopQ:
   102  				return
   103  			}
   104  		}
   105  	}(tty.stopQ)
   106  
   107  	signal.Notify(tty.sig, syscall.SIGWINCH)
   108  	return nil
   109  }
   110  
   111  func (tty *devTty) Drain() error {
   112  	_ = tty.f.SetReadDeadline(time.Now())
   113  	if err := tcSetBufParams(tty.fd, 0, 0); err != nil {
   114  		return err
   115  	}
   116  	return nil
   117  }
   118  
   119  func (tty *devTty) Stop() error {
   120  	tty.l.Lock()
   121  	if err := term.Restore(tty.fd, tty.saved); err != nil {
   122  		tty.l.Unlock()
   123  		return err
   124  	}
   125  	_ = tty.f.SetReadDeadline(time.Now())
   126  
   127  	signal.Stop(tty.sig)
   128  	close(tty.stopQ)
   129  	tty.l.Unlock()
   130  
   131  	tty.wg.Wait()
   132  
   133  	// close our tty device -- we'll get another one if we Start again later.
   134  	_ = tty.f.Close()
   135  
   136  	return nil
   137  }
   138  
   139  func (tty *devTty) WindowSize() (int, int, error) {
   140  	w, h, err := term.GetSize(tty.fd)
   141  	if err != nil {
   142  		return 0, 0, err
   143  	}
   144  	if w == 0 {
   145  		w, _ = strconv.Atoi(os.Getenv("COLUMNS"))
   146  	}
   147  	if w == 0 {
   148  		w = 80 // default
   149  	}
   150  	if h == 0 {
   151  		h, _ = strconv.Atoi(os.Getenv("LINES"))
   152  	}
   153  	if h == 0 {
   154  		h = 25 // default
   155  	}
   156  	return w, h, nil
   157  }
   158  
   159  func (tty *devTty) NotifyResize(cb func()) {
   160  	tty.l.Lock()
   161  	tty.cb = cb
   162  	tty.l.Unlock()
   163  }
   164  
   165  // NewDevTty opens a /dev/tty based Tty.
   166  func NewDevTty() (Tty, error) {
   167  	return NewDevTtyFromDev("/dev/tty")
   168  }
   169  
   170  // NewDevTtyFromDev opens a tty device given a path.  This can be useful to bind to other nodes.
   171  func NewDevTtyFromDev(dev string) (Tty, error) {
   172  	tty := &devTty{
   173  		dev: dev,
   174  		sig: make(chan os.Signal),
   175  	}
   176  	var err error
   177  	if tty.of, err = os.OpenFile(dev, os.O_RDWR, 0); err != nil {
   178  		return nil, err
   179  	}
   180  	tty.fd = int(tty.of.Fd())
   181  	if !term.IsTerminal(tty.fd) {
   182  		_ = tty.f.Close()
   183  		return nil, errors.New("not a terminal")
   184  	}
   185  	if tty.saved, err = term.GetState(tty.fd); err != nil {
   186  		_ = tty.f.Close()
   187  		return nil, fmt.Errorf("failed to get state: %w", err)
   188  	}
   189  	return tty, nil
   190  }
   191  

View as plain text