2020-12-12 22:45:47 +01:00
|
|
|
#[macro_use]
|
|
|
|
extern crate anyhow;
|
|
|
|
extern crate lz_fear;
|
|
|
|
|
|
|
|
use anyhow::{Context, Result};
|
|
|
|
use lz_fear::CompressionSettings;
|
|
|
|
|
|
|
|
use std::default::Default;
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom};
|
|
|
|
use std::process::Command;
|
|
|
|
|
|
|
|
fn main() -> Result<()> {
|
|
|
|
let version = remarkable_version()?;
|
|
|
|
let streamer = if version == "reMarkable 1.0\n" {
|
|
|
|
let width = 1408;
|
|
|
|
let height = 1872;
|
|
|
|
let bytes_per_pixel = 2;
|
|
|
|
ReStreamer::init("/dev/fb0", 0, width, height, bytes_per_pixel)?
|
|
|
|
} else if version == "reMarkable 2.0\n" {
|
|
|
|
let width = 1404;
|
|
|
|
let height = 1872;
|
|
|
|
let bytes_per_pixel = 1;
|
|
|
|
|
|
|
|
let pid = xochitl_pid()?;
|
2020-12-13 22:10:22 +01:00
|
|
|
let offset = rm2_fb_offset(pid)?;
|
2020-12-12 22:45:47 +01:00
|
|
|
let mem = format!("/proc/{}/mem", pid);
|
|
|
|
ReStreamer::init(&mem, offset, width, height, bytes_per_pixel)?
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(
|
|
|
|
"Unknown reMarkable version: {}\nPlease open a feature request to support your device.",
|
|
|
|
version
|
|
|
|
))?
|
|
|
|
};
|
|
|
|
|
|
|
|
let lz4: CompressionSettings = Default::default();
|
|
|
|
lz4.compress(streamer, std::io::stdout().lock())
|
|
|
|
.context("Error while compressing framebuffer stream")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn remarkable_version() -> Result<String> {
|
|
|
|
let content = std::fs::read("/sys/devices/soc0/machine")
|
|
|
|
.context("Failed to read /sys/devices/soc0/machine")?;
|
|
|
|
Ok(String::from_utf8(content)?)
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-12-13 22:10:22 +01:00
|
|
|
let pid_str = std::str::from_utf8(pid)?.trim();
|
|
|
|
pid_str
|
2020-12-12 22:45:47 +01:00
|
|
|
.parse()
|
2020-12-13 22:10:22 +01:00
|
|
|
.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?"
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-13 22:10:22 +01:00
|
|
|
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()
|
2020-12-13 22:10:22 +01:00
|
|
|
.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))?;
|
|
|
|
|
2020-12-13 22:10:22 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|