Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 16 additions & 21 deletions reStream.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ version="1.5.0"

rm2_firmware_version_3_7="3.7.0.1930"
rm2_firmware_version_3_24="3.24"
rm2_firmware_version_3_27="3.27.1.0"

# default values for arguments
remarkable="${REMARKABLE_IP:-10.11.99.1}" # remarkable IP address
landscape=true # rotate 90 degrees to the right
portrait=false # rotate 90 degrees to the right
cursor=false # show a cursor where the pen is hovering
output_path=- # display output through ffplay
format=- # automatic output format
Expand All @@ -34,7 +33,7 @@ while [ $# -gt 0 ]; do
exit
;;
-p | --portrait)
landscape=false
portrait=true
shift
;;
-c | --cursor)
Expand Down Expand Up @@ -140,7 +139,7 @@ is_current_rm_firmware_version_ge() {
exit_rm() {
ssh_cmd 'kill $(pidof restream)'
}
trap exit_rm EXIT INT HUP
trap exit_rm EXIT INT HUP TERM

# SSH_CONNECTION is a variable on reMarkable => ssh '' instead of ssh ""
# shellcheck disable=SC2016
Expand All @@ -157,6 +156,7 @@ if ! ssh_cmd true; then
exit 1
fi

pen_orientation_opt=""
rm_version="$(ssh_cmd cat /sys/devices/soc0/machine)"

case "$rm_version" in
Expand All @@ -182,8 +182,8 @@ case "$rm_version" in
fb_file=":mem:"

# Use updated video settings?
if is_current_rm_firmware_version_ge $rm2_firmware_version_3_27; then
echo "Using the 3.27+ :mem: video settings."
if is_current_rm_firmware_version_ge $rm2_firmware_version_3_24; then
echo "Using the newer :mem: video settings."
bytes_per_pixel=4
pixel_format="bgra"
video_filters="$video_filters,transpose=1"
Expand All @@ -192,18 +192,8 @@ case "$rm_version" in
height=$width
width=$tmp

skip_offset=4705256
elif is_current_rm_firmware_version_ge $rm2_firmware_version_3_24; then
echo "Using the 3.24+ :mem: video settings."
bytes_per_pixel=4
pixel_format="bgra"
video_filters="$video_filters,transpose=2"

tmp=$height
height=$width
width=$tmp

skip_offset=2629636

# Use the previous video settings.
elif is_current_rm_firmware_version_ge $rm2_firmware_version_3_7; then
echo "Using the older :mem: video settings."
Expand Down Expand Up @@ -270,8 +260,8 @@ fi
# store extra ffmpeg arguments in $@
set --

# rotate 90 degrees if landscape=true
$landscape && video_filters="$video_filters,transpose=1"
# rotate 90 degrees if portrait=true
$portrait && video_filters="$video_filters,transpose=2"

# Scale and add padding if we are targeting a webcam because a lot of services
# expect a size of exactly 1280x720 (tested in Firefox, MS Teams, and Skype for
Expand Down Expand Up @@ -314,11 +304,16 @@ set -e # stop if an error occurs
restream_options="-h $height -w $width -b $bytes_per_pixel -f $fb_file -s $skip_offset"

if "$cursor"; then
restream_options="$restream_options -c"
if [ -n "$pen_orientation_opt" ] && ssh_cmd "PATH=\"\$PATH:/opt/bin/:.\" restream --help 2>&1 | grep -q pen-orientation"; then
restream_options="$restream_options -c $pen_orientation_opt"
else
[ -n "$pen_orientation_opt" ] && echo "[reStream] Warning: restream binary on device does not support --pen-orientation; cursor will be misaligned on fw 3.27+. Reinstall restream.arm.static to fix." >&2
restream_options="$restream_options -c"
fi
fi

# shellcheck disable=SC2089
restream_rs="PATH=\"\$PATH:/opt/bin/:/home/root/.vellum/bin:.\" restream $restream_options"
restream_rs="PATH=\"\$PATH:/opt/bin/:.\" restream $restream_options"
if $unsecure_connection; then
listen_port=16789
ssh_cmd "$restream_rs --listen $listen_port" &
Expand Down
Binary file modified restream.arm.static
Binary file not shown.
77 changes: 58 additions & 19 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ pub struct Opts {
/// Memory offset
#[arg(long, name = "skip", short = 's')]
skip: usize,

/// Orientation of the framebuffer content, used to draw the pen cursor at the right
/// position. Inferred from width/height when omitted (landscape if width > height).
#[arg(long, value_enum)]
pen_orientation: Option<PenOrientation>,
}

#[derive(Clone, Copy, Debug, PartialEq, clap::ValueEnum)]
pub enum PenOrientation {
/// Landscape buffer (rM2 firmware before 3.24 with :mem:)
Landscape,
/// Upright portrait buffer (rM1, rm2fb, rM2 firmware 3.24+)
Portrait,
/// Portrait buffer rotated by 180 degrees (rM2 firmware 3.27+)
#[value(name = "portrait180")]
Portrait180,
}

fn main() -> Result<()> {
Expand All @@ -62,13 +78,20 @@ fn main() -> Result<()> {
(opts.file.to_owned(), 0)
};

let pen_orientation = opts.pen_orientation.unwrap_or(if opts.width > opts.height {
PenOrientation::Landscape
} else {
PenOrientation::Portrait
});

let streamer = ReStreamer::init(
&file,
offset,
opts.width,
opts.height,
opts.bytes_per_pixel,
opts.show_cursor,
pen_orientation,
)?;

let stdout = std::io::stdout();
Expand Down Expand Up @@ -148,6 +171,7 @@ pub struct ReStreamer {
bytes_per_pixel: usize,

show_cursor: bool,
pen_orientation: PenOrientation,
input_rx: Receiver<InputEvent>,
pen_pos: Option<(usize, usize)>,
drawing: bool,
Expand All @@ -161,6 +185,7 @@ impl ReStreamer {
height: usize,
bytes_per_pixel: usize,
show_cursor: bool,
pen_orientation: PenOrientation,
) -> Result<ReStreamer> {
let start = offset as u64;
let size = width * height * bytes_per_pixel;
Expand All @@ -181,6 +206,7 @@ impl ReStreamer {
height,
bytes_per_pixel,
show_cursor,
pen_orientation,
input_rx,
pen_pos: None,
drawing: false,
Expand Down Expand Up @@ -234,37 +260,48 @@ impl ReStreamer {

/// Draw pen position into fb data, if necessary (in hover range and not drawing).
fn draw_pen_position(&mut self, buf: &mut [u8]) {
if let (false, Some((y, x))) = (self.drawing, self.pen_pos) {
let flip = self.width > self.height;
let (x, y) = if flip { (y, x) } else { (x, y) };
if let (false, Some((ev_x, ev_y))) = (self.drawing, self.pen_pos) {
// The framebuffer is row-major with a stride of `width` pixels,
// both for the old landscape layout (1872x1404) and the 3.24+
// portrait layout (1404x1872).
// we need negative numbers to calculate offsets correctly
let width = if flip { self.height } else { self.width } as isize;
let height = if flip { self.width } else { self.height } as isize;
let width = self.width as isize;
let height = self.height as isize;
let bpp = self.bytes_per_pixel as isize;
let cursor = self.cursor as isize;
for (i, (yoff, no)) in PEN_IMAGE.iter().enumerate() {
// we draw vertically (lines along y)
let xoff = i as isize - (PEN_IMAGE.len() as isize / 2);
let xstart = x as isize + xoff;
// line outside of canvas?
if xstart < 0 || xstart >= width {

// Pen events are in display coordinates (1404x1872 portrait);
// map them to a (row, column) position in the framebuffer.
let (pen_row, pen_col) = match self.pen_orientation {
PenOrientation::Landscape => (ev_x as isize, width - ev_y as isize),
PenOrientation::Portrait => (ev_y as isize, ev_x as isize),
PenOrientation::Portrait180 => {
(height - ev_y as isize, width - ev_x as isize)
}
};

for (i, (coff, no)) in PEN_IMAGE.iter().enumerate() {
// each entry of PEN_IMAGE is one row of the dot
let row = pen_row + i as isize - (PEN_IMAGE.len() as isize / 2);
// row outside of canvas?
if row < 0 || row >= height {
continue;
}
let mut ystart = (height - y as isize) + yoff;
let mut col = pen_col + coff;
let mut no = *no;
// cut-off at sides
if ystart < 0 {
no += ystart;
ystart = 0;
if col < 0 {
no += col;
col = 0;
}
if ystart + no > height {
no = height - ystart;
if col + no > width {
no = width - col;
}
if no <= 0 {
continue;
}
// translate to buf indexes, check bounds and draw
let mut px_start = (xstart * height + ystart) * bpp;
let mut px_start = (row * width + col) * bpp;
let mut px_end = px_start + no * bpp;
// outside current buf?
if px_end < cursor || px_start >= cursor + buf.len() as isize {
Expand All @@ -277,7 +314,9 @@ impl ReStreamer {
if px_end > cursor + buf.len() as isize {
px_end = cursor + buf.len() as isize;
}
// invert pixel (on RM2)
// invert pixels: byte-wise NOT inverts every supported pixel
// format (gray8, gray16, rgb565, bgra); players ignore the
// inverted alpha byte on bgra rawvideo
// TODO: Do something sensible on RM1
for b in buf[(px_start - cursor) as usize..(px_end - cursor) as usize].iter_mut() {
*b = 255 - *b;
Expand Down
Loading