Compare commits
10 commits
7f1abe1261
...
dc500c1432
Author | SHA1 | Date | |
---|---|---|---|
|
dc500c1432 | ||
|
be1e6e3f1e | ||
|
d8ed7acddd | ||
|
85c60913d5 | ||
|
69ae823e68 | ||
|
4501e333f0 | ||
|
0086fb1774 | ||
|
7c06c663d5 | ||
|
d4de0a9e81 | ||
|
881d225202 |
29
Cargo.lock
generated
|
@ -571,7 +571,18 @@ version = "0.6.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627"
|
checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytecheck_derive",
|
"bytecheck_derive 0.6.11",
|
||||||
|
"ptr_meta",
|
||||||
|
"simdutf8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytecheck"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41502630fe304ce54cbb2f8389e017784dee2b0328147779fcbe43b9db06d35d"
|
||||||
|
dependencies = [
|
||||||
|
"bytecheck_derive 0.7.0",
|
||||||
"ptr_meta",
|
"ptr_meta",
|
||||||
"simdutf8",
|
"simdutf8",
|
||||||
]
|
]
|
||||||
|
@ -587,6 +598,17 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytecheck_derive"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eda88c587085bc07dc201ab9df871bd9baa5e07f7754b745e4d7194b43ac1eda"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
|
@ -905,6 +927,7 @@ dependencies = [
|
||||||
name = "cuttle"
|
name = "cuttle"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytecheck 0.7.0",
|
||||||
"clap",
|
"clap",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui_extras",
|
"egui_extras",
|
||||||
|
@ -2513,7 +2536,7 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab"
|
checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytecheck",
|
"bytecheck 0.6.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2557,7 +2580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58"
|
checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"bytecheck",
|
"bytecheck 0.6.11",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
"ptr_meta",
|
"ptr_meta",
|
||||||
"rend",
|
"rend",
|
||||||
|
|
|
@ -8,6 +8,7 @@ default = ["desktop"]
|
||||||
desktop = ["dep:clap", "dep:eframe", "dep:egui_extras", "dep:fast_qr", "dep:png"]
|
desktop = ["dep:clap", "dep:eframe", "dep:egui_extras", "dep:fast_qr", "dep:png"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bytecheck = "0.7"
|
||||||
clap = { version = "4.3", optional = true, features = ["derive", "env"] }
|
clap = { version = "4.3", optional = true, features = ["derive", "env"] }
|
||||||
eframe = { version = "0.22", default-features = false, optional = true, features = ["default_fonts", "wgpu", "tts", "accesskit"] }
|
eframe = { version = "0.22", default-features = false, optional = true, features = ["default_fonts", "wgpu", "tts", "accesskit"] }
|
||||||
egui_extras = { version = "0.22", default-features = false, optional = true, features = ["chrono", "image"] }
|
egui_extras = { version = "0.22", default-features = false, optional = true, features = ["chrono", "image"] }
|
||||||
|
|
35
README.md
|
@ -22,15 +22,36 @@ Raptor codes for that file. Each piece of Raptor code then gets encoded as a QR
|
||||||
displayed for a period of time on the screen. The user then holds up the receiving computer's camera
|
displayed for a period of time on the screen. The user then holds up the receiving computer's camera
|
||||||
in order to receive the QR encoded raptor codes, and, *voila*, the file has been transferred!
|
in order to receive the QR encoded raptor codes, and, *voila*, the file has been transferred!
|
||||||
|
|
||||||
# Current status
|
# Current status (2023-08-20)
|
||||||
|
|
||||||
The desktop transmitting application is nearly done; already it can convert text input into QR codes
|
Currently, the following is done in terms of functionality:
|
||||||
(either a single static one if the data is small enough, otherwise it will stream a never ending
|
|
||||||
loop of them), and the mobile app has been started. Even without the mobile app, it's still
|
**Desktop**
|
||||||
already useful for transmitting short text strings to the phone, since the Android camera app will
|
- [x] transmit short text given on the commandline with a single qr code
|
||||||
decode any detected QR codes.
|
- [x] transmit large text given on the commandline with a stream of qr codes
|
||||||
|
- [x] transmit small files given on the commandline with a single qr code
|
||||||
|
- [x] transmit large files given on the commandline with a stream of qr codes
|
||||||
|
- [ ] receive text or files with a single qr code
|
||||||
|
- [ ] receive text or files with a stream of qr codes
|
||||||
|
|
||||||
|
**Android app**
|
||||||
|
- [ ] transmit short given text with a single qr code
|
||||||
|
- [ ] transmit large given text with a stream of qr codes
|
||||||
|
- [ ] transmit small files with a single qr code
|
||||||
|
- [ ] transmit large files with a stream of qr codes
|
||||||
|
- [x] receive text or files with a single qr code
|
||||||
|
- [x] receive text or files with a stream of qr codes
|
||||||
|
|
||||||
|
The desktop app (unix-ish only; developed on linux, untested on anything else, but should run on macos)
|
||||||
|
is closer to its final form in terms of UI/UX, though it currently can only transmit. The Android
|
||||||
|
app is about halfway done in terms of functionality, but the UI is, shall we say, "bad":
|
||||||
|
|
||||||
|
![cuttle mobile app receiving data](./mobile_receiving.png)
|
||||||
|
|
||||||
|
I might take a crack at making it less hideous before I start on the mobile transmitting/desktop
|
||||||
|
receiving side of the functionality, but maybe not! I often want to transfer a file or text from my
|
||||||
|
phone to my desktop, and I'm willing to live with an ugly UI for a little bit.
|
||||||
|
|
||||||
The mobile app has been started, and I hope to have it decoding QR streams soon!
|
|
||||||
|
|
||||||
# about the name
|
# about the name
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,38 @@
|
||||||
|
use rand::{seq::SliceRandom, Rng};
|
||||||
|
use raptorq::{Decoder, Encoder, EncodingPacket, ObjectTransmissionInformation};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
//
|
let rng = &mut rand::thread_rng();
|
||||||
|
let len = 20_000;
|
||||||
|
let mut v = Vec::with_capacity(len);
|
||||||
|
|
||||||
|
for _ in 0..len {
|
||||||
|
v.push(rng.gen::<u8>());
|
||||||
|
}
|
||||||
|
let config = ObjectTransmissionInformation::with_defaults(len as u64, 1200);
|
||||||
|
let encoder = Encoder::new(&v, config);
|
||||||
|
let mut packets = encoder
|
||||||
|
.get_encoded_packets(10)
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.serialize())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
packets.shuffle(rng);
|
||||||
|
|
||||||
|
let mut decoder = Decoder::new(config);
|
||||||
|
|
||||||
|
let mut v2 = None;
|
||||||
|
for (i, p) in packets.iter().enumerate() {
|
||||||
|
v2 = decoder.decode(EncodingPacket::deserialize(p));
|
||||||
|
if v2.is_some() {
|
||||||
|
println!(
|
||||||
|
"recovered after {i} packets received, out of {} total",
|
||||||
|
packets.len()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(v2.is_some());
|
||||||
|
assert_eq!(v2.unwrap(), v);
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.7 KiB |
|
@ -1,3 +1,4 @@
|
||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
org.gradle.daemon=false
|
||||||
|
|
BIN
mobile/cuttle_logo_large.png
Normal file
After Width: | Height: | Size: 61 KiB |
|
@ -5,9 +5,16 @@ gen:
|
||||||
flutter_rust_bridge_codegen
|
flutter_rust_bridge_codegen
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
cd native && cargo fmt
|
|
||||||
dart format .
|
dart format .
|
||||||
|
|
||||||
|
native: native-debug native-release
|
||||||
|
|
||||||
|
native-release:
|
||||||
|
cd android && ./gradlew :app:cargoBuildRelease
|
||||||
|
|
||||||
|
native-debug:
|
||||||
|
cd android && ./gradlew :app:cargoBuildDebug
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
flutter clean
|
flutter clean
|
||||||
cd native && cargo clean
|
cd native && cargo clean
|
||||||
|
|
|
@ -9,22 +9,37 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
abstract class Native {
|
abstract class Native {
|
||||||
Future<Platform> platform({dynamic hint});
|
Future<TxConfig?> getTxConfig({required Uint8List bytes, dynamic hint});
|
||||||
|
|
||||||
FlutterRustBridgeTaskConstMeta get kPlatformConstMeta;
|
FlutterRustBridgeTaskConstMeta get kGetTxConfigConstMeta;
|
||||||
|
|
||||||
Future<bool> rustReleaseMode({dynamic hint});
|
Future<void> dropTxConfig({required TxConfig txc, dynamic hint});
|
||||||
|
|
||||||
FlutterRustBridgeTaskConstMeta get kRustReleaseModeConstMeta;
|
FlutterRustBridgeTaskConstMeta get kDropTxConfigConstMeta;
|
||||||
|
|
||||||
|
Future<Uint8List?> decodePackets({required List<RaptorPacket> packets, required TxConfig txconf, dynamic hint});
|
||||||
|
|
||||||
|
FlutterRustBridgeTaskConstMeta get kDecodePacketsConstMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Platform {
|
class RaptorPacket {
|
||||||
Unknown,
|
final Uint8List field0;
|
||||||
AndroidBish,
|
|
||||||
Ios,
|
const RaptorPacket({
|
||||||
Windows,
|
required this.field0,
|
||||||
Unix,
|
});
|
||||||
MacIntel,
|
}
|
||||||
MacApple,
|
|
||||||
Wasm,
|
class TxConfig {
|
||||||
|
final int len;
|
||||||
|
final int mtu;
|
||||||
|
final String description;
|
||||||
|
final String? filename;
|
||||||
|
|
||||||
|
const TxConfig({
|
||||||
|
required this.len,
|
||||||
|
required this.mtu,
|
||||||
|
required this.description,
|
||||||
|
this.filename,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,34 +24,53 @@ class NativeImpl implements Native {
|
||||||
/// Only valid on web/WASM platforms.
|
/// Only valid on web/WASM platforms.
|
||||||
factory NativeImpl.wasm(FutureOr<WasmModule> module) => NativeImpl(module as ExternalLibrary);
|
factory NativeImpl.wasm(FutureOr<WasmModule> module) => NativeImpl(module as ExternalLibrary);
|
||||||
NativeImpl.raw(this._platform);
|
NativeImpl.raw(this._platform);
|
||||||
Future<Platform> platform({dynamic hint}) {
|
Future<TxConfig?> getTxConfig({required Uint8List bytes, dynamic hint}) {
|
||||||
|
var arg0 = _platform.api2wire_uint_8_list(bytes);
|
||||||
return _platform.executeNormal(FlutterRustBridgeTask(
|
return _platform.executeNormal(FlutterRustBridgeTask(
|
||||||
callFfi: (port_) => _platform.inner.wire_platform(port_),
|
callFfi: (port_) => _platform.inner.wire_get_tx_config(port_, arg0),
|
||||||
parseSuccessData: _wire2api_platform,
|
parseSuccessData: _wire2api_opt_box_autoadd_tx_config,
|
||||||
constMeta: kPlatformConstMeta,
|
constMeta: kGetTxConfigConstMeta,
|
||||||
argValues: [],
|
argValues: [bytes],
|
||||||
hint: hint,
|
hint: hint,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
FlutterRustBridgeTaskConstMeta get kPlatformConstMeta => const FlutterRustBridgeTaskConstMeta(
|
FlutterRustBridgeTaskConstMeta get kGetTxConfigConstMeta => const FlutterRustBridgeTaskConstMeta(
|
||||||
debugName: "platform",
|
debugName: "get_tx_config",
|
||||||
argNames: [],
|
argNames: ["bytes"],
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<bool> rustReleaseMode({dynamic hint}) {
|
Future<void> dropTxConfig({required TxConfig txc, dynamic hint}) {
|
||||||
|
var arg0 = _platform.api2wire_box_autoadd_tx_config(txc);
|
||||||
return _platform.executeNormal(FlutterRustBridgeTask(
|
return _platform.executeNormal(FlutterRustBridgeTask(
|
||||||
callFfi: (port_) => _platform.inner.wire_rust_release_mode(port_),
|
callFfi: (port_) => _platform.inner.wire_drop_tx_config(port_, arg0),
|
||||||
parseSuccessData: _wire2api_bool,
|
parseSuccessData: _wire2api_unit,
|
||||||
constMeta: kRustReleaseModeConstMeta,
|
constMeta: kDropTxConfigConstMeta,
|
||||||
argValues: [],
|
argValues: [txc],
|
||||||
hint: hint,
|
hint: hint,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
FlutterRustBridgeTaskConstMeta get kRustReleaseModeConstMeta => const FlutterRustBridgeTaskConstMeta(
|
FlutterRustBridgeTaskConstMeta get kDropTxConfigConstMeta => const FlutterRustBridgeTaskConstMeta(
|
||||||
debugName: "rust_release_mode",
|
debugName: "drop_tx_config",
|
||||||
argNames: [],
|
argNames: ["txc"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<Uint8List?> decodePackets({required List<RaptorPacket> packets, required TxConfig txconf, dynamic hint}) {
|
||||||
|
var arg0 = _platform.api2wire_list_raptor_packet(packets);
|
||||||
|
var arg1 = _platform.api2wire_box_autoadd_tx_config(txconf);
|
||||||
|
return _platform.executeNormal(FlutterRustBridgeTask(
|
||||||
|
callFfi: (port_) => _platform.inner.wire_decode_packets(port_, arg0, arg1),
|
||||||
|
parseSuccessData: _wire2api_opt_uint_8_list,
|
||||||
|
constMeta: kDecodePacketsConstMeta,
|
||||||
|
argValues: [packets, txconf],
|
||||||
|
hint: hint,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
FlutterRustBridgeTaskConstMeta get kDecodePacketsConstMeta => const FlutterRustBridgeTaskConstMeta(
|
||||||
|
debugName: "decode_packets",
|
||||||
|
argNames: ["packets", "txconf"],
|
||||||
);
|
);
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
@ -59,21 +78,70 @@ class NativeImpl implements Native {
|
||||||
}
|
}
|
||||||
// Section: wire2api
|
// Section: wire2api
|
||||||
|
|
||||||
bool _wire2api_bool(dynamic raw) {
|
String _wire2api_String(dynamic raw) {
|
||||||
return raw as bool;
|
return raw as String;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _wire2api_i32(dynamic raw) {
|
TxConfig _wire2api_box_autoadd_tx_config(dynamic raw) {
|
||||||
|
return _wire2api_tx_config(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _wire2api_opt_String(dynamic raw) {
|
||||||
|
return raw == null ? null : _wire2api_String(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
TxConfig? _wire2api_opt_box_autoadd_tx_config(dynamic raw) {
|
||||||
|
return raw == null ? null : _wire2api_box_autoadd_tx_config(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List? _wire2api_opt_uint_8_list(dynamic raw) {
|
||||||
|
return raw == null ? null : _wire2api_uint_8_list(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
TxConfig _wire2api_tx_config(dynamic raw) {
|
||||||
|
final arr = raw as List<dynamic>;
|
||||||
|
if (arr.length != 4) throw Exception('unexpected arr length: expect 4 but see ${arr.length}');
|
||||||
|
return TxConfig(
|
||||||
|
len: _wire2api_u64(arr[0]),
|
||||||
|
mtu: _wire2api_u16(arr[1]),
|
||||||
|
description: _wire2api_String(arr[2]),
|
||||||
|
filename: _wire2api_opt_String(arr[3]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _wire2api_u16(dynamic raw) {
|
||||||
return raw as int;
|
return raw as int;
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform _wire2api_platform(dynamic raw) {
|
int _wire2api_u64(dynamic raw) {
|
||||||
return Platform.values[raw as int];
|
return castInt(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _wire2api_u8(dynamic raw) {
|
||||||
|
return raw as int;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List _wire2api_uint_8_list(dynamic raw) {
|
||||||
|
return raw as Uint8List;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _wire2api_unit(dynamic raw) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section: api2wire
|
// Section: api2wire
|
||||||
|
|
||||||
|
@protected
|
||||||
|
int api2wire_u16(int raw) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
int api2wire_u8(int raw) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
// Section: finalizer
|
// Section: finalizer
|
||||||
|
|
||||||
class NativePlatform extends FlutterRustBridgeBase<NativeWire> {
|
class NativePlatform extends FlutterRustBridgeBase<NativeWire> {
|
||||||
|
@ -81,9 +149,61 @@ class NativePlatform extends FlutterRustBridgeBase<NativeWire> {
|
||||||
|
|
||||||
// Section: api2wire
|
// Section: api2wire
|
||||||
|
|
||||||
|
@protected
|
||||||
|
ffi.Pointer<wire_uint_8_list> api2wire_String(String raw) {
|
||||||
|
return api2wire_uint_8_list(utf8.encoder.convert(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
ffi.Pointer<wire_TxConfig> api2wire_box_autoadd_tx_config(TxConfig raw) {
|
||||||
|
final ptr = inner.new_box_autoadd_tx_config_0();
|
||||||
|
_api_fill_to_wire_tx_config(raw, ptr.ref);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
ffi.Pointer<wire_list_raptor_packet> api2wire_list_raptor_packet(List<RaptorPacket> raw) {
|
||||||
|
final ans = inner.new_list_raptor_packet_0(raw.length);
|
||||||
|
for (var i = 0; i < raw.length; ++i) {
|
||||||
|
_api_fill_to_wire_raptor_packet(raw[i], ans.ref.ptr[i]);
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
ffi.Pointer<wire_uint_8_list> api2wire_opt_String(String? raw) {
|
||||||
|
return raw == null ? ffi.nullptr : api2wire_String(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
int api2wire_u64(int raw) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
ffi.Pointer<wire_uint_8_list> api2wire_uint_8_list(Uint8List raw) {
|
||||||
|
final ans = inner.new_uint_8_list_0(raw.length);
|
||||||
|
ans.ref.ptr.asTypedList(raw.length).setAll(0, raw);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
// Section: finalizer
|
// Section: finalizer
|
||||||
|
|
||||||
// Section: api_fill_to_wire
|
// Section: api_fill_to_wire
|
||||||
|
|
||||||
|
void _api_fill_to_wire_box_autoadd_tx_config(TxConfig apiObj, ffi.Pointer<wire_TxConfig> wireObj) {
|
||||||
|
_api_fill_to_wire_tx_config(apiObj, wireObj.ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _api_fill_to_wire_raptor_packet(RaptorPacket apiObj, wire_RaptorPacket wireObj) {
|
||||||
|
wireObj.field0 = api2wire_uint_8_list(apiObj.field0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _api_fill_to_wire_tx_config(TxConfig apiObj, wire_TxConfig wireObj) {
|
||||||
|
wireObj.len = api2wire_u64(apiObj.len);
|
||||||
|
wireObj.mtu = api2wire_u16(apiObj.mtu);
|
||||||
|
wireObj.description = api2wire_String(apiObj.description);
|
||||||
|
wireObj.filename = api2wire_opt_String(apiObj.filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names
|
// ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names
|
||||||
|
@ -164,28 +284,88 @@ class NativeWire implements FlutterRustBridgeWireBase {
|
||||||
_lookup<ffi.NativeFunction<ffi.IntPtr Function(ffi.Pointer<ffi.Void>)>>('init_frb_dart_api_dl');
|
_lookup<ffi.NativeFunction<ffi.IntPtr Function(ffi.Pointer<ffi.Void>)>>('init_frb_dart_api_dl');
|
||||||
late final _init_frb_dart_api_dl = _init_frb_dart_api_dlPtr.asFunction<int Function(ffi.Pointer<ffi.Void>)>();
|
late final _init_frb_dart_api_dl = _init_frb_dart_api_dlPtr.asFunction<int Function(ffi.Pointer<ffi.Void>)>();
|
||||||
|
|
||||||
void wire_platform(
|
void wire_get_tx_config(
|
||||||
int port_,
|
int port_,
|
||||||
|
ffi.Pointer<wire_uint_8_list> bytes,
|
||||||
) {
|
) {
|
||||||
return _wire_platform(
|
return _wire_get_tx_config(
|
||||||
port_,
|
port_,
|
||||||
|
bytes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _wire_platformPtr = _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>('wire_platform');
|
late final _wire_get_tx_configPtr =
|
||||||
late final _wire_platform = _wire_platformPtr.asFunction<void Function(int)>();
|
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64, ffi.Pointer<wire_uint_8_list>)>>('wire_get_tx_config');
|
||||||
|
late final _wire_get_tx_config =
|
||||||
|
_wire_get_tx_configPtr.asFunction<void Function(int, ffi.Pointer<wire_uint_8_list>)>();
|
||||||
|
|
||||||
void wire_rust_release_mode(
|
void wire_drop_tx_config(
|
||||||
int port_,
|
int port_,
|
||||||
|
ffi.Pointer<wire_TxConfig> _txc,
|
||||||
) {
|
) {
|
||||||
return _wire_rust_release_mode(
|
return _wire_drop_tx_config(
|
||||||
port_,
|
port_,
|
||||||
|
_txc,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _wire_rust_release_modePtr =
|
late final _wire_drop_tx_configPtr =
|
||||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>('wire_rust_release_mode');
|
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64, ffi.Pointer<wire_TxConfig>)>>('wire_drop_tx_config');
|
||||||
late final _wire_rust_release_mode = _wire_rust_release_modePtr.asFunction<void Function(int)>();
|
late final _wire_drop_tx_config =
|
||||||
|
_wire_drop_tx_configPtr.asFunction<void Function(int, ffi.Pointer<wire_TxConfig>)>();
|
||||||
|
|
||||||
|
void wire_decode_packets(
|
||||||
|
int port_,
|
||||||
|
ffi.Pointer<wire_list_raptor_packet> packets,
|
||||||
|
ffi.Pointer<wire_TxConfig> txconf,
|
||||||
|
) {
|
||||||
|
return _wire_decode_packets(
|
||||||
|
port_,
|
||||||
|
packets,
|
||||||
|
txconf,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _wire_decode_packetsPtr = _lookup<
|
||||||
|
ffi.NativeFunction<
|
||||||
|
ffi.Void Function(
|
||||||
|
ffi.Int64, ffi.Pointer<wire_list_raptor_packet>, ffi.Pointer<wire_TxConfig>)>>('wire_decode_packets');
|
||||||
|
late final _wire_decode_packets = _wire_decode_packetsPtr
|
||||||
|
.asFunction<void Function(int, ffi.Pointer<wire_list_raptor_packet>, ffi.Pointer<wire_TxConfig>)>();
|
||||||
|
|
||||||
|
ffi.Pointer<wire_TxConfig> new_box_autoadd_tx_config_0() {
|
||||||
|
return _new_box_autoadd_tx_config_0();
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _new_box_autoadd_tx_config_0Ptr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Pointer<wire_TxConfig> Function()>>('new_box_autoadd_tx_config_0');
|
||||||
|
late final _new_box_autoadd_tx_config_0 =
|
||||||
|
_new_box_autoadd_tx_config_0Ptr.asFunction<ffi.Pointer<wire_TxConfig> Function()>();
|
||||||
|
|
||||||
|
ffi.Pointer<wire_list_raptor_packet> new_list_raptor_packet_0(
|
||||||
|
int len,
|
||||||
|
) {
|
||||||
|
return _new_list_raptor_packet_0(
|
||||||
|
len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _new_list_raptor_packet_0Ptr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Pointer<wire_list_raptor_packet> Function(ffi.Int32)>>('new_list_raptor_packet_0');
|
||||||
|
late final _new_list_raptor_packet_0 =
|
||||||
|
_new_list_raptor_packet_0Ptr.asFunction<ffi.Pointer<wire_list_raptor_packet> Function(int)>();
|
||||||
|
|
||||||
|
ffi.Pointer<wire_uint_8_list> new_uint_8_list_0(
|
||||||
|
int len,
|
||||||
|
) {
|
||||||
|
return _new_uint_8_list_0(
|
||||||
|
len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _new_uint_8_list_0Ptr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Pointer<wire_uint_8_list> Function(ffi.Int32)>>('new_uint_8_list_0');
|
||||||
|
late final _new_uint_8_list_0 = _new_uint_8_list_0Ptr.asFunction<ffi.Pointer<wire_uint_8_list> Function(int)>();
|
||||||
|
|
||||||
void free_WireSyncReturn(
|
void free_WireSyncReturn(
|
||||||
WireSyncReturn ptr,
|
WireSyncReturn ptr,
|
||||||
|
@ -202,6 +382,36 @@ class NativeWire implements FlutterRustBridgeWireBase {
|
||||||
|
|
||||||
final class _Dart_Handle extends ffi.Opaque {}
|
final class _Dart_Handle extends ffi.Opaque {}
|
||||||
|
|
||||||
|
final class wire_uint_8_list extends ffi.Struct {
|
||||||
|
external ffi.Pointer<ffi.Uint8> ptr;
|
||||||
|
|
||||||
|
@ffi.Int32()
|
||||||
|
external int len;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class wire_TxConfig extends ffi.Struct {
|
||||||
|
@ffi.Uint64()
|
||||||
|
external int len;
|
||||||
|
|
||||||
|
@ffi.Uint16()
|
||||||
|
external int mtu;
|
||||||
|
|
||||||
|
external ffi.Pointer<wire_uint_8_list> description;
|
||||||
|
|
||||||
|
external ffi.Pointer<wire_uint_8_list> filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class wire_RaptorPacket extends ffi.Struct {
|
||||||
|
external ffi.Pointer<wire_uint_8_list> field0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class wire_list_raptor_packet extends ffi.Struct {
|
||||||
|
external ffi.Pointer<wire_RaptorPacket> ptr;
|
||||||
|
|
||||||
|
@ffi.Int32()
|
||||||
|
external int len;
|
||||||
|
}
|
||||||
|
|
||||||
typedef DartPostCObjectFnType
|
typedef DartPostCObjectFnType
|
||||||
= ffi.Pointer<ffi.NativeFunction<ffi.Bool Function(DartPort port_id, ffi.Pointer<ffi.Void> message)>>;
|
= ffi.Pointer<ffi.NativeFunction<ffi.Bool Function(DartPort port_id, ffi.Pointer<ffi.Void> message)>>;
|
||||||
typedef DartPort = ffi.Int64;
|
typedef DartPort = ffi.Int64;
|
||||||
|
|
|
@ -9,7 +9,6 @@ export 'bridge_definitions.dart';
|
||||||
|
|
||||||
// Re-export the bridge so it is only necessary to import this file.
|
// Re-export the bridge so it is only necessary to import this file.
|
||||||
export 'bridge_generated.dart';
|
export 'bridge_generated.dart';
|
||||||
import 'dart:io' as io;
|
|
||||||
|
|
||||||
const _base = 'native';
|
const _base = 'native';
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,33 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:cuttle/utils/mlkit_utils.dart';
|
|
||||||
import 'package:camerawesome/camerawesome_plugin.dart';
|
import 'package:camerawesome/camerawesome_plugin.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'ffi.dart' if (dart.library.html) 'ffi_web.dart';
|
|
||||||
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
|
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
import 'ffi.dart';
|
||||||
|
import 'utils/mlkit_utils.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CuttleState {
|
||||||
|
unitialized,
|
||||||
|
receiving,
|
||||||
|
received,
|
||||||
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'camerAwesome App',
|
title: 'Cuttle',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
),
|
),
|
||||||
|
@ -35,15 +45,22 @@ class MyHomePage extends StatefulWidget {
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
final _barcodeScanner = BarcodeScanner(formats: [BarcodeFormat.qrCode]);
|
final _barcodeScanner = BarcodeScanner(formats: [BarcodeFormat.qrCode]);
|
||||||
|
// late final AnalysisController _scannerController;
|
||||||
|
final _rxTextController = BehaviorSubject<String>();
|
||||||
|
late final Stream<String> _rxTextStream = _rxTextController.stream;
|
||||||
|
|
||||||
final _buffer = <String>[];
|
TxConfig? _txConfig;
|
||||||
final _barcodesController = BehaviorSubject<List<String>>();
|
var _cuttleState = CuttleState.unitialized;
|
||||||
late final Stream<List<String>> _barcodesStream = _barcodesController.stream;
|
final HashSet<RaptorPacket> _rxData = HashSet();
|
||||||
final _scrollController = ScrollController();
|
String _rxText = '';
|
||||||
|
int _rxCount = 1;
|
||||||
|
int _bSize = 0;
|
||||||
|
|
||||||
|
final _formatter = NumberFormat('###,###,###');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_barcodesController.close();
|
_rxTextController.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,19 +68,19 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: CameraAwesomeBuilder.previewOnly(
|
body: CameraAwesomeBuilder.previewOnly(
|
||||||
|
zoom: 1.5,
|
||||||
onImageForAnalysis: (img) => _processImageBarcode(img),
|
onImageForAnalysis: (img) => _processImageBarcode(img),
|
||||||
imageAnalysisConfig: AnalysisConfig(
|
imageAnalysisConfig: AnalysisConfig(
|
||||||
androidOptions: const AndroidAnalysisOptions.nv21(
|
androidOptions: const AndroidAnalysisOptions.nv21(
|
||||||
width: 1024,
|
width: 600,
|
||||||
),
|
),
|
||||||
maxFramesPerSecond: 5,
|
maxFramesPerSecond: 20,
|
||||||
autoStart: false,
|
autoStart: true,
|
||||||
),
|
),
|
||||||
builder: (cameraModeState, previewSize, previewRect) {
|
builder: (cameraModeState, previewSize, previewRect) {
|
||||||
return _BarcodeDisplayWidget(
|
//_scannerController = cameraModeState.analysisController!;
|
||||||
barcodesStream: _barcodesStream,
|
return _RxTextDisplayWidget(
|
||||||
scrollController: _scrollController,
|
rxTextStream: _rxTextStream,
|
||||||
analysisController: cameraModeState.analysisController!,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -76,96 +93,133 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
try {
|
try {
|
||||||
var recognizedBarCodes = await _barcodeScanner.processImage(inputImage);
|
var recognizedBarCodes = await _barcodeScanner.processImage(inputImage);
|
||||||
for (Barcode barcode in recognizedBarCodes) {
|
for (Barcode barcode in recognizedBarCodes) {
|
||||||
debugPrint("Barcode: [${barcode.format}]: ${barcode.rawBytes}");
|
var bytes = barcode.rawBytes;
|
||||||
_addBarcode("[${barcode.format.name}]: ${barcode.rawValue}");
|
if (bytes == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Uint8List dbytes = bytes;
|
||||||
|
switch (_cuttleState) {
|
||||||
|
case CuttleState.unitialized:
|
||||||
|
{
|
||||||
|
var txconf = await api.getTxConfig(bytes: dbytes);
|
||||||
|
if (txconf != null) {
|
||||||
|
_txConfig = txconf;
|
||||||
|
_cuttleState = CuttleState.receiving;
|
||||||
|
final fname =
|
||||||
|
_txConfig!.filename ?? "large text on the command line";
|
||||||
|
|
||||||
|
final desc = _txConfig!.description;
|
||||||
|
final text =
|
||||||
|
'Receiving $fname, ${_formatter.format(_txConfig!.len)} bytes ($desc)';
|
||||||
|
_rxText = text;
|
||||||
|
_rxTextController.add(text);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// implicit else here; txconf was null
|
||||||
|
var text = barcode.rawValue;
|
||||||
|
if (text != null) {
|
||||||
|
// it's not a txconfig, and it's not a raptor packet, so it must be a regular qr code
|
||||||
|
_rxText = text;
|
||||||
|
_rxTextController.add(text);
|
||||||
|
_cuttleState = CuttleState.received;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case CuttleState.receiving:
|
||||||
|
{
|
||||||
|
if (_bSize > 0 && dbytes.length < _bSize) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (_bSize == 0) {
|
||||||
|
var txconf = await api.getTxConfig(bytes: dbytes);
|
||||||
|
if (txconf != null) {
|
||||||
|
await api.dropTxConfig(txc: txconf);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_bSize = dbytes.length;
|
||||||
|
final packet = RaptorPacket(field0: dbytes);
|
||||||
|
_rxData.add(packet);
|
||||||
|
_rxCount += 1;
|
||||||
|
final bytesTotal = _rxData.length * dbytes.length;
|
||||||
|
if (_rxCount % 40 == 0) {
|
||||||
|
_rxCount = 1;
|
||||||
|
|
||||||
|
// if we've not received at least as many bytes as txconf.len,
|
||||||
|
// we cannot have enough bytes to reconstruct, so only try to
|
||||||
|
// decode if we've gotten at least that many
|
||||||
|
if (bytesTotal > _txConfig!.len) {
|
||||||
|
final content = await api.decodePackets(
|
||||||
|
packets: _rxData.toList(), txconf: _txConfig!);
|
||||||
|
if (content != null) {
|
||||||
|
_rxData.clear();
|
||||||
|
_barcodeScanner.close();
|
||||||
|
_cuttleState = CuttleState.received;
|
||||||
|
_rxTextController.add("DONE RECEIVING $_rxText");
|
||||||
|
final f =
|
||||||
|
await _saveReceivedFile(_txConfig!.filename, content);
|
||||||
|
_rxTextController.add("Saved content to $f");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final pct = (100.0 * bytesTotal / _txConfig!.len).floor();
|
||||||
|
_rxTextController.add(
|
||||||
|
"$_rxText -- $pct% received (${_formatter.format(bytesTotal)} bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
case CuttleState.received:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugPrint("...sending image resulted error $error");
|
debugPrint("sending image resulted error $error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addBarcode(String value) {
|
Future<String> _saveReceivedFile(String? filename, Uint8List bytes) async {
|
||||||
try {
|
final Directory downloadDir = Directory('/storage/emulated/0/Download');
|
||||||
if (_buffer.length > 300) {
|
final String fname =
|
||||||
_buffer.removeRange(_buffer.length - 300, _buffer.length);
|
filename ?? "cuttle_${DateTime.now().millisecondsSinceEpoch}.txt";
|
||||||
}
|
final String path = "${downloadDir.path}/$fname";
|
||||||
if (_buffer.isEmpty || value != _buffer[0]) {
|
final file = await File(path).create();
|
||||||
_buffer.insert(0, value);
|
await file.writeAsBytes(bytes, flush: true);
|
||||||
_barcodesController.add(_buffer);
|
return path;
|
||||||
_scrollController.animateTo(
|
|
||||||
0,
|
|
||||||
duration: const Duration(milliseconds: 400),
|
|
||||||
curve: Curves.fastLinearToSlowEaseIn,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
debugPrint("...logging error $err");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BarcodeDisplayWidget extends StatefulWidget {
|
class _RxTextDisplayWidget extends StatefulWidget {
|
||||||
final Stream<List<String>> barcodesStream;
|
final Stream<String> rxTextStream;
|
||||||
final ScrollController scrollController;
|
|
||||||
|
|
||||||
final AnalysisController analysisController;
|
const _RxTextDisplayWidget({
|
||||||
|
|
||||||
const _BarcodeDisplayWidget({
|
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
super.key,
|
super.key,
|
||||||
required this.barcodesStream,
|
required this.rxTextStream,
|
||||||
required this.scrollController,
|
|
||||||
required this.analysisController,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_BarcodeDisplayWidget> createState() => _BarcodeDisplayWidgetState();
|
State<_RxTextDisplayWidget> createState() => _RxTextDisplayWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BarcodeDisplayWidgetState extends State<_BarcodeDisplayWidget> {
|
class _RxTextDisplayWidgetState extends State<_RxTextDisplayWidget> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Colors.tealAccent.withOpacity(0.7),
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: CheckboxListTile(
|
|
||||||
value: widget.analysisController.enabled,
|
|
||||||
onChanged: (newValue) async {
|
|
||||||
if (widget.analysisController.enabled == true) {
|
|
||||||
await widget.analysisController.stop();
|
|
||||||
} else {
|
|
||||||
await widget.analysisController.start();
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
title: const Text(
|
|
||||||
"Enable barcode scan",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
Container(
|
||||||
height: 120,
|
height: 120,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: StreamBuilder<List<String>>(
|
child: SelectionArea(
|
||||||
stream: widget.barcodesStream,
|
child: StreamBuilder<String>(
|
||||||
builder: (context, value) => !value.hasData
|
stream: widget.rxTextStream,
|
||||||
? const SizedBox.expand()
|
builder: (context, value) =>
|
||||||
: ListView.separated(
|
!value.hasData ? const SizedBox.expand() : Text(value.data!),
|
||||||
padding: const EdgeInsets.only(top: 8),
|
)),
|
||||||
controller: widget.scrollController,
|
|
||||||
itemCount: value.data!.length,
|
|
||||||
separatorBuilder: (context, index) =>
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
itemBuilder: (context, index) => Text(value.data![index]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|
|
@ -6,8 +6,11 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
cuttle = { path = "../../", default-features = false }
|
||||||
flutter_rust_bridge = "1"
|
flutter_rust_bridge = "1"
|
||||||
|
raptorq = "1.7.0"
|
||||||
|
rkyv = { version = "0.7.42", features = ["validation"] }
|
||||||
|
|
|
@ -1,59 +1,48 @@
|
||||||
// This is the entry point of your Rust library.
|
use raptorq::{Decoder, EncodingPacket, ObjectTransmissionInformation};
|
||||||
// When adding new code to your project, note that only items used
|
|
||||||
// here will be transformed to their Dart equivalents.
|
|
||||||
|
|
||||||
// A plain enum without any fields. This is similar to Dart- or C-style enums.
|
#[derive(Debug, Clone)]
|
||||||
// flutter_rust_bridge is capable of generating code for enums with fields
|
pub struct TxConfig {
|
||||||
// (@freezed classes in Dart and tagged unions in C).
|
pub len: u64,
|
||||||
pub enum Platform {
|
pub mtu: u16,
|
||||||
Unknown,
|
pub description: String,
|
||||||
AndroidBish,
|
pub filename: Option<String>,
|
||||||
Ios,
|
|
||||||
Windows,
|
|
||||||
Unix,
|
|
||||||
MacIntel,
|
|
||||||
MacApple,
|
|
||||||
Wasm,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A function definition in Rust. Similar to Dart, the return type must always be named
|
#[derive(Debug, Clone)]
|
||||||
// and is never inferred.
|
pub struct RaptorPacket(pub Vec<u8>);
|
||||||
pub fn platform() -> Platform {
|
|
||||||
// This is a macro, a special expression that expands into code. In Rust, all macros
|
impl From<cuttle::TxConfig> for TxConfig {
|
||||||
// end with an exclamation mark and can be invoked with all kinds of brackets (parentheses,
|
fn from(value: cuttle::TxConfig) -> Self {
|
||||||
// brackets and curly braces). However, certain conventions exist, for example the
|
TxConfig {
|
||||||
// vector macro is almost always invoked as vec![..].
|
len: value.len,
|
||||||
//
|
mtu: value.mtu,
|
||||||
// The cfg!() macro returns a boolean value based on the current compiler configuration.
|
description: value.description,
|
||||||
// When attached to expressions (#[cfg(..)] form), they show or hide the expression at compile time.
|
filename: value.filename,
|
||||||
// Here, however, they evaluate to runtime values, which may or may not be optimized out
|
}
|
||||||
// by the compiler. A variety of configurations are demonstrated here which cover most of
|
}
|
||||||
// the modern oeprating systems. Try running the Flutter application on different machines
|
}
|
||||||
// and see if it matches your expected OS.
|
|
||||||
//
|
pub fn get_tx_config(bytes: Vec<u8>) -> Option<TxConfig> {
|
||||||
// Furthermore, in Rust, the last expression in a function is the return value and does
|
if let Ok(archive) = rkyv::check_archived_root::<cuttle::TxConfig>(&bytes) {
|
||||||
// not have the trailing semicolon. This entire if-else chain forms a single expression.
|
<cuttle::ArchivedTxConfig as rkyv::Deserialize<cuttle::TxConfig, rkyv::Infallible>>::deserialize(archive, &mut rkyv::Infallible)
|
||||||
if cfg!(windows) {
|
.ok().map(Into::into)
|
||||||
Platform::Windows
|
|
||||||
} else if cfg!(target_os = "android") {
|
|
||||||
Platform::AndroidBish
|
|
||||||
} else if cfg!(target_os = "ios") {
|
|
||||||
Platform::Ios
|
|
||||||
} else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
|
|
||||||
Platform::MacApple
|
|
||||||
} else if cfg!(target_os = "macos") {
|
|
||||||
Platform::MacIntel
|
|
||||||
} else if cfg!(target_family = "wasm") {
|
|
||||||
Platform::Wasm
|
|
||||||
} else if cfg!(unix) {
|
|
||||||
Platform::Unix
|
|
||||||
} else {
|
} else {
|
||||||
Platform::Unknown
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The convention for Rust identifiers is the snake_case,
|
pub fn drop_tx_config(_txc: TxConfig) {}
|
||||||
// and they are automatically converted to camelCase on the Dart side.
|
|
||||||
pub fn rust_release_mode() -> bool {
|
pub fn decode_packets(packets: Vec<RaptorPacket>, txconf: TxConfig) -> Option<Vec<u8>> {
|
||||||
cfg!(not(debug_assertions))
|
let conf = ObjectTransmissionInformation::with_defaults(txconf.len, txconf.mtu);
|
||||||
|
let mut decoder = Decoder::new(conf);
|
||||||
|
|
||||||
|
let mut res = None;
|
||||||
|
for RaptorPacket(p) in &packets {
|
||||||
|
res = decoder.decode(EncodingPacket::deserialize(p));
|
||||||
|
if res.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,23 +2,130 @@ use super::*;
|
||||||
// Section: wire functions
|
// Section: wire functions
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn wire_platform(port_: i64) {
|
pub extern "C" fn wire_get_tx_config(port_: i64, bytes: *mut wire_uint_8_list) {
|
||||||
wire_platform_impl(port_)
|
wire_get_tx_config_impl(port_, bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn wire_rust_release_mode(port_: i64) {
|
pub extern "C" fn wire_drop_tx_config(port_: i64, _txc: *mut wire_TxConfig) {
|
||||||
wire_rust_release_mode_impl(port_)
|
wire_drop_tx_config_impl(port_, _txc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn wire_decode_packets(
|
||||||
|
port_: i64,
|
||||||
|
packets: *mut wire_list_raptor_packet,
|
||||||
|
txconf: *mut wire_TxConfig,
|
||||||
|
) {
|
||||||
|
wire_decode_packets_impl(port_, packets, txconf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section: allocate functions
|
// Section: allocate functions
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn new_box_autoadd_tx_config_0() -> *mut wire_TxConfig {
|
||||||
|
support::new_leak_box_ptr(wire_TxConfig::new_with_null_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn new_list_raptor_packet_0(len: i32) -> *mut wire_list_raptor_packet {
|
||||||
|
let wrap = wire_list_raptor_packet {
|
||||||
|
ptr: support::new_leak_vec_ptr(<wire_RaptorPacket>::new_with_null_ptr(), len),
|
||||||
|
len,
|
||||||
|
};
|
||||||
|
support::new_leak_box_ptr(wrap)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list {
|
||||||
|
let ans = wire_uint_8_list {
|
||||||
|
ptr: support::new_leak_vec_ptr(Default::default(), len),
|
||||||
|
len,
|
||||||
|
};
|
||||||
|
support::new_leak_box_ptr(ans)
|
||||||
|
}
|
||||||
|
|
||||||
// Section: related functions
|
// Section: related functions
|
||||||
|
|
||||||
// Section: impl Wire2Api
|
// Section: impl Wire2Api
|
||||||
|
|
||||||
|
impl Wire2Api<String> for *mut wire_uint_8_list {
|
||||||
|
fn wire2api(self) -> String {
|
||||||
|
let vec: Vec<u8> = self.wire2api();
|
||||||
|
String::from_utf8_lossy(&vec).into_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Wire2Api<TxConfig> for *mut wire_TxConfig {
|
||||||
|
fn wire2api(self) -> TxConfig {
|
||||||
|
let wrap = unsafe { support::box_from_leak_ptr(self) };
|
||||||
|
Wire2Api::<TxConfig>::wire2api(*wrap).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Wire2Api<Vec<RaptorPacket>> for *mut wire_list_raptor_packet {
|
||||||
|
fn wire2api(self) -> Vec<RaptorPacket> {
|
||||||
|
let vec = unsafe {
|
||||||
|
let wrap = support::box_from_leak_ptr(self);
|
||||||
|
support::vec_from_leak_ptr(wrap.ptr, wrap.len)
|
||||||
|
};
|
||||||
|
vec.into_iter().map(Wire2Api::wire2api).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wire2Api<RaptorPacket> for wire_RaptorPacket {
|
||||||
|
fn wire2api(self) -> RaptorPacket {
|
||||||
|
RaptorPacket(self.field0.wire2api())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Wire2Api<TxConfig> for wire_TxConfig {
|
||||||
|
fn wire2api(self) -> TxConfig {
|
||||||
|
TxConfig {
|
||||||
|
len: self.len.wire2api(),
|
||||||
|
mtu: self.mtu.wire2api(),
|
||||||
|
description: self.description.wire2api(),
|
||||||
|
filename: self.filename.wire2api(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wire2Api<Vec<u8>> for *mut wire_uint_8_list {
|
||||||
|
fn wire2api(self) -> Vec<u8> {
|
||||||
|
unsafe {
|
||||||
|
let wrap = support::box_from_leak_ptr(self);
|
||||||
|
support::vec_from_leak_ptr(wrap.ptr, wrap.len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Section: wire structs
|
// Section: wire structs
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct wire_list_raptor_packet {
|
||||||
|
ptr: *mut wire_RaptorPacket,
|
||||||
|
len: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct wire_RaptorPacket {
|
||||||
|
field0: *mut wire_uint_8_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct wire_TxConfig {
|
||||||
|
len: u64,
|
||||||
|
mtu: u16,
|
||||||
|
description: *mut wire_uint_8_list,
|
||||||
|
filename: *mut wire_uint_8_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct wire_uint_8_list {
|
||||||
|
ptr: *mut u8,
|
||||||
|
len: i32,
|
||||||
|
}
|
||||||
|
|
||||||
// Section: impl NewWithNullPtr
|
// Section: impl NewWithNullPtr
|
||||||
|
|
||||||
pub trait NewWithNullPtr {
|
pub trait NewWithNullPtr {
|
||||||
|
@ -31,6 +138,37 @@ impl<T> NewWithNullPtr for *mut T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NewWithNullPtr for wire_RaptorPacket {
|
||||||
|
fn new_with_null_ptr() -> Self {
|
||||||
|
Self {
|
||||||
|
field0: core::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for wire_RaptorPacket {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_with_null_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewWithNullPtr for wire_TxConfig {
|
||||||
|
fn new_with_null_ptr() -> Self {
|
||||||
|
Self {
|
||||||
|
len: Default::default(),
|
||||||
|
mtu: Default::default(),
|
||||||
|
description: core::ptr::null_mut(),
|
||||||
|
filename: core::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for wire_TxConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_with_null_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Section: sync execution mode utility
|
// Section: sync execution mode utility
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|
|
@ -22,24 +22,48 @@ use crate::api::*;
|
||||||
|
|
||||||
// Section: wire functions
|
// Section: wire functions
|
||||||
|
|
||||||
fn wire_platform_impl(port_: MessagePort) {
|
fn wire_get_tx_config_impl(port_: MessagePort, bytes: impl Wire2Api<Vec<u8>> + UnwindSafe) {
|
||||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, Platform>(
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, Option<TxConfig>>(
|
||||||
WrapInfo {
|
WrapInfo {
|
||||||
debug_name: "platform",
|
debug_name: "get_tx_config",
|
||||||
port: Some(port_),
|
port: Some(port_),
|
||||||
mode: FfiCallMode::Normal,
|
mode: FfiCallMode::Normal,
|
||||||
},
|
},
|
||||||
move || move |task_callback| Ok(platform()),
|
move || {
|
||||||
|
let api_bytes = bytes.wire2api();
|
||||||
|
move |task_callback| Ok(get_tx_config(api_bytes))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn wire_rust_release_mode_impl(port_: MessagePort) {
|
fn wire_drop_tx_config_impl(port_: MessagePort, _txc: impl Wire2Api<TxConfig> + UnwindSafe) {
|
||||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, bool>(
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(
|
||||||
WrapInfo {
|
WrapInfo {
|
||||||
debug_name: "rust_release_mode",
|
debug_name: "drop_tx_config",
|
||||||
port: Some(port_),
|
port: Some(port_),
|
||||||
mode: FfiCallMode::Normal,
|
mode: FfiCallMode::Normal,
|
||||||
},
|
},
|
||||||
move || move |task_callback| Ok(rust_release_mode()),
|
move || {
|
||||||
|
let api__txc = _txc.wire2api();
|
||||||
|
move |task_callback| Ok(drop_tx_config(api__txc))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn wire_decode_packets_impl(
|
||||||
|
port_: MessagePort,
|
||||||
|
packets: impl Wire2Api<Vec<RaptorPacket>> + UnwindSafe,
|
||||||
|
txconf: impl Wire2Api<TxConfig> + UnwindSafe,
|
||||||
|
) {
|
||||||
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, Option<Vec<u8>>>(
|
||||||
|
WrapInfo {
|
||||||
|
debug_name: "decode_packets",
|
||||||
|
port: Some(port_),
|
||||||
|
mode: FfiCallMode::Normal,
|
||||||
|
},
|
||||||
|
move || {
|
||||||
|
let api_packets = packets.wire2api();
|
||||||
|
let api_txconf = txconf.wire2api();
|
||||||
|
move |task_callback| Ok(decode_packets(api_packets, api_txconf))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Section: wrapper structs
|
// Section: wrapper structs
|
||||||
|
@ -64,25 +88,38 @@ where
|
||||||
(!self.is_null()).then(|| self.wire2api())
|
(!self.is_null()).then(|| self.wire2api())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Wire2Api<u16> for u16 {
|
||||||
|
fn wire2api(self) -> u16 {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Wire2Api<u64> for u64 {
|
||||||
|
fn wire2api(self) -> u64 {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Wire2Api<u8> for u8 {
|
||||||
|
fn wire2api(self) -> u8 {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Section: impl IntoDart
|
// Section: impl IntoDart
|
||||||
|
|
||||||
impl support::IntoDart for Platform {
|
impl support::IntoDart for TxConfig {
|
||||||
fn into_dart(self) -> support::DartAbi {
|
fn into_dart(self) -> support::DartAbi {
|
||||||
match self {
|
vec![
|
||||||
Self::Unknown => 0,
|
self.len.into_into_dart().into_dart(),
|
||||||
Self::AndroidBish => 1,
|
self.mtu.into_into_dart().into_dart(),
|
||||||
Self::Ios => 2,
|
self.description.into_into_dart().into_dart(),
|
||||||
Self::Windows => 3,
|
self.filename.into_dart(),
|
||||||
Self::Unix => 4,
|
]
|
||||||
Self::MacIntel => 5,
|
|
||||||
Self::MacApple => 6,
|
|
||||||
Self::Wasm => 7,
|
|
||||||
}
|
|
||||||
.into_dart()
|
.into_dart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl support::IntoDartExceptPrimitive for Platform {}
|
impl support::IntoDartExceptPrimitive for TxConfig {}
|
||||||
impl rust2dart::IntoIntoDart<Platform> for Platform {
|
impl rust2dart::IntoIntoDart<TxConfig> for TxConfig {
|
||||||
fn into_into_dart(self) -> Self {
|
fn into_into_dart(self) -> Self {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,19 +33,19 @@ dependencies:
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
camerawesome: ^1.4.0
|
||||||
ffi: ^2.0.1
|
ffi: ^2.0.1
|
||||||
flutter_rust_bridge: ^1.45.0
|
flutter_rust_bridge: ^1.45.0
|
||||||
meta: ^1.8.0
|
|
||||||
uuid: ^3.0.7
|
|
||||||
path_provider: ^2.1.0
|
|
||||||
path: ^1.8.3
|
|
||||||
mobile_scanner: ^3.4.1
|
|
||||||
camerawesome: ^1.4.0
|
|
||||||
google_mlkit_barcode_scanning: ^0.5.0
|
google_mlkit_barcode_scanning: ^0.5.0
|
||||||
rxdart: ^0.27.7
|
|
||||||
image: ^4.0.17
|
|
||||||
google_mlkit_commons: ^0.2.0
|
google_mlkit_commons: ^0.2.0
|
||||||
|
image: ^4.0.17
|
||||||
|
meta: ^1.8.0
|
||||||
|
mobile_scanner: ^3.4.1
|
||||||
|
path: ^1.8.3
|
||||||
|
path_provider: ^2.1.0
|
||||||
|
rxdart: ^0.27.7
|
||||||
|
uuid: ^3.0.7
|
||||||
|
intl: ^0.18.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
BIN
mobile_finished.png
Normal file
After Width: | Height: | Size: 816 KiB |
BIN
mobile_receiving.png
Normal file
After Width: | Height: | Size: 708 KiB |
46
src/lib.rs
|
@ -3,6 +3,8 @@ use std::{
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
|
use raptorq::{Encoder, ObjectTransmissionInformation};
|
||||||
use rkyv::{Archive, Deserialize, Serialize};
|
use rkyv::{Archive, Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
|
@ -11,10 +13,11 @@ mod desktop;
|
||||||
mod qr_utils;
|
mod qr_utils;
|
||||||
|
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
pub use qr_utils::{get_content, mk_qr_bytes, stream_bytes};
|
pub use qr_utils::{get_content, mk_qr_bytes};
|
||||||
|
|
||||||
pub type CuttleSender = std::sync::mpsc::SyncSender<Vec<u8>>;
|
pub type CuttleSender = std::sync::mpsc::SyncSender<Vec<u8>>;
|
||||||
pub type CuttleReceiver = std::sync::mpsc::Receiver<Vec<u8>>;
|
pub type CuttleReceiver = std::sync::mpsc::Receiver<Vec<u8>>;
|
||||||
|
pub const STREAMING_MTU: u16 = 1200;
|
||||||
|
|
||||||
/// The application state
|
/// The application state
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -70,9 +73,48 @@ pub enum StreamStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
|
||||||
#[repr(C)]
|
#[archive(check_bytes)]
|
||||||
pub struct TxConfig {
|
pub struct TxConfig {
|
||||||
pub len: u64,
|
pub len: u64,
|
||||||
pub mtu: u16,
|
pub mtu: u16,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
pub filename: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stream_bytes(
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
tx: CuttleSender,
|
||||||
|
desc: &str,
|
||||||
|
filename: Option<&str>,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
let len = bytes.len() as u64;
|
||||||
|
|
||||||
|
let rng = &mut rand::thread_rng();
|
||||||
|
|
||||||
|
let config = ObjectTransmissionInformation::with_defaults(len, STREAMING_MTU);
|
||||||
|
let encoder = Encoder::new(&bytes, config);
|
||||||
|
|
||||||
|
let mut packets = encoder
|
||||||
|
.get_encoded_packets(10)
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.serialize())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
packets.shuffle(rng);
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
for packet in packets.iter().cycle() {
|
||||||
|
tx.send(packet.clone()).unwrap_or_default();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let txconfig = TxConfig {
|
||||||
|
len,
|
||||||
|
mtu: STREAMING_MTU,
|
||||||
|
description: desc.to_string(),
|
||||||
|
filename: filename.map(|f| f.to_string()),
|
||||||
|
};
|
||||||
|
rkyv::to_bytes::<_, 256>(&txconfig)
|
||||||
|
.expect("tried to serialize the txconfig")
|
||||||
|
.to_vec()
|
||||||
}
|
}
|
||||||
|
|
11
src/main.rs
|
@ -30,16 +30,17 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
let description = if let Some(ref file) = cli.file {
|
let (description, filename) = if let Some(ref file) = cli.file {
|
||||||
let text = cli.text().join(" ");
|
let text = cli.text().join(" ");
|
||||||
let sep = if text.is_empty() { "" } else { ": " };
|
let sep = if text.is_empty() { "" } else { ": " };
|
||||||
let file = std::path::Path::new(&file)
|
let file = std::path::Path::new(&file)
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string_lossy();
|
.to_string_lossy()
|
||||||
format!("{file}{sep}{text}")
|
.to_string();
|
||||||
|
(format!("{file}{sep}{text}"), Some(file))
|
||||||
} else {
|
} else {
|
||||||
"text message".to_string()
|
("text message".to_string(), None)
|
||||||
};
|
};
|
||||||
|
|
||||||
let bytes = if let Some(ref file) = cli.file {
|
let bytes = if let Some(ref file) = cli.file {
|
||||||
|
@ -48,7 +49,7 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
cli.text().join(" ").bytes().collect()
|
cli.text().join(" ").bytes().collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = get_content(bytes, &description);
|
let content = get_content(bytes, &description, filename.as_deref());
|
||||||
|
|
||||||
let flasher = Flasher::new(description, content, cli.fps);
|
let flasher = Flasher::new(description, content, cli.fps);
|
||||||
|
|
||||||
|
|
|
@ -1,46 +1,11 @@
|
||||||
use fast_qr::convert::image::ImageBuilder;
|
use fast_qr::convert::image::ImageBuilder;
|
||||||
use rand::seq::SliceRandom;
|
|
||||||
use raptorq::{Encoder, ObjectTransmissionInformation};
|
|
||||||
|
|
||||||
use crate::{Content, CuttleSender, StreamedContent, TxConfig};
|
use crate::{stream_bytes, Content, StreamedContent};
|
||||||
|
|
||||||
pub const STREAMING_MTU: u16 = 2326;
|
|
||||||
|
|
||||||
pub fn stream_bytes(bytes: Vec<u8>, tx: CuttleSender, desc: String) -> Vec<u8> {
|
|
||||||
let len = bytes.len() as u64;
|
|
||||||
let txconfig = TxConfig {
|
|
||||||
len,
|
|
||||||
mtu: STREAMING_MTU,
|
|
||||||
description: desc.to_string(),
|
|
||||||
};
|
|
||||||
let txconfig = rkyv::to_bytes::<_, 256>(&txconfig)
|
|
||||||
.expect("tried to serialize the txconfig")
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
let rng = &mut rand::thread_rng();
|
|
||||||
|
|
||||||
let config = ObjectTransmissionInformation::with_defaults(len, STREAMING_MTU);
|
|
||||||
let encoder = Encoder::new(&bytes, config);
|
|
||||||
let mut packets = encoder
|
|
||||||
.get_encoded_packets(10)
|
|
||||||
.iter()
|
|
||||||
.map(|p| p.serialize())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
packets.shuffle(rng);
|
|
||||||
|
|
||||||
for packet in packets.iter().cycle() {
|
|
||||||
tx.send(packet.clone()).unwrap_or_default();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
txconfig
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Makes a PNG of a QR code for the given bytes, returns the bytes of the PNG.
|
/// Makes a PNG of a QR code for the given bytes, returns the bytes of the PNG.
|
||||||
pub fn mk_qr_bytes(bytes: &[u8], height: f32) -> Vec<u8> {
|
pub fn mk_qr_bytes(bytes: &[u8], height: f32) -> Vec<u8> {
|
||||||
let qr = fast_qr::QRBuilder::new(bytes)
|
let qr = fast_qr::QRBuilder::new(bytes)
|
||||||
.ecl(fast_qr::ECL::M)
|
.ecl(fast_qr::ECL::L)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -53,13 +18,13 @@ pub fn mk_qr_bytes(bytes: &[u8], height: f32) -> Vec<u8> {
|
||||||
|
|
||||||
/// Turns bytes and a description into either a single QR code, or a stream of
|
/// Turns bytes and a description into either a single QR code, or a stream of
|
||||||
/// them, depending on the size of the input.
|
/// them, depending on the size of the input.
|
||||||
pub fn get_content(bytes: Vec<u8>, desc: &str) -> Content {
|
pub fn get_content(bytes: Vec<u8>, desc: &str, filename: Option<&str>) -> Content {
|
||||||
if bytes.len() < 2000 && fast_qr::QRBuilder::new(bytes.clone()).build().is_ok() {
|
if bytes.len() < 2000 && fast_qr::QRBuilder::new(bytes.clone()).build().is_ok() {
|
||||||
let bytes = bytes.leak();
|
let bytes = bytes.leak();
|
||||||
Content::Static(bytes)
|
Content::Static(bytes)
|
||||||
} else {
|
} else {
|
||||||
let (tx, rx) = std::sync::mpsc::sync_channel(2);
|
let (tx, rx) = std::sync::mpsc::sync_channel(2);
|
||||||
let txconfig = stream_bytes(bytes, tx, desc.to_string()).leak();
|
let txconfig = stream_bytes(bytes, tx, desc, filename).leak();
|
||||||
let stream = StreamedContent::new(txconfig, rx);
|
let stream = StreamedContent::new(txconfig, rx);
|
||||||
Content::Streamed(stream)
|
Content::Streamed(stream)
|
||||||
}
|
}
|
||||||
|
|