mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-06 01:05:48 +02:00
Agent-Logs-Url: https://github.com/kovidgoyal/kitty/sessions/3ee6840f-29cc-43bb-a4b6-bf9ae604ea11 Co-authored-by: kovidgoyal <1308621+kovidgoyal@users.noreply.github.com>
153 lines
3.4 KiB
Go
153 lines
3.4 KiB
Go
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
|
|
|
package tui
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"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")
|
|
|
|
const password_symbol = "🔒"
|
|
const blink_interval = 500 * time.Millisecond
|
|
|
|
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
|
|
lock_visible := true
|
|
|
|
redraw_prompt := func() {
|
|
lp.QueueWriteString("\r")
|
|
lp.ClearToEndOfLine()
|
|
if has_caps_lock {
|
|
lp.QueueWriteString("\x1b[31m[CapsLock on!]\x1b[39m ")
|
|
}
|
|
symbol := password_symbol
|
|
if !lock_visible {
|
|
symbol = strings.Repeat(" ", wcswidth.Stringwidth(password_symbol))
|
|
}
|
|
lp.QueueWriteString(prompt + shadow + symbol)
|
|
}
|
|
|
|
lp.OnInitialize = func() (string, error) {
|
|
lp.SetCursorVisible(false)
|
|
redraw_prompt()
|
|
if _, err := lp.AddTimer(blink_interval, true, func(loop.IdType) error {
|
|
lock_visible = !lock_visible
|
|
redraw_prompt()
|
|
return nil
|
|
}); err != nil {
|
|
return "", err
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
lp.OnFinalize = func() string {
|
|
lp.SetCursorVisible(true)
|
|
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 {
|
|
shadow += strings.Repeat("*", new_width-old_width)
|
|
}
|
|
redraw_prompt()
|
|
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]
|
|
}
|
|
redraw_prompt()
|
|
} 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
|
|
}
|