884 lines
20 KiB
Go
884 lines
20 KiB
Go
// +build darwin linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"gioui.org/app"
|
|
"gioui.org/io/key"
|
|
"gioui.org/io/system"
|
|
"gioui.org/layout"
|
|
"gioui.org/text"
|
|
"gioui.org/unit"
|
|
"gioui.org/widget"
|
|
"gioui.org/widget/material"
|
|
|
|
"gioui.org/font/gofont"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"gopkg.in/yaml.v2"
|
|
|
|
"git.wow.st/gmp/passgo"
|
|
"git.wow.st/gmp/rand"
|
|
)
|
|
|
|
type conf struct {
|
|
StoreDir string
|
|
ClearDelay int
|
|
}
|
|
|
|
func main() {
|
|
if false { go func() {
|
|
f, err := os.Create("cpuprofile")
|
|
if err != nil {
|
|
fmt.Printf("Can't create CPU profile\n")
|
|
os.Exit(-1)
|
|
}
|
|
fmt.Printf("Starting CPU profile\n")
|
|
if err := pprof.StartCPUProfile(f); err != nil {
|
|
fmt.Printf("Can't start CPU profile\n")
|
|
f.Close()
|
|
os.Exit(-1)
|
|
}
|
|
time.Sleep(time.Second * 10)
|
|
fmt.Printf("Stopping CPU profile\n")
|
|
pprof.StopCPUProfile()
|
|
f.Close()
|
|
fmt.Printf("CPU profile written\n")
|
|
}() }
|
|
var fd *os.File
|
|
var err error
|
|
confDir, err = getConfDir()
|
|
if err != nil {
|
|
fmt.Printf("Can't get config directory")
|
|
os.Exit(-1)
|
|
}
|
|
confFile := path.Join(confDir, "config.yml")
|
|
if _, err := os.Stat(confFile); os.IsNotExist(err) {
|
|
fd, err = os.Create(confFile)
|
|
if err != nil {
|
|
log(Fatal, "Cannot create configuration file: ", err)
|
|
}
|
|
}
|
|
|
|
confbytes, err := ioutil.ReadFile(confFile)
|
|
if err != nil {
|
|
log(Fatal, "Cannot read configuration file: ", err)
|
|
}
|
|
if err = yaml.UnmarshalStrict(confbytes, &Config); err != nil {
|
|
log(Fatal, "Cannot parse configuration file: ", err)
|
|
}
|
|
store.Dir = Config.StoreDir
|
|
log(Info, " StoreDir = ", store.Dir)
|
|
go func() {
|
|
err = passgo.GetStore(&store)
|
|
if err != nil {
|
|
log(Info, err)
|
|
}
|
|
}()
|
|
if Config.ClearDelay == 0 {
|
|
Config.ClearDelay = 45
|
|
}
|
|
|
|
if fd != nil { // we still have an empty conf file open:
|
|
Config.StoreDir = store.Dir
|
|
saveConf(fd)
|
|
}
|
|
|
|
reload = make(chan struct{})
|
|
updated = make(chan struct{})
|
|
chdir = make(chan struct{})
|
|
passch = make(chan []byte)
|
|
|
|
go func() {
|
|
log(Info,"passgo.Identities()")
|
|
passgo.Identities()
|
|
}()
|
|
go Updater()
|
|
log(Info, "Staring event loop")
|
|
go eventLoop()
|
|
app.Main()
|
|
log(Info, "Event loop returned")
|
|
}
|
|
|
|
var (
|
|
fontSize float32
|
|
confDir string
|
|
Config conf
|
|
l []passgo.Pass
|
|
mux sync.Mutex
|
|
store passgo.Store
|
|
reload chan struct{}
|
|
updated chan struct{}
|
|
chdir chan struct{}
|
|
passch chan []byte
|
|
th *material.Theme
|
|
)
|
|
|
|
func Updater() {
|
|
//time.Sleep(time.Second * 2)
|
|
update := func() {
|
|
fmt.Printf("update()\n")
|
|
ltmp, err := store.List()
|
|
if err != nil {
|
|
log(Info, err)
|
|
}
|
|
mux.Lock()
|
|
l = ltmp
|
|
mux.Unlock()
|
|
updated <- struct{}{}
|
|
}
|
|
update()
|
|
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
log(Fatal, err)
|
|
}
|
|
dir := store.Dir
|
|
watcher.Add(dir)
|
|
for {
|
|
select {
|
|
case <-reload:
|
|
update()
|
|
case <-watcher.Events:
|
|
update()
|
|
case e := <-watcher.Errors:
|
|
log(Info, "Watcher error: ", e)
|
|
case <-chdir:
|
|
err := watcher.Remove(dir)
|
|
if err != nil {
|
|
log(Info, "Error removing watcher: ", err)
|
|
}
|
|
watcher.Add(store.Dir)
|
|
update()
|
|
}
|
|
}
|
|
}
|
|
|
|
func saveConf(fds ...*os.File) {
|
|
var fd *os.File
|
|
var err error
|
|
if len(fds) > 0 && fds[0] != nil {
|
|
fd = fds[0]
|
|
} else {
|
|
fd, err = os.Create(path.Join(confDir, "config.yml"))
|
|
if err != nil {
|
|
log(Error, "Config file = ", path.Join(confDir, "config.yml"))
|
|
log(Fatal, "Cannot open config file: ", err.Error())
|
|
}
|
|
}
|
|
defer fd.Close()
|
|
|
|
confbytes, err := yaml.Marshal(Config)
|
|
if err != nil {
|
|
log(Fatal, "Cannot save configuration: ", err)
|
|
}
|
|
_, err = fd.Write(confbytes)
|
|
if err != nil {
|
|
log(Fatal, "Cannot write to configuration: ", err)
|
|
}
|
|
}
|
|
|
|
func eventLoop() {
|
|
gofont.Register()
|
|
th = material.NewTheme()
|
|
th.TextSize = unit.Sp(fontSize)
|
|
w := app.NewWindow(
|
|
app.Size(unit.Dp(250), unit.Dp(500)),
|
|
app.Title("passgo"))
|
|
initPgp(w)
|
|
gtx := &layout.Context{Queue: w.Queue()}
|
|
//time.Sleep(time.Second/5)
|
|
|
|
var margincs layout.Constraints
|
|
|
|
var c1 layout.FlexChild // flex child for title bar
|
|
|
|
sysinset := &layout.Inset{}
|
|
margin := layout.UniformInset(unit.Dp(10))
|
|
|
|
title := th.Body1("passgo")
|
|
dotsBtn := &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: "\xe2\x8b\xae",
|
|
Alignment: text.Middle,
|
|
Color: black,
|
|
Background: gray,
|
|
}
|
|
|
|
titleflex := &layout.Flex{Axis: layout.Horizontal}
|
|
|
|
flex := &layout.Flex{Axis: layout.Vertical}
|
|
lst := &layout.List{Axis: layout.Vertical}
|
|
passBtns := make([]*Button, 0)
|
|
pathnames := make([]string, 0)
|
|
copied := &Overlay{Size: unit.Sp(fontSize), Text: "copied to clipboard",
|
|
Color: black,
|
|
Background: darkgray,
|
|
Alignment: text.Middle,
|
|
}
|
|
cleared := &Overlay{Size: unit.Sp(fontSize), Text: "clipboard cleared",
|
|
Color: black,
|
|
Background: darkgray,
|
|
Alignment: text.Middle,
|
|
}
|
|
overlay := copied
|
|
var overlayStart time.Time
|
|
|
|
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{
|
|
Size: unit.Sp(fontSize),
|
|
Label: strings.Join([]string{s, n, z}, ""),
|
|
Background: gray,
|
|
})
|
|
pathnames = append(pathnames, x.Pathname)
|
|
}
|
|
mux.Unlock()
|
|
}
|
|
|
|
idBtns := make([]*Button, 0)
|
|
|
|
updateIdBtns := func() {
|
|
log(Info,"passgo.Identities()")
|
|
ids, err := passgo.Identities()
|
|
if err != nil {
|
|
log(Info, err)
|
|
return
|
|
}
|
|
for i, n := range ids {
|
|
if i >= len(idBtns) {
|
|
idBtns = append(idBtns, &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: n,
|
|
Alignment: text.End,
|
|
Color: black,
|
|
Background: gray,
|
|
})
|
|
} else {
|
|
idBtns[i].Label = n
|
|
}
|
|
}
|
|
idBtns = idBtns[:len(ids)]
|
|
}
|
|
|
|
confBtn := &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: "configure",
|
|
Alignment: text.Middle,
|
|
Color: black,
|
|
Background: gray,
|
|
}
|
|
|
|
storeDirLabel := th.Label(unit.Sp(fontSize), "Store directory")
|
|
storeDirEd := &widget.Editor{ SingleLine: true}
|
|
storeDirEd.SetText(store.Dir)
|
|
saveBtn := &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: "save",
|
|
Alignment: text.End,
|
|
Color: black,
|
|
Background: gray,
|
|
}
|
|
backBtn := &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: "back",
|
|
Alignment: text.End,
|
|
Color: black,
|
|
Background: gray,
|
|
}
|
|
confirmLabel := th.Label(unit.Sp(fontSize), "Password exists. Overwrite?")
|
|
yesBtn := &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: "yes",
|
|
Alignment: text.End,
|
|
Color: black,
|
|
Background: gray,
|
|
}
|
|
|
|
promptLabel := th.Label(unit.Sp(fontSize), "passphrase")
|
|
promptEd := &widget.Editor{ SingleLine: true, Submit: true }
|
|
okBtn := &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: "ok",
|
|
Alignment: text.End,
|
|
Color: black,
|
|
Background: gray,
|
|
}
|
|
plusBtn := &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: "+",
|
|
Alignment: text.Middle,
|
|
Color: black,
|
|
Background: gray,
|
|
}
|
|
|
|
insertLabel := th.Label(unit.Sp(fontSize), "Insert")
|
|
passnameLabel := th.Label(unit.Sp(fontSize), "password name:")
|
|
passnameEd := &widget.Editor{ SingleLine: true }
|
|
passvalLabel := th.Label(unit.Sp(fontSize), "password value:")
|
|
passvalEd := &widget.Editor{ SingleLine: true, Submit: true }
|
|
|
|
noidLabel := th.Label(unit.Sp(fontSize), noidLabelText)
|
|
idLabel := th.Label(unit.Sp(fontSize), "Select ID")
|
|
|
|
anim := &time.Ticker{}
|
|
animating := false
|
|
animOn := func() {
|
|
log(Info, "animOn()")
|
|
anim = time.NewTicker(time.Second / 30)
|
|
animating = true
|
|
w.Invalidate()
|
|
}
|
|
animOff := func() {
|
|
log(Info, "animOff()")
|
|
anim.Stop()
|
|
animating = false
|
|
}
|
|
|
|
var listPage, idPage, insertPage, confirmPage, confPage, promptPage, page func()
|
|
_ = idPage
|
|
|
|
prompt := func() []byte {
|
|
page = promptPage
|
|
promptEd.SetText("")
|
|
w.Invalidate()
|
|
return <-passch
|
|
}
|
|
|
|
listPage = func() {
|
|
// timing variables used for animation
|
|
fade1a, fade1b := 1.5, 2.0
|
|
start2 := float64(Config.ClearDelay)
|
|
fade2a, end := start2+1.5, start2+2.0
|
|
|
|
c2 := flex.Flex(gtx, 1.0, func() {
|
|
mux.Lock()
|
|
if lst.Dragging() {
|
|
key.HideInputOp{}.Add(gtx.Ops)
|
|
}
|
|
switch {
|
|
case store.Empty:
|
|
confBtn.Layout(gtx)
|
|
if confBtn.Clicked() {
|
|
log(Info, "Configure")
|
|
w.Invalidate()
|
|
page = confPage
|
|
}
|
|
case store.Id == "":
|
|
idLabel.Layout(gtx)
|
|
w.Invalidate()
|
|
page = idPage
|
|
default:
|
|
lst.Layout(gtx, len(passBtns), func(i int) {
|
|
btn := passBtns[i]
|
|
gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
|
|
btn.Layout(gtx)
|
|
if btn.Clicked() {
|
|
log(Info, "Clicked ", btn.Label)
|
|
// don't block UI thread on decryption attempt
|
|
go func(name string) {
|
|
p, err := store.Decrypt(name, prompt)
|
|
//p, err := store.Decrypt(name)
|
|
if err == nil {
|
|
passgo.Clip(p)
|
|
overlayStart = time.Now()
|
|
overlay = copied
|
|
overlay.Color = black
|
|
overlay.Background = darkgray
|
|
w.Invalidate()
|
|
go func() {
|
|
time.Sleep(time.Millisecond * time.Duration(fade1a*1000))
|
|
animOn()
|
|
}()
|
|
go func() {
|
|
time.Sleep(time.Millisecond * time.Duration(Config.ClearDelay*1000))
|
|
log(Info, "clearing clipboard")
|
|
passgo.Clip("")
|
|
}()
|
|
} else {
|
|
log(Info, "Can't decrypt ", name)
|
|
log(Info, err)
|
|
}
|
|
}(pathnames[i])
|
|
}
|
|
})
|
|
}
|
|
mux.Unlock()
|
|
})
|
|
flex.Layout(gtx, c1, c2)
|
|
x := time.Since(overlayStart).Seconds()
|
|
if x >= fade1b && x < start2 && animating {
|
|
animOff()
|
|
go func() {
|
|
time.Sleep(time.Millisecond * time.Duration((start2-x)*1000))
|
|
w.Invalidate()
|
|
time.Sleep(time.Millisecond * time.Duration((fade2a-start2)*1000))
|
|
animOn()
|
|
}()
|
|
}
|
|
if (x >= fade1a && x < fade1b) || (x > fade2a && x < end) {
|
|
if !animating {
|
|
animOn()
|
|
}
|
|
var fade float64
|
|
switch {
|
|
case x < fade1b:
|
|
fade = (fade1b - x) / (fade1b - fade1a)
|
|
case x > fade2a:
|
|
fade = (end - x) / (end - fade2a)
|
|
}
|
|
overlay.Color.R = uint8(float64(black.R) * fade)
|
|
overlay.Color.G = uint8(float64(black.G) * fade)
|
|
overlay.Color.B = uint8(float64(black.B) * fade)
|
|
overlay.Color.A = uint8(float64(black.A) * fade)
|
|
overlay.Background.R = uint8(float64(darkgray.R) * fade)
|
|
overlay.Background.G = uint8(float64(darkgray.G) * fade)
|
|
overlay.Background.B = uint8(float64(darkgray.B) * fade)
|
|
overlay.Background.A = uint8(float64(darkgray.A) * fade)
|
|
}
|
|
if x <= fade1b || (x > start2 && x < end) {
|
|
if x > start2 && overlay == copied {
|
|
overlay = cleared
|
|
overlay.Color = black
|
|
overlay.Background = darkgray
|
|
}
|
|
gtx.Constraints = margincs
|
|
al := layout.Align(layout.SE)
|
|
al.Layout(gtx, func() {
|
|
gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
|
|
overlay.Layout(gtx)
|
|
})
|
|
}
|
|
if x > start2 && x < fade2a {
|
|
// animOff()
|
|
}
|
|
if animating && x > end {
|
|
animOff()
|
|
}
|
|
}
|
|
|
|
updateBtn := &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: "Update",
|
|
Alignment: text.Middle,
|
|
Color: black,
|
|
Background: gray,
|
|
}
|
|
idEd := &widget.Editor{SingleLine: true, Submit: true}
|
|
idSubmitBtn := &Button{
|
|
Size: unit.Sp(fontSize),
|
|
Label: "Submit",
|
|
Alignment: text.Middle,
|
|
Color: black,
|
|
Background: gray,
|
|
}
|
|
|
|
idPage = func() {
|
|
if !animating {
|
|
animOn()
|
|
}
|
|
c2 := flex.Rigid(gtx, func() {
|
|
idLabel.Layout(gtx)
|
|
})
|
|
var c3 layout.FlexChild
|
|
var c4 layout.FlexChild
|
|
var c5 layout.FlexChild
|
|
var c6 layout.FlexChild
|
|
var c7 layout.FlexChild
|
|
if len(idBtns) == 0 {
|
|
c3 = flex.Rigid(gtx, func() {
|
|
updateBtn.Layout(gtx)
|
|
})
|
|
if updateBtn.Clicked() {
|
|
updateIdBtns()
|
|
w.Invalidate()
|
|
}
|
|
c4 = flex.Rigid(gtx, func() {
|
|
th.Editor("id").Layout(gtx, idEd)
|
|
})
|
|
for _, e := range idEd.Events(gtx) {
|
|
switch e.(type) {
|
|
case widget.SubmitEvent:
|
|
log(Info, "Submit")
|
|
store.Id = idEd.Text()
|
|
page = listPage
|
|
}
|
|
}
|
|
c5 = flex.Rigid(gtx, func() {
|
|
idSubmitBtn.Layout(gtx)
|
|
})
|
|
if idSubmitBtn.Clicked() {
|
|
store.Id = idEd.Text()
|
|
page = listPage
|
|
}
|
|
c6 = flex.Rigid(gtx, func() {
|
|
noidLabel.Layout(gtx)
|
|
})
|
|
} else {
|
|
c3 = flex.Rigid(gtx, func() { })
|
|
c4 = flex.Rigid(gtx, func() { })
|
|
c5 = flex.Rigid(gtx, func() { })
|
|
c6 = flex.Rigid(gtx, func() { })
|
|
}
|
|
c7 = flex.Rigid(gtx, func() {
|
|
if len(idBtns) > 0 { // still zero after update
|
|
for i := 0; i < len(idBtns); i++ {
|
|
lst.Layout(gtx, len(idBtns), func(i int) {
|
|
idBtns[i].Layout(gtx)
|
|
})
|
|
}
|
|
for _, btn := range idBtns {
|
|
if btn.Clicked() {
|
|
log(Info, "ID selected: ", btn.Label)
|
|
store.SetID(btn.Label)
|
|
w.Invalidate()
|
|
animOff()
|
|
page = listPage
|
|
}
|
|
}
|
|
}
|
|
})
|
|
flex.Layout(gtx, c1, c2, c3, c4, c5, c6, c7)
|
|
}
|
|
|
|
var insName, insValue string
|
|
genBtn := &SelButton{SelColor: gray}
|
|
genBtn.Button = Button{Size: unit.Sp(fontSize), Label: "generate"}
|
|
symBtn := &SelButton{SelColor: gray}
|
|
numBtn := &SelButton{SelColor: gray}
|
|
symBtn.Button = Button{Size: unit.Sp(fontSize), Label: "@"}
|
|
numBtn.Button = Button{Size: unit.Sp(fontSize), Label: "#"}
|
|
symBtn.Select()
|
|
numBtn.Select()
|
|
lenEd := &widget.Editor{ SingleLine: true, Alignment: text.End }
|
|
lenEd.SetText("15")
|
|
lBtn := &Button{Size: unit.Sp(fontSize), Label: "<", Background: gray}
|
|
rBtn := &Button{Size: unit.Sp(fontSize), Label: ">", Background: gray}
|
|
|
|
updatePw := func() {
|
|
if !genBtn.Selected {
|
|
passvalEd.SetText("")
|
|
return
|
|
}
|
|
var gen rand.Generator
|
|
switch {
|
|
case symBtn.Selected && numBtn.Selected:
|
|
gen = rand.Char
|
|
case symBtn.Selected:
|
|
gen = rand.LettersSymbols
|
|
case numBtn.Selected:
|
|
gen = rand.LetterDigits
|
|
default:
|
|
gen = rand.Letter
|
|
}
|
|
l, _ := strconv.Atoi(lenEd.Text())
|
|
pw, _ := rand.Slice(gen, l)
|
|
passvalEd.SetText(string(pw))
|
|
}
|
|
|
|
insertPage = func() {
|
|
c2 := flex.Rigid(gtx, func() { insertLabel.Layout(gtx) })
|
|
c3 := flex.Rigid(gtx, func() { passnameLabel.Layout(gtx) })
|
|
c4 := flex.Rigid(gtx, func() { th.Editor("name").Layout(gtx, passnameEd) })
|
|
c5 := flex.Rigid(gtx, func() { passvalLabel.Layout(gtx) })
|
|
c6 := flex.Rigid(gtx, func() { th.Editor("password").Layout(gtx, passvalEd) })
|
|
|
|
btnflx := &layout.Flex{Axis: layout.Horizontal}
|
|
al := layout.Align(layout.E)
|
|
c7 := flex.Rigid(gtx, func() {
|
|
bc1 := btnflx.Rigid(gtx, func() { lBtn.Layout(gtx) })
|
|
bc2 := btnflx.Rigid(gtx, func() {
|
|
gtx.Constraints.Width.Min = 60
|
|
th.Editor("len").Layout(gtx, lenEd)
|
|
})
|
|
|
|
bc3 := btnflx.Rigid(gtx, func() { rBtn.Layout(gtx) })
|
|
bc4 := btnflx.Rigid(gtx, func() { symBtn.Layout(gtx) })
|
|
bc5 := btnflx.Rigid(gtx, func() { numBtn.Layout(gtx) })
|
|
bc6 := btnflx.Rigid(gtx, func() { genBtn.Layout(gtx) })
|
|
|
|
al.Layout(gtx, func() {
|
|
btnflx.Layout(gtx, bc1, bc2, bc3, bc4, bc5, bc6)
|
|
})
|
|
})
|
|
|
|
c8 := flex.Rigid(gtx, func() {
|
|
bc1 := btnflx.Rigid(gtx, func() { backBtn.Layout(gtx) })
|
|
bc2 := btnflx.Rigid(gtx, func() { saveBtn.Layout(gtx) })
|
|
al.Layout(gtx, func() {
|
|
btnflx.Layout(gtx, bc1, bc2)
|
|
})
|
|
})
|
|
|
|
flex.Layout(gtx, c1, c2, c3, c4, c5, c6, c7, c8)
|
|
|
|
if lBtn.Clicked() {
|
|
l, _ := strconv.Atoi(lenEd.Text())
|
|
if l > 0 {
|
|
l -= 1
|
|
}
|
|
lenEd.SetText(strconv.Itoa(l))
|
|
updatePw()
|
|
w.Invalidate()
|
|
}
|
|
if rBtn.Clicked() {
|
|
l, _ := strconv.Atoi(lenEd.Text())
|
|
lenEd.SetText(strconv.Itoa(l + 1))
|
|
updatePw()
|
|
w.Invalidate()
|
|
}
|
|
if genBtn.Clicked() {
|
|
updatePw()
|
|
w.Invalidate()
|
|
}
|
|
if symBtn.Clicked() {
|
|
updatePw()
|
|
w.Invalidate()
|
|
}
|
|
if numBtn.Clicked() {
|
|
updatePw()
|
|
w.Invalidate()
|
|
}
|
|
if backBtn.Clicked() {
|
|
w.Invalidate()
|
|
page = listPage
|
|
}
|
|
if saveBtn.Clicked() {
|
|
w.Invalidate()
|
|
page = listPage
|
|
insName = passnameEd.Text()
|
|
insValue = passvalEd.Text()
|
|
for _, n := range pathnames {
|
|
if insName == n {
|
|
log(Info, "Password exists")
|
|
page = confirmPage
|
|
w.Invalidate()
|
|
return
|
|
}
|
|
}
|
|
//Do not block the UI thread.
|
|
go func() {
|
|
err := store.Insert(passnameEd.Text(), passvalEd.Text())
|
|
if err != nil {
|
|
page = idPage
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
confirmPage = func() {
|
|
c2 := flex.Rigid(gtx, func() {
|
|
confirmLabel.Layout(gtx)
|
|
})
|
|
al := layout.Align(layout.E)
|
|
btnflx := &layout.Flex{Axis: layout.Horizontal}
|
|
c3 := flex.Rigid(gtx, func() {
|
|
bc1 := btnflx.Rigid(gtx, func() { backBtn.Layout(gtx) })
|
|
bc2 := btnflx.Rigid(gtx, func() { yesBtn.Layout(gtx) })
|
|
|
|
al.Layout(gtx, func() {
|
|
btnflx.Layout(gtx, bc1, bc2)
|
|
})
|
|
})
|
|
flex.Layout(gtx, c1, c2, c3)
|
|
|
|
if backBtn.Clicked() {
|
|
w.Invalidate()
|
|
page = insertPage
|
|
}
|
|
if yesBtn.Clicked() {
|
|
w.Invalidate()
|
|
page = listPage
|
|
go func() {
|
|
err := store.Insert(insName, insValue)
|
|
if err != nil {
|
|
page = idPage
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
confPage = func() {
|
|
c2 := flex.Rigid(gtx, func() { storeDirLabel.Layout(gtx) })
|
|
c3 := flex.Rigid(gtx, func() { th.Editor("directory").Layout(gtx, storeDirEd) })
|
|
|
|
al := layout.Align(layout.E)
|
|
c4 := flex.Rigid(gtx, func() {
|
|
btnflx := &layout.Flex{Axis: layout.Horizontal}
|
|
bc1 := btnflx.Rigid(gtx, func() {
|
|
backBtn.Layout(gtx)
|
|
})
|
|
bc2 := btnflx.Rigid(gtx, func() {
|
|
saveBtn.Layout(gtx)
|
|
})
|
|
al.Layout(gtx, func() {
|
|
btnflx.Layout(gtx, bc1, bc2)
|
|
})
|
|
})
|
|
flex.Layout(gtx, c1, c2, c3, c4)
|
|
|
|
if backBtn.Clicked() {
|
|
log(Info, "Back")
|
|
storeDirEd.SetText(store.Dir)
|
|
w.Invalidate()
|
|
page = listPage
|
|
}
|
|
if saveBtn.Clicked() {
|
|
log(Info, "Save")
|
|
go func() { // do not block UI thread
|
|
store.Dir = storeDirEd.Text()
|
|
store.Id = ""
|
|
passgo.GetStore(&store)
|
|
Config.StoreDir = store.Dir
|
|
store.Mkdir()
|
|
saveConf()
|
|
chdir <- struct{}{}
|
|
}()
|
|
w.Invalidate()
|
|
page = listPage
|
|
}
|
|
}
|
|
|
|
promptPage = func() {
|
|
submit := false
|
|
for _, e := range promptEd.Events(gtx) {
|
|
switch e.(type) {
|
|
case widget.SubmitEvent:
|
|
log(Info, "Submit")
|
|
submit = true
|
|
}
|
|
}
|
|
c2 := flex.Rigid(gtx, func() { promptLabel.Layout(gtx) })
|
|
c3 := flex.Rigid(gtx, func() { th.Editor("password").Layout(gtx, promptEd) })
|
|
c4 := flex.Rigid(gtx, func() {
|
|
al := layout.Align(layout.E)
|
|
btnflx := &layout.Flex{Axis: layout.Horizontal}
|
|
bc1 := btnflx.Rigid(gtx, func() {
|
|
backBtn.Layout(gtx)
|
|
})
|
|
bc2 := btnflx.Rigid(gtx, func() {
|
|
okBtn.Layout(gtx)
|
|
})
|
|
al.Layout(gtx, func() {
|
|
btnflx.Layout(gtx, bc1, bc2)
|
|
})
|
|
})
|
|
flex.Layout(gtx, c1, c2, c3, c4)
|
|
|
|
if submit || okBtn.Clicked() {
|
|
log(Info, "Ok")
|
|
go func() { // do not block UI thread
|
|
passch <- []byte(promptEd.Text())
|
|
}()
|
|
w.Invalidate()
|
|
page = listPage
|
|
}
|
|
if backBtn.Clicked() {
|
|
log(Info, "Back")
|
|
go func() {
|
|
passch <- nil // cancel prompt
|
|
}()
|
|
w.Invalidate()
|
|
page = listPage
|
|
}
|
|
}
|
|
|
|
page = listPage
|
|
|
|
ms := &runtime.MemStats{}
|
|
x := 0
|
|
var mallocs uint64
|
|
for {
|
|
select {
|
|
case <-updated:
|
|
log(Info, "UPDATE")
|
|
updateBtns()
|
|
w.Invalidate()
|
|
case <-anim.C:
|
|
w.Invalidate()
|
|
case e := <-w.Events():
|
|
x++
|
|
if x == 100 {
|
|
runtime.ReadMemStats(ms)
|
|
mallocs = ms.Mallocs
|
|
}
|
|
switch e := e.(type) {
|
|
case system.DestroyEvent:
|
|
return
|
|
case system.StageEvent:
|
|
if e.Stage == system.StageRunning {
|
|
go func() {
|
|
updateIdBtns()
|
|
}()
|
|
}
|
|
case system.FrameEvent:
|
|
gtx.Reset(e.Config, e.Size)
|
|
|
|
sysinset.Top = e.Insets.Top
|
|
sysinset.Bottom = e.Insets.Bottom
|
|
sysinset.Left = e.Insets.Left
|
|
sysinset.Right = e.Insets.Right
|
|
|
|
sysinset.Layout(gtx, func() {
|
|
margin.Layout(gtx, func() {
|
|
margincs = gtx.Constraints
|
|
c1 = flex.Rigid(gtx, func() {
|
|
ct2 := titleflex.Rigid(gtx, func() {
|
|
plusBtn.Layout(gtx)
|
|
})
|
|
ct3 := titleflex.Rigid(gtx, func() {
|
|
dotsBtn.Layout(gtx)
|
|
})
|
|
ct1 := titleflex.Flex(gtx, 1.0, func() {
|
|
gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
|
|
title.Layout(gtx)
|
|
})
|
|
titleflex.Layout(gtx, ct1, ct2, ct3)
|
|
})
|
|
|
|
page()
|
|
})
|
|
})
|
|
|
|
if dotsBtn.Clicked() {
|
|
log(Info, "Configure")
|
|
w.Invalidate()
|
|
page = confPage
|
|
}
|
|
if plusBtn.Clicked() {
|
|
log(Info, "Plus")
|
|
w.Invalidate()
|
|
insName, insValue = "", ""
|
|
page = insertPage
|
|
}
|
|
e.Frame(gtx.Ops)
|
|
}
|
|
if x == 100 {
|
|
x = 0
|
|
runtime.ReadMemStats(ms)
|
|
fmt.Printf("mallocs: %d\n", ms.Mallocs-mallocs)
|
|
}
|
|
}
|
|
}
|
|
}
|