pb_x.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // +build linux darwin freebsd netbsd openbsd solaris dragonfly
  2. // +build !appengine
  3. package pb
  4. import (
  5. "errors"
  6. "fmt"
  7. "os"
  8. "os/signal"
  9. "sync"
  10. "syscall"
  11. "golang.org/x/sys/unix"
  12. )
  13. var ErrPoolWasStarted = errors.New("Bar pool was started")
  14. var (
  15. echoLockMutex sync.Mutex
  16. origTermStatePtr *unix.Termios
  17. tty *os.File
  18. )
  19. func init() {
  20. echoLockMutex.Lock()
  21. defer echoLockMutex.Unlock()
  22. var err error
  23. tty, err = os.Open("/dev/tty")
  24. if err != nil {
  25. tty = os.Stdin
  26. }
  27. }
  28. // terminalWidth returns width of the terminal.
  29. func terminalWidth() (int, error) {
  30. echoLockMutex.Lock()
  31. defer echoLockMutex.Unlock()
  32. fd := int(tty.Fd())
  33. ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
  34. if err != nil {
  35. return 0, err
  36. }
  37. return int(ws.Col), nil
  38. }
  39. func lockEcho() (shutdownCh chan struct{}, err error) {
  40. echoLockMutex.Lock()
  41. defer echoLockMutex.Unlock()
  42. if origTermStatePtr != nil {
  43. return shutdownCh, ErrPoolWasStarted
  44. }
  45. fd := int(tty.Fd())
  46. origTermStatePtr, err = unix.IoctlGetTermios(fd, ioctlReadTermios)
  47. if err != nil {
  48. return nil, fmt.Errorf("Can't get terminal settings: %v", err)
  49. }
  50. oldTermios := *origTermStatePtr
  51. newTermios := oldTermios
  52. newTermios.Lflag &^= syscall.ECHO
  53. newTermios.Lflag |= syscall.ICANON | syscall.ISIG
  54. newTermios.Iflag |= syscall.ICRNL
  55. if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newTermios); err != nil {
  56. return nil, fmt.Errorf("Can't set terminal settings: %v", err)
  57. }
  58. shutdownCh = make(chan struct{})
  59. go catchTerminate(shutdownCh)
  60. return
  61. }
  62. func unlockEcho() error {
  63. echoLockMutex.Lock()
  64. defer echoLockMutex.Unlock()
  65. if origTermStatePtr == nil {
  66. return nil
  67. }
  68. fd := int(tty.Fd())
  69. if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, origTermStatePtr); err != nil {
  70. return fmt.Errorf("Can't set terminal settings: %v", err)
  71. }
  72. origTermStatePtr = nil
  73. return nil
  74. }
  75. // listen exit signals and restore terminal state
  76. func catchTerminate(shutdownCh chan struct{}) {
  77. sig := make(chan os.Signal, 1)
  78. signal.Notify(sig, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL)
  79. defer signal.Stop(sig)
  80. select {
  81. case <-shutdownCh:
  82. unlockEcho()
  83. case <-sig:
  84. unlockEcho()
  85. }
  86. }