Use TUI to build a nice password read control
This commit is contained in:
parent
42a5129553
commit
9d56f8eed2
@ -5,6 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"kitty/tools/tty"
|
"kitty/tools/tty"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kitty/tools/utils"
|
"kitty/tools/utils"
|
||||||
@ -120,6 +121,32 @@ func CreateLoop() (*Loop, error) {
|
|||||||
return &l, nil
|
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) {
|
func (self *Loop) Run() (err error) {
|
||||||
signal_read_file, signal_write_file, err := os.Pipe()
|
signal_read_file, signal_write_file, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -229,6 +256,23 @@ func (self *Loop) queue_write_to_tty(data []byte) {
|
|||||||
self.write_buf = append(self.write_buf, data...)
|
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 {
|
func (self *Loop) write_to_tty() error {
|
||||||
if len(self.write_buf) == 0 || self.controlling_term == nil {
|
if len(self.write_buf) == 0 || self.controlling_term == nil {
|
||||||
return 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
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -21,6 +22,33 @@ const (
|
|||||||
SIGALRM Signal = 9
|
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 {
|
func as_signal(which os.Signal) Signal {
|
||||||
switch which {
|
switch which {
|
||||||
case os.Interrupt:
|
case os.Interrupt:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user