Skip to content

Commit

Permalink
Support scanning QR Code for receiving files
Browse files Browse the repository at this point in the history
QR Codes can be generated from the wormwhole-william cli tool via the
`--qr` flag.

The code embeds the relay url, but currently we are ignoring that and
just using the relay set in the config.

Closes: #7 [via git-merge-pr]
  • Loading branch information
psanford committed May 29, 2021
1 parent c0fca2a commit a4ca4d7
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 7 deletions.
3 changes: 3 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ android {

dependencies {
implementation ':wormhole-william@aar'
implementation 'com.android.support:appcompat-v7:26.1.0'
compile 'com.google.zxing:core:3.2.1'
compile 'com.journeyapps:zxing-android-embedded:3.2.0@aar'
}
12 changes: 11 additions & 1 deletion android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.sanford.wormhole_william">
package="io.sanford.wormhole_william"
xmlns:tools="http://schemas.android.com/tools"
>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Expand Down Expand Up @@ -28,5 +30,13 @@
<data android:mimeType="*/*" />
</intent-filter>
</activity>

<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="portrait"
tools:replace="android:screenOrientation"
android:stateNotNeeded="true">
</activity>

</application>
</manifest>
54 changes: 54 additions & 0 deletions android/src/main/java/io/sanford/wormholewilliam/Scan.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.sanford.wormholewilliam;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import java.lang.String;

public class Scan extends Fragment {
public Scan() {
}

public void register(View view) {
Context ctx = view.getContext();
Handler handler = new Handler(ctx.getMainLooper());
Scan inst = this;
handler.post(new Runnable() {
public void run() {
Activity act = (Activity)ctx;
FragmentTransaction ft = act.getFragmentManager().beginTransaction();
ft.add(inst, "Scan");
ft.commitNow();
}
});
}

@Override public void onAttach(Context ctx) {
super.onAttach(ctx);

IntentIntegrator integrator = IntentIntegrator.forFragment(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES);
integrator.setPrompt("Scan");
integrator.setCameraId(0);
integrator.setBeepEnabled(false);
integrator.setBarcodeImageEnabled(false);
integrator.initiateScan();
}

@Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if(result != null) {
Log.d("wormhole", "Scan.onActivityResult() " + result.getContents());
scanResult(result.getContents());
}
}

static private native void scanResult(String contents);
}
64 changes: 60 additions & 4 deletions jgo/jgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import (
)

var (
globalStateMux sync.Mutex
pendingResult chan picker.PickResult

sharedEventCh chan picker.SharedEvent
globalStateMux sync.Mutex
pendingResult chan picker.PickResult
pendingQRScanResult chan string
sharedEventCh chan picker.SharedEvent
)

func PickFile(viewEvt app.ViewEvent) <-chan picker.PickResult {
Expand Down Expand Up @@ -101,6 +101,42 @@ func NotifyDownloadManager(viewEvt app.ViewEvent, name, path, contentType string
}()
}

func ScanQRCode(viewEvt app.ViewEvent) chan string {
globalStateMux.Lock()
pendingQRScanResult = make(chan string, 1)
globalStateMux.Unlock()

go func() {
jvm := jni.JVMFor(app.JavaVM())
err := jni.Do(jvm, func(env jni.Env) error {
var uptr = app.AppContext()
appCtx := *(*jni.Object)(unsafe.Pointer(&uptr))
loader := jni.ClassLoaderFor(env, appCtx)
cls, err := jni.LoadClass(env, loader, "io.sanford.wormholewilliam.Scan")
if err != nil {
log.Printf("Load io.sanford.wormholewilliam.Scan error: %s", err)
}

mid := jni.GetMethodID(env, cls, "<init>", "()V")

inst, err := jni.NewObject(env, cls, mid)
if err != nil {
log.Printf("NewObject err: %s", err)
}
sig := "(Landroid/view/View;)V"

mid = jni.GetMethodID(env, cls, "register", sig)

return jni.CallVoidMethod(env, inst, mid, jni.Value(viewEvt.View))
})
if err != nil {
log.Printf("ScnQRCode jvm err: %s", err)
}
}()

return pendingQRScanResult
}

//export Java_io_sanford_wormholewilliam_Jni_pickerResult
func Java_io_sanford_wormholewilliam_Jni_pickerResult(env *C.JNIEnv, cls C.jclass, jpath, jname, jerr C.jstring) {

Expand Down Expand Up @@ -164,3 +200,23 @@ func Java_io_sanford_wormholewilliam_Share_gotSharedItem(env *C.JNIEnv, cls C.jc
log.Printf("sharedEvent send timeout")
}
}

//export Java_io_sanford_wormholewilliam_Scan_scanResult
func Java_io_sanford_wormholewilliam_Scan_scanResult(env *C.JNIEnv, cls C.jclass, jCode C.jstring) {
globalStateMux.Lock()
defer globalStateMux.Unlock()
if pendingQRScanResult == nil {
return
}

jenv := jni.EnvFor(uintptr(unsafe.Pointer(env)))
code := jni.GoString(jenv, jni.String(jCode))

log.Printf("scan code result: %s", code)

select {
case pendingQRScanResult <- code:
case <-time.After(10 * time.Second):
log.Printf("pendingQRScanResult send timeout")
}
}
6 changes: 5 additions & 1 deletion ui/platform_android.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ func (a *androidPlatform) notifyDownloadManager(name, path, contentType string,

}

