Compare commits

..

No commits in common. "7b3d59a581684d33637366ddde1fe74ecd127694" and "28ccd6d107fbb2224694bf2837c839fe0b89087f" have entirely different histories.

5 changed files with 68 additions and 389 deletions

4
.gitignore vendored
View File

@ -1,2 +1,2 @@
cmd/passgo/passgo cmd/gpass/passgo
cmd/passgo-gui/passgo-gui cmd/gpass-gui/passgo-gui

View File

@ -1,61 +0,0 @@
//+build !android !linux
package main
import (
"os"
"os/user"
"path"
"golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/font/sfnt"
)
var (
regular *sfnt.Font
confDir string
)
func setFont() error {
f, err := os.Open("/System/Library/Fonts/AppleSDGothicNeo.ttc")
if err != nil {
log(Info, "Cannot open system font.")
return err
}
collection, err := sfnt.ParseCollectionReaderAt(f)
if err != nil {
log(Info, "Cannot parse system font.")
return err
}
regular, err = collection.Font(0)
if err != nil {
log(Info, "Cannot access first font in collection.")
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)
}
}
usr, err := user.Current()
if err != nil {
log(Fatal, "Cannot get current user: ", err)
}
confDir = path.Join(usr.HomeDir, ".config/passgo")
if _, err := os.Stat(confDir); os.IsNotExist(err) {
err = os.MkdirAll(confDir, 0700)
if err != nil {
log(Info, "Cannot create configuration directory ", confDir)
log(Fatal, err)
} else {
log(Info, "Configuration directory created")
}
}
}

View File

@ -3,14 +3,12 @@
package main package main
import ( import (
//"fmt"
"image" "image"
"image/color" "image/color"
"io/ioutil"
"os"
"path" "path"
"strings" "strings"
"sync" "sync"
"time"
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/app" "gioui.org/ui/app"
@ -25,47 +23,19 @@ import (
"gioui.org/ui/text" "gioui.org/ui/text"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"gopkg.in/yaml.v2" "golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/font/sfnt"
"git.wow.st/gmp/passgo" "git.wow.st/gmp/passgo"
) )
type conf struct {
StoreDir string
}
func main() { func main() {
var fd *os.File var err error
confFile := path.Join(confDir, "config.yml") store, err = passgo.GetStore()
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)
err = passgo.GetStore(&store)
if err != nil { if err != nil {
log(Fatal, err) log(Fatal, err)
} }
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{}) updated = make(chan struct{})
go Updater() go Updater()
log(Info, "Staring event loop") log(Info, "Staring event loop")
go eventLoop() go eventLoop()
@ -74,23 +44,17 @@ func main() {
} }
var ( var (
Config conf
l []passgo.Pass l []passgo.Pass
mux sync.Mutex mux sync.Mutex
store passgo.Store store *passgo.Store
reload chan struct{}
updated chan struct{} updated chan struct{}
black = color.RGBA{A: 0xff, R: 0, G: 0, B: 0}
white = color.RGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}
gray = color.RGBA{A: 0xff, R: 0xf0, G: 0xf0, B: 0xf0}
darkgray = color.RGBA{A: 0xff, R: 0xa0, G: 0xa0, B: 0xa0}
) )
func Updater() { func Updater() {
update := func() { update := func() {
ltmp, err := store.List() ltmp, err := store.List()
if err != nil { if err != nil {
log(Info, err) log(Fatal, err)
} }
mux.Lock() mux.Lock()
l = ltmp l = ltmp
@ -106,8 +70,6 @@ func Updater() {
watcher.Add(store.Dir) watcher.Add(store.Dir)
for { for {
select { select {
case <-reload:
update()
case <-watcher.Events: case <-watcher.Events:
update() update()
case e := <-watcher.Errors: case e := <-watcher.Errors:
@ -116,70 +78,11 @@ func Updater() {
} }
} }
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(Fatal, "Cannot open config file: ", err)
}
}
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)
}
}
type Overlay struct {
Face text.Face
Text string
Click gesture.Click
Color color.RGBA
Background color.RGBA
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)
}
type Button struct { type Button struct {
Face text.Face Face text.Face
Label string Label string
Click gesture.Click Click gesture.Click
Color color.RGBA Color color.RGBA
Background color.RGBA
Alignment text.Alignment
clicked bool clicked bool
} }
@ -210,7 +113,7 @@ func rrect(ops *ui.Ops, width, height, se, sw, nw, ne float32) {
b.End() b.End()
} }
func (b *Button) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions { func (b *Button) Layout(c ui.Config, ops *ui.Ops, q input.Queue, 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, ok := b.Click.Next(q); ok; ev, ok = b.Click.Next(q) {
if ev.Type == gesture.TypeClick { if ev.Type == gesture.TypeClick {
@ -227,16 +130,14 @@ func (b *Button) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Const
l := text.Label{ l := text.Label{
Face: b.Face, Face: b.Face,
Text: b.Label, Text: b.Label,
Alignment: b.Alignment,
} }
ins := layout.UniformInset(ui.Dp(4)) ins := layout.UniformInset(ui.Dp(4))
paint.ColorOp{Color: b.Color}.Add(ops)
dims = ins.End(l.Layout(ops, ins.Begin(c, ops, cs))) 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) pointer.RectAreaOp{image.Rect(0, 0, dims.Size.X, dims.Size.Y)}.Add(ops)
b.Click.Add(ops) b.Click.Add(ops)
} }
c2 := st.End(dims) c2 := st.End(dims)
c1 := st.End(layoutRRect(b.Background, c, ops, st.Expand())) c1 := st.End(layoutRRect(b.Color, c, ops, st.Expand()))
dims = st.Layout(c1, c2) dims = st.Layout(c1, c2)
return ins.End(dims) return ins.End(dims)
} }
@ -248,43 +149,24 @@ func (b *Button) Clicked() bool {
func eventLoop() { func eventLoop() {
w := app.NewWindow(app.WithWidth(ui.Dp(250))) w := app.NewWindow(app.WithWidth(ui.Dp(250)))
q := w.Queue() q := w.Queue()
var c ui.Config _ = q
ops := new(ui.Ops) ops := new(ui.Ops)
var dims layout.Dimensions
var cs layout.Constraints
var margincs layout.Constraints
var faces measure.Faces var faces measure.Faces
var c1 layout.FlexChild // flex child for title bar regular, err := sfnt.Parse(goregular.TTF)
if err != nil {
log(Fatal, "Cannot parse font.")
}
face := faces.For(regular, ui.Sp(16)) face := faces.For(regular, ui.Sp(16))
margin := layout.UniformInset(ui.Dp(10)) margin := layout.UniformInset(ui.Dp(10))
title := &text.Label{Face: face, Text: "passgo"}
dotsbtn := &Button{
Face: face,
Label: "\xe2\x8b\xae",
Alignment: text.End,
Color: black,
Background: gray,
}
titleflex := &layout.Flex{Axis: layout.Horizontal}
flex := &layout.Flex{Axis: layout.Vertical}
passInput := &text.Editor{Face: face, SingleLine: true} passInput := &text.Editor{Face: face, SingleLine: true}
passInput.SetText("passphrase") passInput.SetText("passphrase")
passSubmit := &Button{Face: face, Label: "submit", Color: black} passSubmit := &Button{Face: face, Label: "submit"}
lst := &layout.List{Axis: layout.Vertical} lst := &layout.List{Axis: layout.Vertical}
passBtns := make([]*Button, 0) passBtns := make([]*Button, 0)
_ = passInput _ = passInput
_ = passSubmit _ = passSubmit
pathnames := make([]string, 0) pathnames := make([]string, 0)
copied := &Overlay{Face: face, Text: "copied to clipboard",
Color: black,
Background: darkgray,
Alignment: text.Middle,
}
var copiedWhen time.Time
updateBtns := func() { updateBtns := func() {
passBtns = passBtns[:0] passBtns = passBtns[:0]
@ -300,7 +182,7 @@ func eventLoop() {
passBtns = append(passBtns, &Button{ passBtns = append(passBtns, &Button{
Face: face, Face: face,
Label: strings.Join([]string{s, n, z}, ""), Label: strings.Join([]string{s, n, z}, ""),
Background: gray, Color: color.RGBA{A: 0xff, R: 0xf0, G: 0xf0, B: 0xf0},
}) })
pathnames = append(pathnames, x.Pathname) pathnames = append(pathnames, x.Pathname)
} }
@ -308,76 +190,40 @@ func eventLoop() {
} }
updateBtns() updateBtns()
confBtn := &Button{ for {
Face: face, select {
Label: "configure", case <-updated:
Alignment: text.Middle, log(Info, "UPDATE")
Color: black, updateBtns()
Background: gray,
}
storeDirLabel := &text.Label{Face: face, Text: "Store directory"}
storeDirEd := &text.Editor{Face: face, SingleLine: true}
storeDirEd.SetText(store.Dir)
saveBtn := &Button{
Face: face,
Label: "save",
Alignment: text.End,
Color: black,
Background: gray,
}
backBtn := &Button{
Face: face,
Label: "back",
Alignment: text.End,
Color: black,
Background: gray,
}
anim := &time.Ticker{}
animating := false
animOn := func() {
anim = time.NewTicker(time.Second / 120)
animating = true
w.Invalidate() w.Invalidate()
} case e := <-w.Events():
animOff := func() { switch e := e.(type) {
anim.Stop() case app.DestroyEvent:
animating = false 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)
var listPage, confPage, page func()
_ = confPage
listPage = func() {
cs = flex.Flexible(1.0)
mux.Lock() mux.Lock()
if lst.Dragging() { if lst.Dragging() {
key.HideInputOp{}.Add(ops) key.HideInputOp{}.Add(ops)
} }
var c2 layout.FlexChild
if len(passBtns) == 0 {
c2 = flex.End(confBtn.Layout(c, q, ops, cs))
if confBtn.Clicked() {
log(Info, "Configure")
w.Invalidate()
page = confPage
}
} else {
for lst.Init(c, q, ops, cs, len(passBtns)); lst.More(); lst.Next() { for lst.Init(c, q, ops, cs, len(passBtns)); lst.More(); lst.Next() {
btn := passBtns[lst.Index()] btn := passBtns[lst.Index()]
dims = btn.Layout(c, q, ops, lst.Constraints()) dims = btn.Layout(c, ops, q, lst.Constraints())
lst.End(dims) lst.End(dims)
if btn.Clicked() { if btn.Clicked() {
log(Info, "Clicked ", btn.Label)
// don't block UI thread on decryption attempt // don't block UI thread on decryption attempt
log(Info, "Clicked ", btn.Label)
go func(name string) { go func(name string) {
//p,err := store.Decrypt(name, prompt) //p,err := store.Decrypt(name, prompt)
p, err := store.Decrypt(name) p, err := store.Decrypt(name)
if err == nil { if err == nil {
passgo.Clip(p) passgo.Clip(p)
copiedWhen = time.Now()
animOn()
} else { } else {
log(Info, "Can't decrypt ", name) log(Info, "Can't decrypt ", name)
log(Info, err) log(Info, err)
@ -385,113 +231,9 @@ func eventLoop() {
}(pathnames[lst.Index()]) }(pathnames[lst.Index()])
} }
} }
c2 = flex.End(lst.Layout())
}
mux.Unlock() mux.Unlock()
if dotsbtn.Clicked() { dims = lst.Layout()
log(Info, "Configure") dims = margin.End(dims)
w.Invalidate()
page = confPage
}
flex.Layout(c1, c2)
x := time.Since(copiedWhen).Seconds()
start, end := 1.5, 1.75
switch {
case x > start && x < end:
fade := (end - x) / (end - start)
copied.Color.R = uint8(float64(black.R) * fade)
copied.Color.G = uint8(float64(black.G) * fade)
copied.Color.B = uint8(float64(black.B) * fade)
copied.Color.A = uint8(float64(black.A) * fade)
copied.Background.R = uint8(float64(darkgray.R) * fade)
copied.Background.G = uint8(float64(darkgray.G) * fade)
copied.Background.B = uint8(float64(darkgray.B) * fade)
copied.Background.A = uint8(float64(darkgray.A) * fade)
fallthrough
case x <= start:
cs = margincs
al := layout.Align{Alignment: layout.SE}
cs = al.Begin(ops, cs)
cs.Width.Min = cs.Width.Max
dims = al.End(copied.Layout(c, ops, cs))
case animating:
copied.Color = black
copied.Background = darkgray
animOff()
}
}
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)
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()
passgo.GetStore(&store)
Config.StoreDir = store.Dir
saveConf()
reload <- struct{}{}
}()
w.Invalidate()
page = listPage
}
}
page = listPage
for {
select {
case <-updated:
log(Info, "UPDATE")
updateBtns()
w.Invalidate()
case <-anim.C:
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)
cs = layout.RigidConstraints(e.Size)
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(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))
page()
margin.End(dims)
w.Update(ops) w.Update(ops)
} }
} }

View File

@ -35,8 +35,7 @@ func parse(args []string) ([]string, options) {
} }
func main() { func main() {
var store passgo.Store store, err := passgo.GetStore()
err := passgo.GetStore(&store)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(-1) os.Exit(-1)

17
main.go
View File

@ -34,26 +34,25 @@ type Store struct {
keyring openpgp.KeyRing keyring openpgp.KeyRing
} }
func GetStore(store *Store) error { func GetStore() (*Store, error) {
ret := &Store{}
u, err := user.Current() u, err := user.Current()
if err != nil { if err != nil {
return fmt.Errorf("Can't get current user.") return ret, fmt.Errorf("Can't get current user.")
}
if store.Dir == "" {
store.Dir = path.Join(u.HomeDir, ".password-store")
} }
ret.Dir = path.Join(u.HomeDir, ".password-store")
fd, err := os.Open(path.Join(u.HomeDir, ".gnupg/secring.gpg")) fd, err := os.Open(path.Join(u.HomeDir, ".gnupg/secring.gpg"))
defer fd.Close() defer fd.Close()
if err != nil { if err != nil {
return fmt.Errorf("Can't open keyring file") return ret, fmt.Errorf("Can't open keyring file")
} }
kr, err := openpgp.ReadKeyRing(fd) kr, err := openpgp.ReadKeyRing(fd)
if err != nil { if err != nil {
return fmt.Errorf("Can't open gnupg keyring.") return ret, fmt.Errorf("Can't open gnupg keyring.")
} }
store.keyring = kr ret.keyring = kr
return nil return ret, nil
} }
type caseInsensitive []os.FileInfo type caseInsensitive []os.FileInfo