diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index 2b2e52a..0000000 --- a/.cargo/config +++ /dev/null @@ -1,14 +0,0 @@ -[target.armv7-unknown-linux-gnueabihf] -linker = "/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi-gcc" -rustflags = [ - "-C", "link-arg=-march=armv7-a", - "-C", "link-arg=-marm", - "-C", "link-arg=-mfpu=neon", - "-C", "link-arg=-mfloat-abi=hard", - "-C", "link-arg=-mcpu=cortex-a9", - "-C", "link-arg=--sysroot=/usr/local/oecore-x86_64/sysroots/cortexa9hf-neon-oe-linux-gnueabi", -] - -[build] -# Set the default --target flag -target = "armv7-unknown-linux-gnueabihf" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a468cde..a4bf63c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -33,3 +33,19 @@ jobs: - name: Shell Formatter run: docker run -v $GITHUB_WORKSPACE:/mnt -w /mnt mvdan/shfmt -d reStream.sh + + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - uses: cachix/install-nix-action@v14.1 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - run: nix-shell + - run: cargo build diff --git a/Cargo.lock b/Cargo.lock index db26ce9..70d8462 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "aabb-quadtree" version = "0.1.0" @@ -989,7 +991,7 @@ dependencies = [ [[package]] name = "restream" -version = "1.1.0" +version = "1.2.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 7a33386..f5ad417 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "restream" -version = "1.1.0" +version = "1.2.0" authors = ["Rien Maertens "] edition = "2018" diff --git a/README.md b/README.md index bd7a0b4..9874719 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ On your **host** machine 1. Install `lz4` on your host with your usual package manager. On Ubuntu, `apt install liblz4-tool` will do the trick. 2. [Set up an SSH key and add it to the ssh-agent](https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent), then add your key to the reMarkable with `ssh-copy-id root@10.11.99.1`. -> **Note:** the reMarkable 2 doesn't support `ed25519` keys, those users should generate and `rsa` key. Try out `ssh root@10.11.99.1`, it should **not** prompt for a password. +> **Note:** the reMarkable 2 doesn't support `ed25519` keys. If it's your case, try generating an `ecdsa` or `rsa` key. Try out `ssh root@10.11.99.1`, it should **not** prompt for root@10.11.99.1's password. #### Windows @@ -51,7 +51,8 @@ Download [reStream.sh](https://github.com/rien/reStream/releases/latest/download ``` $ chmod +x reStream.sh ``` -> **Tip** +##### Tip + > If you save `reStream.sh` in a `PATH` directory as `reStream`, you can launch it as `reStream`. > On Ubuntu, list these folders with `echo $PATH`. One should be`/usr/local/bin`. > As root, download the executable there: @@ -98,6 +99,7 @@ $ ssh root@10.11.99.1 'chmod +x /home/root/restream' - `-o --output`: path of the output where the video should be recorded, as understood by `ffmpeg`; if this is `-`, the video is displayed in a new window and not recorded anywhere (default: `-`) - `-f --format`: when recording to an output, this option is used to force the encoding format; if this is `-`, `ffmpeg`’s auto format detection based on the file extension is used (default: `-`). - `-w --webcam`: record to a video4linux2 web cam device. By default the first found web cam is taken, this can be overwritten with `-o`. The video is scaled to 1280x720 to ensure compatibility with MS Teams, Skype for business and other programs which need this specific format. See [Video4Linux Loopback](#video4linux-loopback) for installation instructions. +- `--mirror`: mirror the web cam video (`--webcam` has to be set). By default or as only choice, some programs, such as Zoom and Discord, mirror the camera. This flag restores the correct orientation. - `-m --measure`: use `pv` to measure how much data throughput you have (good to experiment with parameters to speed up the pipeline) - `-t --title`: set a custom window title for the video stream. The default title is "reStream". This option is disabled when using `-o --output` - `-u --unsecure-connection`: send framebuffer data over an unencrypted TCP-connection, resulting in more fps and less load on the reMarkable. See [Netcat](#netcat) for installation instructions. @@ -151,7 +153,50 @@ Steps you can try if the script isn't working: - [Set up an SSH key](#installation) - Update `ffmpeg` to version 4. +- Make sure RSA keys are allowed on your system: + - In some modern Unix distributions, RSA keys are considered ["legacy"](https://fedoraproject.org/wiki/Changes/StrongCryptoSettings2) and will no longer work out of the box. + - Therefore you need to add a section to your `~.ssh/config` file to allow use of RSA ssh keys for specified hosts. (according to [https://remarkablewiki.com/tech/ssh](https://remarkablewiki.com/tech/ssh), Remarkable devices might not work with non-RSA keys, which is the reason for why this is necessary.) + - This example should work without any additional configuration, although `PubkeyAcceptedKeyTypes=ssh-rsa` is required if you want to modify it: + ``` + Host remarkable + HostName 10.11.99.1 + User root + PubkeyAcceptedKeyTypes=ssh-rsa + ``` + - You can then use the -s flag to connect to the Remarkable: `./reStream.sh -s remarkable` ## Development -If you want to play with the `restream` code, you will have to [install Rust](https://www.rust-lang.org/learn/get-started) and [setup the reMarkable toolchain](https://github.com/canselcik/libremarkable#setting-up-the-toolchain) to do cross-platform development. +If you want to play with the `restream` code, you will have to [install Rust](https://www.rust-lang.org/learn/get-started). + +There are three ways of building the required restream binary for streaming the reMarkable framebuffer. For these approaches, the generated restream binary will be located under `target/armv7-unknown-linux-gnueabihf/release/restream`. + +- **Using nix flakes** + With [Nix](https://nixos.org/guides/install-nix.html) installed you can + create the development environment with `nix-shell` or (when using + [Nix flakes](https://nixos.wiki/wiki/Flakes#Installing_flakes) `nix-develop`. + After which you can simply run `cargo build --release` to build the restream + binary on your machine. + +- **Using docker and the toltec toolchain:** + You can use the [toltec toolchain docker images](https://github.com/toltec-dev/toolchain) to build a restream binary compatible with the reMarkable. + + ``` + docker run --rm -v $(pwd):/project -v /project/.cargo -w "/project" ghcr.io/toltec-dev/rust:latest cargo build --release --target=armv7-unknown-linux-gnueabihf + ``` + +- **Using the reMarkable toolchain:** + [Setup the reMarkable toolchain](https://github.com/canselcik/libremarkable#setting-up-the-toolchain) to do cross-platform development. + +## Like using reStream? + +I made this project in my spare time and received help from a handful of +wonderful contributors. If you want to say thanks, please +[send me an email](mailto:thanks@rxn.be) and be sure to mention how you are +using this project. + +I do not accept donations. There are charities that need more financial support +than I do, so please consider supporting a local charity instead. Preferably one +that promotes diversity in technology like +[GirlsWhoCode](https://girlswhocode.com/), [CoderDojo](https://coderdojo.com/), +etc. diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..59c78bf --- /dev/null +++ b/default.nix @@ -0,0 +1,28 @@ +{ + devShell = pkgs: with pkgs; mkShell { + buildInputs = [ + remarkable-toolchain + (rust-bin.stable.latest.default.override { + targets = [ "armv7-unknown-linux-gnueabihf" ]; + extensions = [ "rust-src" ]; + }) + openssl.dev + pkg-config + cargo-watch + cargo-limit + shellcheck + shfmt + lz4 + ]; + CARGO_BUILD_TARGET="armv7-unknown-linux-gnueabihf"; + CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER="${pkgs.remarkable-toolchain}/sysroots/x86_64-codexsdk-linux/usr/bin/arm-remarkable-linux-gnueabi/arm-remarkable-linux-gnueabi-gcc"; + CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_RUSTFLAGS=[ + "-C link-arg=-march=armv7-a" + "-C link-arg=-marm" + "-C link-arg=-mfpu=neon" + "-C link-arg=-mfloat-abi=hard" + "-C link-arg=-mcpu=cortex-a9" + "-C link-arg=--sysroot=${pkgs.remarkable-toolchain}/sysroots/cortexa9hf-neon-remarkable-linux-gnueabi" + ]; + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a66f1b6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,67 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1634851050, + "narHash": "sha256-N83GlSGPJJdcqhUxSCS/WwW5pksYf3VP1M13cDRTSVA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c91f3de5adaf1de973b797ef7485e441a65b8935", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1635844945, + "narHash": "sha256-tZcL307dj28jgEU1Wdn+zwG9neyW0H2+ZjdVhvJxh9g=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "b67e752c29f18a0ca5534a07661366d6a2c2e649", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1635992196, + "narHash": "sha256-+Duob7Ashxc/2pyhzZI3peZRLUlbzcmy2ZLzZN0Oups=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "a7d961b654c38b85877db7af4fac57ba3eaede16", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..23ee015 --- /dev/null +++ b/flake.nix @@ -0,0 +1,25 @@ +{ + description = "Stream the reMarkable screen to your computer"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; + }; + outputs = {self, nixpkgs, flake-utils, rust-overlay, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + in + with pkgs; + { + devShell = (import ./default.nix).devShell pkgs; + }); +} diff --git a/reStream.sh b/reStream.sh index 8b13f59..e9c32f4 100755 --- a/reStream.sh +++ b/reStream.sh @@ -1,20 +1,28 @@ #!/bin/sh +# Current reStream.sh version +version="1.2.0" + # default values for arguments -remarkable="10.11.99.1" # remarkable connected through USB -landscape=true # 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 -webcam=false # not to a webcam -measure_throughput=false # measure how fast data is being transferred -window_title=reStream # stream window title is reStream -video_filters="" # list of ffmpeg filters to apply -unsecure_connection=false # Establish a unsecure connection that is faster +remarkable="${REMARKABLE_IP:-10.11.99.1}" # remarkable IP address +landscape=true # 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 +webcam=false # not to a webcam +hflip=false # horizontal flip webcam +measure_throughput=false # measure how fast data is being transferred +window_title=reStream # stream window title is reStream +video_filters="" # list of ffmpeg filters to apply +unsecure_connection=false # Establish a unsecure connection that is faster # loop through arguments and process them while [ $# -gt 0 ]; do case "$1" in + -v | --version) + echo "reStream version: v$version" + exit + ;; -p | --portrait) landscape=false shift @@ -63,6 +71,11 @@ while [ $# -gt 0 ]; do fi shift ;; + --mirror) + # do nothing if --webcam is not set + hflip=true + shift + ;; -t | --title) window_title="$2" shift @@ -73,7 +86,7 @@ while [ $# -gt 0 ]; do shift ;; -h | --help | *) - echo "Usage: $0 [-p] [-c] [-u] [-s ] [-o ] [-f ] [-t ]" + echo "Usage: $0 [-p] [-c] [-u] [-s <source>] [-o <output>] [-f <format>] [-t <title>] [-m] [-w] [--hflip]" echo "Examples:" echo " $0 # live view in landscape" echo " $0 -p # live view in portrait" @@ -81,8 +94,8 @@ while [ $# -gt 0 ]; do echo " $0 -s 192.168.0.10 # connect to different IP" echo " $0 -o remarkable.mp4 # record to a file" echo " $0 -o udp://dest:1234 -f mpegts # record to a stream" - echo " $0 -w # write to a webcam (yuv420p + resize)" - echo " $0 -u # establish a unsecure but faster connection" + echo " $0 -w --mirror # write to a webcam (yuv420p + resize + mirror)" + echo " $0 -u # establish a unsecure but faster connection" exit 1 ;; esac @@ -90,7 +103,24 @@ done ssh_cmd() { echo "[SSH]" "$@" >&2 - ssh -o ConnectTimeout=1 -o PasswordAuthentication=no "root@$remarkable" "$@" + ssh -o ConnectTimeout=1 \ + -o PasswordAuthentication=no \ + -o PubkeyAcceptedKeyTypes=+ssh-rsa \ + -o HostKeyAlgorithms=+ssh-rsa \ + "root@$remarkable" "$@" +} + +# kill reStream on remarkable at the end. +# shellcheck disable=SC2016 +exit_rm() { + ssh_cmd 'kill $(pidof restream)' +} +trap exit_rm EXIT INT HUP + +# SSH_CONNECTION is a variable on reMarkable => ssh '' instead of ssh "" +# shellcheck disable=SC2016 +remarkable_ip() { + ssh_cmd 'echo $SSH_CONNECTION' | cut -d\ -f3 } # check if we are able to reach the remarkable @@ -180,6 +210,10 @@ if $webcam; then video_filters="$video_filters,format=pix_fmts=yuv420p" video_filters="$video_filters,scale=-1:720" video_filters="$video_filters,pad=1280:0:-1:0:#eeeeee" + + # Some applications, eg Zoom and Discord, mirror by default the webcam video + # Restore the correct orientation + $hflip && video_filters="$video_filters,hflip" fi # set each frame presentation time to the time it is received @@ -215,7 +249,7 @@ if $unsecure_connection; then listen_port=16789 ssh_cmd "$restream_rs --listen $listen_port" & sleep 1 # give some time to restream.rs to start listening - receive_cmd="nc 10.11.99.1 $listen_port" + receive_cmd="nc $(remarkable_ip) $listen_port" else receive_cmd="ssh_cmd $restream_rs" fi @@ -224,12 +258,16 @@ fi $receive_cmd \ | $decompress \ | $host_passthrough \ - | "$output_cmd" \ - -vcodec rawvideo \ - -loglevel "$loglevel" \ - -f rawvideo \ - -pixel_format "$pixel_format" \ - -video_size "$width,$height" \ - $window_title_option \ - -i - \ - "$@" + | ( + "$output_cmd" \ + -vcodec rawvideo \ + -loglevel "$loglevel" \ + -f rawvideo \ + -pixel_format "$pixel_format" \ + -video_size "$width,$height" \ + $window_title_option \ + -i - \ + "$@" \ + ; + kill $$ + ) diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..d69c239 --- /dev/null +++ b/shell.nix @@ -0,0 +1,8 @@ +let + pkgs = import <nixpkgs> { + overlays = [ + (import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz")) + ]; + }; + reStreamDev = import ./default.nix; +in reStreamDev.devShell pkgs