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, #[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, } impl Cli { pub fn text(&self) -> &Vec { &self.text } } fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). let cli = Cli::parse(); 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(" ") }; stream_file(file, tx, cli.rate); Flasher { heading, content, rate: cli.rate, } } else { let heading = "text message".to_string(); let bytes: Vec = 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( "Cuttle: optical insanity", options, Box::new(move |_cc| Box::new(flasher)), ) } struct Flasher { pub heading: String, pub content: Content, pub rate: u64, } enum Content { Static(&'static [u8]), Streaming(std::sync::mpsc::Receiver>), } 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| { 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(); RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap() } fn stream_file(file: &OsString, tx: std::sync::mpsc::SyncSender>, 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::>(); 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)); } }); }