Displays either a static qr code or a stream of raptor-encoded ones.

Kinda needs a client now to really test.
This commit is contained in:
Joe Ardent 2023-08-03 13:40:10 -07:00
parent 4b3869466b
commit 7ecb07eec3
4 changed files with 136 additions and 57 deletions

29
Cargo.lock generated
View file

@ -901,6 +901,21 @@ dependencies = [
"typenum",
]
[[package]]
name = "cuttle"
version = "0.1.0"
dependencies = [
"clap",
"eframe",
"egui_extras",
"env_logger",
"fast_qr",
"png",
"rand",
"raptorq",
"rkyv",
]
[[package]]
name = "d3d12"
version = "0.6.0"
@ -1443,20 +1458,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "godiva"
version = "0.1.0"
dependencies = [
"clap",
"eframe",
"egui_extras",
"env_logger",
"fast_qr",
"png",
"raptorq",
"rkyv",
]
[[package]]
name = "gpu-alloc"
version = "0.5.4"

View file

@ -1,5 +1,5 @@
[package]
name = "godiva"
name = "cuttle"
version = "0.1.0"
edition = "2021"
@ -11,7 +11,7 @@ eframe = { version = "0.22", default-features = false, features = ["default_font
egui_extras = { version = "0.22", default-features = false, features = ["chrono", "image"] }
env_logger = "*"
fast_qr = { version = "0.9", default-features = false, features = ["image"] }
png = "0.17.6" # pinning this to resolve conflict between eframe and fast_qr with the image crate
rand = { version = "0.8", default-features = false, features = ["std"] }
raptorq = "1.7"
# pinning this to resolve conflict between eframe and fast_qr with the image crate
png = "0.17.6"
rkyv = { version = "0.7.42", features = ["validation"] }

View file

@ -1,11 +1,11 @@
# Godiva, a flasher with a message
# Cuttle, a flasher with a message
Godiva is a desktop application for transfering data from one computer to another by displaying a [possibly
endless] stream of [QR encoded](https://en.wikipedia.org/wiki/QR_code) [Raptor
codes](https://en.wikipedia.org/wiki/Raptor_code), and then on the receiving side, a camera to read
the QR codes and turn them back into the original content.
Cuttle is a desktop application for transfering data from one computer to another by displaying a
[possibly endless] stream of [QR encoded](https://en.wikipedia.org/wiki/QR_code) [Raptor
codes](https://en.wikipedia.org/wiki/Raptor_code), so that a camera-equipped different computer can
read the QR codes and turn them back into the original content.
## what?
## Tell me more!
Raptor codes are a type of [fountain code](https://en.wikipedia.org/wiki/Fountain_code); fountain
codes are a way to encode data as a stream of error-correcting pieces of that data; as long as you
@ -16,21 +16,24 @@ and not care if anyone is receiving, and receivers don't have to worry about mis
needed; they're guaranteed to get everything they need as long as they listen long enough. I think
that's pretty cool!
So the idea here is to give it a file (or short string of text) you want transferred to a different
machine (probably a mobile phone running a little app I'll be writing), and it will start producing
raptor codes for that file. Each piece of raptor code then gets encoded as a QR code, and then
So the idea here is to give it a file (or short string of text) that you want transferred to a
different computer (like a mobile phone running a little companion app), and it will start producing
Raptor codes for that file. Each piece of Raptor code then gets encoded as a QR code, and then
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!
# status
# Current status
I literally just started this today, and currently it will produce a single QR code for a given
input string, and display that. Honestly, this is already genuinely useful to me; sometimes I want
to look at a url on my phone, and the camera app will let me look at a QR code and interpret text as
a link. But I hope to have a quick and dirty mobile app soon that will let me test streaming codes.
The desktop transmitting application is nearly done; already it can convert text input into QR codes
(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
already useful for transmitting short text strings to the phone, since the Android camera app will
decode any detected QR codes.
The mobile app has been started, and I hope to have it decoding QR streams soon!
# about the name
[Lady Godiva](https://en.wikipedia.org/wiki/Lady_Godiva), in legend, rode naked through her town in
order to send a message to her husband about the plight of the downtrodden under his
landlordship.
[Cuttlefish](https://en.wikipedia.org/wiki/Cuttlefish) are small but very intelligent cephalopods
whose bodies are covered in a 15hz multi-color, multi-polarity, pixel-mapped display that they use
to visually communicate with other cuttlefish, as well as to dazzle predators and prey.

View file

@ -1,16 +1,25 @@
use std::ffi::OsString;
use std::{ffi::OsString, time::Duration};
use clap::Parser;
use eframe::egui;
use egui_extras::RetainedImage;
use fast_qr::convert::image::ImageBuilder;
use rand::seq::SliceRandom;
use raptorq::{Encoder, ObjectTransmissionInformation};
pub const MTU: u16 = 776;
#[derive(Parser, Debug)]
#[clap(author, version, trailing_var_arg = true)]
struct Cli {
#[clap(long, short, help = "File to expose")]
pub file: Option<OsString>,
#[clap(help = "all remaining arguments treated as a string; ignored if `-f` is given")]
#[clap(long, short, help = "Frames per second", default_value_t = 10)]
pub rate: u64,
#[clap(
help = "all remaining arguments treated as a string; this string is the whole message if `-f` is not given, otherwise it's an optional description of the file"
)]
text: Vec<String>,
}
@ -25,46 +34,112 @@ fn main() -> Result<(), eframe::Error> {
let cli = Cli::parse();
let content = if let Some(file) = cli.file {
file.to_string_lossy().bytes().collect()
let flasher = if let Some(ref file) = cli.file {
let (tx, rx) = std::sync::mpsc::sync_channel(1);
let content = Content::Streaming(rx);
let heading = if cli.text.is_empty() {
file.to_string_lossy().to_string()
} else {
cli.text().join(" ").bytes().collect()
cli.text().join(" ")
};
//println!("content: {content}");
stream_file(file, tx, cli.rate);
Flasher {
heading,
content,
rate: cli.rate,
}
} else {
let heading = "text message".to_string();
let bytes: Vec<u8> = cli.text().join(" ").bytes().collect();
let bytes = bytes.leak();
let content = Content::Static(bytes);
Flasher {
heading,
content,
rate: cli.rate,
}
};
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(1200.0, 1200.0)),
active: true,
vsync: true,
..Default::default()
};
eframe::run_native(
"Show an image with eframe/egui",
"Cuttle: optical insanity",
options,
Box::new(|_cc| Box::new(Flasher { content })),
Box::new(move |_cc| Box::new(flasher)),
)
}
struct Flasher {
pub content: Vec<u8>,
pub heading: String,
pub content: Content,
pub rate: u64,
}
enum Content {
Static(&'static [u8]),
Streaming(std::sync::mpsc::Receiver<Vec<u8>>),
}
impl eframe::App for Flasher {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let sleep = 1000.0 / self.rate as f64;
ctx.request_repaint_after(Duration::from_millis(sleep as u64));
egui::CentralPanel::default().show(ctx, |ui| {
let heading_text = String::from_utf8(self.content.clone()).unwrap();
ui.heading(heading_text);
let qr = fast_qr::QRBuilder::new(self.content.clone())
.build()
.unwrap();
ui.heading(&self.heading);
let img = match self.content {
Content::Static(bytes) => mk_img(bytes),
Content::Streaming(ref rx) => {
if let Ok(bytes) = rx.try_recv() {
mk_img(&bytes)
} else {
return;
}
}
};
img.show(ui);
});
}
}
fn mk_img(bytes: &[u8]) -> RetainedImage {
let qr = fast_qr::QRBuilder::new(bytes).build().unwrap();
let bytes = ImageBuilder::default()
.fit_width(1100)
.to_pixmap(&qr)
.encode_png()
.unwrap();
let img = RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap();
img.show(ui);
});
}
RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap()
}
fn stream_file(file: &OsString, tx: std::sync::mpsc::SyncSender<Vec<u8>>, rate: u64) {
let bytes = std::fs::read(file).unwrap_or_else(|e| panic!("tried to open {file:?}, got {e:?}"));
let sleep = (1000.0 / rate as f64) as u64;
std::thread::spawn(move || {
let rng = &mut rand::thread_rng();
let config = ObjectTransmissionInformation::with_defaults(bytes.len() as u64, 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);
dbg!(&packets.len());
for (_counter, packet) in packets.iter().cycle().enumerate() {
tx.send(packet.clone()).unwrap();
std::thread::sleep(Duration::from_millis(sleep));
}
});
}