restream/src/main.rs

175 lines
5.1 KiB
Rust
Raw Normal View History

2020-12-12 22:45:47 +01:00
#[macro_use]
extern crate anyhow;
extern crate lz_fear;
use anyhow::{Context, Result};
2024-01-09 16:46:28 +01:00
use clap::Parser;
2020-12-12 22:45:47 +01:00
use lz_fear::CompressionSettings;
use std::default::Default;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
2024-01-09 16:46:28 +01:00
use std::net::{TcpListener, TcpStream};
2020-12-12 22:45:47 +01:00
use std::process::Command;
use std::sync::mpsc::channel;
2024-01-09 16:46:28 +01:00
use std::thread;
use std::time::Duration;
2020-12-12 22:45:47 +01:00
2024-01-09 16:46:28 +01:00
#[derive(Parser, Debug)]
#[command(author, version)]
pub struct Opts {
2024-01-09 16:46:28 +01:00
/// Listen for an (unsecure) TCP connection to send the data to which reduces some load on the reMarkable and improves fps.
#[arg(long, name = "port", short = 'l')]
listen: Option<usize>,
2024-01-09 16:46:28 +01:00
/// Height (in pixels) of the framebuffer.
#[arg(long, name = "height", short = 'h')]
height: usize,
2024-01-09 16:46:28 +01:00
/// Width (in pixels) of the framebuffer.
#[arg(long, name = "width", short = 'w')]
width: usize,
2024-01-09 16:46:28 +01:00
/// How many bytes represent one pixel in the framebuffer.
#[arg(long, name = "bytes", short = 'b')]
bytes_per_pixel: usize,
2024-01-09 16:46:28 +01:00
/// File containing the framebuffer data. If this equals the string ':mem:' it will try to read the framebuffer from xochitl's process memory (rM2 only).
#[arg(long, name = "path", short = 'f')]
file: String,
}
2020-12-12 22:45:47 +01:00
fn main() -> Result<()> {
let ref opts: Opts = Opts::parse();
let (file, offset) = if opts.file == ":mem:" {
let pid = xochitl_pid()?;
let offset = rm2_fb_offset(pid)?;
let mem = format!("/proc/{}/mem", pid);
(mem, offset)
2020-12-12 22:45:47 +01:00
} else {
(opts.file.to_owned(), 0)
2020-12-12 22:45:47 +01:00
};
let streamer = ReStreamer::init(&file, offset, opts.width, opts.height, opts.bytes_per_pixel)?;
let stdout = std::io::stdout();
let data_target: Box<dyn Write> = if let Some(port) = opts.listen {
Box::new(listen_timeout(port, Duration::from_secs(3))?)
} else {
Box::new(stdout.lock())
};
2020-12-12 22:45:47 +01:00
let lz4: CompressionSettings = Default::default();
lz4.compress(streamer, data_target)
2020-12-12 22:45:47 +01:00
.context("Error while compressing framebuffer stream")
}
fn listen_timeout(port: usize, timeout: Duration) -> Result<TcpStream> {
let listen_addr = format!("0.0.0.0:{}", port);
let listen = TcpListener::bind(&listen_addr)?;
eprintln!("[rM] listening for a TCP connection on {}", listen_addr);
let (tx, rx) = channel();
thread::spawn(move || {
tx.send(listen.accept()).unwrap();
});
2024-01-09 16:46:28 +01:00
let (conn, conn_addr) = rx
.recv_timeout(timeout)
.context("Timeout while waiting for host to connect to reMarkable")??;
eprintln!("[rM] connection received from {}", conn_addr);
conn.set_write_timeout(Some(timeout))?;
Ok(conn)
}
2020-12-12 22:45:47 +01:00
fn xochitl_pid() -> Result<usize> {
let output = Command::new("/bin/pidof")
.args(&["xochitl"])
.output()
.context("Failed to run `/bin/pidof xochitl`")?;
if output.status.success() {
let pid = &output.stdout;
let pid_str = std::str::from_utf8(pid)?.trim();
pid_str
2020-12-12 22:45:47 +01:00
.parse()
.with_context(|| format!("Failed to parse xochitl's pid: {}", pid_str))
2020-12-12 22:45:47 +01:00
} else {
Err(anyhow!(
"Could not find pid of xochitl, is xochitl running?"
))
}
}
fn rm2_fb_offset(pid: usize) -> Result<usize> {
2020-12-12 22:45:47 +01:00
let file = File::open(format!("/proc/{}/maps", &pid))?;
let line = BufReader::new(file)
.lines()
.skip_while(|line| matches!(line, Ok(l) if !l.ends_with("/dev/fb0")))
.skip(1)
2020-12-12 22:45:47 +01:00
.next()
.with_context(|| format!("No line containing /dev/fb0 in /proc/{}/maps file", pid))?
.with_context(|| format!("Error reading file /proc/{}/maps", pid))?;
let addr = line
.split("-")
.next()
.with_context(|| format!("Error parsing line in /proc/{}/maps", pid))?;
let address = usize::from_str_radix(addr, 16).context("Error parsing framebuffer address")?;
Ok(address + 8)
2020-12-12 22:45:47 +01:00
}
pub struct ReStreamer {
file: File,
start: u64,
cursor: usize,
size: usize,
}
impl ReStreamer {
pub fn init(
path: &str,
offset: usize,
width: usize,
height: usize,
bytes_per_pixel: usize,
) -> Result<ReStreamer> {
let start = offset as u64;
let size = width * height * bytes_per_pixel;
let cursor = 0;
let file = File::open(path)?;
let mut streamer = ReStreamer {
file,
start: start,
cursor,
size,
};
streamer.next_frame()?;
Ok(streamer)
}
pub fn next_frame(&mut self) -> std::io::Result<()> {
self.file.seek(SeekFrom::Start(self.start))?;
self.cursor = 0;
Ok(())
}
}
impl Read for ReStreamer {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let requested = buf.len();
let bytes_read = if self.cursor + requested < self.size {
self.file.read(buf)?
} else {
let rest = self.size - self.cursor;
self.file.read(&mut buf[0..rest])?
};
self.cursor += bytes_read;
if self.cursor == self.size {
self.next_frame()?;
}
Ok(bytes_read)
}
}