They are not needed and there is always a small risk that a release event could be delivered after the kitten has stopped reading from the tty, thereby leaking into the environment.
138 lines
3.0 KiB
Go
138 lines
3.0 KiB
Go
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
|
|
|
package tui
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"kitty/tools/tui/loop"
|
|
"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
|
|
}
|