func (d *androidPlatform) sharedEventCh() chan picker.SharedEvent {
func (a *androidPlatform) sharedEventCh() chan picker.SharedEvent {
return jgo.GetSharedEventCh()
}

func (a *androidPlatform) scanQRCode() <-chan string {
return jgo.ScanQRCode(a.viewEvent)
}
4 changes: 4 additions & 0 deletions ui/platform_dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ func (d *dummyPlatform) notifyDownloadManager(name, path, contentType string, si
func (d *dummyPlatform) sharedEventCh() chan picker.SharedEvent {
return nil
}

func (d *dummyPlatform) scanQRCode() <-chan string {
return nil
}
60 changes: 59 additions & 1 deletion ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package ui

import (
"context"
"errors"
"fmt"
"image"
"image/color"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -67,7 +69,8 @@ func (ui *UI) loop(w *app.Window) error {
}

var (
pickResult <-chan picker.PickResult
pickResult <-chan picker.PickResult
qrCodeResult <-chan string

ctx = context.Background()
platformHandler = newPlatformHandler()
Expand All @@ -76,6 +79,19 @@ func (ui *UI) loop(w *app.Window) error {
var ops op.Ops
for {
select {
case code := <-qrCodeResult:
if code != "" {

parsed, err := parseCodeURI(code)
if err != nil {
statusMsg = fmt.Sprintf("invalid code: %s", err)
continue
}

recvCodeEditor.SetText(parsed.code)
w.Invalidate()
}
qrCodeResult = nil
case shareEvt := <-platformHandler.sharedEventCh():
if shareEvt.Type == picker.Text {
textMsgEditor.SetText(shareEvt.Text)
Expand Down Expand Up @@ -117,6 +133,7 @@ func (ui *UI) loop(w *app.Window) error {
sendFileClicked bool
sendTextClicked bool
recvClicked bool
scanQRClicked bool
acceptClicked bool
cancelClicked bool
)
Expand All @@ -129,6 +146,7 @@ func (ui *UI) loop(w *app.Window) error {
{"sendFileBtn", sendFileBtn, &sendFileClicked},
{"sendTextBtn", sendTextBtn, &sendTextClicked},
{"recvMsgBtn", recvMsgBtn, &recvClicked},
{"scanQRBtn", scanQRBtn, &scanQRClicked},
{"acceptBtn", acceptBtn, &acceptClicked},
{"cancelBtn", cancelBtn, &cancelClicked},
}
Expand All @@ -153,6 +171,10 @@ func (ui *UI) loop(w *app.Window) error {
}
}

if scanQRClicked {
qrCodeResult = platformHandler.scanQRCode()
}

if sendFileClicked {
pickResult = platformHandler.pickFile()
}
Expand Down Expand Up @@ -489,6 +511,7 @@ var (

recvCodeEditor = new(RichEditor)
recvMsgBtn = new(widget.Clickable)
scanQRBtn = new(widget.Clickable)
recvTxtMsg = new(Copyable)
itemList = &layout.List{
Axis: layout.Vertical,
Expand Down Expand Up @@ -652,6 +675,12 @@ var recvTab = Tab{
widgets := []layout.Widget{
textField(gtx, th, "Code", "Code", recvCodeEditor),

func(gtx C) D {
if transferInProgress || recvCodeEditor.Text() != "" {
gtx = gtx.Disabled()
}
return material.Button(th, scanQRBtn, "Scan QR Code").Layout(gtx)
},
func(gtx C) D {
if transferInProgress {
gtx = gtx.Disabled()
Expand Down Expand Up @@ -802,6 +831,7 @@ type platformHandler interface {
pickFile() <-chan picker.PickResult
sharedEventCh() chan picker.SharedEvent
notifyDownloadManager(name, path, contentType string, size int64)
scanQRCode() <-chan string
}

// Test colors.
Expand All @@ -821,3 +851,31 @@ func ColorBox(gtx layout.Context, size image.Point, color color.NRGBA) layout.Di
paint.PaintOp{}.Add(gtx.Ops)
return layout.Dimensions{Size: size}
}

type parsedCode struct {
relay string
code string
}

func parseCodeURI(codeStr string) (*parsedCode, error) {
if !strings.HasPrefix(codeStr, "wormhole:") {
return nil, errors.New("not a wormhole code")
}

u := strings.TrimPrefix(codeStr, "wormhole:")

url, err := url.Parse(u)
if err != nil {
return nil, err
}

code := url.Query().Get("code")
if code == "" {
return nil, errors.New("no code")
}

return &parsedCode{
relay: url.Host,
code: code,
}, nil
}

0 comments on commit a4ca4d7

Please sign in to comment.