Use TUI to build a nice password read control
This commit is contained in:
parent
42a5129553
commit
9d56f8eed2
@ -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
90
tools/tui/password.go
Normal 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
|
||||
}
|
||||
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user