password.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package tui
  3. import (
  4. "errors"
  5. "fmt"
  6. "strings"
  7. "kitty/tools/tui/loop"
  8. "kitty/tools/wcswidth"
  9. )
  10. type KilledBySignal struct {
  11. Msg string
  12. SignalName string
  13. }
  14. func (self *KilledBySignal) Error() string { return self.Msg }
  15. var Canceled = errors.New("Canceled by user")
  16. func ReadPassword(prompt string, kill_if_signaled bool) (password string, err error) {
  17. lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.FullKeyboardProtocol)
  18. shadow := ""
  19. if err != nil {
  20. return
  21. }
  22. capspress_was_locked := false
  23. has_caps_lock := false
  24. redraw_prompt := func() {
  25. text := prompt + shadow
  26. lp.QueueWriteString("\r")
  27. lp.ClearToEndOfLine()
  28. if has_caps_lock {
  29. lp.QueueWriteString("\x1b[31m[CapsLock on!]\x1b[39m ")
  30. }
  31. lp.QueueWriteString(text)
  32. }
  33. lp.OnInitialize = func() (string, error) {
  34. lp.QueueWriteString(prompt)
  35. lp.SetCursorShape(loop.BAR_CURSOR, true)
  36. return "", nil
  37. }
  38. lp.OnFinalize = func() string {
  39. lp.SetCursorShape(loop.BLOCK_CURSOR, true)
  40. return "\r\n"
  41. }
  42. lp.OnText = func(text string, from_key_event bool, in_bracketed_paste bool) error {
  43. old_width := wcswidth.Stringwidth(password)
  44. password += text
  45. new_width := wcswidth.Stringwidth(password)
  46. if new_width > old_width {
  47. extra := strings.Repeat("*", new_width-old_width)
  48. lp.QueueWriteString(extra)
  49. shadow += extra
  50. }
  51. return nil
  52. }
  53. lp.OnKeyEvent = func(event *loop.KeyEvent) error {
  54. has_caps := false
  55. if strings.ToLower(event.Key) == "caps_lock" {
  56. if event.Type == loop.RELEASE {
  57. has_caps = !capspress_was_locked
  58. capspress_was_locked = false
  59. } else {
  60. capspress_was_locked = event.HasCapsLock()
  61. has_caps = true
  62. }
  63. } else {
  64. has_caps = event.HasCapsLock()
  65. }
  66. if has_caps_lock != has_caps {
  67. has_caps_lock = has_caps
  68. redraw_prompt()
  69. }
  70. if event.MatchesPressOrRepeat("backspace") || event.MatchesPressOrRepeat("delete") {
  71. event.Handled = true
  72. if len(password) > 0 {
  73. old_width := wcswidth.Stringwidth(password)
  74. password = password[:len(password)-1]
  75. new_width := wcswidth.Stringwidth(password)
  76. delta := old_width - new_width
  77. if delta > 0 {
  78. if delta > len(shadow) {
  79. delta = len(shadow)
  80. }
  81. shadow = shadow[:len(shadow)-delta]
  82. lp.QueueWriteString(strings.Repeat("\x08\x1b[P", delta))
  83. }
  84. } else {
  85. lp.Beep()
  86. }
  87. }
  88. if event.MatchesPressOrRepeat("enter") || event.MatchesPressOrRepeat("return") {
  89. event.Handled = true
  90. if password == "" {
  91. lp.Quit(1)
  92. } else {
  93. lp.Quit(0)
  94. }
  95. }
  96. if event.MatchesPressOrRepeat("esc") {
  97. event.Handled = true
  98. lp.Quit(1)
  99. return Canceled
  100. }
  101. return nil
  102. }
  103. lp.OnResumeFromStop = func() error {
  104. redraw_prompt()
  105. return nil
  106. }
  107. err = lp.Run()
  108. if err != nil {
  109. return
  110. }
  111. ds := lp.DeathSignalName()
  112. if ds != "" {
  113. if kill_if_signaled {
  114. lp.KillIfSignalled()
  115. return
  116. }
  117. return "", &KilledBySignal{Msg: fmt.Sprint("Killed by signal: ", ds), SignalName: ds}
  118. }
  119. if lp.ExitCode() != 0 {
  120. password = ""
  121. }
  122. return password, nil
  123. }