File: password.go

package info (click to toggle)
kitty 0.45.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,468 kB
  • sloc: ansic: 84,285; python: 57,992; objc: 5,432; sh: 1,333; xml: 364; makefile: 144; javascript: 78
file content (137 lines) | stat: -rw-r--r-- 3,147 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>

package tui

import (
	"errors"
	"fmt"
	"strings"

	"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")

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
}