buncha stuff
This commit is contained in:
parent
cd059a6552
commit
2b96b28db6
3 changed files with 85 additions and 48 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -3,15 +3,17 @@ name = "cuttle"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[features]
|
||||||
|
default = ["desktop"]
|
||||||
|
desktop = ["dep:eframe", "dep:egui_extras"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.3.19", features = ["derive", "env"] }
|
clap = { version = "4.3.19", features = ["derive", "env"] }
|
||||||
eframe = { version = "0.22", default-features = false, 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, features = ["chrono", "image"] }
|
egui_extras = { version = "0.22", default-features = false, optional = true, 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
|
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"] }
|
rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] }
|
||||||
raptorq = "1.7"
|
raptorq = "1.7"
|
||||||
rkyv = { version = "0.7.42", features = ["validation"] }
|
rkyv = { version = "0.7.42", features = ["validation"] }
|
||||||
|
|
100
src/lib.rs
100
src/lib.rs
|
@ -1,79 +1,105 @@
|
||||||
use std::{fmt::Debug, time::Duration};
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
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 rand::seq::SliceRandom;
|
||||||
use raptorq::{Encoder, ObjectTransmissionInformation};
|
use raptorq::{Encoder, ObjectTransmissionInformation};
|
||||||
use rkyv::{Archive, Deserialize, Serialize};
|
use rkyv::{Archive, Deserialize, Serialize};
|
||||||
|
|
||||||
pub const STREAMING_MTU: u16 = 776;
|
pub const STREAMING_MTU: u16 = 2326;
|
||||||
|
|
||||||
pub type Receiver = std::sync::mpsc::Receiver<Vec<u8>>;
|
pub type Receiver = std::sync::mpsc::Receiver<(usize, Vec<u8>)>;
|
||||||
pub type Sender = std::sync::mpsc::SyncSender<Vec<u8>>;
|
pub type Sender = std::sync::mpsc::SyncSender<(usize, Vec<u8>)>;
|
||||||
|
|
||||||
|
/// The application state
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Flasher {
|
||||||
|
pub heading: String,
|
||||||
|
pub content: Content,
|
||||||
|
pub sleep: Duration,
|
||||||
|
pub last: Instant,
|
||||||
|
pub idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Content {
|
||||||
|
Static(&'static [u8]),
|
||||||
|
Streaming(Receiver),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
|
||||||
|
#[repr(C)]
|
||||||
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 fn mk_img(bytes: &[u8]) -> RetainedImage {
|
pub fn stream_bytes(bytes: Vec<u8>, tx: Sender, desc: String) {
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stream_bytes(bytes: Vec<u8>, tx: Sender, rate: u64) {
|
|
||||||
let sleep = (1000.0 / rate as f64) as u64;
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let rng = &mut rand::thread_rng();
|
let rng = &mut rand::thread_rng();
|
||||||
let config =
|
|
||||||
ObjectTransmissionInformation::with_defaults(bytes.len() as u64, STREAMING_MTU);
|
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");
|
||||||
|
|
||||||
|
let config = ObjectTransmissionInformation::with_defaults(len, STREAMING_MTU);
|
||||||
let encoder = Encoder::new(&bytes, config);
|
let encoder = Encoder::new(&bytes, config);
|
||||||
let mut packets = encoder
|
let mut packets = encoder
|
||||||
.get_encoded_packets(10)
|
.get_encoded_packets(10)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| p.serialize())
|
.map(|p| p.serialize())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let txcfgs = vec![txconfig.to_vec(); packets.len() / 10];
|
||||||
|
packets.extend(txcfgs);
|
||||||
|
|
||||||
packets.shuffle(rng);
|
packets.shuffle(rng);
|
||||||
|
|
||||||
for (_counter, packet) in packets.iter().cycle().enumerate() {
|
for (counter, packet) in packets.iter().cycle().enumerate() {
|
||||||
tx.send(packet.clone()).unwrap();
|
tx.send((counter, packet.clone())).unwrap();
|
||||||
std::thread::sleep(Duration::from_millis(sleep));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Flasher {
|
pub fn mk_img(bytes: &[u8]) -> Vec<u8> {
|
||||||
pub heading: String,
|
let qr = fast_qr::QRBuilder::new(bytes)
|
||||||
pub content: Content,
|
.ecl(fast_qr::ECL::M)
|
||||||
pub rate: u64,
|
.build()
|
||||||
}
|
.unwrap();
|
||||||
|
|
||||||
pub enum Content {
|
ImageBuilder::default()
|
||||||
Static(&'static [u8]),
|
.fit_width(1100)
|
||||||
Streaming(Receiver),
|
.to_pixmap(&qr)
|
||||||
|
.encode_png()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
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(self.sleep);
|
||||||
ctx.request_repaint_after(Duration::from_millis(sleep as u64));
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading(&self.heading);
|
ui.heading(&self.heading);
|
||||||
let img = match self.content {
|
let img = match self.content {
|
||||||
Content::Static(bytes) => mk_img(bytes),
|
Content::Static(bytes) => {
|
||||||
|
RetainedImage::from_image_bytes("generated qr code", &mk_img(bytes)).unwrap()
|
||||||
|
}
|
||||||
Content::Streaming(ref rx) => {
|
Content::Streaming(ref rx) => {
|
||||||
if let Ok(bytes) = rx.try_recv() {
|
if let Ok((_counter, ref bytes)) = rx.try_recv() {
|
||||||
mk_img(&bytes)
|
RetainedImage::from_image_bytes("generated qr code", &mk_img(bytes))
|
||||||
|
.unwrap()
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -1,4 +1,7 @@
|
||||||
use std::ffi::OsString;
|
use std::{
|
||||||
|
ffi::OsString,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cuttle::{stream_bytes, Content, Flasher};
|
use cuttle::{stream_bytes, Content, Flasher};
|
||||||
|
@ -10,8 +13,9 @@ 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(long, short, help = "Frames per second", default_value_t = 10)]
|
#[clap(long, help = "Frames per second", default_value_t = 10)]
|
||||||
pub rate: u64,
|
pub fps: u64,
|
||||||
|
|
||||||
#[clap(
|
#[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"
|
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"
|
||||||
)]
|
)]
|
||||||
|
@ -41,12 +45,17 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
"text message".to_string()
|
"text message".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let content: Content = get_content(&cli);
|
let content: Content = get_content(&cli, &description);
|
||||||
|
|
||||||
|
let sleep = 1000.0 / cli.fps as f64;
|
||||||
|
let sleep = Duration::from_millis(sleep as u64);
|
||||||
|
let last = Instant::now();
|
||||||
let flasher = Flasher {
|
let flasher = Flasher {
|
||||||
heading: description,
|
heading: description,
|
||||||
content,
|
content,
|
||||||
rate: cli.rate,
|
sleep,
|
||||||
|
last,
|
||||||
|
idx: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
|
@ -63,7 +72,7 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_content(cli: &Cli) -> Content {
|
fn get_content(cli: &Cli, desc: &str) -> Content {
|
||||||
let bytes = if let Some(ref file) = cli.file {
|
let bytes = if let Some(ref file) = cli.file {
|
||||||
std::fs::read(file).unwrap_or_else(|e| panic!("tried to open {file:?}, got {e:?}"))
|
std::fs::read(file).unwrap_or_else(|e| panic!("tried to open {file:?}, got {e:?}"))
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,7 +84,7 @@ fn get_content(cli: &Cli) -> Content {
|
||||||
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);
|
||||||
stream_bytes(bytes, tx, cli.rate);
|
stream_bytes(bytes, tx, desc.to_string());
|
||||||
Content::Streaming(rx)
|
Content::Streaming(rx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue