Compare commits
1 Commits
master
...
fix/recent
| Author | SHA1 | Date | |
|---|---|---|---|
| a022236d87 |
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,5 +2,3 @@ cmd/passgo/passgo
|
||||||
cmd/passgo-gui/passgo-gui
|
cmd/passgo-gui/passgo-gui
|
||||||
nohup.out
|
nohup.out
|
||||||
*.apk
|
*.apk
|
||||||
*.idsig
|
|
||||||
/passgo-gui
|
|
||||||
|
|
|
||||||
BIN
Permissions.jar
BIN
Permissions.jar
Binary file not shown.
|
|
@ -1,91 +0,0 @@
|
||||||
package st.wow.git.passgo;
|
|
||||||
|
|
||||||
import java.lang.Runnable;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.app.FragmentManager;
|
|
||||||
import android.app.FragmentTransaction;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
// Requests the storage permissions the app needs to read the password
|
|
||||||
// store in shared storage. On Android 11+ (API 30+) the
|
|
||||||
// MANAGE_EXTERNAL_STORAGE ("All files access") permission cannot be
|
|
||||||
// requested with a dialog; the user must toggle it in the system
|
|
||||||
// settings, so we launch that settings screen directly for this app.
|
|
||||||
public class Permissions extends Fragment {
|
|
||||||
Context ctx;
|
|
||||||
Handler handler;
|
|
||||||
final int PERMISSIONS_REQUEST = 1;
|
|
||||||
|
|
||||||
public Permissions(View view) {
|
|
||||||
Log.d("passgo", "Permissions()");
|
|
||||||
this.ctx = view.getContext();
|
|
||||||
this.handler = new Handler(this.ctx.getMainLooper());
|
|
||||||
Permissions inst = this;
|
|
||||||
handler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
Activity act = (Activity)ctx;
|
|
||||||
FragmentTransaction ft = act.getFragmentManager().beginTransaction();
|
|
||||||
ft.add(inst, "Permissions");
|
|
||||||
ft.commitNow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
Log.d("passgo", "Permissions onAttach()");
|
|
||||||
|
|
||||||
if (!(context instanceof Activity)) {
|
|
||||||
Log.w("passgo", "Context is not an Activity");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Activity activity = (Activity) context;
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
||||||
if (context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
|
||||||
requestPermissions(
|
|
||||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
|
||||||
PERMISSIONS_REQUEST
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
|
||||||
&& !Environment.isExternalStorageManager()) {
|
|
||||||
Log.d("passgo", "Requesting all files access");
|
|
||||||
Intent intent = new Intent(
|
|
||||||
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
|
||||||
Uri.parse("package:" + activity.getPackageName())
|
|
||||||
);
|
|
||||||
if (intent.resolveActivity(activity.getPackageManager()) != null) {
|
|
||||||
startActivity(intent);
|
|
||||||
} else {
|
|
||||||
Intent fallback = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
|
|
||||||
if (fallback.resolveActivity(activity.getPackageManager()) != null) {
|
|
||||||
startActivity(fallback);
|
|
||||||
} else {
|
|
||||||
Log.e("passgo", "No activity found for all files access settings");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
Log.d("passgo", "Permissions onActivityResult(" + requestCode + "): " + resultCode);
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
PgpConnect.jar
BIN
PgpConnect.jar
Binary file not shown.
28
README.md
28
README.md
|
|
@ -32,31 +32,3 @@ p, err := store.Decrypt("myPass")
|
||||||
|
|
||||||
Also included is a simple GUI front-end using [Gio](https://gioui.org)
|
Also included is a simple GUI front-end using [Gio](https://gioui.org)
|
||||||
that works on MacOS and Android. See `cmd/passgo-gui`.
|
that works on MacOS and Android. See `cmd/passgo-gui`.
|
||||||
|
|
||||||
## Building the Android APK
|
|
||||||
|
|
||||||
Use `scripts/build.sh` — do not build by hand:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./scripts/build.sh phone # build + install on the phone
|
|
||||||
./scripts/build.sh phone --no-install
|
|
||||||
./scripts/build.sh emu # emulator build (amd64)
|
|
||||||
```
|
|
||||||
|
|
||||||
It outputs `cmd/passgo-gui/passgo-gui.apk` (APKs are git-ignored and
|
|
||||||
regenerated by the script). The script, in order:
|
|
||||||
|
|
||||||
1. `gogio -target android -targetsdk 34` (from `~/go/bin`; packages the
|
|
||||||
Go code plus the `*.jar` JNI classes found in `cmd/passgo-gui/`),
|
|
||||||
2. apktool decode → inject `MANAGE_EXTERNAL_STORAGE` into the manifest
|
|
||||||
(gogio's hardcoded permission table lacks it, and without it the
|
|
||||||
system "All files access" toggle is grayed out on Android 11+) →
|
|
||||||
apktool rebuild,
|
|
||||||
3. sign with the debug keystore (`~/.android/debug.keystore`),
|
|
||||||
4. `adb install -r`.
|
|
||||||
|
|
||||||
Prereqs: JDK, Go + gogio, Android SDK at `~/android-sdk`
|
|
||||||
(platform-tools + build-tools 35.0.0). apktool v3.0.3 is auto-downloaded
|
|
||||||
to `~/android-sdk/tools/apktool.jar` if missing. The script checks each
|
|
||||||
prereq and says what is missing. The phone is reached via adb over the
|
|
||||||
WireGuard address (see the global AGENTS.md).
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
//go:build android
|
//+build android
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"git.wow.st/gmp/passgo"
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
"git.wow.st/gmp/passgo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -22,15 +22,17 @@ func init() {
|
||||||
|
|
||||||
func handleEvent(e event.Event) {
|
func handleEvent(e event.Event) {
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
case app.AndroidViewEvent:
|
case app.ViewEvent:
|
||||||
// A zero View means the view is detached (e.g. the activity going
|
// View == 0 is Gio's detach signal: GioView.onDestroyView sends
|
||||||
// away on a recents-wipe); there is nothing to register for a
|
// ViewEvent{View: 0} when the view is destroyed (e.g. the activity
|
||||||
// detached view. A re-attach arrives as a fresh event with a live
|
// going away on a recents-wipe). There is nothing to register for a
|
||||||
// view.
|
// detached view; passing the null ref on would abort in JNI
|
||||||
if e.View != 0 {
|
// (GetObjectClass on null). A re-attach arrives as a fresh event
|
||||||
initPgp(e.View)
|
// with a live view.
|
||||||
initPermissions(e.View)
|
if e.View == 0 {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
initPgp(e.View)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,10 +40,6 @@ func initPgp(view uintptr) {
|
||||||
passgo.InitPgp(view)
|
passgo.InitPgp(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initPermissions(view uintptr) {
|
|
||||||
passgo.InitPermissions(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfDir() (string, error) {
|
func getConfDir() (string, error) {
|
||||||
ret, err := app.DataDir()
|
ret, err := app.DataDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
//go:build darwin
|
//+build darwin
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"gioui.org/font/opentype"
|
"gioui.org/font/opentype"
|
||||||
|
"gioui.org/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -22,18 +22,22 @@ func setFont() error {
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
defer f.Close()
|
fnts, err := opentype.ParseCollectionReaderAt(f)
|
||||||
data, err := io.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
log(Info, "Cannot read system font collection")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fnts, err := opentype.ParseCollection(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log(Info, "Cannot parse font collection")
|
log(Info, "Cannot parse font collection")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
collection = append(collection, fnts...)
|
fnt, err := fnts.Font(0)
|
||||||
|
if err != nil {
|
||||||
|
log(Info, "Cannot get font from collection")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection = append(collection, text.FontFace{Font: text.Font{}, Face: fnt})
|
||||||
|
if err != nil {
|
||||||
|
log(Info, "Cannot access font from font collection")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//font.Register(text.Font{}, face)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
//go:build linux && !android
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gioui.org/app"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
noidLabelText = "No GPG ids available"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
fontSize = 16
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfDir() (string, error) {
|
|
||||||
ret, err := app.DataDir()
|
|
||||||
if err != nil {
|
|
||||||
log(Error, "Cannot get data directory:", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(ret); os.IsNotExist(err) {
|
|
||||||
err = os.MkdirAll(ret, 0700)
|
|
||||||
if err != nil {
|
|
||||||
log(Error, "Cannot create configuration directory ", ret)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !android
|
//+build !android
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build darwin linux
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -10,11 +12,12 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
"gioui.org/font/gofont"
|
"gioui.org/font/gofont"
|
||||||
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/io/system"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
|
|
@ -40,8 +43,7 @@ type conf struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if false {
|
if false { go func() {
|
||||||
go func() {
|
|
||||||
f, err := os.Create("cpuprofile")
|
f, err := os.Create("cpuprofile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Can't create CPU profile\n")
|
fmt.Printf("Can't create CPU profile\n")
|
||||||
|
|
@ -58,8 +60,7 @@ func main() {
|
||||||
pprof.StopCPUProfile()
|
pprof.StopCPUProfile()
|
||||||
f.Close()
|
f.Close()
|
||||||
fmt.Printf("CPU profile written\n")
|
fmt.Printf("CPU profile written\n")
|
||||||
}()
|
}() }
|
||||||
}
|
|
||||||
var fd *os.File
|
var fd *os.File
|
||||||
var err error
|
var err error
|
||||||
confDir, err = getConfDir()
|
confDir, err = getConfDir()
|
||||||
|
|
@ -100,7 +101,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
reload = make(chan struct{})
|
reload = make(chan struct{})
|
||||||
updated = make(chan struct{}, 1)
|
updated = make(chan struct{})
|
||||||
chdir = make(chan struct{})
|
chdir = make(chan struct{})
|
||||||
passch = make(chan []byte)
|
passch = make(chan []byte)
|
||||||
|
|
||||||
|
|
@ -128,7 +129,6 @@ var (
|
||||||
chdir chan struct{}
|
chdir chan struct{}
|
||||||
passch chan []byte
|
passch chan []byte
|
||||||
th *material.Theme
|
th *material.Theme
|
||||||
win atomic.Pointer[app.Window]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Updater() {
|
func Updater() {
|
||||||
|
|
@ -142,20 +142,7 @@ func Updater() {
|
||||||
mux.Lock()
|
mux.Lock()
|
||||||
l = ltmp
|
l = ltmp
|
||||||
mux.Unlock()
|
mux.Unlock()
|
||||||
// Non-blocking: if a refresh is already pending it will pick up
|
updated <- struct{}{}
|
||||||
// the latest list, so dropping the duplicate is fine.
|
|
||||||
select {
|
|
||||||
case updated <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
// Wake the window: the event loop blocks in w.Event(), which only
|
|
||||||
// returns when the window is invalidated or the user interacts
|
|
||||||
// with it. Without this, a list refresh signalled while the loop
|
|
||||||
// is blocked (e.g. the initial load on startup) would not be
|
|
||||||
// drawn until the next tap.
|
|
||||||
if w := win.Load(); w != nil {
|
|
||||||
w.Invalidate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
update()
|
update()
|
||||||
|
|
||||||
|
|
@ -209,36 +196,28 @@ func saveConf(fds ...*os.File) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func eventLoop() {
|
func eventLoop() {
|
||||||
var ops op.Ops
|
if collection == nil {
|
||||||
th = material.NewTheme()
|
collection = gofont.Collection()
|
||||||
th.TextSize = unit.Sp(fontSize)
|
|
||||||
if len(collection) > 0 {
|
|
||||||
// Use the embedded Go font plus the platform fonts (e.g. the
|
|
||||||
// Unicode font installed on Darwin) instead of the system fonts.
|
|
||||||
th.Shaper = text.NewShaper(text.NoSystemFonts(),
|
|
||||||
text.WithCollection(append(gofont.Collection(), collection...)))
|
|
||||||
} else {
|
|
||||||
// Always provide the embedded Go font: the system font scan
|
|
||||||
// finds no fonts on Android, and a fontless shaper renders a
|
|
||||||
// blank screen.
|
|
||||||
th.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
|
|
||||||
}
|
}
|
||||||
w := new(app.Window)
|
|
||||||
w.Option(
|
var ops op.Ops
|
||||||
|
th = material.NewTheme(collection)
|
||||||
|
th.TextSize = unit.Sp(fontSize)
|
||||||
|
w := app.NewWindow(
|
||||||
app.Size(unit.Dp(250), unit.Dp(500)),
|
app.Size(unit.Dp(250), unit.Dp(500)),
|
||||||
app.Title("passgo"))
|
app.Title("passgo"))
|
||||||
win.Store(w)
|
|
||||||
|
|
||||||
var margincs layout.Constraints
|
var margincs layout.Constraints
|
||||||
|
|
||||||
var c1 layout.FlexChild // flex child for title bar
|
var c1 layout.FlexChild // flex child for title bar
|
||||||
|
|
||||||
|
sysinset := &layout.Inset{}
|
||||||
margin := layout.UniformInset(unit.Dp(10))
|
margin := layout.UniformInset(unit.Dp(10))
|
||||||
|
|
||||||
title := material.Body1(th, "passgo")
|
title := material.Body1(th, "passgo")
|
||||||
dotsBtn := &Button{
|
dotsBtn := &Button{
|
||||||
Size: unit.Sp(fontSize),
|
Size: unit.Sp(fontSize),
|
||||||
Label: "\u2026",
|
Label: "\xe2\x8b\xae",
|
||||||
Alignment: text.Middle,
|
Alignment: text.Middle,
|
||||||
Color: black,
|
Color: black,
|
||||||
Background: gray,
|
Background: gray,
|
||||||
|
|
@ -264,7 +243,6 @@ func eventLoop() {
|
||||||
var overlayStart time.Time
|
var overlayStart time.Time
|
||||||
|
|
||||||
FilterEd := &widget.Editor{SingleLine: true}
|
FilterEd := &widget.Editor{SingleLine: true}
|
||||||
var lastFilter string
|
|
||||||
updateBtns := func() {
|
updateBtns := func() {
|
||||||
passBtns = passBtns[:0]
|
passBtns = passBtns[:0]
|
||||||
pathnames = pathnames[:0]
|
pathnames = pathnames[:0]
|
||||||
|
|
@ -291,7 +269,6 @@ func eventLoop() {
|
||||||
passBtns = append(passBtns, &Button{
|
passBtns = append(passBtns, &Button{
|
||||||
Size: unit.Sp(fontSize),
|
Size: unit.Sp(fontSize),
|
||||||
Label: lbl,
|
Label: lbl,
|
||||||
Color: black,
|
|
||||||
Background: gray,
|
Background: gray,
|
||||||
})
|
})
|
||||||
pathnames = append(pathnames, x.Pathname)
|
pathnames = append(pathnames, x.Pathname)
|
||||||
|
|
@ -311,7 +288,6 @@ func eventLoop() {
|
||||||
passBtns = append(passBtns, &Button{
|
passBtns = append(passBtns, &Button{
|
||||||
Size: unit.Sp(fontSize),
|
Size: unit.Sp(fontSize),
|
||||||
Label: lbl,
|
Label: lbl,
|
||||||
Color: black,
|
|
||||||
Background: gray,
|
Background: gray,
|
||||||
})
|
})
|
||||||
pathnames = append(pathnames, x.Pathname)
|
pathnames = append(pathnames, x.Pathname)
|
||||||
|
|
@ -417,49 +393,18 @@ func eventLoop() {
|
||||||
noidLabel := material.Label(th, unit.Sp(fontSize), noidLabelText)
|
noidLabel := material.Label(th, unit.Sp(fontSize), noidLabelText)
|
||||||
idLabel := material.Label(th, unit.Sp(fontSize), "Select ID")
|
idLabel := material.Label(th, unit.Sp(fontSize), "Select ID")
|
||||||
|
|
||||||
// timing variables used for the "copied to clipboard" animation
|
|
||||||
// (seconds relative to overlayStart)
|
|
||||||
fade1a, fade1b := 1.5, 2.0
|
|
||||||
start2 := float64(Config.ClearDelay)
|
|
||||||
fade2a, end := start2+1.5, start2+2.0
|
|
||||||
|
|
||||||
anim := &time.Ticker{}
|
anim := &time.Ticker{}
|
||||||
var animDone chan struct{}
|
|
||||||
animating := false
|
animating := false
|
||||||
animOn := func() {
|
animOn := func() {
|
||||||
log(Info, "animOn()")
|
log(Info, "animOn()")
|
||||||
anim = time.NewTicker(time.Second / 90)
|
anim = time.NewTicker(time.Second / 90)
|
||||||
animating = true
|
animating = true
|
||||||
w.Invalidate()
|
w.Invalidate()
|
||||||
// The event loop blocks in w.Event() between frames, so ticks it
|
|
||||||
// misses never trigger a redraw and the fade would freeze. Wake
|
|
||||||
// the window on each tick, but only while a fade is actually
|
|
||||||
// visible.
|
|
||||||
t := anim
|
|
||||||
animDone = make(chan struct{})
|
|
||||||
d := animDone
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
x := time.Since(overlayStart).Seconds()
|
|
||||||
if (x >= fade1a && x < fade1b) || (x > fade2a && x < end) {
|
|
||||||
w.Invalidate()
|
|
||||||
}
|
|
||||||
case <-d:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
animOff := func() {
|
animOff := func() {
|
||||||
log(Info, "animOff()")
|
log(Info, "animOff()")
|
||||||
anim.Stop()
|
anim.Stop()
|
||||||
animating = false
|
animating = false
|
||||||
if animDone != nil { // stops the wake-up goroutine started by animOn
|
|
||||||
close(animDone)
|
|
||||||
animDone = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var listPage, idPage, insertPage, confirmPage, confPage, promptPage, page func(C) D
|
var listPage, idPage, insertPage, confirmPage, confPage, promptPage, page func(C) D
|
||||||
|
|
@ -474,6 +419,11 @@ func eventLoop() {
|
||||||
|
|
||||||
lpf1 := &layout.Flex{Axis: layout.Horizontal}
|
lpf1 := &layout.Flex{Axis: layout.Horizontal}
|
||||||
listPage = func(gtx C) D {
|
listPage = func(gtx C) D {
|
||||||
|
// timing variables used for animation
|
||||||
|
fade1a, fade1b := 1.5, 2.0
|
||||||
|
start2 := float64(Config.ClearDelay)
|
||||||
|
fade2a, end := start2+1.5, start2+2.0
|
||||||
|
|
||||||
c2 := layout.Rigid(func(gtx C) D {
|
c2 := layout.Rigid(func(gtx C) D {
|
||||||
c21 := layout.Flexed(1, func(gtx C) D {
|
c21 := layout.Flexed(1, func(gtx C) D {
|
||||||
return material.Editor(th, FilterEd, "filter").Layout(gtx)
|
return material.Editor(th, FilterEd, "filter").Layout(gtx)
|
||||||
|
|
@ -483,7 +433,6 @@ func eventLoop() {
|
||||||
})
|
})
|
||||||
if xBtn.Clicked() {
|
if xBtn.Clicked() {
|
||||||
FilterEd.SetText("")
|
FilterEd.SetText("")
|
||||||
lastFilter = ""
|
|
||||||
updateBtns()
|
updateBtns()
|
||||||
}
|
}
|
||||||
return lpf1.Layout(gtx, c21, c22)
|
return lpf1.Layout(gtx, c21, c22)
|
||||||
|
|
@ -491,6 +440,9 @@ func eventLoop() {
|
||||||
c3 := layout.Flexed(1.0, func(gtx C) D {
|
c3 := layout.Flexed(1.0, func(gtx C) D {
|
||||||
var ret D
|
var ret D
|
||||||
mux.Lock()
|
mux.Lock()
|
||||||
|
if lst.Dragging() {
|
||||||
|
key.HideInputOp{}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case store.Empty:
|
case store.Empty:
|
||||||
confBtn.Layout(gtx)
|
confBtn.Layout(gtx)
|
||||||
|
|
@ -591,11 +543,7 @@ func eventLoop() {
|
||||||
if animating && x > end {
|
if animating && x > end {
|
||||||
animOff()
|
animOff()
|
||||||
}
|
}
|
||||||
// material.Editor.Layout (above) already consumes the editor's
|
if len(FilterEd.Events()) > 0 {
|
||||||
// input events, so we detect a filter change by comparing the
|
|
||||||
// current text against the previous frame's.
|
|
||||||
if t := FilterEd.Text(); t != lastFilter {
|
|
||||||
lastFilter = t
|
|
||||||
updateBtns()
|
updateBtns()
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -640,14 +588,19 @@ func eventLoop() {
|
||||||
c4 = layout.Rigid(func(gtx C) D {
|
c4 = layout.Rigid(func(gtx C) D {
|
||||||
return material.Editor(th, idEd, "id").Layout(gtx)
|
return material.Editor(th, idEd, "id").Layout(gtx)
|
||||||
})
|
})
|
||||||
|
for _, e := range idEd.Events() {
|
||||||
|
switch e.(type) {
|
||||||
|
case widget.SubmitEvent:
|
||||||
|
log(Info, "Submit")
|
||||||
|
store.Id = idEd.Text()
|
||||||
|
page = listPage
|
||||||
|
}
|
||||||
|
}
|
||||||
c5 = layout.Rigid(func(gtx C) D {
|
c5 = layout.Rigid(func(gtx C) D {
|
||||||
return idSubmitBtn.Layout(gtx)
|
return idSubmitBtn.Layout(gtx)
|
||||||
})
|
})
|
||||||
if idSubmitBtn.Clicked() {
|
if idSubmitBtn.Clicked() {
|
||||||
store.Id = idEd.Text()
|
store.Id = idEd.Text()
|
||||||
// Persist the ID so it survives a restart (the manual entry
|
|
||||||
// path has no keyring to re-derive it from).
|
|
||||||
store.SetID(store.Id)
|
|
||||||
page = listPage
|
page = listPage
|
||||||
}
|
}
|
||||||
c6 = layout.Rigid(func(gtx C) D {
|
c6 = layout.Rigid(func(gtx C) D {
|
||||||
|
|
@ -679,31 +632,22 @@ func eventLoop() {
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
})
|
})
|
||||||
if ev, ok := idEd.Update(gtx); ok {
|
|
||||||
if _, isSubmit := ev.(widget.SubmitEvent); isSubmit {
|
|
||||||
log(Info, "Submit")
|
|
||||||
store.Id = idEd.Text()
|
|
||||||
store.SetID(store.Id)
|
|
||||||
w.Invalidate()
|
|
||||||
page = listPage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flex.Layout(gtx, c1, c2, c3, c4, c5, c6, c7)
|
return flex.Layout(gtx, c1, c2, c3, c4, c5, c6, c7)
|
||||||
}
|
}
|
||||||
|
|
||||||
var insName, insValue string
|
var insName, insValue string
|
||||||
genBtn := &SelButton{SelColor: gray}
|
genBtn := &SelButton{SelColor: gray}
|
||||||
genBtn.Button = Button{Size: unit.Sp(fontSize), Label: "generate", Color: black}
|
genBtn.Button = Button{Size: unit.Sp(fontSize), Label: "generate"}
|
||||||
symBtn := &SelButton{SelColor: gray}
|
symBtn := &SelButton{SelColor: gray}
|
||||||
numBtn := &SelButton{SelColor: gray}
|
numBtn := &SelButton{SelColor: gray}
|
||||||
symBtn.Button = Button{Size: unit.Sp(fontSize), Label: "@", Color: black}
|
symBtn.Button = Button{Size: unit.Sp(fontSize), Label: "@"}
|
||||||
numBtn.Button = Button{Size: unit.Sp(fontSize), Label: "#", Color: black}
|
numBtn.Button = Button{Size: unit.Sp(fontSize), Label: "#"}
|
||||||
symBtn.Select()
|
symBtn.Select()
|
||||||
numBtn.Select()
|
numBtn.Select()
|
||||||
lenEd := &widget.Editor{ SingleLine: true, Alignment: text.End }
|
lenEd := &widget.Editor{ SingleLine: true, Alignment: text.End }
|
||||||
lenEd.SetText("15")
|
lenEd.SetText("15")
|
||||||
lBtn := &Button{Size: unit.Sp(fontSize), Label: "<", Color: black, Background: gray}
|
lBtn := &Button{Size: unit.Sp(fontSize), Label: "<", Background: gray}
|
||||||
rBtn := &Button{Size: unit.Sp(fontSize), Label: ">", Color: black, Background: gray}
|
rBtn := &Button{Size: unit.Sp(fontSize), Label: ">", Background: gray}
|
||||||
|
|
||||||
updatePw := func() {
|
updatePw := func() {
|
||||||
if !genBtn.Selected {
|
if !genBtn.Selected {
|
||||||
|
|
@ -891,8 +835,9 @@ func eventLoop() {
|
||||||
|
|
||||||
promptPage = func(gtx C) D {
|
promptPage = func(gtx C) D {
|
||||||
submit := false
|
submit := false
|
||||||
if ev, ok := promptEd.Update(gtx); ok {
|
for _, e := range promptEd.Events() {
|
||||||
if _, isSubmit := ev.(widget.SubmitEvent); isSubmit {
|
switch e.(type) {
|
||||||
|
case widget.SubmitEvent:
|
||||||
log(Info, "Submit")
|
log(Info, "Submit")
|
||||||
submit = true
|
submit = true
|
||||||
}
|
}
|
||||||
|
|
@ -937,36 +882,39 @@ func eventLoop() {
|
||||||
ms := &runtime.MemStats{}
|
ms := &runtime.MemStats{}
|
||||||
x := 0
|
x := 0
|
||||||
var mallocs uint64
|
var mallocs uint64
|
||||||
var idsInit bool
|
|
||||||
for {
|
for {
|
||||||
// Window.Event and FrameEvent.Frame must be called from the same
|
|
||||||
// goroutine; drain the auxiliary channels non-blocking instead of
|
|
||||||
// bridging the window events through a channel.
|
|
||||||
select {
|
select {
|
||||||
case <-updated:
|
case <-updated:
|
||||||
log(Info, "UPDATE")
|
log(Info, "UPDATE")
|
||||||
updateBtns()
|
updateBtns()
|
||||||
w.Invalidate()
|
w.Invalidate()
|
||||||
default:
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-anim.C:
|
case <-anim.C:
|
||||||
w.Invalidate()
|
w.Invalidate()
|
||||||
default:
|
case e := <-w.Events():
|
||||||
}
|
|
||||||
switch e := w.Event().(type) {
|
|
||||||
case app.DestroyEvent:
|
|
||||||
os.Exit(0)
|
|
||||||
case app.FrameEvent:
|
|
||||||
x++
|
x++
|
||||||
if !idsInit { // the app is running; refresh the ID list
|
if x == 100 {
|
||||||
idsInit = true
|
runtime.ReadMemStats(ms)
|
||||||
go updateIdBtns()
|
mallocs = ms.Mallocs
|
||||||
}
|
}
|
||||||
// NewContext resets ops and adjusts for e.Insets.
|
switch e := e.(type) {
|
||||||
gtx := app.NewContext(&ops, e)
|
case system.DestroyEvent:
|
||||||
|
os.Exit(0)
|
||||||
|
case system.StageEvent:
|
||||||
|
if e.Stage == system.StageRunning {
|
||||||
|
go func() {
|
||||||
|
updateIdBtns()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
case system.FrameEvent:
|
||||||
|
gtx := layout.NewContext(&ops, e)
|
||||||
|
|
||||||
margin.Layout(gtx, func(gtx C) D {
|
sysinset.Top = e.Insets.Top
|
||||||
|
sysinset.Bottom = e.Insets.Bottom
|
||||||
|
sysinset.Left = e.Insets.Left
|
||||||
|
sysinset.Right = e.Insets.Right
|
||||||
|
|
||||||
|
sysinset.Layout(gtx, func(gtx C) D {
|
||||||
|
return margin.Layout(gtx, func(gtx C) D {
|
||||||
margincs = gtx.Constraints
|
margincs = gtx.Constraints
|
||||||
c1 = layout.Rigid(func(gtx C) D {
|
c1 = layout.Rigid(func(gtx C) D {
|
||||||
ct2 := layout.Rigid(func(gtx C) D {
|
ct2 := layout.Rigid(func(gtx C) D {
|
||||||
|
|
@ -984,6 +932,7 @@ func eventLoop() {
|
||||||
|
|
||||||
return page(gtx)
|
return page(gtx)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
if dotsBtn.Clicked() {
|
if dotsBtn.Clicked() {
|
||||||
log(Info, "Configure")
|
log(Info, "Configure")
|
||||||
|
|
@ -996,7 +945,7 @@ func eventLoop() {
|
||||||
insName, insValue = "", ""
|
insName, insValue = "", ""
|
||||||
page = insertPage
|
page = insertPage
|
||||||
}
|
}
|
||||||
e.Frame(&ops)
|
e.Frame(gtx.Ops)
|
||||||
default:
|
default:
|
||||||
handleEvent(e)
|
handleEvent(e)
|
||||||
}
|
}
|
||||||
|
|
@ -1007,3 +956,4 @@ func eventLoop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"gioui.org/f32"
|
||||||
"gioui.org/gesture"
|
"gioui.org/gesture"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
"gioui.org/op/paint"
|
"gioui.org/op/paint"
|
||||||
|
|
@ -15,18 +16,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
black = color.NRGBA{A: 0xff, R: 0, G: 0, B: 0}
|
black = color.RGBA{A: 0xff, R: 0, G: 0, B: 0}
|
||||||
white = color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}
|
white = color.RGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}
|
||||||
gray = color.NRGBA{A: 0xff, R: 0xf0, G: 0xf0, B: 0xf0}
|
gray = color.RGBA{A: 0xff, R: 0xf0, G: 0xf0, B: 0xf0}
|
||||||
darkgray = color.NRGBA{A: 0xff, R: 0xa0, G: 0xa0, B: 0xa0}
|
darkgray = color.RGBA{A: 0xff, R: 0xa0, G: 0xa0, B: 0xa0}
|
||||||
)
|
)
|
||||||
|
|
||||||
type Overlay struct {
|
type Overlay struct {
|
||||||
Size unit.Sp
|
Size unit.Value
|
||||||
Text string
|
Text string
|
||||||
Click gesture.Click
|
Click gesture.Click
|
||||||
Color color.NRGBA
|
Color color.RGBA
|
||||||
Background color.NRGBA
|
Background color.RGBA
|
||||||
Alignment text.Alignment
|
Alignment text.Alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,9 +39,11 @@ func (b *Overlay) Layout(gtx C) D {
|
||||||
l := material.Label(th, b.Size, b.Text)
|
l := material.Label(th, b.Size, b.Text)
|
||||||
ins := layout.UniformInset(unit.Dp(4))
|
ins := layout.UniformInset(unit.Dp(4))
|
||||||
l.Color = b.Color
|
l.Color = b.Color
|
||||||
return ins.Layout(gtx, func(gtx C) D {
|
ret := ins.Layout(gtx, func(gtx C) D {
|
||||||
return l.Layout(gtx)
|
return l.Layout(gtx)
|
||||||
})
|
})
|
||||||
|
pointer.Rect(image.Rect(0, 0, ret.Size.X, ret.Size.Y)).Add(gtx.Ops)
|
||||||
|
return ret
|
||||||
})
|
})
|
||||||
c1 := layout.Expanded(func(gtx C) D {
|
c1 := layout.Expanded(func(gtx C) D {
|
||||||
return layoutRRect(b.Background, gtx)
|
return layoutRRect(b.Background, gtx)
|
||||||
|
|
@ -51,64 +54,55 @@ func (b *Overlay) Layout(gtx C) D {
|
||||||
|
|
||||||
type SelButton struct {
|
type SelButton struct {
|
||||||
Button
|
Button
|
||||||
SelColor color.NRGBA
|
SelColor color.RGBA
|
||||||
Selected bool
|
Selected bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Button struct {
|
type Button struct {
|
||||||
Size unit.Sp
|
Size unit.Value
|
||||||
Label string
|
Label string
|
||||||
Click gesture.Click
|
Click gesture.Click
|
||||||
Color color.NRGBA
|
Color color.RGBA
|
||||||
Background color.NRGBA
|
Background color.RGBA
|
||||||
Alignment text.Alignment
|
Alignment text.Alignment
|
||||||
clicked bool
|
clicked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func layoutRRect(col color.NRGBA, gtx C) D {
|
func layoutRRect(col color.RGBA, gtx C) D {
|
||||||
r := gtx.Dp(4)
|
r := float32(gtx.Px(unit.Dp(4)))
|
||||||
sz := image.Point{X: gtx.Constraints.Min.X, Y: gtx.Constraints.Min.Y}
|
sz := image.Point{X: gtx.Constraints.Min.X, Y: gtx.Constraints.Min.Y}
|
||||||
// Push the rounded-rect clip, paint, then restore the previous clip.
|
w, h := float32(sz.X), float32(sz.Y)
|
||||||
stk := clip.UniformRRect(image.Rectangle{Max: sz}, r).Push(gtx.Ops)
|
rect := f32.Rectangle{
|
||||||
|
f32.Point{0, 0},
|
||||||
|
f32.Point{w, h},
|
||||||
|
}
|
||||||
|
//clip.RoundRect(gtx.Ops, rect, r, r, r, r)
|
||||||
|
clip.RRect{Rect: rect, NE: r, NW: r, SE: r, SW: r}.Add(gtx.Ops)
|
||||||
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
||||||
paint.PaintOp{}.Add(gtx.Ops)
|
paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(gtx.Ops)
|
||||||
stk.Pop()
|
|
||||||
return layout.Dimensions{Size: sz}
|
return layout.Dimensions{Size: sz}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Button) Layout(gtx C) D {
|
func (b *Button) Layout(gtx C) D {
|
||||||
mwidth := gtx.Constraints.Min.X
|
mwidth := gtx.Constraints.Min.X
|
||||||
b.clicked = false
|
b.clicked = false
|
||||||
|
for _, ev := range b.Click.Events(gtx) {
|
||||||
|
if ev.Type == gesture.TypeClick {
|
||||||
|
b.clicked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
ins := layout.UniformInset(unit.Dp(1))
|
ins := layout.UniformInset(unit.Dp(1))
|
||||||
return ins.Layout(gtx, func(gtx C) D {
|
return ins.Layout(gtx, func(gtx C) D {
|
||||||
st := layout.Stack{}
|
st := layout.Stack{}
|
||||||
c2 := layout.Stacked(func(gtx C) D {
|
c2 := layout.Stacked(func(gtx C) D {
|
||||||
l := material.Label(th, b.Size, b.Label)
|
l := material.Label(th, b.Size, b.Label)
|
||||||
l.Color = b.Color
|
|
||||||
ins := layout.UniformInset(unit.Dp(4))
|
ins := layout.UniformInset(unit.Dp(4))
|
||||||
|
//paint.ColorOp{Color: b.Color}.Add(ops)
|
||||||
ret := ins.Layout(gtx, func(gtx C) D {
|
ret := ins.Layout(gtx, func(gtx C) D {
|
||||||
return l.Layout(gtx)
|
return l.Layout(gtx)
|
||||||
})
|
})
|
||||||
// Register the click handler for the whole button area, not
|
pointer.Rect(image.Rect(0, 0, ret.Size.X, ret.Size.Y)).Add(gtx.Ops)
|
||||||
// just the label: otherwise buttons with short labels (in
|
|
||||||
// full-width rows, for example) have a tiny tap target.
|
|
||||||
hit := ret.Size
|
|
||||||
if hit.X < gtx.Constraints.Max.X {
|
|
||||||
hit.X = gtx.Constraints.Max.X
|
|
||||||
}
|
|
||||||
stk := clip.Rect(image.Rectangle{Max: hit}).Push(gtx.Ops)
|
|
||||||
event.Op(gtx.Ops, b)
|
|
||||||
b.Click.Add(gtx.Ops)
|
b.Click.Add(gtx.Ops)
|
||||||
stk.Pop()
|
|
||||||
for {
|
|
||||||
ev, ok := b.Click.Update(gtx.Source)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if ev.Kind == gesture.KindClick {
|
|
||||||
b.clicked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
return ret
|
||||||
})
|
})
|
||||||
c1 := layout.Expanded(func(gtx C) D {
|
c1 := layout.Expanded(func(gtx C) D {
|
||||||
|
|
|
||||||
20
go.mod
20
go.mod
|
|
@ -1,23 +1,15 @@
|
||||||
module git.wow.st/gmp/passgo
|
module git.wow.st/gmp/passgo
|
||||||
|
|
||||||
go 1.24.0
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gioui.org v0.10.2
|
gioui.org v0.0.0-20200827132523-d57edbb49d3c
|
||||||
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5
|
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5
|
||||||
|
git.wow.st/gmp/jni v0.0.0-20200827154156-014cd5c7c4c0
|
||||||
git.wow.st/gmp/rand v0.0.0-20191001220155-a81bebfaf8b0
|
git.wow.st/gmp/rand v0.0.0-20191001220155-a81bebfaf8b0
|
||||||
github.com/fsnotify/fsnotify v1.8.0
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/jcmdev0/gpgagent v0.0.0-20180509014935-5601b32d936c
|
github.com/jcmdev0/gpgagent v0.0.0-20180509014935-5601b32d936c
|
||||||
golang.org/x/crypto v0.46.0
|
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
|
||||||
|
golang.org/x/image v0.0.0-20200618115811-c13761719519
|
||||||
gopkg.in/yaml.v2 v2.2.7
|
gopkg.in/yaml.v2 v2.2.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
|
||||||
gioui.org/shader v1.0.9 // indirect
|
|
||||||
github.com/go-text/typesetting v0.3.4 // indirect
|
|
||||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
|
||||||
golang.org/x/image v0.26.0 // indirect
|
|
||||||
golang.org/x/net v0.48.0 // indirect
|
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
|
||||||
golang.org/x/text v0.32.0 // indirect
|
|
||||||
)
|
|
||||||
|
|
|
||||||
73
go.sum
73
go.sum
|
|
@ -1,40 +1,51 @@
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
gioui.org v0.0.0-20191126175243-2ca2e5462f16 h1:p31rtmKm51xpj2QtqGNlljAyHEP1oStU8MDRl2Dv7Gs=
|
||||||
gioui.org v0.10.2 h1:bZU5CORROwc51sNha0zYdE2qWVaDncOp5EjV5nrZQZ8=
|
gioui.org v0.0.0-20191126175243-2ca2e5462f16/go.mod h1:KqFFi2Dq5gYA3FJ0sDOt8OBXoMsuxMtE8v2f0JExXAY=
|
||||||
gioui.org v0.10.2/go.mod h1:iKILKNq6+LHMWhP/HjGDW/wDidUzRnb7B6c7ZD9y1Mg=
|
gioui.org v0.0.0-20191218180754-3dd7c8121c67 h1:y9md+l1thtMqJu/ulhF1Upv3pnOpGotpJDssO8X3LbY=
|
||||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org v0.0.0-20191218180754-3dd7c8121c67/go.mod h1:KqFFi2Dq5gYA3FJ0sDOt8OBXoMsuxMtE8v2f0JExXAY=
|
||||||
gioui.org/shader v1.0.9 h1:XxnqIfmClWpN49kizxH2W0JcCFrrEP4q3jZmNYaltbs=
|
gioui.org v0.0.0-20200827132523-d57edbb49d3c h1:TY1A2dzvxASVC+crmYfkDF8JNkd4rKaXZG2A4WboyU4=
|
||||||
gioui.org/shader v1.0.9/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
gioui.org v0.0.0-20200827132523-d57edbb49d3c/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
|
||||||
|
gioui.org v0.0.0-20200829162755-829ee4559c5a h1:mciXRGzQwU0TbgCILZzl6L1nYIWU31lDGIb+8RYige8=
|
||||||
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5 h1:OKeTjZST+/TKvtdA258NXJH+/gIx/xwyZxKrAezNFvk=
|
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5 h1:OKeTjZST+/TKvtdA258NXJH+/gIx/xwyZxKrAezNFvk=
|
||||||
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5/go.mod h1:NLdpaBoMQNFqncwP8OVRNWUDw1Kt9XWm3snfT7cXu24=
|
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5/go.mod h1:NLdpaBoMQNFqncwP8OVRNWUDw1Kt9XWm3snfT7cXu24=
|
||||||
|
git.wow.st/gmp/jni v0.0.0-20200827154156-014cd5c7c4c0 h1:Ynp3h+TC8k1clvf45D28VFQlmy0bPx8M/MG5bB24Vj8=
|
||||||
|
git.wow.st/gmp/jni v0.0.0-20200827154156-014cd5c7c4c0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
|
||||||
git.wow.st/gmp/rand v0.0.0-20191001220155-a81bebfaf8b0 h1:08wP00wvbDINsct1fzKV1xGGLvvtNsSb2X4CtIdpBzM=
|
git.wow.st/gmp/rand v0.0.0-20191001220155-a81bebfaf8b0 h1:08wP00wvbDINsct1fzKV1xGGLvvtNsSb2X4CtIdpBzM=
|
||||||
git.wow.st/gmp/rand v0.0.0-20191001220155-a81bebfaf8b0/go.mod h1:8+2Gwnrpc5yuk8Wp6cBhxvGcNLumYiPbQ7n0SQ8h29A=
|
git.wow.st/gmp/rand v0.0.0-20191001220155-a81bebfaf8b0/go.mod h1:8+2Gwnrpc5yuk8Wp6cBhxvGcNLumYiPbQ7n0SQ8h29A=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU=
|
|
||||||
github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY=
|
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos=
|
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
|
||||||
github.com/jcmdev0/gpgagent v0.0.0-20180509014935-5601b32d936c h1:DCnjNrPDSEslcqqBgcZBxlLUIhk2elQVyf2V+HkyxJI=
|
github.com/jcmdev0/gpgagent v0.0.0-20180509014935-5601b32d936c h1:DCnjNrPDSEslcqqBgcZBxlLUIhk2elQVyf2V+HkyxJI=
|
||||||
github.com/jcmdev0/gpgagent v0.0.0-20180509014935-5601b32d936c/go.mod h1:vdJ2op9pzpbH8CbpYKYBD6zjURqDY13PmnVn2I/uYBs=
|
github.com/jcmdev0/gpgagent v0.0.0-20180509014935-5601b32d936c/go.mod h1:vdJ2op9pzpbH8CbpYKYBD6zjURqDY13PmnVn2I/uYBs=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs=
|
||||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
|
||||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
|
||||||
|
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
//go:build android
|
|
||||||
//+build android
|
//+build android
|
||||||
|
|
||||||
//go:generate mkdir -p classes
|
//go:generate mkdir -p classes
|
||||||
//go:generate javac -nowarn -classpath $ANDROID_HOME/platforms/android-35/android.jar:openpgp-api.jar -d classes PgpConnect.java Permissions.java
|
//go:generate javac -bootclasspath $ANDROID_HOME/platforms/android-29/android.jar -classpath openpgp-api.jar -d classes PgpConnect.java Foo.java
|
||||||
//go:generate jar cf PgpConnect.jar -C classes .
|
//go:generate jar cf PgpConnect.jar -C classes .
|
||||||
//go:generate rm -rf classes
|
//go:generate rm -rf classes
|
||||||
|
|
||||||
|
|
@ -17,8 +15,8 @@ import "C"
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"path"
|
"path"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
@ -51,8 +49,11 @@ func Java_st_wow_git_passgo_PgpConnect_installComplete(env *C.JNIEnv, class C.jc
|
||||||
|
|
||||||
func InitPgp(view uintptr) {
|
func InitPgp(view uintptr) {
|
||||||
log.Printf("InitPgp()")
|
log.Printf("InitPgp()")
|
||||||
|
// view == 0 is Gio's detach signal (GioView.onDestroyView); nothing to
|
||||||
|
// register. Guard here as well so callers that forward the event
|
||||||
|
// unfiltered cannot drive registerFragment with a null ref (JNI abort).
|
||||||
if view == 0 {
|
if view == 0 {
|
||||||
return // detach signal; nothing to register
|
return
|
||||||
}
|
}
|
||||||
jvm = app.JavaVM()
|
jvm = app.JavaVM()
|
||||||
SetJVM(jvm) // why?
|
SetJVM(jvm) // why?
|
||||||
|
|
@ -61,19 +62,6 @@ func InitPgp(view uintptr) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitPermissions registers the Permissions fragment, which requests the
|
|
||||||
// storage permissions (launching the system "All files access" settings
|
|
||||||
// screen on Android 11+) so the app can read the store in shared storage.
|
|
||||||
func InitPermissions(view uintptr) {
|
|
||||||
log.Printf("InitPermissions()")
|
|
||||||
if view == 0 {
|
|
||||||
return // detach signal; nothing to register
|
|
||||||
}
|
|
||||||
RunInJVM(func(env *JNIEnv) {
|
|
||||||
C.registerPermissionsFragment(env, (C.jobject)(unsafe.Pointer(view)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopPgp() {
|
func stopPgp() {
|
||||||
waitch = make(chan struct{})
|
waitch = make(chan struct{})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
//go:build darwin
|
|
||||||
//+build darwin
|
//+build darwin
|
||||||
|
|
||||||
package passgo
|
package passgo
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
//go:build !android && linux
|
|
||||||
//+build !android,linux
|
//+build !android,linux
|
||||||
|
|
||||||
package passgo
|
package passgo
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
//go:build darwin || (!android && linux)
|
|
||||||
//+build darwin !android,linux
|
//+build darwin !android,linux
|
||||||
|
|
||||||
package passgo
|
package passgo
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ void
|
||||||
registerFragment(JNIEnv *env, jobject view) {
|
registerFragment(JNIEnv *env, jobject view) {
|
||||||
if (view == NULL) {
|
if (view == NULL) {
|
||||||
// Detach signal (Gio sends ViewEvent{View: 0} when the view is
|
// Detach signal (Gio sends ViewEvent{View: 0} when the view is
|
||||||
// destroyed, e.g. on a recents-wipe); the Go side already
|
// destroyed, e.g. the activity going away on a recents-wipe).
|
||||||
// filters these, but GetObjectClass(null) aborts the process.
|
// The Go side already filters these; this guard keeps the JNI
|
||||||
|
// calls safe if one ever slips through (GetObjectClass on null
|
||||||
|
// aborts the process).
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
jclass cls = (*env)->GetObjectClass(env, view);
|
jclass cls = (*env)->GetObjectClass(env, view);
|
||||||
|
|
@ -27,25 +29,6 @@ registerFragment(JNIEnv *env, jobject view) {
|
||||||
jobject inst = (*env)->NewObject(env, cls, mid, view);
|
jobject inst = (*env)->NewObject(env, cls, mid, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
registerPermissionsFragment(JNIEnv *env, jobject view) {
|
|
||||||
if (view == NULL) {
|
|
||||||
return; // detach signal; see registerFragment
|
|
||||||
}
|
|
||||||
jclass cls = (*env)->GetObjectClass(env, view);
|
|
||||||
jmethodID mid = (*env)->GetMethodID(env, cls, "getContext", "()Landroid/content/Context;");
|
|
||||||
jobject ctx = (*env)->CallObjectMethod(env, view, mid);
|
|
||||||
cls = (*env)->GetObjectClass(env, ctx);
|
|
||||||
mid = (*env)->GetMethodID(env, cls, "getClassLoader", "()Ljava/lang/ClassLoader;");
|
|
||||||
jobject loader = (*env)->CallObjectMethod(env, ctx, mid);
|
|
||||||
cls = (*env)->GetObjectClass(env, loader);
|
|
||||||
mid = (*env)->GetMethodID(env, cls, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
|
|
||||||
jstring str = (*env)->NewStringUTF(env, "st/wow/git/passgo/Permissions");
|
|
||||||
cls = (*env)->CallObjectMethod(env, loader, mid, str);
|
|
||||||
mid = (*env)->GetMethodID(env, cls, "<init>", "(Landroid/view/View;)V");
|
|
||||||
jobject inst = (*env)->NewObject(env, cls, mid, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
GetId(JNIEnv* env, jobject p, int chint) {
|
GetId(JNIEnv* env, jobject p, int chint) {
|
||||||
jclass cls = (*env)->GetObjectClass(env, p);
|
jclass cls = (*env)->GetObjectClass(env, p);
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var theJVM *C.JavaVM
|
var theJVM *C.JavaVM
|
||||||
|
|
||||||
type JNIEnv = C.JNIEnv
|
type JNIEnv = C.JNIEnv
|
||||||
|
|
||||||
func SetJVM(jvm uintptr) {
|
func SetJVM(jvm uintptr) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
void registerFragment(JNIEnv *env, jobject view);
|
void registerFragment(JNIEnv *env, jobject view);
|
||||||
void registerPermissionsFragment(JNIEnv *env, jobject view);
|
|
||||||
void GetId(JNIEnv* env, jobject p, int chint);
|
void GetId(JNIEnv* env, jobject p, int chint);
|
||||||
void Decrypt(JNIEnv* env, jobject p, char* cdata, int datalen, int chint);
|
void Decrypt(JNIEnv* env, jobject p, char* cdata, int datalen, int chint);
|
||||||
void Encrypt(JNIEnv* env, jobject p, char* cid, int idlen, char* cdata, int datalen, int chint);
|
void Encrypt(JNIEnv* env, jobject p, char* cid, int idlen, char* cdata, int datalen, int chint);
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -49,8 +49,6 @@ func GetStore(store *Store) error {
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(store.Dir); os.IsNotExist(err) {
|
if _, err := os.Stat(store.Dir); os.IsNotExist(err) {
|
||||||
store.Empty = true
|
store.Empty = true
|
||||||
} else {
|
|
||||||
store.Empty = false
|
|
||||||
}
|
}
|
||||||
id, err := ioutil.ReadFile(path.Join(store.Dir, ".gpg-id"))
|
id, err := ioutil.ReadFile(path.Join(store.Dir, ".gpg-id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Build (and optionally install) the passgo-gui Android APK.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# ./scripts/build.sh [phone|emu] # build + install (default: phone)
|
|
||||||
# ./scripts/build.sh phone --no-install # build only
|
|
||||||
#
|
|
||||||
# Output: cmd/passgo-gui/passgo-gui.apk
|
|
||||||
#
|
|
||||||
# Prereqs on this workstation:
|
|
||||||
# - JDK (java on PATH)
|
|
||||||
# - Go + gogio (~/go/bin)
|
|
||||||
# - Android SDK at ~/android-sdk (platform-tools, build-tools 35.0.0)
|
|
||||||
# - debug keystore at ~/.android/debug.keystore
|
|
||||||
# apktool is not a prerequisite: if it is missing, the script downloads
|
|
||||||
# v3.0.3 to the stable location ~/android-sdk/tools/apktool.jar.
|
|
||||||
#
|
|
||||||
# Why the apktool detour: gogio's manifest permissions come from a hardcoded
|
|
||||||
# table (only the legacy READ/WRITE_EXTERNAL_STORAGE). On Android 11+ the
|
|
||||||
# "All files access" settings toggle is grayed out unless the app declares
|
|
||||||
# MANAGE_EXTERNAL_STORAGE, which Permissions.java opens at startup. So we
|
|
||||||
# decode the gogio APK, inject the permission, rebuild, and re-sign with
|
|
||||||
# the debug key.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
REPO=$(cd "$(dirname "$0")/.." && pwd)
|
|
||||||
export ANDROID_HOME=${ANDROID_HOME:-$HOME/android-sdk}
|
|
||||||
export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/35.0.0:$HOME/go/bin
|
|
||||||
|
|
||||||
MODE=${1:-phone}
|
|
||||||
INSTALL=1
|
|
||||||
[ "${2:-}" = "--no-install" ] && INSTALL=0
|
|
||||||
case "$MODE" in
|
|
||||||
phone) ARCH="arm64,arm";;
|
|
||||||
emu) ARCH="amd64";;
|
|
||||||
*) echo "usage: $0 [phone|emu] [--no-install]" >&2; exit 1;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
|
||||||
|
|
||||||
command -v java >/dev/null 2>&1 || die "java not found"
|
|
||||||
command -v gogio >/dev/null 2>&1 || die "gogio not found (need ~/go/bin)"
|
|
||||||
command -v apksigner >/dev/null 2>&1 || die "apksigner not found (need $ANDROID_HOME/build-tools/35.0.0)"
|
|
||||||
command -v adb >/dev/null 2>&1 || die "adb not found (need $ANDROID_HOME/platform-tools)"
|
|
||||||
[ -f "$HOME/.android/debug.keystore" ] || die "debug keystore missing: $HOME/.android/debug.keystore"
|
|
||||||
|
|
||||||
# apktool: auto-download to a stable location if missing.
|
|
||||||
APKTOOL="$ANDROID_HOME/tools/apktool.jar"
|
|
||||||
if [ ! -f "$APKTOOL" ]; then
|
|
||||||
echo "=== apktool missing; downloading v3.0.3 to $APKTOOL ==="
|
|
||||||
mkdir -p "$ANDROID_HOME/tools"
|
|
||||||
curl -fsSL -o "$APKTOOL" \
|
|
||||||
https://github.com/iBotPeaches/Apktool/releases/download/v3.0.3/apktool_3.0.3.jar \
|
|
||||||
|| die "apktool download failed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
WORK=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$WORK"' EXIT
|
|
||||||
|
|
||||||
echo "=== gogio (Go -> APK) ==="
|
|
||||||
# targetSdk 34: targeting 35 makes Android 15 force edge-to-edge, which the
|
|
||||||
# app does not handle.
|
|
||||||
(cd "$REPO/cmd/passgo-gui" && gogio -target android -targetsdk 34 -arch "$ARCH" -o "$WORK/passgo-raw.apk" .)
|
|
||||||
[ -f "$WORK/passgo-raw.apk" ] || die "gogio did not produce an APK"
|
|
||||||
|
|
||||||
echo "=== apktool decode + inject MANAGE_EXTERNAL_STORAGE ==="
|
|
||||||
java -jar "$APKTOOL" d "$WORK/passgo-raw.apk" -o "$WORK/decoded" -f >/dev/null
|
|
||||||
python3 - "$WORK/decoded/AndroidManifest.xml" << 'PYEOF'
|
|
||||||
import sys, pathlib
|
|
||||||
p = pathlib.Path(sys.argv[1])
|
|
||||||
t = p.read_text()
|
|
||||||
perm = '<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>'
|
|
||||||
if perm not in t:
|
|
||||||
t = t.replace(" <application", " " + perm + "\n <application", 1)
|
|
||||||
if perm not in t:
|
|
||||||
sys.exit("ERROR: failed to inject MANAGE_EXTERNAL_STORAGE")
|
|
||||||
p.write_text(t)
|
|
||||||
print("manifest: MANAGE_EXTERNAL_STORAGE present")
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
echo "=== apktool rebuild ==="
|
|
||||||
java -jar "$APKTOOL" b "$WORK/decoded" -o "$WORK/passgo-unsigned.apk" >/dev/null
|
|
||||||
|
|
||||||
echo "=== sign ==="
|
|
||||||
apksigner sign \
|
|
||||||
--ks "$HOME/.android/debug.keystore" --ks-pass pass:android \
|
|
||||||
--key-pass pass:android --ks-key-alias androiddebugkey \
|
|
||||||
--out "$REPO/cmd/passgo-gui/passgo-gui.apk" "$WORK/passgo-unsigned.apk"
|
|
||||||
|
|
||||||
# ADB_SERIAL selects the device when several are attached (adb devices).
|
|
||||||
ADB="adb${ADB_SERIAL:+ -s $ADB_SERIAL}"
|
|
||||||
|
|
||||||
if [ "$INSTALL" = "1" ]; then
|
|
||||||
echo "=== install ==="
|
|
||||||
$ADB get-state >/dev/null 2>&1 || die "no adb device (connect the phone; set ADB_SERIAL if several are attached)"
|
|
||||||
$ADB install -r "$REPO/cmd/passgo-gui/passgo-gui.apk"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "=== DONE: $REPO/cmd/passgo-gui/passgo-gui.apk ==="
|
|
||||||
Loading…
Reference in New Issue
Block a user