244 lines
5.6 KiB
Go
244 lines
5.6 KiB
Go
// +build darwin linux
|
|
|
|
package main
|
|
|
|
import (
|
|
//"fmt"
|
|
"path"
|
|
"strings"
|
|
"image"
|
|
"image/color"
|
|
"sync"
|
|
|
|
"gioui.org/ui"
|
|
"gioui.org/ui/app"
|
|
"gioui.org/ui/input"
|
|
"gioui.org/ui/key"
|
|
"gioui.org/ui/layout"
|
|
"gioui.org/ui/text"
|
|
"gioui.org/ui/measure"
|
|
"gioui.org/ui/f32"
|
|
"gioui.org/ui/paint"
|
|
"gioui.org/ui/gesture"
|
|
"gioui.org/ui/pointer"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"golang.org/x/image/font/gofont/goregular"
|
|
"golang.org/x/image/font/sfnt"
|
|
|
|
|
|
"git.wow.st/gmp/pass"
|
|
)
|
|
|
|
func main() {
|
|
var err error
|
|
store,err = pass.GetStore()
|
|
if err != nil {
|
|
log(Fatal, err)
|
|
}
|
|
updated = make(chan struct{})
|
|
go Updater()
|
|
log(Info,"Staring event loop")
|
|
go eventLoop()
|
|
app.Main()
|
|
log(Info,"Event loop returned")
|
|
}
|
|
|
|
var (
|
|
l []pass.Pass
|
|
mux sync.Mutex
|
|
store *pass.Store
|
|
updated chan struct{}
|
|
)
|
|
|
|
func Updater() {
|
|
update := func() {
|
|
ltmp,err := store.List()
|
|
if err != nil {
|
|
log(Fatal, err)
|
|
}
|
|
mux.Lock()
|
|
l = ltmp
|
|
mux.Unlock()
|
|
updated <- struct{}{}
|
|
}
|
|
update()
|
|
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
log(Fatal, err)
|
|
}
|
|
watcher.Add(store.Dir)
|
|
for {
|
|
select {
|
|
case <-watcher.Events:
|
|
update()
|
|
case e := <-watcher.Errors:
|
|
log(Info, "Watcher error: ",e)
|
|
}
|
|
}
|
|
}
|
|
|
|
type Button struct {
|
|
Face text.Face
|
|
Label string
|
|
Click gesture.Click
|
|
Color color.RGBA
|
|
clicked bool
|
|
}
|
|
|
|
func layoutRRect(col color.RGBA, c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
|
|
r := float32(c.Px(ui.Dp(4)))
|
|
sz := image.Point{X: cs.Width.Min, Y: cs.Height.Min}
|
|
w, h := float32(sz.X), float32(sz.Y)
|
|
rrect(ops, w, h, r, r, r, r)
|
|
paint.ColorOp{Color: col}.Add(ops)
|
|
paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(ops)
|
|
return layout.Dimensions{Size: sz}
|
|
}
|
|
|
|
// https://pomax.github.io/bezierinfo/#circles_cubic.
|
|
func rrect(ops *ui.Ops, width, height, se, sw, nw, ne float32) {
|
|
w, h := float32(width), float32(height)
|
|
const c = 0.55228475 // 4*(sqrt(2)-1)/3
|
|
var b paint.PathBuilder
|
|
b.Init(ops)
|
|
b.Move(f32.Point{X: w, Y: h - se})
|
|
b.Cube(f32.Point{X: 0, Y: se * c}, f32.Point{X: -se + se*c, Y: se}, f32.Point{X: -se, Y: se}) // SE
|
|
b.Line(f32.Point{X: sw - w + se, Y: 0})
|
|
b.Cube(f32.Point{X: -sw * c, Y: 0}, f32.Point{X: -sw, Y: -sw + sw*c}, f32.Point{X: -sw, Y: -sw}) // SW
|
|
b.Line(f32.Point{X: 0, Y: nw - h + sw})
|
|
b.Cube(f32.Point{X: 0, Y: -nw * c}, f32.Point{X: nw - nw*c, Y: -nw}, f32.Point{X: nw, Y: -nw}) // NW
|
|
b.Line(f32.Point{X: w - ne - nw, Y: 0})
|
|
b.Cube(f32.Point{X: ne * c, Y: 0}, f32.Point{X: ne, Y: ne - ne*c}, f32.Point{X: ne, Y: ne}) // NE
|
|
b.End()
|
|
}
|
|
|
|
|
|
func (b *Button) Layout(c ui.Config, ops *ui.Ops, q input.Queue, cs layout.Constraints) layout.Dimensions {
|
|
b.clicked = false
|
|
for ev, ok := b.Click.Next(q); ok; ev, ok = b.Click.Next(q) {
|
|
if ev.Type == gesture.TypeClick {
|
|
b.clicked = true
|
|
}
|
|
}
|
|
ins := layout.UniformInset(ui.Dp(1))
|
|
cs = ins.Begin(c, ops, cs)
|
|
var dims layout.Dimensions
|
|
st := layout.Stack{}
|
|
st.Init(ops, cs)
|
|
{
|
|
cs = st.Rigid()
|
|
l := text.Label{
|
|
Face: b.Face,
|
|
Text: b.Label,
|
|
}
|
|
ins := layout.UniformInset(ui.Dp(4))
|
|
dims = ins.End(l.Layout(ops, ins.Begin(c, ops, cs)))
|
|
pointer.RectAreaOp{image.Rect(0, 0, dims.Size.X, dims.Size.Y)}.Add(ops)
|
|
b.Click.Add(ops)
|
|
}
|
|
c2 := st.End(dims)
|
|
c1 := st.End(layoutRRect(b.Color, c, ops, st.Expand()))
|
|
dims = st.Layout(c1, c2)
|
|
return ins.End(dims)
|
|
}
|
|
|
|
func (b *Button) Clicked() bool {
|
|
return b.clicked
|
|
}
|
|
|
|
func eventLoop() {
|
|
w := app.NewWindow(app.WithWidth(ui.Dp(250)))
|
|
q := w.Queue()
|
|
_ = q
|
|
ops := new(ui.Ops)
|
|
var faces measure.Faces
|
|
regular, err := sfnt.Parse(goregular.TTF)
|
|
if err != nil {
|
|
log(Fatal, "Cannot parse font.")
|
|
}
|
|
face := faces.For(regular, ui.Sp(16))
|
|
|
|
margin := layout.UniformInset(ui.Dp(10))
|
|
passInput := &text.Editor{Face: face, SingleLine: true}
|
|
passInput.SetText("passphrase")
|
|
passSubmit := &Button{Face: face, Label: "submit"}
|
|
lst := &layout.List{Axis: layout.Vertical}
|
|
passBtns := make([]*Button,0)
|
|
_ = passInput
|
|
_ = passSubmit
|
|
pathnames := make([]string,0)
|
|
|
|
updateBtns := func() {
|
|
passBtns = passBtns[:0]
|
|
pathnames = pathnames[:0]
|
|
mux.Lock()
|
|
for _,x := range l {
|
|
_, n := path.Split(x.Pathname)
|
|
s := strings.Repeat(" /",x.Level)
|
|
z := ""
|
|
if x.Dir { z = "/" }
|
|
passBtns = append(passBtns,&Button{
|
|
Face:face,
|
|
Label:strings.Join([]string{s,n,z},""),
|
|
Color: color.RGBA{A: 0xff, R: 0xf0, G: 0xf0, B: 0xf0},
|
|
})
|
|
pathnames = append(pathnames, x.Pathname)
|
|
}
|
|
mux.Unlock()
|
|
}
|
|
updateBtns()
|
|
|
|
|
|
for {
|
|
select {
|
|
case <-updated:
|
|
log(Info,"UPDATE")
|
|
updateBtns()
|
|
w.Invalidate()
|
|
case e := <-w.Events():
|
|
switch e := e.(type) {
|
|
case app.DestroyEvent:
|
|
return
|
|
case app.UpdateEvent:
|
|
c := &e.Config
|
|
ops.Reset()
|
|
faces.Reset(c)
|
|
var dims layout.Dimensions
|
|
cs := layout.RigidConstraints(e.Size)
|
|
cs = margin.Begin(c, ops, cs)
|
|
|
|
mux.Lock()
|
|
if lst.Dragging() {
|
|
key.HideInputOp{}.Add(ops)
|
|
}
|
|
for lst.Init(c, q, ops, cs, len(passBtns)); lst.More(); lst.Next() {
|
|
btn := passBtns[lst.Index()]
|
|
dims = btn.Layout(c, ops, q, lst.Constraints())
|
|
lst.End(dims)
|
|
if btn.Clicked() {
|
|
// don't block UI thread on decryption attempt
|
|
log(Info,"Clicked ", btn.Label)
|
|
go func(name string) {
|
|
//p,err := store.Decrypt(name, prompt)
|
|
p,err := store.Decrypt(name)
|
|
if err == nil {
|
|
pass.Clip(p)
|
|
} else {
|
|
log(Info,"Can't decrypt ", name)
|
|
log(Info,err)
|
|
}
|
|
}(pathnames[lst.Index()])
|
|
}
|
|
}
|
|
mux.Unlock()
|
|
dims = lst.Layout()
|
|
dims = margin.End(dims)
|
|
w.Update(ops)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|