Files
blekin/crates/ericrfb/examples/record.rs
rob thijssen c8f981f045
All checks were successful
CI / fmt (push) Successful in 36s
CI / check (push) Successful in 1m1s
CI / clippy (push) Successful in 1m4s
feat: phase 5 — Tight decoder with zlib streams and sub-palettes
codec/tight.rs:
- Full Tight (encoding 7) decoder per ByteColorRFBRenderer.a() line 324
- Control byte: bottom 4 bits = zlib stream reset flags,
  top 4 bits = subencoding (0-15)
- Subencoding 8: solid fill (1-byte color)
- Subencoding 15: palette-indexed fill (selector + index)
- Subencodings 4-7: filtered data with optional 2-color palette
- Subencodings 10-13: reduced bit-depth packed (1/2/4 bpp)
- Subencodings 0-3: raw 8bpp data
- Data >= 12 bytes uses zlib compression with varint length
- 4 persistent zlib streams with reset-on-flag logic
- All 4 hardcoded sub-palettes ported as RGB332 indices:
  PALETTE_BW (2), PALETTE_GRAY4 (4), PALETTE_GRAY16 (16),
  PALETTE_COLOR16 (16 EGA-like colors)
- Bit-depth unpackers: 1bpp, 2bpp, 4bpp (MSB-first)
- 5 unit tests

Updated examples to request [7, 5, 1, 0, -250] (Tight preferred).
Tested against real OmniView: correct rendering with Tight encoding.
32 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 14:44:06 +03:00

109 lines
3.6 KiB
Rust

use std::env;
use std::fs::{self, File};
use std::io::BufWriter;
use std::path::Path;
use std::time::{Duration, Instant};
use ericrfb::handshake::Config;
use ericrfb::session::{ActiveSession, Event};
fn save_png(fb: &ericrfb::framebuffer::Framebuffer, path: &Path) {
let rgba = fb.to_rgba();
let file = File::create(path).expect("cannot create PNG file");
let bw = BufWriter::new(file);
let mut encoder = png::Encoder::new(bw, fb.width as u32, fb.height as u32);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().expect("png header failed");
writer.write_image_data(&rgba).expect("png write failed");
}
fn main() {
let args: Vec<String> = env::args().collect();
let host = args
.iter()
.position(|a| a == "--host")
.and_then(|i| args.get(i + 1))
.expect("usage: --host <ip> --applet-id <token> [--port <port>] [--duration <secs>]");
let applet_id = args
.iter()
.position(|a| a == "--applet-id")
.and_then(|i| args.get(i + 1))
.expect("usage: --host <ip> --applet-id <token> [--port <port>] [--duration <secs>]");
let port: u16 = args
.iter()
.position(|a| a == "--port")
.and_then(|i| args.get(i + 1))
.and_then(|s| s.parse().ok())
.unwrap_or(443);
let duration_secs: u64 = args
.iter()
.position(|a| a == "--duration")
.and_then(|i| args.get(i + 1))
.and_then(|s| s.parse().ok())
.unwrap_or(30);
let out_dir = Path::new("out");
fs::create_dir_all(out_dir).expect("cannot create out/");
let cfg = Config::new(host, port, applet_id);
println!("Connecting to {}:{}...", cfg.host, cfg.port);
// Request Tight (7), Hextile (5), CopyRect (1), Raw (0), cursor pseudo (-250)
let mut session = ActiveSession::connect(&cfg, &[7, 5, 1, 0, -250]).expect("connect failed");
println!(
"Connected: {}x{}, recording for {duration_secs}s...",
session.framebuffer.width, session.framebuffer.height
);
let start = Instant::now();
let duration = Duration::from_secs(duration_secs);
let mut last_save = Instant::now() - Duration::from_secs(2); // force first save
let mut frame_count = 0u32;
loop {
if start.elapsed() >= duration {
break;
}
match session.process_one() {
Ok(Some(Event::FramebufferDirty)) => {
// Save at most 1 PNG per second
if last_save.elapsed() >= Duration::from_secs(1) {
let path = out_dir.join(format!("frame_{frame_count:04}.png"));
save_png(&session.framebuffer, &path);
println!(
"[{:.1}s] saved {}",
start.elapsed().as_secs_f64(),
path.display()
);
frame_count += 1;
last_save = Instant::now();
}
// Request next update
if let Err(e) = session.request_update() {
eprintln!("Error requesting update: {e}");
break;
}
}
Ok(Some(Event::Resize { width, height })) => {
println!("Resized to {width}x{height}");
}
Ok(Some(Event::Debug(s))) => {
eprintln!("[debug] {s}");
}
Ok(Some(_)) | Ok(None) => {}
Err(e) => {
eprintln!("Error: {e}");
break;
}
}
}
println!("Done. Saved {frame_count} frames to {}/", out_dir.display());
}