util_unix.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // Copyright 2010 Jonas mg
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. // +build !plan9,!windows
  7. package term
  8. //#include <unistd.h>
  9. //import "C"
  10. import (
  11. "bytes"
  12. "math/rand"
  13. "os"
  14. "os/signal"
  15. "syscall"
  16. "time"
  17. "unicode"
  18. "unicode/utf8"
  19. "github.com/kless/term/sys"
  20. )
  21. var shellsWithoutANSI = []string{"dumb", "cons25"}
  22. // SupportANSI checks if the terminal supports ANSI escape sequences.
  23. func SupportANSI() bool {
  24. term := os.Getenv("TERM")
  25. if term == "" {
  26. return false
  27. }
  28. for _, v := range shellsWithoutANSI {
  29. if v == term {
  30. return false
  31. }
  32. }
  33. return true
  34. }
  35. // char *ttyname(int fd)
  36. // http://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/ttyname.c;hb=HEAD
  37. // http://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/posix/ttyname.c;hb=HEAD
  38. // GetName gets the name of a term.
  39. /*func GetName(fd int) (string, error) {
  40. name, errno := C.ttyname(C.int(fd))
  41. if errno != nil {
  42. return "", fmt.Errorf("term.TTYName: %s", errno)
  43. }
  44. return C.GoString(name), nil
  45. }*/
  46. // int isatty(int fd)
  47. // http://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/posix/isatty.c;hb=HEAD
  48. // IsTerminal returns true if the file descriptor is a term.
  49. func IsTerminal(fd int) bool {
  50. return sys.Getattr(fd, &sys.Termios{}) == nil
  51. }
  52. // ReadPassword reads characters from the input until press Enter or until
  53. // fill in the given slice.
  54. //
  55. // Only reads characters that include letters, marks, numbers, punctuation,
  56. // and symbols from Unicode categories L, M, N, P, S, besides of the
  57. // ASCII space character.
  58. // Ctrl-C interrumpts, and backspace removes the last character read.
  59. //
  60. // Returns the number of bytes read.
  61. func ReadPassword(password []byte) (n int, err error) {
  62. ter, err := New()
  63. if err != nil {
  64. return 0, err
  65. }
  66. defer func() {
  67. err2 := ter.Restore()
  68. if err2 != nil && err == nil {
  69. err = err2
  70. }
  71. }()
  72. if err = ter.RawMode(); err != nil {
  73. return 0, err
  74. }
  75. key := make([]byte, 4) // In-memory representation of a rune.
  76. lenPassword := 0 // Number of characters read.
  77. if PasswordShadowed {
  78. rand.Seed(int64(time.Now().Nanosecond()))
  79. }
  80. L:
  81. for {
  82. n, err = syscall.Read(InputFD, key)
  83. if err != nil {
  84. return 0, err
  85. }
  86. if n == 1 {
  87. switch key[0] {
  88. case sys.K_RETURN:
  89. break L
  90. case sys.K_BACK:
  91. if lenPassword != 0 {
  92. lenPassword--
  93. password[lenPassword] = 0
  94. }
  95. continue
  96. case sys.K_CTRL_C:
  97. syscall.Write(syscall.Stdout, _CTRL_C)
  98. // Clean data stored, if any.
  99. for i, v := range password {
  100. if v == 0 {
  101. break
  102. }
  103. password[i] = 0
  104. }
  105. return 0, nil
  106. }
  107. }
  108. char, _ := utf8.DecodeRune(key)
  109. if unicode.IsPrint(char) {
  110. password[lenPassword] = key[0] // Only want a character by key
  111. lenPassword++
  112. if PasswordShadowed {
  113. syscall.Write(syscall.Stdout, bytes.Repeat(_SHADOW_CHAR, rand.Intn(3)+1))
  114. }
  115. if lenPassword == len(password) {
  116. break
  117. }
  118. }
  119. }
  120. syscall.Write(syscall.Stdout, _RETURN)
  121. n = lenPassword
  122. return
  123. }
  124. // WinSize represents a channel, Change, to know when the window size has
  125. // changed through function DetectWinSize.
  126. type WinSize struct {
  127. Change chan bool
  128. quit chan bool
  129. wait chan bool
  130. }
  131. // DetectWinSize caughts a signal named SIGWINCH whenever the window size changes,
  132. // being indicated in channel `WinSize.Change`.
  133. func DetectWinSize() *WinSize {
  134. w := &WinSize{
  135. make(chan bool),
  136. make(chan bool),
  137. make(chan bool),
  138. }
  139. changeSig := make(chan os.Signal)
  140. signal.Notify(changeSig, syscall.SIGWINCH)
  141. go func() {
  142. for {
  143. select {
  144. case <-changeSig:
  145. // Add a pause because it is sent two signals at maximizing a window.
  146. time.Sleep(7 * time.Millisecond)
  147. w.Change <- true
  148. case <-w.quit:
  149. w.wait <- true
  150. return
  151. }
  152. }
  153. }()
  154. return w
  155. }
  156. // Close closes the goroutine started to trap the signal.
  157. func (w *WinSize) Close() {
  158. w.quit <- true
  159. <-w.wait
  160. }