From ea9b4b38614adb208a6baddfea30aae99797ee88 Mon Sep 17 00:00:00 2001 From: Greg Date: Wed, 20 Nov 2019 14:30:43 -0500 Subject: [PATCH] Add cmd/passgo-gui files updated for Android implementation. --- PgpConnect.java | 19 +- cmd/passgo-gui/impl_android.go | 30 +- cmd/passgo-gui/impl_darwin.go | 41 ++- cmd/passgo-gui/main.go | 542 +++++++++++++++++++-------------- cmd/passgo-gui/ui.go | 167 +++++----- main.go | 13 + 6 files changed, 444 insertions(+), 368 deletions(-) diff --git a/PgpConnect.java b/PgpConnect.java index dcd1c4c..ecad887 100644 --- a/PgpConnect.java +++ b/PgpConnect.java @@ -64,6 +64,15 @@ public class PgpConnect extends Fragment { 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) { Log.d("gio", "onActivityResult(" + requestCode + "): " + resultCode); @@ -74,12 +83,6 @@ public class PgpConnect extends Fragment { stringResult(requestCode, null); return; } - Bundle bundle = data.getExtras(); - if (bundle != null) { - for (String k : bundle.keySet()) { - Log.d("gio", "data extra:" + k); - } - } switch (data.getAction()) { case OpenPgpApi.ACTION_DECRYPT_VERIFY: { Log.d("gio", "action decrypt"); @@ -247,7 +250,7 @@ public class PgpConnect extends Fragment { case OpenPgpApi.RESULT_CODE_SUCCESS: { try { String ret = os.toString("UTF-8"); - Log.d(OpenPgpApi.TAG, "output: " + ret); + //Log.d(OpenPgpApi.TAG, "output: " + ret); stringResult(chint, ret); } catch (UnsupportedEncodingException e) { Log.e("gio", "UnsupportedEncodingException", e); @@ -264,7 +267,7 @@ public class PgpConnect extends Fragment { PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); try { IntentSender sender = pi.getIntentSender(); - Log.d("PgpConnect", "IntentSender:" + sender.toString()); + //Log.d("PgpConnect", "IntentSender:" + sender.toString()); startIntentSenderForResult(sender, chint, null, 0, 0, 0, null); } catch (IntentSender.SendIntentException e) { Log.e("gio", "SendIntentException", e); diff --git a/cmd/passgo-gui/impl_android.go b/cmd/passgo-gui/impl_android.go index 84c9f41..026e4b0 100644 --- a/cmd/passgo-gui/impl_android.go +++ b/cmd/passgo-gui/impl_android.go @@ -5,27 +5,37 @@ package main import ( "os" - "golang.org/x/image/font/gofont/goregular" - "golang.org/x/image/font/sfnt" + "git.wow.st/gmp/passgo" + "gioui.org/app" ) func init() { log(Info, "Android start") - var err error - regular, err = sfnt.Parse(goregular.TTF) + // Use a larger font on Android + fontSize = 24 +} + +func initPgp(w *app.Window) { + passgo.InitPgp(w) +} + +func getConfDir() (string, error) { + ret, err := app.DataDir() 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(confDir); os.IsNotExist(err) { - err = os.MkdirAll(confDir, 0700) + if _, err := os.Stat(ret); os.IsNotExist(err) { + err = os.MkdirAll(ret, 0700) if err != nil { - log(Info, "Cannot create configuration directory ", confDir) - log(Fatal, err) + log(Error, "Cannot create configuration directory ", ret) + return "", err } else { log(Info, "Configuration directory created") + return ret, nil } } else { log(Info, "Configuration directory found") + return ret, nil } } diff --git a/cmd/passgo-gui/impl_darwin.go b/cmd/passgo-gui/impl_darwin.go index 8a91419..3f9e3b2 100644 --- a/cmd/passgo-gui/impl_darwin.go +++ b/cmd/passgo-gui/impl_darwin.go @@ -7,11 +7,10 @@ import ( "os/user" "path" - "golang.org/x/image/font/gofont/goregular" - "golang.org/x/image/font/sfnt" + "gioui.org/app" ) -func setFont() error { +/*func setFont() error { f, err := os.Open("/System/Library/Fonts/AppleSDGothicNeo.ttc") if err != nil { log(Info, "Cannot open system font.") @@ -29,28 +28,38 @@ func setFont() error { return err } return nil -} +}*/ func init() { - err := setFont() - if err != nil { - regular, err = sfnt.Parse(goregular.TTF) - if err != nil { - log(Fatal, "Cannot parse default font: ", err) - } - } + fontSize = 16 + //err := setFont() + //if err != nil { + // regular, err = sfnt.Parse(goregular.TTF) + // if err != nil { + // log(Fatal, "Cannot parse default font: ", err) + // } + //} +} + +func initPgp(w *app.Window) { +} + +func getConfDir() (string, error) { usr, err := user.Current() 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") - if _, err := os.Stat(confDir); os.IsNotExist(err) { - err = os.MkdirAll(confDir, 0700) + ret := path.Join(usr.HomeDir, ".config/passgo") + if _, err := os.Stat(ret); os.IsNotExist(err) { + err = os.MkdirAll(ret, 0700) if err != nil { log(Info, "Cannot create configuration directory ", confDir) - log(Fatal, err) + return "", err } else { log(Info, "Configuration directory created") + return ret, nil } } + return ret, nil } diff --git a/cmd/passgo-gui/main.go b/cmd/passgo-gui/main.go index 635e71d..3aabea6 100644 --- a/cmd/passgo-gui/main.go +++ b/cmd/passgo-gui/main.go @@ -3,22 +3,27 @@ package main import ( + "fmt" "io/ioutil" "os" "path" + "runtime" + "runtime/pprof" "strconv" "strings" "sync" "time" - "gioui.org/ui" - "gioui.org/ui/app" - "gioui.org/ui/key" - "gioui.org/ui/layout" - "gioui.org/ui/measure" - "gioui.org/ui/text" + "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" - "golang.org/x/image/font/sfnt" + "gioui.org/font/gofont" "github.com/fsnotify/fsnotify" "gopkg.in/yaml.v2" @@ -33,7 +38,31 @@ type conf struct { } 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) @@ -71,7 +100,10 @@ func main() { chdir = make(chan struct{}) passch = make(chan []byte) - passgo.Identities() + go func() { + log(Info,"passgo.Identities()") + passgo.Identities() + }() go Updater() log(Info, "Staring event loop") go eventLoop() @@ -80,8 +112,8 @@ func main() { } var ( + fontSize float32 confDir string - regular *sfnt.Font Config conf l []passgo.Pass mux sync.Mutex @@ -90,10 +122,13 @@ var ( 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) @@ -138,7 +173,8 @@ func saveConf(fds ...*os.File) { } else { fd, err = os.Create(path.Join(confDir, "config.yml")) 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() @@ -154,25 +190,25 @@ func saveConf(fds ...*os.File) { } func eventLoop() { + gofont.Register() + th = material.NewTheme() + th.TextSize = unit.Sp(fontSize) w := app.NewWindow( - app.WithWidth(ui.Dp(250)), - app.WithTitle("passgo")) - q := w.Queue() - var c ui.Config - ops := new(ui.Ops) - var dims layout.Dimensions - var cs layout.Constraints + app.Size(unit.Dp(250), unit.Dp(500)), + app.Title("passgo")) + gtx := &layout.Context{Queue: w.Queue()} + time.Sleep(time.Second/5) + var margincs layout.Constraints - var faces measure.Faces + var c1 layout.FlexChild // flex child for title bar - face := faces.For(regular, ui.Sp(16)) 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{ - Face: face, + Size: unit.Sp(fontSize), Label: "\xe2\x8b\xae", Alignment: text.Middle, Color: black, @@ -185,12 +221,12 @@ func eventLoop() { lst := &layout.List{Axis: layout.Vertical} passBtns := make([]*Button, 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, Background: darkgray, Alignment: text.Middle, } - cleared := &Overlay{Face: face, Text: "clipboard cleared", + cleared := &Overlay{Size: unit.Sp(fontSize), Text: "clipboard cleared", Color: black, Background: darkgray, Alignment: text.Middle, @@ -210,7 +246,7 @@ func eventLoop() { z = "/" } passBtns = append(passBtns, &Button{ - Face: face, + Size: unit.Sp(fontSize), Label: strings.Join([]string{s, n, z}, ""), Background: gray, }) @@ -223,6 +259,8 @@ func eventLoop() { idBtns := make([]*Button, 0) updateIdBtns := func() { + //return + log(Info,"passgo.Identities()") ids, err := passgo.Identities() if err != nil { log(Info, err) @@ -231,7 +269,7 @@ func eventLoop() { for i, n := range ids { if i >= len(idBtns) { idBtns = append(idBtns, &Button{ - Face: face, + Size: unit.Sp(fontSize), Label: n, Alignment: text.End, Color: black, @@ -245,70 +283,70 @@ func eventLoop() { } confBtn := &Button{ - Face: face, + Size: unit.Sp(fontSize), Label: "configure", Alignment: text.Middle, Color: black, Background: gray, } - storeDirLabel := &text.Label{Face: face, Text: "Store directory"} - storeDirEd := &text.Editor{Face: face, SingleLine: true} + storeDirLabel := th.Label(unit.Sp(fontSize), "Store directory") + storeDirEd := &widget.Editor{ SingleLine: true} storeDirEd.SetText(store.Dir) saveBtn := &Button{ - Face: face, + Size: unit.Sp(fontSize), Label: "save", Alignment: text.End, Color: black, Background: gray, } backBtn := &Button{ - Face: face, + Size: unit.Sp(fontSize), Label: "back", Alignment: text.End, Color: black, Background: gray, } - confirmLabel := &text.Label{Face: face, Text: "Password exists. Overwrite?"} + confirmLabel := th.Label(unit.Sp(fontSize), "Password exists. Overwrite?") yesBtn := &Button{ - Face: face, + Size: unit.Sp(fontSize), Label: "yes", Alignment: text.End, Color: black, Background: gray, } - promptLabel := &text.Label{Face: face, Text: "passphrase"} - promptEd := &text.Editor{Face: face, SingleLine: true, Submit: true} + promptLabel := th.Label(unit.Sp(fontSize), "passphrase") + promptEd := &widget.Editor{ SingleLine: true, Submit: true } okBtn := &Button{ - Face: face, + Size: unit.Sp(fontSize), Label: "ok", Alignment: text.End, Color: black, Background: gray, } plusBtn := &Button{ - Face: face, + Size: unit.Sp(fontSize), Label: "+", Alignment: text.Middle, Color: black, Background: gray, } - insertLabel := &text.Label{Face: face, Text: "Insert"} - passnameLabel := &text.Label{Face: face, Text: "password name:"} - passnameEd := &text.Editor{Face: face, SingleLine: true, Submit: true} - passvalLabel := &text.Label{Face: face, Text: "password value:"} - passvalEd := &text.Editor{Face: face, SingleLine: true, Submit: true} + 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 := &text.Label{Face: face, Text: "No GPG ids available. Please create a private key"} - idLabel := &text.Label{Face: face, Text: "Select ID"} + noidLabel := th.Label(unit.Sp(fontSize), "No GPG ids available. Please create a private key") + idLabel := th.Label(unit.Sp(fontSize), "Select ID") anim := &time.Ticker{} animating := false animOn := func() { log(Info, "animOn()") - anim = time.NewTicker(time.Second / 120) + anim = time.NewTicker(time.Second / 30) animating = true w.Invalidate() } @@ -319,6 +357,7 @@ func eventLoop() { } var listPage, idPage, insertPage, confirmPage, confPage, promptPage, page func() + _ = idPage prompt := func() []byte { page = promptPage @@ -333,63 +372,62 @@ func eventLoop() { start2 := float64(Config.ClearDelay) fade2a, end := start2+1.5, start2+2.0 - cs = flex.Flexible(1.0) - mux.Lock() - if lst.Dragging() { - key.HideInputOp{}.Add(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 + c2 := flex.Flex(gtx, 1.0, func() { + mux.Lock() + if lst.Dragging() { + key.HideInputOp{}.Add(gtx.Ops) } - case store.Id == "": - c2 = flex.End(idLabel.Layout(ops, cs)) - w.Invalidate() - page = idPage - default: - for lst.Init(c, q, ops, cs, len(passBtns)); lst.More(); lst.Next() { - i := lst.Index() - 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]) + 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: + 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() - flex.Layout(c1, c2) + mux.Unlock() + }) + flex.Layout(gtx, c1, c2) x := time.Since(overlayStart).Seconds() if x >= fade1b && x < start2 && animating { animOff() @@ -426,11 +464,12 @@ func eventLoop() { overlay.Color = black overlay.Background = darkgray } - cs = margincs - al := layout.Align{Alignment: layout.SE} - cs = al.Begin(ops, cs) - cs.Width.Min = cs.Width.Max - dims = al.End(overlay.Layout(c, ops, cs)) + 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() @@ -441,47 +480,52 @@ func eventLoop() { } idPage = func() { + log(Info,"idPage()") if !animating { animOn() } - cs = flex.Rigid() - c2 := flex.End(idLabel.Layout(ops, cs)) - var c3 layout.FlexChild + c2 := flex.Rigid(gtx, func() { + idLabel.Layout(gtx) + }) //if len(idBtns) == 0 { updateIdBtns() //} - cs = flex.Rigid() - if len(idBtns) == 0 { // still zero after update - c3 = flex.End(noidLabel.Layout(ops, cs)) - } else { - for lst.Init(c, q, ops, cs, len(idBtns)); lst.More(); lst.Next() { - lst.End(idBtns[lst.Index()].Layout(c, q, ops, lst.Constraints())) - } - c3 = flex.End(lst.Layout()) - for _, btn := range idBtns { - if btn.Clicked() { - log(Info, "ID selected: ", btn.Label) - store.SetID(btn.Label) - w.Invalidate() - animOff() - page = listPage + c3 := flex.Rigid(gtx, func() { + if len(idBtns) == 0 { // still zero after update + noidLabel.Layout(gtx) + } else { + 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(c1, c2, c3) + }) + flex.Layout(gtx, c1, c2, c3) } var insName, insValue string - genBtn := &SelButton{SelColor: darkgray} - genBtn.Button = Button{Face: face, Label: "generate", Background: gray} - symBtn := &SelButton{SelColor: gray, Selected: true} - numBtn := &SelButton{SelColor: gray, Selected: true} - symBtn.Button = Button{Face: face, Label: "@", Background: darkgray} - numBtn.Button = Button{Face: face, Label: "#", Background: darkgray} - lenEd := &text.Editor{Face: face, SingleLine: true, Alignment: text.End} + 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{Face: face, Label: "<", Background: gray} - rBtn := &Button{Face: face, Label: ">", Background: gray} + lBtn := &Button{Size: unit.Sp(fontSize), Label: "<", Background: gray} + rBtn := &Button{Size: unit.Sp(fontSize), Label: ">", Background: gray} updatePw := func() { if !genBtn.Selected { @@ -499,46 +543,49 @@ func eventLoop() { default: gen = rand.Letter } - l,_ := strconv.Atoi(lenEd.Text()) - pw,_ := rand.Slice(gen,l) + l, _ := strconv.Atoi(lenEd.Text()) + pw, _ := rand.Slice(gen, l) passvalEd.SetText(string(pw)) } - resetInsertPage := func() { - passnameEd.SetText("") - passvalEd.SetText("") - genBtn.Deselect() - } - 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.Init(ops, al.Begin(ops, flex.Rigid())) - bc1 := btnflx.End(lBtn.Layout(c, q, ops, btnflx.Rigid())) - cs := btnflx.Rigid() - cs.Width.Min = 60 - bc2 := btnflx.End(lenEd.Layout(c, q, ops, cs)) - bc3 := btnflx.End(rBtn.Layout(c, q, ops, btnflx.Rigid())) - 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))) + 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) + }) - btnflx.Init(ops, al.Begin(ops, flex.Rigid())) - bc1 = btnflx.End(backBtn.Layout(c, q, ops, btnflx.Rigid())) - bc2 = btnflx.End(saveBtn.Layout(c, q, ops, btnflx.Rigid())) - c8 := flex.End(al.End(btnflx.Layout(bc1, bc2))) - flex.Layout(c1, c2, c3, c4, c5, c6, c7, c8) + 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()) + l, _ := strconv.Atoi(lenEd.Text()) if l > 0 { l -= 1 } @@ -547,8 +594,8 @@ func eventLoop() { w.Invalidate() } if rBtn.Clicked() { - l,_ := strconv.Atoi(lenEd.Text()) - lenEd.SetText(strconv.Itoa(l+1)) + l, _ := strconv.Atoi(lenEd.Text()) + lenEd.SetText(strconv.Itoa(l + 1)) updatePw() w.Invalidate() } @@ -566,7 +613,6 @@ func eventLoop() { } if backBtn.Clicked() { w.Invalidate() - resetInsertPage() page = listPage } if saveBtn.Clicked() { @@ -582,20 +628,26 @@ func eventLoop() { return } } - store.Insert(passnameEd.Text(), passvalEd.Text()) + //Do not block the UI thread. + go store.Insert(passnameEd.Text(), passvalEd.Text()) } } confirmPage = func() { - cs = flex.Rigid() - c2 := flex.End(confirmLabel.Layout(ops, cs)) - al := &layout.Align{Alignment: layout.E} + c2 := flex.Rigid(gtx, func() { + confirmLabel.Layout(gtx) + }) + al := layout.Align(layout.E) btnflx := &layout.Flex{Axis: layout.Horizontal} - btnflx.Init(ops, al.Begin(ops, flex.Rigid())) - bc1 := btnflx.End(backBtn.Layout(c, q, ops, btnflx.Rigid())) - bc2 := btnflx.End(yesBtn.Layout(c, q, ops, btnflx.Rigid())) - c3 := flex.End(al.End(btnflx.Layout(bc1, bc2))) - flex.Layout(c1, c2, c3) + 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() @@ -604,28 +656,29 @@ func eventLoop() { if yesBtn.Clicked() { w.Invalidate() page = listPage - resetInsertPage() - store.Insert(insName, insValue) + go store.Insert(insName, insValue) } } confPage = func() { - cs = flex.Rigid() - c2 := flex.End(storeDirLabel.Layout(ops, cs)) - cs = flex.Rigid() - c3 := flex.End(storeDirEd.Layout(c, q, ops, cs)) - cs = flex.Rigid() - al := &layout.Align{Alignment: layout.E} - cs = al.Begin(ops, cs) - btnflx := &layout.Flex{Axis: layout.Horizontal} - btnflx.Init(ops, cs) - cs = btnflx.Rigid() - bc1 := btnflx.End(backBtn.Layout(c, q, ops, cs)) - cs = btnflx.Rigid() - bc2 := btnflx.End(saveBtn.Layout(c, q, ops, cs)) - dims = btnflx.Layout(bc1, bc2) - c4 := flex.End(al.End(dims)) - flex.Layout(c1, c2, c3, c4) + 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) @@ -650,29 +703,30 @@ func eventLoop() { promptPage = func() { 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) { - case text.SubmitEvent: + case widget.SubmitEvent: log(Info, "Submit") submit = true } } - cs = flex.Rigid() - c2 := flex.End(promptLabel.Layout(ops, cs)) - cs = flex.Rigid() - c3 := flex.End(promptEd.Layout(c, q, ops, cs)) - cs = flex.Rigid() - al := &layout.Align{Alignment: layout.E} - cs = al.Begin(ops, cs) - btnflx := &layout.Flex{Axis: layout.Horizontal} - btnflx.Init(ops, cs) - cs = btnflx.Rigid() - bc1 := btnflx.End(backBtn.Layout(c, q, ops, cs)) - cs = btnflx.Rigid() - bc2 := btnflx.End(okBtn.Layout(c, q, ops, cs)) - dims = btnflx.Layout(bc1, bc2) - c4 := flex.End(al.End(dims)) - flex.Layout(c1, c2, c3, c4) + 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 @@ -693,6 +747,9 @@ func eventLoop() { page = listPage + ms := &runtime.MemStats{} + x := 0 + var mallocs uint64 for { select { case <-updated: @@ -702,36 +759,46 @@ func eventLoop() { case <-anim.C: w.Invalidate() case e := <-w.Events(): + x++ + if x == 100 { + runtime.ReadMemStats(ms) + mallocs = ms.Mallocs + } switch e := e.(type) { - case app.DestroyEvent: + case system.DestroyEvent: return - case app.UpdateEvent: - c = &e.Config - ops.Reset() - faces.Reset(c) - cs = layout.RigidConstraints(e.Size) + case system.StageEvent: + if e.Stage == system.StageRunning { + initPgp(w) + } + 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 - 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() { log(Info, "Configure") @@ -744,7 +811,12 @@ func eventLoop() { insName, insValue = "", "" 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) } } } diff --git a/cmd/passgo-gui/ui.go b/cmd/passgo-gui/ui.go index 3fdc74f..25b25b7 100644 --- a/cmd/passgo-gui/ui.go +++ b/cmd/passgo-gui/ui.go @@ -4,14 +4,14 @@ import ( "image" "image/color" - "gioui.org/ui" - "gioui.org/ui/f32" - "gioui.org/ui/gesture" - "gioui.org/ui/input" - "gioui.org/ui/layout" - "gioui.org/ui/paint" - "gioui.org/ui/pointer" - "gioui.org/ui/text" + "gioui.org/f32" + "gioui.org/gesture" + "gioui.org/io/pointer" + "gioui.org/layout" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/text" + "gioui.org/unit" ) var ( @@ -22,7 +22,7 @@ var ( ) type Overlay struct { - Face text.Face + Size unit.Value Text string Click gesture.Click Color color.RGBA @@ -30,30 +30,24 @@ type Overlay struct { Alignment text.Alignment } -func (b *Overlay) Layout(c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.Dimensions { - 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.Text, - Alignment: b.Alignment, - } - ins := layout.UniformInset(ui.Dp(4)) - l.Material.Record(ops) - paint.ColorOp{Color: b.Color}.Add(ops) - l.Material.Stop() - 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) +func (b *Overlay) Layout(gtx *layout.Context) { + ins := layout.UniformInset(unit.Dp(1)) + ins.Layout(gtx, func() { + st := layout.Stack{} + c2 := st.Rigid(gtx, func() { + l := th.Label(b.Size, b.Text) + ins := layout.UniformInset(unit.Dp(4)) + l.Color = b.Color + ins.Layout(gtx, func() { + l.Layout(gtx) + }) + pointer.RectAreaOp{image.Rect(0, 0, gtx.Dimensions.Size.X, gtx.Dimensions.Size.Y)}.Add(gtx.Ops) + }) + c1 := st.Expand(gtx, func() { + layoutRRect(b.Background, gtx) + }) + st.Layout(gtx, c1, c2) + }) } type SelButton struct { @@ -63,7 +57,7 @@ type SelButton struct { } type Button struct { - Face text.Face + Size unit.Value Label string Click gesture.Click Color color.RGBA @@ -72,89 +66,65 @@ type Button struct { 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} +func layoutRRect(col color.RGBA, gtx *layout.Context) { + r := float32(gtx.Config.Px(unit.Dp(4))) + sz := image.Point{X: gtx.Constraints.Width.Min, Y: gtx.Constraints.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} + rect := f32.Rectangle{ + f32.Point{0, 0}, + f32.Point{w, h}, + } + 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 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 { +func (b *Button) Layout(gtx *layout.Context) { 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 { 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, - Alignment: b.Alignment, - } - ins := layout.UniformInset(ui.Dp(4)) - //paint.ColorOp{Color: b.Color}.Add(ops) - 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.Background, c, ops, st.Expand())) - dims = st.Layout(c1, c2) - return ins.End(dims) + ins := layout.UniformInset(unit.Dp(1)) + ins.Layout(gtx, func() { + st := layout.Stack{} + c2 := st.Rigid(gtx, func() { + l := th.Label(b.Size, b.Label) + ins := layout.UniformInset(unit.Dp(4)) + //paint.ColorOp{Color: b.Color}.Add(ops) + ins.Layout(gtx, func() { + l.Layout(gtx) + }) + pointer.RectAreaOp{image.Rect(0, 0, gtx.Dimensions.Size.X, gtx.Dimensions.Size.Y)}.Add(gtx.Ops) + b.Click.Add(gtx.Ops) + }) + c1 := st.Expand(gtx, func() { + layoutRRect(b.Background, gtx) + }) + st.Layout(gtx, c1, c2) + }) } func (b *Button) Clicked() bool { return b.clicked } -func (b *SelButton) Select() { - if b.Selected { - return - } - b.Selected = true +func (b *SelButton) Toggle() { + b.Selected = !b.Selected b.SelColor, b.Background = b.Background, b.SelColor } +func (b *SelButton) Select() { + if !b.Selected { + b.Toggle() + } +} + 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 { - b.Deselect() - } else { - b.Select() + b.Toggle() } } @@ -166,4 +136,3 @@ func (b *SelButton) Clicked() bool { return false } } - diff --git a/main.go b/main.go index 673c910..73281ee 100644 --- a/main.go +++ b/main.go @@ -54,10 +54,23 @@ func GetStore(store *Store) error { if err != nil { 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' { 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) + log.Printf("ID = %s", store.Id) return getKeyring() }