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:
parent
4b3869466b
commit
7ecb07eec3
4 changed files with 136 additions and 57 deletions
29
Cargo.lock
generated
29
Cargo.lock
generated
|
@ -901,6 +901,21 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cuttle"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"eframe",
|
||||||
|
"egui_extras",
|
||||||
|
"env_logger",
|
||||||
|
"fast_qr",
|
||||||
|
"png",
|
||||||
|
"rand",
|
||||||
|
"raptorq",
|
||||||
|
"rkyv",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "d3d12"
|
name = "d3d12"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -1443,20 +1458,6 @@ dependencies = [
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "godiva"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"clap",
|
|
||||||
"eframe",
|
|
||||||
"egui_extras",
|
|
||||||
"env_logger",
|
|
||||||
"fast_qr",
|
|
||||||
"png",
|
|
||||||
"raptorq",
|
|
||||||
"rkyv",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpu-alloc"
|
name = "gpu-alloc"
|
||||||
version = "0.5.4"
|
version = "0.5.4"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "godiva"
|
name = "cuttle"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
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"] }
|
egui_extras = { version = "0.22", default-features = false, features = ["chrono", "image"] }
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
fast_qr = { version = "0.9", default-features = false, features = ["image"] }
|
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"
|
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"] }
|
rkyv = { version = "0.7.42", features = ["validation"] }
|
||||||
|
|
37
README.md
37
README.md
|
@ -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
|
Cuttle is a desktop application for transfering data from one computer to another by displaying a
|
||||||
endless] stream of [QR encoded](https://en.wikipedia.org/wiki/QR_code) [Raptor
|
[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
|
codes](https://en.wikipedia.org/wiki/Raptor_code), so that a camera-equipped different computer can
|
||||||
the QR codes and turn them back into the original content.
|
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
|
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
|
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
|
needed; they're guaranteed to get everything they need as long as they listen long enough. I think
|
||||||
that's pretty cool!
|
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
|
So the idea here is to give it a file (or short string of text) that you want transferred to a
|
||||||
machine (probably a mobile phone running a little app I'll be writing), and it will start producing
|
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
|
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
|
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!
|
||||||
|
|
||||||
# status
|
# Current status
|
||||||
|
|
||||||
I literally just started this today, and currently it will produce a single QR code for a given
|
The desktop transmitting application is nearly done; already it can convert text input into QR codes
|
||||||
input string, and display that. Honestly, this is already genuinely useful to me; sometimes I want
|
(either a single static one if the data is small enough, otherwise it will stream a never ending
|
||||||
to look at a url on my phone, and the camera app will let me look at a QR code and interpret text as
|
loop of them), and the mobile app has been started. Even without the mobile app, it's still
|
||||||
a link. But I hope to have a quick and dirty mobile app soon that will let me test streaming codes.
|
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
|
# about the name
|
||||||
|
|
||||||
[Lady Godiva](https://en.wikipedia.org/wiki/Lady_Godiva), in legend, rode naked through her town in
|
[Cuttlefish](https://en.wikipedia.org/wiki/Cuttlefish) are small but very intelligent cephalopods
|
||||||
order to send a message to her husband about the plight of the downtrodden under his
|
whose bodies are covered in a 15hz multi-color, multi-polarity, pixel-mapped display that they use
|
||||||
landlordship.
|
to visually communicate with other cuttlefish, as well as to dazzle predators and prey.
|
||||||
|
|
109
src/main.rs
109
src/main.rs
|
@ -1,16 +1,25 @@
|
||||||
use std::ffi::OsString;
|
use std::{ffi::OsString, time::Duration};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui_extras::RetainedImage;
|
use egui_extras::RetainedImage;
|
||||||
use fast_qr::convert::image::ImageBuilder;
|
use fast_qr::convert::image::ImageBuilder;
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
|
use raptorq::{Encoder, ObjectTransmissionInformation};
|
||||||
|
|
||||||
|
pub const MTU: u16 = 776;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(author, version, trailing_var_arg = true)]
|
#[clap(author, version, trailing_var_arg = true)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
#[clap(long, short, help = "File to expose")]
|
#[clap(long, short, help = "File to expose")]
|
||||||
pub file: Option<OsString>,
|
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>,
|
text: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,46 +34,112 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
let content = if let Some(file) = cli.file {
|
let flasher = if let Some(ref file) = cli.file {
|
||||||
file.to_string_lossy().bytes().collect()
|
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 {
|
} 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 {
|
let options = eframe::NativeOptions {
|
||||||
initial_window_size: Some(egui::vec2(1200.0, 1200.0)),
|
initial_window_size: Some(egui::vec2(1200.0, 1200.0)),
|
||||||
|
active: true,
|
||||||
|
vsync: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"Show an image with eframe/egui",
|
"Cuttle: optical insanity",
|
||||||
options,
|
options,
|
||||||
Box::new(|_cc| Box::new(Flasher { content })),
|
Box::new(move |_cc| Box::new(flasher)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct 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 {
|
impl eframe::App for Flasher {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
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| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
let heading_text = String::from_utf8(self.content.clone()).unwrap();
|
ui.heading(&self.heading);
|
||||||
ui.heading(heading_text);
|
let img = match self.content {
|
||||||
let qr = fast_qr::QRBuilder::new(self.content.clone())
|
Content::Static(bytes) => mk_img(bytes),
|
||||||
.build()
|
Content::Streaming(ref rx) => {
|
||||||
.unwrap();
|
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()
|
let bytes = ImageBuilder::default()
|
||||||
.fit_width(1100)
|
.fit_width(1100)
|
||||||
.to_pixmap(&qr)
|
.to_pixmap(&qr)
|
||||||
.encode_png()
|
.encode_png()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let img = RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap();
|
RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap()
|
||||||
img.show(ui);
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue