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