Use TUI to build a nice password read control

This commit is contained in:
Kovid Goyal 2022-08-24 12:23:11 +05:30
parent 42a5129553
commit 9d56f8eed2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 162 additions and 0 deletions

View File

@ -5,6 +5,7 @@ import (
"io"
"kitty/tools/tty"
"os"
"syscall"
"time"
"kitty/tools/utils"
@ -120,6 +121,32 @@ func CreateLoop() (*Loop, error) {
return &l, nil
}
func (self *Loop) UseAlternateScreen() {
self.terminal_options.alternate_screen = true
}
func (self *Loop) MouseTracking(mt MouseTracking) {
self.terminal_options.mouse_tracking = mt
}
func (self *Loop) DeathSignalName() string {
if self.death_signal != SIGNULL {
return self.death_signal.String()
}
return ""
}
func (self *Loop) KillIfSignalled() {
switch self.death_signal {
case SIGINT:
syscall.Kill(-1, syscall.SIGINT)
case SIGTERM:
syscall.Kill(-1, syscall.SIGTERM)
case SIGHUP:
syscall.Kill(-1, syscall.SIGHUP)
}
}
func (self *Loop) Run() (err error) {
signal_read_file, signal_write_file, err := os.Pipe()
if err != nil {
@ -229,6 +256,23 @@ func (self *Loop) queue_write_to_tty(data []byte) {
self.write_buf = append(self.write_buf, data...)
}
func (self *Loop) QueueWriteString(data string) {
self.queue_write_to_tty([]byte(data))
}
func (self *Loop) ExitCode() int {
return self.exit_code
}
func (self *Loop) Beep() {
self.QueueWriteString("\a")
}
func (self *Loop) Quit(exit_code int) {
self.exit_code = exit_code
self.keep_going = false
}
func (self *Loop) write_to_tty() error {
if len(self.write_buf) == 0 || self.controlling_term == nil {
return nil

90
tools/tui/password.go Normal file
View File

@ -0,0 +1,90 @@
package tui
import (
"errors"
"fmt"
"strings"
"github.com/mattn/go-runewidth"
)
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) {
loop, err := CreateLoop()
shadow := ""
if err != nil {
return
}
loop.OnText = func(loop *Loop, text string, from_key_event bool, in_bracketed_paste bool) error {
old_width := runewidth.StringWidth(password)
password += text
new_width := runewidth.StringWidth(password)
if new_width > old_width {
extra := strings.Repeat("*", new_width-old_width)
loop.QueueWriteString(extra)
shadow += extra
}
return nil
}
loop.OnKeyEvent = func(loop *Loop, event *KeyEvent) error {
if event.MatchesPressOrRepeat("backscape") || event.MatchesPressOrRepeat("delete") {
event.Handled = true
if len(password) > 0 {
old_width := runewidth.StringWidth(password)
password = password[:len(password)-1]
new_width := runewidth.StringWidth(password)
delta := new_width - old_width
if delta > 0 {
if delta > len(shadow) {
delta = len(shadow)
}
shadow = shadow[:len(shadow)-delta]
loop.QueueWriteString(strings.Repeat("\x08\x1b[P", delta))
}
} else {
loop.Beep()
}
}
if event.MatchesPressOrRepeat("enter") || event.MatchesPressOrRepeat("return") {
event.Handled = true
if password == "" {
loop.Quit(1)
} else {
loop.Quit(0)
}
}
if event.MatchesPressOrRepeat("esc") {
event.Handled = true
loop.Quit(1)
return Canceled
}
return nil
}
err = loop.Run()
if err != nil {
return
}
ds := loop.DeathSignalName()
if ds != "" {
if kill_if_signaled {
loop.KillIfSignalled()
return
}
return "", &KilledBySignal{Msg: fmt.Sprint("Killed by signal: ", ds), SignalName: ds}
}
if loop.ExitCode() != 0 {
password = ""
}
return password, nil
}

View File

@ -1,6 +1,7 @@
package tui
import (
"fmt"
"os"
"os/signal"
"syscall"
@ -21,6 +22,33 @@ const (
SIGALRM Signal = 9
)
func (self *Signal) String() string {
switch *self {
case SIGNULL:
return "SIGNULL"
case SIGINT:
return "SIGINT"
case SIGTERM:
return "SIGTERM"
case SIGTSTP:
return "SIGTSTP"
case SIGHUP:
return "SIGHUP"
case SIGTTIN:
return "SIGTTIN"
case SIGTTOU:
return "SIGTTOU"
case SIGUSR1:
return "SIGUSR1"
case SIGUSR2:
return "SIGUSR2"
case SIGALRM:
return "SIGALRM"
default:
return fmt.Sprintf("SIG#%s", *self)
}
}
func as_signal(which os.Signal) Signal {
switch which {
case os.Interrupt: