-
Notifications
You must be signed in to change notification settings - Fork 11
/
main.go
208 lines (184 loc) · 4.47 KB
/
main.go
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package main
//go:generate go run vendor/github.com/limetext/qml-go/cmd/genqrc/main.go assets
import (
"fmt"
"os"
"time"
"github.com/atotto/clipboard"
"github.com/limetext/qml-go"
)
// UI is the model for the password UI
type UI struct {
Status string
query string
Countdown float64
countingDown bool
countdownDone chan bool
ShowMetadata bool
Password struct {
Name string
Metadata string
Info string
Cached bool
}
}
// Passwords is the model for the password list
type Passwords struct {
Selected int
Len int
store *PasswordStore
hits []Password
}
// Quit the application
func (ui *UI) Quit() {
os.Exit(0)
}
// Clearmetadata clears the displayed metadata
func (ui *UI) Clearmetadata() {
ui.setMetadata("")
}
// ToggleShowMetadata toggles between showing and not showing metadata
func (ui *UI) ToggleShowMetadata() {
ui.ShowMetadata = !ui.ShowMetadata
passwords.Update("")
qml.Changed(ui, &ui.ShowMetadata)
}
// Get gets the password at a specific index
func (p *Passwords) Get(index int) Password {
if index > len(p.hits) {
fmt.Println("Bad password fetch", index, len(p.hits), p.Len)
return Password{}
}
pw := p.hits[index]
return pw
}
// ClearClipboard clears the clipboard
func (ui *UI) ClearClipboard() {
if ui.countingDown {
ui.countdownDone <- true
}
ui.countingDown = true
tick := 10 * time.Millisecond
t := time.NewTicker(tick)
remaining := 15.0
for {
select {
case <-ui.countdownDone:
t.Stop()
ui.countingDown = false
return
case <-t.C:
ui.setCountdown(remaining)
ui.setStatus(fmt.Sprintf("Will clear in %.f seconds", remaining))
remaining -= tick.Seconds()
if remaining <= 0 {
clipboard.WriteAll("")
ui.Clearmetadata()
ui.setStatus("Clipboard cleared")
ui.countingDown = false
t.Stop()
return
}
}
}
}
// CopyToClipboard copies the selected password to the system clipboard
func (p *Passwords) CopyToClipboard(selected int) {
if selected >= len(p.hits) {
ui.setStatus("No password selected")
return
}
pw := (p.hits)[selected]
pass := pw.Password()
if err := clipboard.WriteAll(pass); err != nil {
panic(err)
}
ui.setStatus("Copied to clipboard")
go ui.ClearClipboard()
p.Update("") // Trigger a manual update, since the key is probably unlocked now
}
// Select the password with the specified index
func (p *Passwords) Select(selected int) {
p.Selected = selected
// Trigger an update in a goroutine to keep QML from warning about a binding loop
go func() { p.Update("") }()
}
// Query updates the hitlist with the given query
func (ui *UI) Query(q string) {
ui.query = q
passwords.Update("queried")
}
func (ui *UI) setStatus(s string) {
ui.Status = s
qml.Changed(ui, &ui.Status)
}
func (ui *UI) setCountdown(c float64) {
ui.Countdown = c
qml.Changed(ui, &ui.Countdown)
}
func (ui *UI) setMetadata(s string) {
ui.Password.Metadata = s
qml.Changed(ui, &ui.Password.Metadata)
}
// Update is called whenever the store is updated, so the UI needs refreshing
func (p *Passwords) Update(status string) {
p.hits = p.store.Query(ui.query)
p.Len = len(p.hits)
var pw Password
ui.Password.Info = "Test"
if p.Selected < p.Len {
pw = (p.hits)[p.Selected]
ki := pw.KeyInfo()
if ki.Algorithm != "" {
ui.Password.Info = fmt.Sprintf("Encrypted with %d bit %s key %s",
ki.BitLength, ki.Algorithm, ki.Fingerprint)
ui.Password.Cached = ki.Cached
} else {
ui.Password.Info = "Not encrypted"
ui.Password.Cached = false
}
ui.Password.Name = pw.Name
}
if ui.ShowMetadata {
ui.Password.Metadata = pw.Metadata()
} else {
ui.Password.Metadata = "Press enter to decrypt"
ui.Password.Metadata = pw.Raw()
}
qml.Changed(p, &p.Len)
qml.Changed(&ui, &ui.Password)
qml.Changed(&ui, &ui.Password.Metadata)
qml.Changed(&ui, &ui.Password.Name)
ui.setStatus(status)
}
var ui UI
var passwords Passwords
var ps *PasswordStore
func main() {
ps = NewPasswordStore()
passwords.store = ps
ps.Subscribe(passwords.Update)
passwords.Update("Started")
if err := qml.Run(run); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
func run() error {
qml.SetApplicationName("GoPass")
engine := qml.NewEngine()
engine.Context().SetVar("passwords", &passwords)
engine.Context().SetVar("ui", &ui)
_, err := engine.LoadFile("qrc:/assets/RoundButton.qml")
if err != nil {
return err
}
controls, err := engine.LoadFile("qrc:/assets/main.qml")
if err != nil {
return err
}
window := controls.CreateWindow(nil)
window.Show()
window.Wait()
return nil
}