Add cmd/passgo-gui files updated for Android implementation.

This commit is contained in:
Greg 2019-11-20 14:30:43 -05:00
parent 8611bfb5e0
commit ea9b4b3861
6 changed files with 444 additions and 368 deletions

View File

@ -64,6 +64,15 @@ public class PgpConnect extends Fragment {
installComplete(this); installComplete(this);
} }
//For debugging:
/*private void printExtras(Intent data) {
Bundle bundle = data.getExtras();
if (bundle != null) {
for (String k : bundle.keySet()) {
Log.d("gio", "extra:" + k);
}
}
}*/
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d("gio", "onActivityResult(" + requestCode + "): " + resultCode); Log.d("gio", "onActivityResult(" + requestCode + "): " + resultCode);
@ -74,12 +83,6 @@ public class PgpConnect extends Fragment {
stringResult(requestCode, null); stringResult(requestCode, null);
return; return;
} }
Bundle bundle = data.getExtras();
if (bundle != null) {
for (String k : bundle.keySet()) {
Log.d("gio", "data extra:" + k);
}
}
switch (data.getAction()) { switch (data.getAction()) {
case OpenPgpApi.ACTION_DECRYPT_VERIFY: { case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
Log.d("gio", "action decrypt"); Log.d("gio", "action decrypt");
@ -247,7 +250,7 @@ public class PgpConnect extends Fragment {
case OpenPgpApi.RESULT_CODE_SUCCESS: { case OpenPgpApi.RESULT_CODE_SUCCESS: {
try { try {
String ret = os.toString("UTF-8"); String ret = os.toString("UTF-8");
Log.d(OpenPgpApi.TAG, "output: " + ret); //Log.d(OpenPgpApi.TAG, "output: " + ret);
stringResult(chint, ret); stringResult(chint, ret);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
Log.e("gio", "UnsupportedEncodingException", e); Log.e("gio", "UnsupportedEncodingException", e);
@ -264,7 +267,7 @@ public class PgpConnect extends Fragment {
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
try { try {
IntentSender sender = pi.getIntentSender(); IntentSender sender = pi.getIntentSender();
Log.d("PgpConnect", "IntentSender:" + sender.toString()); //Log.d("PgpConnect", "IntentSender:" + sender.toString());
startIntentSenderForResult(sender, chint, null, 0, 0, 0, null); startIntentSenderForResult(sender, chint, null, 0, 0, 0, null);
} catch (IntentSender.SendIntentException e) { } catch (IntentSender.SendIntentException e) {
Log.e("gio", "SendIntentException", e); Log.e("gio", "SendIntentException", e);

View File

@ -5,27 +5,37 @@ package main
import ( import (
"os" "os"
"golang.org/x/image/font/gofont/goregular" "git.wow.st/gmp/passgo"
"golang.org/x/image/font/sfnt" "gioui.org/app"
) )
func init() { func init() {
log(Info, "Android start") log(Info, "Android start")
var err error // Use a larger font on Android
regular, err = sfnt.Parse(goregular.TTF) fontSize = 24
}
func initPgp(w *app.Window) {
passgo.InitPgp(w)
}
func getConfDir() (string, error) {
ret, err := app.DataDir()
if err != nil { if err != nil {
log(Fatal, "Cannot parse default font: ", err) log(Error, "Cannot get data directory:", err)
return "", err
} }
confDir = app.DataDir() if _, err := os.Stat(ret); os.IsNotExist(err) {
if _, err := os.Stat(confDir); os.IsNotExist(err) { err = os.MkdirAll(ret, 0700)
err = os.MkdirAll(confDir, 0700)
if err != nil { if err != nil {
log(Info, "Cannot create configuration directory ", confDir) log(Error, "Cannot create configuration directory ", ret)
log(Fatal, err) return "", err
} else { } else {
log(Info, "Configuration directory created") log(Info, "Configuration directory created")
return ret, nil
} }
} else { } else {
log(Info, "Configuration directory found") log(Info, "Configuration directory found")
return ret, nil
} }
} }

View File

@ -7,11 +7,10 @@ import (
"os/user" "os/user"
"path" "path"
"golang.org/x/image/font/gofont/goregular" "gioui.org/app"
"golang.org/x/image/font/sfnt"
) )
func setFont() error { /*func setFont() error {
f, err := os.Open("/System/Library/Fonts/AppleSDGothicNeo.ttc") f, err := os.Open("/System/Library/Fonts/AppleSDGothicNeo.ttc")
if err != nil { if err != nil {
log(Info, "Cannot open system font.") log(Info, "Cannot open system font.")
@ -29,28 +28,38 @@ func setFont() error {
return err return err
} }
return nil return nil
} }*/
func init() { func init() {
err := setFont() fontSize = 16
if err != nil { //err := setFont()
regular, err = sfnt.Parse(goregular.TTF) //if err != nil {
if err != nil { // regular, err = sfnt.Parse(goregular.TTF)
log(Fatal, "Cannot parse default font: ", err) // if err != nil {
} // log(Fatal, "Cannot parse default font: ", err)
} // }
//}
}
func initPgp(w *app.Window) {
}
func getConfDir() (string, error) {
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
log(Fatal, "Cannot get current user: ", err) log(Error, "Cannot get current user: ", err)
return "", err
} }
confDir = path.Join(usr.HomeDir, ".config/passgo") ret := path.Join(usr.HomeDir, ".config/passgo")
if _, err := os.Stat(confDir); os.IsNotExist(err) { if _, err := os.Stat(ret); os.IsNotExist(err) {
err = os.MkdirAll(confDir, 0700) err = os.MkdirAll(ret, 0700)
if err != nil { if err != nil {
log(Info, "Cannot create configuration directory ", confDir) log(Info, "Cannot create configuration directory ", confDir)
log(Fatal, err) return "", err
} else { } else {
log(Info, "Configuration directory created") log(Info, "Configuration directory created")
return ret, nil
} }
} }
return ret, nil
} }

View File

@ -3,22 +3,27 @@
package main package main
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"runtime"
"runtime/pprof"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"gioui.org/ui" "gioui.org/app"
"gioui.org/ui/app" "gioui.org/io/key"
"gioui.org/ui/key" "gioui.org/io/system"
"gioui.org/ui/layout" "gioui.org/layout"
"gioui.org/ui/measure" "gioui.org/text"
"gioui.org/ui/text" "gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"golang.org/x/image/font/sfnt" "gioui.org/font/gofont"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -33,7 +38,31 @@ type conf struct {
} }
func main() { 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 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") confFile := path.Join(confDir, "config.yml")
if _, err := os.Stat(confFile); os.IsNotExist(err) { if _, err := os.Stat(confFile); os.IsNotExist(err) {
fd, err = os.Create(confFile) fd, err = os.Create(confFile)
@ -71,7 +100,10 @@ func main() {
chdir = make(chan struct{}) chdir = make(chan struct{})
passch = make(chan []byte) passch = make(chan []byte)
passgo.Identities() go func() {
log(Info,"passgo.Identities()")
passgo.Identities()
}()
go Updater() go Updater()
log(Info, "Staring event loop") log(Info, "Staring event loop")
go eventLoop() go eventLoop()
@ -80,8 +112,8 @@ func main() {
} }
var ( var (
fontSize float32
confDir string confDir string
regular *sfnt.Font
Config conf Config conf
l []passgo.Pass l []passgo.Pass
mux sync.Mutex mux sync.Mutex
@ -90,10 +122,13 @@ var (
updated chan struct{} updated chan struct{}
chdir chan struct{} chdir chan struct{}
passch chan []byte passch chan []byte
th *material.Theme
) )
func Updater() { func Updater() {
time.Sleep(time.Second * 2)
update := func() { update := func() {
fmt.Printf("update()\n")
ltmp, err := store.List() ltmp, err := store.List()
if err != nil { if err != nil {
log(Info, err) log(Info, err)
@ -138,7 +173,8 @@ func saveConf(fds ...*os.File) {
} else { } else {
fd, err = os.Create(path.Join(confDir, "config.yml")) fd, err = os.Create(path.Join(confDir, "config.yml"))
if err != nil { if err != nil {
log(Fatal, "Cannot open config file: ", err) log(Error, "Config file = ", path.Join(confDir, "config.yml"))
log(Fatal, "Cannot open config file: ", err.Error())
} }
} }
defer fd.Close() defer fd.Close()
@ -154,25 +190,25 @@ func saveConf(fds ...*os.File) {
} }
func eventLoop() { func eventLoop() {
gofont.Register()
th = material.NewTheme()
th.TextSize = unit.Sp(fontSize)
w := app.NewWindow( w := app.NewWindow(
app.WithWidth(ui.Dp(250)), app.Size(unit.Dp(250), unit.Dp(500)),
app.WithTitle("passgo")) app.Title("passgo"))
q := w.Queue() gtx := &layout.Context{Queue: w.Queue()}
var c ui.Config time.Sleep(time.Second/5)
ops := new(ui.Ops)
var dims layout.Dimensions
var cs layout.Constraints
var margincs layout.Constraints var margincs layout.Constraints
var faces measure.Faces
var c1 layout.FlexChild // flex child for title bar var c1 layout.FlexChild // flex child for title bar
face := faces.For(regular, ui.Sp(16))
sysinset := &layout.Inset{} sysinset := &layout.Inset{}
margin := layout.UniformInset(ui.Dp(10)) margin := layout.UniformInset(unit.Dp(10))
title := &text.Label{Face: face, Text: "passgo"} title := th.Body1("passgo")
dotsBtn := &Button{ dotsBtn := &Button{
Face: face, Size: unit.Sp(fontSize),
Label: "\xe2\x8b\xae", Label: "\xe2\x8b\xae",
Alignment: text.Middle, Alignment: text.Middle,
Color: black, Color: black,
@ -185,12 +221,12 @@ func eventLoop() {
lst := &layout.List{Axis: layout.Vertical} lst := &layout.List{Axis: layout.Vertical}
passBtns := make([]*Button, 0) passBtns := make([]*Button, 0)
pathnames := make([]string, 0) pathnames := make([]string, 0)
copied := &Overlay{Face: face, Text: "copied to clipboard", copied := &Overlay{Size: unit.Sp(fontSize), Text: "copied to clipboard",
Color: black, Color: black,
Background: darkgray, Background: darkgray,
Alignment: text.Middle, Alignment: text.Middle,
} }
cleared := &Overlay{Face: face, Text: "clipboard cleared", cleared := &Overlay{Size: unit.Sp(fontSize), Text: "clipboard cleared",
Color: black, Color: black,
Background: darkgray, Background: darkgray,
Alignment: text.Middle, Alignment: text.Middle,
@ -210,7 +246,7 @@ func eventLoop() {
z = "/" z = "/"
} }
passBtns = append(passBtns, &Button{ passBtns = append(passBtns, &Button{
Face: face, Size: unit.Sp(fontSize),
Label: strings.Join([]string{s, n, z}, ""), Label: strings.Join([]string{s, n, z}, ""),
Background: gray, Background: gray,
}) })
@ -223,6 +259,8 @@ func eventLoop() {
idBtns := make([]*Button, 0) idBtns := make([]*Button, 0)
updateIdBtns := func() { updateIdBtns := func() {
//return
log(Info,"passgo.Identities()")
ids, err := passgo.Identities() ids, err := passgo.Identities()
if err != nil { if err != nil {
log(Info, err) log(Info, err)
@ -231,7 +269,7 @@ func eventLoop() {
for i, n := range ids { for i, n := range ids {
if i >= len(idBtns) { if i >= len(idBtns) {
idBtns = append(idBtns, &Button{ idBtns = append(idBtns, &Button{
Face: face, Size: unit.Sp(fontSize),
Label: n, Label: n,
Alignment: text.End, Alignment: text.End,
Color: black, Color: black,
@ -245,70 +283,70 @@ func eventLoop() {
} }
confBtn := &Button{ confBtn := &Button{
Face: face, Size: unit.Sp(fontSize),
Label: "configure", Label: "configure",
Alignment: text.Middle, Alignment: text.Middle,
Color: black, Color: black,
Background: gray, Background: gray,
} }
storeDirLabel := &text.Label{Face: face, Text: "Store directory"} storeDirLabel := th.Label(unit.Sp(fontSize), "Store directory")
storeDirEd := &text.Editor{Face: face, SingleLine: true} storeDirEd := &widget.Editor{ SingleLine: true}
storeDirEd.SetText(store.Dir) storeDirEd.SetText(store.Dir)
saveBtn := &Button{ saveBtn := &Button{
Face: face, Size: unit.Sp(fontSize),
Label: "save", Label: "save",
Alignment: text.End, Alignment: text.End,
Color: black, Color: black,
Background: gray, Background: gray,
} }
backBtn := &Button{ backBtn := &Button{
Face: face, Size: unit.Sp(fontSize),
Label: "back", Label: "back",
Alignment: text.End, Alignment: text.End,
Color: black, Color: black,
Background: gray, Background: gray,
} }
confirmLabel := &text.Label{Face: face, Text: "Password exists. Overwrite?"} confirmLabel := th.Label(unit.Sp(fontSize), "Password exists. Overwrite?")
yesBtn := &Button{ yesBtn := &Button{
Face: face, Size: unit.Sp(fontSize),
Label: "yes", Label: "yes",
Alignment: text.End, Alignment: text.End,
Color: black, Color: black,
Background: gray, Background: gray,
} }
promptLabel := &text.Label{Face: face, Text: "passphrase"} promptLabel := th.Label(unit.Sp(fontSize), "passphrase")
promptEd := &text.Editor{Face: face, SingleLine: true, Submit: true} promptEd := &widget.Editor{ SingleLine: true, Submit: true }
okBtn := &Button{ okBtn := &Button{
Face: face, Size: unit.Sp(fontSize),
Label: "ok", Label: "ok",
Alignment: text.End, Alignment: text.End,
Color: black, Color: black,
Background: gray, Background: gray,
} }
plusBtn := &Button{ plusBtn := &Button{
Face: face, Size: unit.Sp(fontSize),
Label: "+", Label: "+",
Alignment: text.Middle, Alignment: text.Middle,
Color: black, Color: black,
Background: gray, Background: gray,
} }
insertLabel := &text.Label{Face: face, Text: "Insert"} insertLabel := th.Label(unit.Sp(fontSize), "Insert")
passnameLabel := &text.Label{Face: face, Text: "password name:"} passnameLabel := th.Label(unit.Sp(fontSize), "password name:")
passnameEd := &text.Editor{Face: face, SingleLine: true, Submit: true} passnameEd := &widget.Editor{ SingleLine: true }
passvalLabel := &text.Label{Face: face, Text: "password value:"} passvalLabel := th.Label(unit.Sp(fontSize), "password value:")
passvalEd := &text.Editor{Face: face, SingleLine: true, Submit: true} passvalEd := &widget.Editor{ SingleLine: true, Submit: true }
noidLabel := &text.Label{Face: face, Text: "No GPG ids available. Please create a private key"} noidLabel := th.Label(unit.Sp(fontSize), "No GPG ids available. Please create a private key")
idLabel := &text.Label{Face: face, Text: "Select ID"} idLabel := th.Label(unit.Sp(fontSize), "Select ID")
anim := &time.Ticker{} anim := &time.Ticker{}
animating := false animating := false
animOn := func() { animOn := func() {
log(Info, "animOn()") log(Info, "animOn()")
anim = time.NewTicker(time.Second / 120) anim = time.NewTicker(time.Second / 30)
animating = true animating = true
w.Invalidate() w.Invalidate()
} }
@ -319,6 +357,7 @@ func eventLoop() {
} }
var listPage, idPage, insertPage, confirmPage, confPage, promptPage, page func() var listPage, idPage, insertPage, confirmPage, confPage, promptPage, page func()
_ = idPage
prompt := func() []byte { prompt := func() []byte {
page = promptPage page = promptPage
@ -333,63 +372,62 @@ func eventLoop() {
start2 := float64(Config.ClearDelay) start2 := float64(Config.ClearDelay)
fade2a, end := start2+1.5, start2+2.0 fade2a, end := start2+1.5, start2+2.0
cs = flex.Flexible(1.0) c2 := flex.Flex(gtx, 1.0, func() {
mux.Lock() mux.Lock()
if lst.Dragging() { if lst.Dragging() {
key.HideInputOp{}.Add(ops) key.HideInputOp{}.Add(gtx.Ops)
}
var c2 layout.FlexChild
switch {
case store.Empty:
c2 = flex.End(confBtn.Layout(c, q, ops, cs))
if confBtn.Clicked() {
log(Info, "Configure")
w.Invalidate()
page = confPage
} }
case store.Id == "": switch {
c2 = flex.End(idLabel.Layout(ops, cs)) case store.Empty:
w.Invalidate() confBtn.Layout(gtx)
page = idPage if confBtn.Clicked() {
default: log(Info, "Configure")
for lst.Init(c, q, ops, cs, len(passBtns)); lst.More(); lst.Next() { w.Invalidate()
i := lst.Index() page = confPage
btn := passBtns[i]
dims = btn.Layout(c, q, ops, lst.Constraints())
lst.End(dims)
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])
} }
//case store.Id == "":
// idLabel.Layout(gtx)
// w.Invalidate()
// //page = idPage
default:
gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
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])
}
})
} }
c2 = flex.End(lst.Layout()) mux.Unlock()
} })
mux.Unlock() flex.Layout(gtx, c1, c2)
flex.Layout(c1, c2)
x := time.Since(overlayStart).Seconds() x := time.Since(overlayStart).Seconds()
if x >= fade1b && x < start2 && animating { if x >= fade1b && x < start2 && animating {
animOff() animOff()
@ -426,11 +464,12 @@ func eventLoop() {
overlay.Color = black overlay.Color = black
overlay.Background = darkgray overlay.Background = darkgray
} }
cs = margincs gtx.Constraints = margincs
al := layout.Align{Alignment: layout.SE} al := layout.Align(layout.SE)
cs = al.Begin(ops, cs) al.Layout(gtx, func() {
cs.Width.Min = cs.Width.Max gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
dims = al.End(overlay.Layout(c, ops, cs)) overlay.Layout(gtx)
})
} }
if x > start2 && x < fade2a { if x > start2 && x < fade2a {
// animOff() // animOff()
@ -441,47 +480,52 @@ func eventLoop() {
} }
idPage = func() { idPage = func() {
log(Info,"idPage()")
if !animating { if !animating {
animOn() animOn()
} }
cs = flex.Rigid() c2 := flex.Rigid(gtx, func() {
c2 := flex.End(idLabel.Layout(ops, cs)) idLabel.Layout(gtx)
var c3 layout.FlexChild })
//if len(idBtns) == 0 { //if len(idBtns) == 0 {
updateIdBtns() updateIdBtns()
//} //}
cs = flex.Rigid() c3 := flex.Rigid(gtx, func() {
if len(idBtns) == 0 { // still zero after update if len(idBtns) == 0 { // still zero after update
c3 = flex.End(noidLabel.Layout(ops, cs)) noidLabel.Layout(gtx)
} else { } else {
for lst.Init(c, q, ops, cs, len(idBtns)); lst.More(); lst.Next() { for i := 0; i < len(idBtns); i++ {
lst.End(idBtns[lst.Index()].Layout(c, q, ops, lst.Constraints())) lst.Layout(gtx, len(idBtns), func(i int) {
} idBtns[i].Layout(gtx)
c3 = flex.End(lst.Layout()) })
for _, btn := range idBtns { }
if btn.Clicked() { for _, btn := range idBtns {
log(Info, "ID selected: ", btn.Label) if btn.Clicked() {
store.SetID(btn.Label) log(Info, "ID selected: ", btn.Label)
w.Invalidate() store.SetID(btn.Label)
animOff() w.Invalidate()
page = listPage animOff()
page = listPage
}
} }
} }
} })
flex.Layout(c1, c2, c3) flex.Layout(gtx, c1, c2, c3)
} }
var insName, insValue string var insName, insValue string
genBtn := &SelButton{SelColor: darkgray} genBtn := &SelButton{SelColor: gray}
genBtn.Button = Button{Face: face, Label: "generate", Background: gray} genBtn.Button = Button{Size: unit.Sp(fontSize), Label: "generate"}
symBtn := &SelButton{SelColor: gray, Selected: true} symBtn := &SelButton{SelColor: gray}
numBtn := &SelButton{SelColor: gray, Selected: true} numBtn := &SelButton{SelColor: gray}
symBtn.Button = Button{Face: face, Label: "@", Background: darkgray} symBtn.Button = Button{Size: unit.Sp(fontSize), Label: "@"}
numBtn.Button = Button{Face: face, Label: "#", Background: darkgray} numBtn.Button = Button{Size: unit.Sp(fontSize), Label: "#"}
lenEd := &text.Editor{Face: face, SingleLine: true, Alignment: text.End} symBtn.Select()
numBtn.Select()
lenEd := &widget.Editor{ SingleLine: true, Alignment: text.End }
lenEd.SetText("15") lenEd.SetText("15")
lBtn := &Button{Face: face, Label: "<", Background: gray} lBtn := &Button{Size: unit.Sp(fontSize), Label: "<", Background: gray}
rBtn := &Button{Face: face, Label: ">", Background: gray} rBtn := &Button{Size: unit.Sp(fontSize), Label: ">", Background: gray}
updatePw := func() { updatePw := func() {
if !genBtn.Selected { if !genBtn.Selected {
@ -499,46 +543,49 @@ func eventLoop() {
default: default:
gen = rand.Letter gen = rand.Letter
} }
l,_ := strconv.Atoi(lenEd.Text()) l, _ := strconv.Atoi(lenEd.Text())
pw,_ := rand.Slice(gen,l) pw, _ := rand.Slice(gen, l)
passvalEd.SetText(string(pw)) passvalEd.SetText(string(pw))
} }
resetInsertPage := func() {
passnameEd.SetText("")
passvalEd.SetText("")
genBtn.Deselect()
}
insertPage = func() { insertPage = func() {
c2 := flex.End(insertLabel.Layout(ops, flex.Rigid())) 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) })
c3 := flex.End(passnameLabel.Layout(ops, flex.Rigid()))
c4 := flex.End(passnameEd.Layout(c, q, ops, flex.Rigid()))
c5 := flex.End(passvalLabel.Layout(ops, flex.Rigid()))
c6 := flex.End(passvalEd.Layout(c, q, ops, flex.Rigid()))
al := &layout.Align{Alignment: layout.E}
btnflx := &layout.Flex{Axis: layout.Horizontal} btnflx := &layout.Flex{Axis: layout.Horizontal}
btnflx.Init(ops, al.Begin(ops, flex.Rigid())) al := layout.Align(layout.E)
bc1 := btnflx.End(lBtn.Layout(c, q, ops, btnflx.Rigid())) c7 := flex.Rigid(gtx, func() {
cs := btnflx.Rigid() bc1 := btnflx.Rigid(gtx, func() { lBtn.Layout(gtx) })
cs.Width.Min = 60 bc2 := btnflx.Rigid(gtx, func() {
bc2 := btnflx.End(lenEd.Layout(c, q, ops, cs)) gtx.Constraints.Width.Min = 60
bc3 := btnflx.End(rBtn.Layout(c, q, ops, btnflx.Rigid())) th.Editor("len").Layout(gtx, lenEd)
bc4 := btnflx.End(symBtn.Layout(c, q, ops, btnflx.Rigid())) })
bc5 := btnflx.End(numBtn.Layout(c, q, ops, btnflx.Rigid()))
bc6 := btnflx.End(genBtn.Layout(c, q, ops, btnflx.Rigid()))
c7 := flex.End(al.End(btnflx.Layout(bc1, bc2, bc3, bc4, bc5, bc6)))
btnflx.Init(ops, al.Begin(ops, flex.Rigid())) bc3 := btnflx.Rigid(gtx, func() { rBtn.Layout(gtx) })
bc1 = btnflx.End(backBtn.Layout(c, q, ops, btnflx.Rigid())) bc4 := btnflx.Rigid(gtx, func() { symBtn.Layout(gtx) })
bc2 = btnflx.End(saveBtn.Layout(c, q, ops, btnflx.Rigid())) bc5 := btnflx.Rigid(gtx, func() { numBtn.Layout(gtx) })
c8 := flex.End(al.End(btnflx.Layout(bc1, bc2))) bc6 := btnflx.Rigid(gtx, func() { genBtn.Layout(gtx) })
flex.Layout(c1, c2, c3, c4, c5, c6, c7, c8)
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() { if lBtn.Clicked() {
l,_ := strconv.Atoi(lenEd.Text()) l, _ := strconv.Atoi(lenEd.Text())
if l > 0 { if l > 0 {
l -= 1 l -= 1
} }
@ -547,8 +594,8 @@ func eventLoop() {
w.Invalidate() w.Invalidate()
} }
if rBtn.Clicked() { if rBtn.Clicked() {
l,_ := strconv.Atoi(lenEd.Text()) l, _ := strconv.Atoi(lenEd.Text())
lenEd.SetText(strconv.Itoa(l+1)) lenEd.SetText(strconv.Itoa(l + 1))
updatePw() updatePw()
w.Invalidate() w.Invalidate()
} }
@ -566,7 +613,6 @@ func eventLoop() {
} }
if backBtn.Clicked() { if backBtn.Clicked() {
w.Invalidate() w.Invalidate()
resetInsertPage()
page = listPage page = listPage
} }
if saveBtn.Clicked() { if saveBtn.Clicked() {
@ -582,20 +628,26 @@ func eventLoop() {
return return
} }
} }
store.Insert(passnameEd.Text(), passvalEd.Text()) //Do not block the UI thread.
go store.Insert(passnameEd.Text(), passvalEd.Text())
} }
} }
confirmPage = func() { confirmPage = func() {
cs = flex.Rigid() c2 := flex.Rigid(gtx, func() {
c2 := flex.End(confirmLabel.Layout(ops, cs)) confirmLabel.Layout(gtx)
al := &layout.Align{Alignment: layout.E} })
al := layout.Align(layout.E)
btnflx := &layout.Flex{Axis: layout.Horizontal} btnflx := &layout.Flex{Axis: layout.Horizontal}
btnflx.Init(ops, al.Begin(ops, flex.Rigid())) c3 := flex.Rigid(gtx, func() {
bc1 := btnflx.End(backBtn.Layout(c, q, ops, btnflx.Rigid())) bc1 := btnflx.Rigid(gtx, func() { backBtn.Layout(gtx) })
bc2 := btnflx.End(yesBtn.Layout(c, q, ops, btnflx.Rigid())) bc2 := btnflx.Rigid(gtx, func() { yesBtn.Layout(gtx) })
c3 := flex.End(al.End(btnflx.Layout(bc1, bc2)))
flex.Layout(c1, c2, c3) al.Layout(gtx, func() {
btnflx.Layout(gtx, bc1, bc2)
})
})
flex.Layout(gtx, c1, c2, c3)
if backBtn.Clicked() { if backBtn.Clicked() {
w.Invalidate() w.Invalidate()
@ -604,28 +656,29 @@ func eventLoop() {
if yesBtn.Clicked() { if yesBtn.Clicked() {
w.Invalidate() w.Invalidate()
page = listPage page = listPage
resetInsertPage() go store.Insert(insName, insValue)
store.Insert(insName, insValue)
} }
} }
confPage = func() { confPage = func() {
cs = flex.Rigid() c2 := flex.Rigid(gtx, func() { storeDirLabel.Layout(gtx) })
c2 := flex.End(storeDirLabel.Layout(ops, cs)) c3 := flex.Rigid(gtx, func() { th.Editor("directory").Layout(gtx, storeDirEd) })
cs = flex.Rigid()
c3 := flex.End(storeDirEd.Layout(c, q, ops, cs)) al := layout.Align(layout.E)
cs = flex.Rigid() c4 := flex.Rigid(gtx, func() {
al := &layout.Align{Alignment: layout.E} btnflx := &layout.Flex{Axis: layout.Horizontal}
cs = al.Begin(ops, cs) bc1 := btnflx.Rigid(gtx, func() {
btnflx := &layout.Flex{Axis: layout.Horizontal} backBtn.Layout(gtx)
btnflx.Init(ops, cs) })
cs = btnflx.Rigid() bc2 := btnflx.Rigid(gtx, func() {
bc1 := btnflx.End(backBtn.Layout(c, q, ops, cs)) saveBtn.Layout(gtx)
cs = btnflx.Rigid() })
bc2 := btnflx.End(saveBtn.Layout(c, q, ops, cs)) al.Layout(gtx, func() {
dims = btnflx.Layout(bc1, bc2) btnflx.Layout(gtx, bc1, bc2)
c4 := flex.End(al.End(dims)) })
flex.Layout(c1, c2, c3, c4) })
flex.Layout(gtx, c1, c2, c3, c4)
if backBtn.Clicked() { if backBtn.Clicked() {
log(Info, "Back") log(Info, "Back")
storeDirEd.SetText(store.Dir) storeDirEd.SetText(store.Dir)
@ -650,29 +703,30 @@ func eventLoop() {
promptPage = func() { promptPage = func() {
submit := false submit := false
for e, ok := promptEd.Next(c, q); ok; e, ok = promptEd.Next(c, q) { for _, e := range promptEd.Events(gtx) {
switch e.(type) { switch e.(type) {
case text.SubmitEvent: case widget.SubmitEvent:
log(Info, "Submit") log(Info, "Submit")
submit = true submit = true
} }
} }
cs = flex.Rigid() c2 := flex.Rigid(gtx, func() { promptLabel.Layout(gtx) })
c2 := flex.End(promptLabel.Layout(ops, cs)) c3 := flex.Rigid(gtx, func() { th.Editor("password").Layout(gtx, promptEd) })
cs = flex.Rigid() c4 := flex.Rigid(gtx, func() {
c3 := flex.End(promptEd.Layout(c, q, ops, cs)) al := layout.Align(layout.E)
cs = flex.Rigid() btnflx := &layout.Flex{Axis: layout.Horizontal}
al := &layout.Align{Alignment: layout.E} bc1 := btnflx.Rigid(gtx, func() {
cs = al.Begin(ops, cs) backBtn.Layout(gtx)
btnflx := &layout.Flex{Axis: layout.Horizontal} })
btnflx.Init(ops, cs) bc2 := btnflx.Rigid(gtx, func() {
cs = btnflx.Rigid() okBtn.Layout(gtx)
bc1 := btnflx.End(backBtn.Layout(c, q, ops, cs)) })
cs = btnflx.Rigid() al.Layout(gtx, func() {
bc2 := btnflx.End(okBtn.Layout(c, q, ops, cs)) btnflx.Layout(gtx, bc1, bc2)
dims = btnflx.Layout(bc1, bc2) })
c4 := flex.End(al.End(dims)) })
flex.Layout(c1, c2, c3, c4) flex.Layout(gtx, c1, c2, c3, c4)
if submit || okBtn.Clicked() { if submit || okBtn.Clicked() {
log(Info, "Ok") log(Info, "Ok")
go func() { // do not block UI thread go func() { // do not block UI thread
@ -693,6 +747,9 @@ func eventLoop() {
page = listPage page = listPage
ms := &runtime.MemStats{}
x := 0
var mallocs uint64
for { for {
select { select {
case <-updated: case <-updated:
@ -702,36 +759,46 @@ func eventLoop() {
case <-anim.C: case <-anim.C:
w.Invalidate() w.Invalidate()
case e := <-w.Events(): case e := <-w.Events():
x++
if x == 100 {
runtime.ReadMemStats(ms)
mallocs = ms.Mallocs
}
switch e := e.(type) { switch e := e.(type) {
case app.DestroyEvent: case system.DestroyEvent:
return return
case app.UpdateEvent: case system.StageEvent:
c = &e.Config if e.Stage == system.StageRunning {
ops.Reset() initPgp(w)
faces.Reset(c) }
cs = layout.RigidConstraints(e.Size) case system.FrameEvent:
gtx.Reset(e.Config, e.Size)
sysinset.Top = e.Insets.Top sysinset.Top = e.Insets.Top
sysinset.Bottom = e.Insets.Bottom sysinset.Bottom = e.Insets.Bottom
sysinset.Left = e.Insets.Left sysinset.Left = e.Insets.Left
sysinset.Right = e.Insets.Right sysinset.Right = e.Insets.Right
cs = sysinset.Begin(c, ops, cs)
cs = margin.Begin(c, ops, cs)
margincs = cs
flex.Init(ops, cs)
cs = flex.Rigid()
titleflex.Init(ops, cs)
cs = titleflex.Rigid()
ct2 := titleflex.End(plusBtn.Layout(c, q, ops, cs))
cs = titleflex.Rigid()
ct3 := titleflex.End(dotsBtn.Layout(c, q, ops, cs))
cs = titleflex.Flexible(1.0)
cs.Width.Min = cs.Width.Max
ct1 := titleflex.End(title.Layout(ops, cs))
c1 = flex.End(titleflex.Layout(ct1, ct2, ct3))
page() 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)
})
sysinset.End(margin.End(dims)) page()
})
})
if dotsBtn.Clicked() { if dotsBtn.Clicked() {
log(Info, "Configure") log(Info, "Configure")
@ -744,7 +811,12 @@ func eventLoop() {
insName, insValue = "", "" insName, insValue = "", ""
page = insertPage page = insertPage
} }
w.Update(ops) e.Frame(gtx.Ops)
}
if x == 100 {
x = 0
runtime.ReadMemStats(ms)
fmt.Printf("mallocs: %d\n", ms.Mallocs-mallocs)
} }
} }
} }

View File

@ -4,14 +4,14 @@ import (
"image" "image"
"image/color" "image/color"
"gioui.org/ui" "gioui.org/f32"
"gioui.org/ui/f32" "gioui.org/gesture"
"gioui.org/ui/gesture" "gioui.org/io/pointer"
"gioui.org/ui/input" "gioui.org/layout"
"gioui.org/ui/layout" "gioui.org/op/clip"
"gioui.org/ui/paint" "gioui.org/op/paint"
"gioui.org/ui/pointer" "gioui.org/text"
"gioui.org/ui/text" "gioui.org/unit"
) )
var ( var (
@ -22,7 +22,7 @@ var (
) )
type Overlay struct { type Overlay struct {
Face text.Face Size unit.Value
Text string Text string
Click gesture.Click Click gesture.Click
Color color.RGBA Color color.RGBA
@ -30,30 +30,24 @@ type Overlay struct {
Alignment text.Alignment Alignment text.Alignment
} }
func (b *Overlay) Layout(c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.Dimensions { func (b *Overlay) Layout(gtx *layout.Context) {
ins := layout.UniformInset(ui.Dp(1)) ins := layout.UniformInset(unit.Dp(1))
cs = ins.Begin(c, ops, cs) ins.Layout(gtx, func() {
var dims layout.Dimensions st := layout.Stack{}
st := layout.Stack{} c2 := st.Rigid(gtx, func() {
st.Init(ops, cs) l := th.Label(b.Size, b.Text)
{ ins := layout.UniformInset(unit.Dp(4))
cs = st.Rigid() l.Color = b.Color
l := text.Label{ ins.Layout(gtx, func() {
Face: b.Face, l.Layout(gtx)
Text: b.Text, })
Alignment: b.Alignment, pointer.RectAreaOp{image.Rect(0, 0, gtx.Dimensions.Size.X, gtx.Dimensions.Size.Y)}.Add(gtx.Ops)
} })
ins := layout.UniformInset(ui.Dp(4)) c1 := st.Expand(gtx, func() {
l.Material.Record(ops) layoutRRect(b.Background, gtx)
paint.ColorOp{Color: b.Color}.Add(ops) })
l.Material.Stop() st.Layout(gtx, c1, c2)
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)
}
c2 := st.End(dims)
c1 := st.End(layoutRRect(b.Background, c, ops, st.Expand()))
dims = st.Layout(c1, c2)
return ins.End(dims)
} }
type SelButton struct { type SelButton struct {
@ -63,7 +57,7 @@ type SelButton struct {
} }
type Button struct { type Button struct {
Face text.Face Size unit.Value
Label string Label string
Click gesture.Click Click gesture.Click
Color color.RGBA Color color.RGBA
@ -72,89 +66,65 @@ type Button struct {
clicked bool clicked bool
} }
func layoutRRect(col color.RGBA, c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.Dimensions { func layoutRRect(col color.RGBA, gtx *layout.Context) {
r := float32(c.Px(ui.Dp(4))) r := float32(gtx.Config.Px(unit.Dp(4)))
sz := image.Point{X: cs.Width.Min, Y: cs.Height.Min} sz := image.Point{X: gtx.Constraints.Width.Min, Y: gtx.Constraints.Height.Min}
w, h := float32(sz.X), float32(sz.Y) w, h := float32(sz.X), float32(sz.Y)
rrect(ops, w, h, r, r, r, r) rect := f32.Rectangle{
paint.ColorOp{Color: col}.Add(ops) f32.Point{0, 0},
paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(ops) f32.Point{w, h},
return layout.Dimensions{Size: sz} }
clip.RoundRect(gtx.Ops, rect, r, r, r, r)
paint.ColorOp{Color: col}.Add(gtx.Ops)
paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(gtx.Ops)
gtx.Dimensions = layout.Dimensions{Size: sz}
} }
// https://pomax.github.io/bezierinfo/#circles_cubic. func (b *Button) Layout(gtx *layout.Context) {
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, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
b.clicked = false b.clicked = false
for ev, ok := b.Click.Next(q); ok; ev, ok = b.Click.Next(q) { for _, ev := range b.Click.Events(gtx) {
if ev.Type == gesture.TypeClick { if ev.Type == gesture.TypeClick {
b.clicked = true b.clicked = true
} }
} }
ins := layout.UniformInset(ui.Dp(1)) ins := layout.UniformInset(unit.Dp(1))
cs = ins.Begin(c, ops, cs) ins.Layout(gtx, func() {
var dims layout.Dimensions st := layout.Stack{}
st := layout.Stack{} c2 := st.Rigid(gtx, func() {
st.Init(ops, cs) l := th.Label(b.Size, b.Label)
{ ins := layout.UniformInset(unit.Dp(4))
cs = st.Rigid() //paint.ColorOp{Color: b.Color}.Add(ops)
l := text.Label{ ins.Layout(gtx, func() {
Face: b.Face, l.Layout(gtx)
Text: b.Label, })
Alignment: b.Alignment, pointer.RectAreaOp{image.Rect(0, 0, gtx.Dimensions.Size.X, gtx.Dimensions.Size.Y)}.Add(gtx.Ops)
} b.Click.Add(gtx.Ops)
ins := layout.UniformInset(ui.Dp(4)) })
//paint.ColorOp{Color: b.Color}.Add(ops) c1 := st.Expand(gtx, func() {
dims = ins.End(l.Layout(ops, ins.Begin(c, ops, cs))) layoutRRect(b.Background, gtx)
pointer.RectAreaOp{image.Rect(0, 0, dims.Size.X, dims.Size.Y)}.Add(ops) })
b.Click.Add(ops) st.Layout(gtx, c1, c2)
} })
c2 := st.End(dims)
c1 := st.End(layoutRRect(b.Background, c, ops, st.Expand()))
dims = st.Layout(c1, c2)
return ins.End(dims)
} }
func (b *Button) Clicked() bool { func (b *Button) Clicked() bool {
return b.clicked return b.clicked
} }
func (b *SelButton) Select() { func (b *SelButton) Toggle() {
if b.Selected { b.Selected = !b.Selected
return
}
b.Selected = true
b.SelColor, b.Background = b.Background, b.SelColor b.SelColor, b.Background = b.Background, b.SelColor
} }
func (b *SelButton) Select() {
if !b.Selected {
b.Toggle()
}
}
func (b *SelButton) Deselect() { func (b *SelButton) Deselect() {
if !b.Selected {
return
}
b.Selected = false
b.SelColor, b.Background = b.Background, b.SelColor
}
func (b *SelButton) Toggle() {
if b.Selected { if b.Selected {
b.Deselect() b.Toggle()
} else {
b.Select()
} }
} }
@ -166,4 +136,3 @@ func (b *SelButton) Clicked() bool {
return false return false
} }
} }

13
main.go
View File

@ -54,10 +54,23 @@ func GetStore(store *Store) error {
if err != nil { if err != nil {
return fmt.Errorf("Can't read .gpg-id: %s", err) return fmt.Errorf("Can't read .gpg-id: %s", err)
} }
// extract only the email address portion of the ID (required
// for Android)
if id[len(id)-1] == '\n' { if id[len(id)-1] == '\n' {
id = id[:len(id)-1] id = id[:len(id)-1]
} }
for i := len(id) - 1; i > 0; i-- {
if id[i] == '>' {
for j := i-1; j > 0; j-- {
if id[j] == '<' {
id = id[j+1:i]
}
}
break
}
}
store.Id = string(id) store.Id = string(id)
log.Printf("ID = %s", store.Id)
return getKeyring() return getKeyring()
} }