├── .github └── workflows │ ├── main.yml │ └── publish.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── screenshot.png ├── src ├── dump_image.rs ├── ffi.rs ├── framebuffer.rs ├── hyperion.rs ├── hyperion_reply_generated.rs ├── hyperion_request_generated.rs ├── image_decoder.rs └── main.rs └── systemd └── drm-capture.service /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | push: 7 | tags: 8 | - 'v[0-9]+.[0-9]+.[0-9]+' 9 | 10 | env: 11 | BIN_NAME: drm-vc4-grabber 12 | PROJECT_NAME: drm-vc4-grabber 13 | REPO_NAME: rudihorn/drm-vc4-grabber 14 | 15 | jobs: 16 | dist: 17 | name: Dist 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | build: [aarch64-linux] 23 | include: 24 | - build: aarch64-linux 25 | os: ubuntu-20.04 26 | rust: stable 27 | target: aarch64-unknown-linux-musl 28 | cross: true 29 | 30 | steps: 31 | - name: Checkout sources 32 | uses: actions/checkout@v2 33 | with: 34 | submodules: true 35 | 36 | - name: Install ${{ matrix.rust }} toolchain 37 | uses: actions-rs/toolchain@v1 38 | with: 39 | profile: minimal 40 | toolchain: ${{ matrix.rust }} 41 | target: ${{ matrix.target }} 42 | override: true 43 | 44 | - name: Run cargo test 45 | uses: actions-rs/cargo@v1 46 | with: 47 | use-cross: ${{ matrix.cross }} 48 | command: test 49 | args: --release --locked --target ${{ matrix.target }} 50 | 51 | - name: Build release binary 52 | uses: actions-rs/cargo@v1 53 | with: 54 | use-cross: ${{ matrix.cross }} 55 | command: build 56 | args: --release --locked --target ${{ matrix.target }} 57 | 58 | # - name: Strip release binary (arm) 59 | # if: matrix.build == 'aarch64-linux' 60 | # run: | 61 | # docker run --rm -v \ 62 | # "$PWD/target:/target:Z" \ 63 | # rustembedded/cross:${{ matrix.target }} \ 64 | # aarch64-linux-gnu-strip \ 65 | # /target/${{ matrix.target }}/release/$BIN_NAME 66 | 67 | - name: Build archive 68 | shell: bash 69 | run: | 70 | mkdir dist 71 | cp "target/${{ matrix.target }}/release/$BIN_NAME" "dist/" 72 | 73 | - uses: actions/upload-artifact@v4 74 | with: 75 | name: bins-${{ matrix.build }} 76 | path: dist 77 | 78 | publish: 79 | name: Publish 80 | needs: [dist] 81 | runs-on: ubuntu-latest 82 | if: startsWith(github.ref, 'refs/tags/') 83 | steps: 84 | - name: Checkout sources 85 | uses: actions/checkout@v2 86 | with: 87 | submodules: false 88 | 89 | - uses: actions/download-artifact@v4 90 | 91 | - run: ls -al bins-* 92 | 93 | - name: Calculate tag name 94 | run: | 95 | name=dev 96 | if [[ $GITHUB_REF == refs/tags/v* ]]; then 97 | name=${GITHUB_REF:10} 98 | fi 99 | echo ::set-output name=val::$name 100 | echo TAG=$name >> $GITHUB_ENV 101 | id: tagname 102 | 103 | - name: Build archive 104 | shell: bash 105 | run: | 106 | set -ex 107 | 108 | rm -rf tmp 109 | mkdir tmp 110 | mkdir dist 111 | 112 | for dir in bins-* ; do 113 | platform=${dir#"bins-"} 114 | pkgname=$PROJECT_NAME-$TAG-$platform 115 | mkdir tmp/$pkgname 116 | # cp LICENSE README.md tmp/$pkgname 117 | mv bins-$platform/$BIN_NAME$exe tmp/$pkgname 118 | chmod +x tmp/$pkgname/$BIN_NAME$exe 119 | 120 | tar cJf dist/$pkgname.tar.xz -C tmp $pkgname 121 | done 122 | 123 | - name: Upload binaries to release 124 | uses: svenstaro/upload-release-action@v2 125 | with: 126 | repo_token: ${{ secrets.GITHUB_TOKEN }} 127 | file: dist/* 128 | file_glob: true 129 | tag: ${{ steps.tagname.outputs.val }} 130 | overwrite: true 131 | 132 | - name: Extract version 133 | id: extract-version 134 | run: | 135 | printf "::set-output name=%s::%s\n" tag-name "${GITHUB_REF#refs/tags/}" 136 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - 'v[0-9]+.[0-9]+.[0-9]+' 6 | 7 | env: 8 | BIN_NAME: drm-vc4-grabber 9 | PROJECT_NAME: drm-vc4-grabber 10 | REPO_NAME: rudihorn/drm-vc4-grabber 11 | 12 | jobs: 13 | 14 | publish: 15 | name: Publish 16 | needs: [dist] 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout sources 20 | uses: actions/checkout@v2 21 | with: 22 | submodules: false 23 | 24 | - uses: actions/download-artifact@v4 25 | 26 | - run: ls -al bins-* 27 | 28 | - name: Calculate tag name 29 | run: | 30 | name=dev 31 | if [[ $GITHUB_REF == refs/tags/v* ]]; then 32 | name=${GITHUB_REF:10} 33 | fi 34 | echo ::set-output name=val::$name 35 | echo TAG=$name >> $GITHUB_ENV 36 | id: tagname 37 | 38 | - name: Build archive 39 | shell: bash 40 | run: | 41 | set -ex 42 | 43 | rm -rf tmp 44 | mkdir tmp 45 | mkdir dist 46 | 47 | for dir in bins-* ; do 48 | platform=${dir#"bins-"} 49 | pkgname=$PROJECT_NAME-$TAG-$platform 50 | mkdir tmp/$pkgname 51 | # cp LICENSE README.md tmp/$pkgname 52 | mv bins-$platform/$BIN_NAME$exe tmp/$pkgname 53 | chmod +x tmp/$pkgname/$BIN_NAME$exe 54 | 55 | tar cJf dist/$pkgname.tar.xz -C tmp $pkgname 56 | done 57 | 58 | - name: Upload binaries to release 59 | uses: svenstaro/upload-release-action@v2 60 | with: 61 | repo_token: ${{ secrets.GITHUB_TOKEN }} 62 | file: dist/* 63 | file_glob: true 64 | tag: ${{ steps.tagname.outputs.val }} 65 | overwrite: true 66 | 67 | - name: Extract version 68 | id: extract-version 69 | run: | 70 | printf "::set-output name=%s::%s\n" tag-name "${GITHUB_REF#refs/tags/}" 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | _build 4 | .#* -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drm-vc4-grabber" 3 | version = "0.1.0" 4 | authors = ["Rudi "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | libc = "0.2.148" 9 | drm = "0.4.0" 10 | nix = "0.20.0" 11 | drm-ffi = "0.1.0" 12 | drm-sys = "0.1.0" 13 | drm-fourcc = "2.2.0" 14 | image = "0.23.14" 15 | flatbuffers = "2.0.0" 16 | byteorder = "1.4.3" 17 | clap = "2.33.3" 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Rudi Horn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyperion DRM VC4 screen grabber 2 | 3 | This is an experimental attempt to capture a screenshot from a Raspberry Pi that 4 | is rendering using the [Direct Rendering 5 | Manager](https://en.wikipedia.org/wiki/Direct_Rendering_Manager). It currently 6 | works by opening the default card adapter, looping through all the planes and 7 | finding the underlying framebuffers. Using the framebuffer it is possible to 8 | determine the buffer handle for the underlying buffer object handle. 9 | 10 | The buffer object handle can be mapped to memory using the VC4 specific DRM 11 | API's (see `/usr/include/drm/vc4_drm.h`), specifically using the ioctl 12 | `drm_vc4_mmap_bo`. The memory data is stored using XRGB8888 in little-endian 32 13 | bit words, and is tiled in 32x32 bit squares 14 | ([reference](https://docs.mesa3d.org/drivers/vc4.html#tiled-rendering)). There 15 | is also some other interlacing or similar I have not quite figured out yet. 16 | 17 | The current implementation connects to Hyperion at `127.0.0.1:19400` and 18 | directly uploads the images. 19 | 20 | ## Usage 21 | 22 | 1. Download the latest release archive. 23 | 2. Extract the archive, e.g. `tar xvf drm-vc4-grabber-v0.1.0-aarch64-linux.tar.xz` 24 | 3. Run the grabber in the background, e.g. `nohup ./drm-vc4-grabber-v0.1.0-aarch64-linux/drm-vc4-grabber` 25 | 4. Optionally use systemd to automatically start it in the background. On LibreELEC, copy `systemd/drm-capture.service` to `~/.config/system.d/` and then run `systemctl enable drm-capture.service`. 26 | 27 | ## Compiling 28 | 29 | 1. Ensure rust is installed (with rustup and cargo). 30 | 2. Install the target toolchain for raspberry pi: `rustup target install aarch64-unknown-linux-gnu`. 31 | 3. Ensure the linker for this toolchain is installed, e.g. `sudo apt install gcc-aarch64-linux-gnu` 32 | 4. Set the linker in your env var: `export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc` 33 | 5. Compile: `cargo build --release --target aarch64-unknown-linux-gnu` 34 | 6. The built file will be at `target/aarch64-unknown-linux-gnu/release/drm-v4-capture` 35 | 36 | ## Example 37 | 38 | The following is an example screen capture in the current codes state. 39 | 40 | ![Image capture](screenshot.png "Raspberry pi using latest OSMC devel branch and kodi 19") 41 | 42 | 43 | ## Debug Output 44 | 45 | ``` 46 | Driver: Driver { name: SmallOsString { data: [118, 99, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 3, as_ref(): "vc4" }, date: SmallOsString { data: [50, 48, 49, 52, 48, 54, 49, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 8, as_ref(): "20140616" }, desc: SmallOsString { data: [66, 114, 111, 97, 100, 99, 111, 109, 32, 86, 67, 52, 32, 103, 114, 97, 112, 104, 105, 99, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 21, as_ref(): "Broadcom VC4 graphics" } } 47 | Plane Info: Info { handle: plane::Handle(84), crtc: Some(crtc::Handle(83)), fb: Some(framebuffer::Handle(207)), pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 48 | -> FB Info: Info { handle: framebuffer::Handle(207), size: (1920, 1080), pitch: 7680, bpp: 32, depth: 24, buffer: 1 } 49 | -> FB Info 2: drm_mode_fb_cmd2 { fb_id: 207, width: 1920, height: 1080, pixel_format: 875713112, flags: 2, handles: [2, 0, 0, 0], pitches: [7680, 0, 0, 0], offsets: [0, 0, 0, 0], modifier: [504403158265495553, 0, 0, 0] } 50 | -> Offset: 306663424 51 | Plane Info: Info { handle: plane::Handle(90), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 52 | Plane Info: Info { handle: plane::Handle(96), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 53 | Plane Info: Info { handle: plane::Handle(102), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 54 | Plane Info: Info { handle: plane::Handle(108), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 55 | Plane Info: Info { handle: plane::Handle(114), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 56 | Plane Info: Info { handle: plane::Handle(120), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 57 | Plane Info: Info { handle: plane::Handle(126), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 58 | Plane Info: Info { handle: plane::Handle(132), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 59 | Plane Info: Info { handle: plane::Handle(138), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 60 | Plane Info: Info { handle: plane::Handle(144), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 61 | Plane Info: Info { handle: plane::Handle(150), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 62 | Plane Info: Info { handle: plane::Handle(156), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 63 | Plane Info: Info { handle: plane::Handle(162), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 64 | Plane Info: Info { handle: plane::Handle(168), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 65 | Plane Info: Info { handle: plane::Handle(174), crtc: None, fb: None, pos_crtcs: 15, formats: [0, 0, 0, 0, 0, 0, 0, 0], fmt_len: 8 } 66 | CRTC Info: Info { handle: crtc::Handle(62), position: (0, 0), mode: None, fb: None, gamma_length: 256 } 67 | CRTC Info: Info { handle: crtc::Handle(69), position: (0, 0), mode: None, fb: None, gamma_length: 256 } 68 | CRTC Info: Info { handle: crtc::Handle(76), position: (0, 0), mode: None, fb: None, gamma_length: 256 } 69 | CRTC Info: Info { handle: crtc::Handle(83), position: (0, 0), mode: Some(Mode { name: "1920x1080", clock: 148500, size: (1920, 1080), hsync: (2008, 2052, 2200), vsync: (1084, 1089, 1125), hskew: 0, vscan: 0, vrefresh: 60 }), fb: None, gamma_length: 256 } 70 | ``` 71 | 72 | ## Donations 73 | 74 | I am in no way dependent on any donations, and am not asking for any support. 75 | However, if you would still like to show your appreciation you may do so using 76 | either of the following methods: 77 | 78 | | Paypal | Bitcoin | 79 | |-------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| 80 | | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.me/rudihppal) | [bitcoin:bc1qjantllys0pg3zvsr97krxzz9dxzlmxmgy5qk4v](bitcoin:bc1qjantllys0pg3zvsr97krxzz9dxzlmxmgy5qk4v) | 81 | 82 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rudihorn/drm-vc4-grabber/6229d5e8323c5c6ec96823dce598bc4b8bb36a0e/screenshot.png -------------------------------------------------------------------------------- /src/dump_image.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::TryFrom, mem::size_of, os::fd::AsRawFd}; 2 | 3 | use drm::control::framebuffer::Handle; 4 | use drm::SystemError; 5 | use drm_fourcc::{DrmFourcc, DrmModifier}; 6 | use image::{GenericImage, RgbImage}; 7 | use libc::close; 8 | use nix::sys::mman; 9 | 10 | use crate::{ 11 | ffi::{self, gem_close}, 12 | image_decoder::{ 13 | decode_image, decode_image_multichannel, decode_small_image_multichannel, 14 | decode_tiled_small_image, rgb565_to_rgb888, ToRgb, YUV420Pixel, 15 | }, 16 | Card, 17 | }; 18 | 19 | fn copy_buffer( 20 | card: &Card, 21 | handle: u32, 22 | to: &mut [T], 23 | verbose: bool, 24 | ) -> Result<(), SystemError> { 25 | let length = to.len() * size_of::(); 26 | 27 | let hfd = ffi::prime_handle_to_fd(card.as_raw_fd(), handle)?; 28 | 29 | if verbose { 30 | println!("handle fd {}", hfd); 31 | } 32 | 33 | let addr = core::ptr::null_mut(); 34 | let prot = mman::ProtFlags::PROT_READ; 35 | let flags = mman::MapFlags::MAP_SHARED; 36 | unsafe { 37 | let map = mman::mmap(addr, length as _, prot, flags, hfd, 0).unwrap(); 38 | 39 | let mapping: &mut [T] = std::slice::from_raw_parts_mut(map as *mut _, to.len()); 40 | to.copy_from_slice(mapping); 41 | mman::munmap(map, length as _).unwrap(); 42 | 43 | if close(hfd) == -1 { 44 | panic!("Failed to close prime fd."); 45 | }; 46 | } 47 | 48 | Ok(()) 49 | } 50 | 51 | fn decimate_image_4(size: (usize, usize), image: &[u32], copy: &mut [u32]) { 52 | let decim = (4, 4); 53 | let newsize = (size.0 / decim.0, size.1 / decim.1); 54 | 55 | for y in 0..newsize.1 { 56 | let ty = decim.1 * y; 57 | for x in 0..newsize.0 { 58 | let tx = decim.0 * x; 59 | copy[y * newsize.0 + x] = image[ty * size.0 + tx]; 60 | } 61 | } 62 | } 63 | 64 | fn decode_p030_image( 65 | card: &Card, 66 | size: (usize, usize), 67 | pitches: u32, 68 | handle: u32, 69 | modifier: u64, 70 | offset: usize, 71 | verbose: bool, 72 | ) -> Result { 73 | // We assume the DRM BROADCOM SAND128 format 74 | if u64::from(drm_fourcc::DrmModifier::Broadcom_sand128) != modifier & !(0xFFFF << 8) { 75 | panic!("Unsupported P030 modifier value"); 76 | } 77 | 78 | let stride = 128 / 4; // each column is 128 bytes wide, we use 4 bytes per word 79 | let colpx = 96; 80 | 81 | let ypitch = pitches as usize / (32 / 8); 82 | let ylines = ((modifier >> 8) & 0xFFFFFFFF) as usize; 83 | let length = ylines * (size.0 / colpx) * stride; 84 | let crcboffset = offset / 4; // offset of the CrCb information in each column 85 | 86 | if verbose { 87 | println!( 88 | "P030, size: {:?}, lines: {}, pitches: {}, length: {}", 89 | size, ylines, ypitch, length 90 | ); 91 | } 92 | 93 | let mut yplane = vec![0u32; length as _]; 94 | copy_buffer(card, handle, &mut yplane, verbose)?; 95 | 96 | let decim = 3; 97 | let mut img = RgbImage::new((size.0 / decim) as _, (size.1 / decim) as _); 98 | for y in 0..size.1 / decim { 99 | let ty = y * decim; 100 | for x in 0..size.0 / decim { 101 | let tx = x * decim; 102 | let col = tx / colpx; 103 | let col_offset = col * stride * ylines; 104 | let x_mod = (tx % colpx) / decim; 105 | 106 | let ypx = unsafe { yplane.get_unchecked(col_offset + ty * stride + x_mod) }; 107 | let rx = x_mod / 2 * 2; 108 | let crcind = col_offset + crcboffset + ty / 2 * stride + rx; 109 | let crcbpx = unsafe { yplane.get_unchecked(crcind + 1) }; 110 | 111 | let yuv = YUV420Pixel::new((ypx >> 2) as u8, (crcbpx >> 12) as u8, (crcbpx >> 2) as u8); 112 | 113 | unsafe { 114 | img.unsafe_put_pixel(x as _, y as _, yuv.rgb()); 115 | } 116 | } 117 | } 118 | 119 | Ok(img) 120 | } 121 | 122 | fn decode_nv12_image( 123 | card: &Card, 124 | size: (usize, usize), 125 | pitches: u32, 126 | handle: u32, 127 | modifier: u64, 128 | offset: usize, 129 | verbose: bool, 130 | ) -> Result { 131 | // We assume the DRM BROADCOM SAND128 format 132 | if u64::from(drm_fourcc::DrmModifier::Broadcom_sand128) != modifier & !(0xFFFF << 8) { 133 | panic!("Unsupported NV12 modifier value"); 134 | } 135 | 136 | let stride = 128 / 4; // each column is 128 bytes wide, we use 4 bytes per word 137 | let colpx = 128; // 1 byte per pixel 138 | 139 | let ypitch = pitches as usize / (32 / 8); 140 | let ylines = ((modifier >> 8) & 0xFFFFFFFF) as usize; 141 | let length = ylines * (size.0 / colpx) * stride; 142 | let crcboffset = offset / 4; // offset of the CrCb information in each column 143 | 144 | if verbose { 145 | println!( 146 | "NV12, size: {:?}, lines: {}, pitches: {}, length: {}", 147 | size, ylines, ypitch, length 148 | ); 149 | } 150 | 151 | let mut yplane = vec![0u32; length as _]; 152 | copy_buffer(card, handle, &mut yplane, verbose)?; 153 | 154 | let decim: usize = 4; 155 | let mut img = RgbImage::new((size.0 / decim) as _, (size.1 / decim) as _); 156 | for y in 0..size.1 / decim { 157 | let ty = y * decim; 158 | for x in 0..size.0 / decim { 159 | let tx = x * decim; 160 | let col = tx / colpx; 161 | let col_offset = col * stride * ylines; 162 | let x_mod = (tx % colpx) / decim; 163 | 164 | let ypx = unsafe { yplane.get_unchecked(col_offset + ty * stride + x_mod) }; 165 | let rx = x_mod / 2 * 2; 166 | let crcind = col_offset + crcboffset + ty / 2 * stride + rx; 167 | let crcbpx = unsafe { yplane.get_unchecked(crcind + 1) }; 168 | 169 | let yuv = YUV420Pixel::new((ypx >> 0) as u8, (crcbpx >> 0) as u8, (crcbpx >> 8) as u8); 170 | 171 | unsafe { 172 | img.unsafe_put_pixel(x as _, y as _, yuv.rgb()); 173 | } 174 | } 175 | } 176 | 177 | Ok(img) 178 | } 179 | 180 | fn dump_linear_to_image( 181 | card: &Card, 182 | pitch: u32, 183 | size: (u32, u32), 184 | bpp: u32, 185 | handle: u32, 186 | verbose: bool, 187 | ) -> Result { 188 | let size = (size.0, size.1); 189 | 190 | let length = pitch * size.1 / (bpp / 8); 191 | 192 | println!( 193 | "linear, size: {:?}, pitch: {}, bpp: {}, length: {}", 194 | size, pitch, bpp, length 195 | ); 196 | let mut copy = vec![0u32; length as _]; 197 | copy_buffer(card, handle, &mut copy, verbose)?; 198 | 199 | let mut dec = vec![0u32; (length / (4 * 4)) as _]; 200 | decimate_image_4( 201 | (size.0 as _, size.1 as _), 202 | copy.as_slice(), 203 | dec.as_mut_slice(), 204 | ); 205 | 206 | Ok(decode_image( 207 | dec.as_mut_slice(), 208 | pitch / 4, 209 | (size.0 / 4, size.1 / 4), 210 | )) 211 | } 212 | 213 | fn dump_rgb565_to_image( 214 | card: &Card, 215 | pitch: u32, 216 | size: (u32, u32), 217 | bpp: u32, 218 | handle: u32, 219 | verbose: bool, 220 | ) -> Result { 221 | // let size = (size.0, size.1 / 64); 222 | 223 | let length = pitch * size.1 / (bpp / 8); 224 | 225 | println!( 226 | "rgb565, size: {:?}, pitch: {}, bpp: {}, length: {}", 227 | size, pitch, bpp, length 228 | ); 229 | let mut copy = vec![0u16; length as _]; 230 | copy_buffer(card, handle, &mut copy, verbose)?; 231 | 232 | Ok(rgb565_to_rgb888(copy.as_mut_slice(), pitch, size)) 233 | } 234 | 235 | fn dump_broadcom_tiled_to_image( 236 | card: &Card, 237 | size: (u32, u32), 238 | bpp: u32, 239 | handle: u32, 240 | verbose: bool, 241 | ) -> Result { 242 | let tilesize = 32; 243 | let tile_count = |n| (n + tilesize - 1) / tilesize; 244 | let tiles = (tile_count(size.0), tile_count(size.1)); 245 | let total_tiles = tiles.0 * tiles.1; 246 | 247 | let length = total_tiles * tilesize * tilesize * (bpp / 8); 248 | 249 | let mut copy = vec![0; (length / 4) as _]; 250 | copy_buffer(card, handle, &mut copy, verbose)?; 251 | 252 | Ok(decode_tiled_small_image( 253 | copy.as_mut_slice(), 254 | tilesize, 255 | tiles, 256 | size, 257 | )) 258 | } 259 | 260 | fn dump_yuv420_to_image( 261 | card: &Card, 262 | size: (u32, u32), 263 | pitches: [u32; 4], 264 | handles: [u32; 4], 265 | offsets: [u32; 4], 266 | verbose: bool, 267 | ) -> Result { 268 | // The length of the entire buffer is the length of the last buffer plus its 269 | // offset (assuming they are in order). The U and V buffers are grouped into 270 | // 2x2 tiles, hence the length is divided by 4. 271 | let length = offsets[2] + size.1 * pitches[2] / (pitches[0] / pitches[2]); 272 | //println!(" -> Mounting @{} +{}", offset, length); 273 | 274 | let mut copy = vec![0; length as _]; 275 | copy_buffer(card, handles[0], &mut copy, verbose)?; 276 | 277 | let buffer_range = |i| { 278 | offsets[i] as usize..(offsets[i] + size.1 * pitches[i] / (pitches[0] / pitches[i])) as usize 279 | }; 280 | 281 | let mappings = [ 282 | ©[buffer_range(0)], 283 | ©[buffer_range(1)], 284 | ©[buffer_range(2)], 285 | ]; 286 | 287 | let mut pitches1 = [0; 3]; 288 | pitches1.copy_from_slice(&pitches[0..3]); 289 | 290 | if size.0 > 640 { 291 | // If the image is large then just decode a smaller image 292 | Ok(decode_small_image_multichannel(mappings, size, pitches1)) 293 | } else { 294 | Ok(decode_image_multichannel(mappings, size, pitches1)) 295 | } 296 | } 297 | 298 | pub fn dump_framebuffer_to_image( 299 | card: &Card, 300 | fb: Handle, 301 | verbose: bool, 302 | ) -> Result { 303 | let fbinfo2 = ffi::fb_cmd2(card.as_raw_fd(), fb.into())?; 304 | 305 | if verbose { 306 | println!(" -> FB Info 2: {:?}", fbinfo2); 307 | } 308 | 309 | let size = (fbinfo2.width, fbinfo2.height); 310 | 311 | if fbinfo2.pixel_format == 808661072 { 312 | return decode_p030_image( 313 | card, 314 | (size.0 as _, size.1 as _), 315 | fbinfo2.pitches[0], 316 | fbinfo2.handles[0], 317 | fbinfo2.modifier[0], 318 | fbinfo2.offsets[1] as _, 319 | verbose, 320 | ); 321 | } 322 | 323 | let fourcc = drm_fourcc::DrmFourcc::try_from(fbinfo2.pixel_format).unwrap(); 324 | let modifier = drm_fourcc::DrmModifier::try_from(fbinfo2.modifier[0]).unwrap(); 325 | 326 | let image_result = match fourcc { 327 | DrmFourcc::Xrgb8888 => match modifier { 328 | DrmModifier::Broadcom_vc4_t_tiled => { 329 | dump_broadcom_tiled_to_image(card, size, 32, fbinfo2.handles[0], verbose) 330 | } 331 | DrmModifier::Linear => dump_linear_to_image( 332 | card, 333 | fbinfo2.pitches[0], 334 | size, 335 | 32, 336 | fbinfo2.handles[0], 337 | verbose, 338 | ), 339 | _ => panic!("Unsupported framebuffer modifier: {:?}", modifier), 340 | }, 341 | DrmFourcc::Argb8888 => match modifier { 342 | DrmModifier::Broadcom_vc4_t_tiled => { 343 | dump_broadcom_tiled_to_image(card, size, 32, fbinfo2.handles[0], verbose) 344 | } 345 | DrmModifier::Linear => dump_linear_to_image( 346 | card, 347 | fbinfo2.pitches[0], 348 | size, 349 | 32, 350 | fbinfo2.handles[0], 351 | verbose, 352 | ), 353 | _ => panic!("Unsupported framebuffer modifier: {:?}", modifier), 354 | }, 355 | DrmFourcc::Yuv420 => dump_yuv420_to_image( 356 | card, 357 | size, 358 | fbinfo2.pitches, 359 | fbinfo2.handles, 360 | fbinfo2.offsets, 361 | verbose, 362 | ), 363 | DrmFourcc::Rgb565 => dump_rgb565_to_image( 364 | card, 365 | fbinfo2.pitches[0], 366 | size, 367 | 16, 368 | fbinfo2.handles[0], 369 | verbose, 370 | ), 371 | DrmFourcc::Nv12 => decode_nv12_image( 372 | card, 373 | (size.0 as _, size.1 as _), 374 | fbinfo2.pitches[0], 375 | fbinfo2.handles[0], 376 | fbinfo2.modifier[0], 377 | fbinfo2.offsets[1] as _, 378 | verbose, 379 | ), 380 | 381 | _ => panic!( 382 | "Unsupported framebuffer pixel format: {} {:x}", 383 | fourcc, fbinfo2.pixel_format 384 | ), 385 | }; 386 | 387 | gem_close(card.as_raw_fd(), fbinfo2.handles[0]).unwrap(); 388 | 389 | let image = image_result?; 390 | 391 | Ok(image) 392 | } 393 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | use drm_ffi::result::SystemError; 2 | use drm_sys::drm_mode_fb_cmd2; 3 | use drm_sys::*; 4 | use std::os::unix::prelude::RawFd; 5 | 6 | ioctl_readwrite!(drm_mode_getfb2, DRM_IOCTL_BASE, 0xCE, drm_mode_fb_cmd2); 7 | 8 | pub fn fb_cmd2(fd: RawFd, handle: u32) -> Result { 9 | let mut fb = drm_mode_fb_cmd2 { 10 | fb_id: handle, 11 | width: 0, 12 | height: 0, 13 | pixel_format: 0, 14 | flags: 0, 15 | handles: [0; 4], 16 | pitches: [0; 4], 17 | offsets: [0; 4], 18 | modifier: [0; 4], 19 | }; 20 | 21 | unsafe { 22 | drm_mode_getfb2(fd, &mut fb)?; 23 | } 24 | 25 | Ok(fb) 26 | } 27 | 28 | ioctl_readwrite!(drm_prime_handle_to_fd, DRM_IOCTL_BASE, 0x2D, drm_prime_handle); 29 | 30 | pub fn prime_handle_to_fd(fd: RawFd, handle: u32) -> Result { 31 | let mut ph = drm_prime_handle { 32 | handle, 33 | flags: 0, 34 | fd: 0, 35 | }; 36 | 37 | unsafe { 38 | drm_prime_handle_to_fd(fd, &mut ph)?; 39 | } 40 | 41 | Ok(ph.fd) 42 | } 43 | 44 | ioctl_write_ptr!(drm_gem_close_ioctl, DRM_IOCTL_BASE, 0x09, drm_gem_close); 45 | 46 | pub fn gem_close(fd: RawFd, handle: u32) -> Result<(), SystemError> { 47 | let mut ph = drm_gem_close { 48 | handle, 49 | pad: 0, 50 | }; 51 | 52 | unsafe { 53 | drm_gem_close_ioctl(fd, &mut ph)?; 54 | } 55 | 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /src/framebuffer.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::max; 2 | 3 | use drm_ffi::drm_mode_fb_cmd2; 4 | use image::{GenericImage, RgbImage}; 5 | 6 | use crate::image_decoder::{ToRgb, YUV420Pixel}; 7 | 8 | pub struct YUV420Plane { 9 | pub pitch: u32, 10 | pub size: (u32, u32), 11 | pub offset: u32, 12 | } 13 | 14 | impl YUV420Plane { 15 | pub fn len(&self) -> usize { 16 | (self.pitch * self.size.1) as _ 17 | } 18 | 19 | pub fn end(&self) -> usize { 20 | (self.offset as usize) + self.len() 21 | } 22 | 23 | pub fn offset(&self, x: usize, y: usize) -> usize { 24 | self.pitch as usize * y + x 25 | } 26 | } 27 | 28 | pub struct YUV420 { 29 | pub size: (u32, u32), 30 | pub planes: [YUV420Plane; 3], 31 | } 32 | 33 | impl YUV420 { 34 | pub fn from(fbinfo: drm_mode_fb_cmd2) -> YUV420 { 35 | let plane = |i| YUV420Plane { 36 | pitch: fbinfo.pitches[i], 37 | size: ( 38 | fbinfo.pitches[i], 39 | fbinfo.height * fbinfo.pitches[i] / fbinfo.pitches[0], 40 | ), 41 | offset: fbinfo.offsets[i], 42 | }; 43 | YUV420 { 44 | size: (fbinfo.width, fbinfo.height), 45 | planes: [plane(0), plane(1), plane(2)], 46 | } 47 | } 48 | } 49 | 50 | pub struct FramebufferYUV420 { 51 | pub info2: drm_mode_fb_cmd2, 52 | pub planes: [YUV420Plane; 3], 53 | } 54 | 55 | impl FramebufferYUV420 { 56 | pub fn len(&self) -> usize { 57 | let mut res = 0; 58 | for plane in self.planes.iter() { 59 | res = max(plane.end(), res); 60 | } 61 | res as _ 62 | } 63 | } 64 | 65 | pub trait Framebuffer

{ 66 | fn info<'a>(&'a self) -> &'a drm_mode_fb_cmd2; 67 | fn get(&self, mappings: [&[u8]; 3], x: usize, y: usize) -> P; 68 | } 69 | 70 | impl Framebuffer for FramebufferYUV420 { 71 | fn info<'a>(&'a self) -> &'a drm_mode_fb_cmd2 { 72 | &self.info2 73 | } 74 | 75 | fn get(&self, mappings: [&[u8]; 3], x: usize, y: usize) -> YUV420Pixel { 76 | let offset: usize = self.planes[0].offset(x, y); 77 | let offset1: usize = self.planes[1].offset(x / 2, y / 2); 78 | let offset2: usize = self.planes[2].offset(x / 2, y / 2); 79 | 80 | YUV420Pixel::new( 81 | mappings[0][offset], 82 | mappings[1][offset1], 83 | mappings[2][offset2], 84 | ) 85 | } 86 | } 87 | 88 | pub struct FramebufferCopy<'a, P> { 89 | fb: &'a dyn Framebuffer

, 90 | } 91 | 92 | impl<'a, P> FramebufferCopy<'a, P> 93 | where 94 | P: ToRgb, 95 | { 96 | pub fn decode_image(&self, mappings: [&[u8]; 3]) { 97 | let mut img = RgbImage::new(self.fb.info().width, self.fb.info().height); 98 | for y in 0..self.fb.info().height { 99 | for x in 0..self.fb.info().width { 100 | unsafe { 101 | img.unsafe_put_pixel(x, y, self.fb.get(mappings, x as _, y as _).rgb()); 102 | } 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/hyperion.rs: -------------------------------------------------------------------------------- 1 | use std::{io::{Cursor, Read, Write}, net::TcpStream}; 2 | 3 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 4 | use flatbuffers::FlatBufferBuilder; 5 | use image::{EncodableLayout, RgbImage}; 6 | 7 | use std::io::Result as StdResult; 8 | 9 | use crate::hyperion_reply_generated::hyperionnet as reply; 10 | use crate::hyperion_request_generated::hyperionnet as request; 11 | 12 | pub fn read_reply(socket: &mut TcpStream, verbose : bool) -> StdResult<()> { 13 | let mut size = [0u8; 4]; 14 | socket.read_exact(&mut size)?; 15 | 16 | let v = Cursor::new(size).read_u32::()? as usize; 17 | let mut msg = vec![0; v]; 18 | socket.read_exact(&mut msg)?; 19 | 20 | let request = reply::root_as_reply(&msg).unwrap(); 21 | 22 | if verbose { 23 | println!("Response {:?}", request); 24 | } 25 | 26 | Ok(()) 27 | } 28 | 29 | pub fn register_direct(socket: &mut TcpStream) -> StdResult<()> { 30 | let mut builder = FlatBufferBuilder::new(); 31 | 32 | let origin = builder.create_string("DRM"); 33 | let register = request::Register::create( 34 | &mut builder, 35 | &request::RegisterArgs { 36 | origin: Some(origin), 37 | priority: 150, 38 | }, 39 | ); 40 | let offset = request::Request::create( 41 | &mut builder, 42 | &request::RequestArgs { 43 | command_type: request::Command::Register, 44 | command: Some(register.as_union_value()), 45 | }, 46 | ); 47 | request::finish_request_buffer(&mut builder, offset); 48 | 49 | let dat = builder.finished_data(); 50 | 51 | socket.write_u32::(dat.len() as _)?; 52 | socket.write_all(dat)?; 53 | socket.flush()?; 54 | 55 | Ok(()) 56 | } 57 | 58 | pub fn send_image(socket: &mut TcpStream, image: & RgbImage, verbose: bool) -> StdResult<()> { 59 | let mut builder = FlatBufferBuilder::new(); 60 | 61 | let raw_bytes = image.as_bytes(); 62 | 63 | if verbose { 64 | println!( 65 | "Sending image {}x{} (size: {})", 66 | image.width(), 67 | image.height(), 68 | raw_bytes.len() 69 | ); 70 | } 71 | 72 | let data = builder.create_vector(&raw_bytes); 73 | let raw_image = request::RawImage::create( 74 | &mut builder, 75 | &request::RawImageArgs { 76 | data: Some(data), 77 | width: image.width() as _, 78 | height: image.height() as _, 79 | }, 80 | ); 81 | 82 | let image = request::Image::create( 83 | &mut builder, 84 | &request::ImageArgs { 85 | data_type: request::ImageType::RawImage, 86 | data: Some(raw_image.as_union_value()), 87 | duration: 1000, 88 | }, 89 | ); 90 | 91 | let offset = request::Request::create( 92 | &mut builder, 93 | &request::RequestArgs { 94 | command_type: request::Command::Image, 95 | command: Some(image.as_union_value()), 96 | }, 97 | ); 98 | 99 | request::finish_request_buffer(&mut builder, offset); 100 | 101 | let dat = builder.finished_data(); 102 | socket.write_u32::(dat.len() as _)?; 103 | socket.write_all(dat)?; 104 | socket.flush()?; 105 | 106 | read_reply(socket, verbose)?; 107 | 108 | Ok(()) 109 | } 110 | 111 | pub fn send_color_red(socket: &mut TcpStream, verbose: bool) -> StdResult<()> { 112 | println!("Setting color"); 113 | let mut builder = flatbuffers::FlatBufferBuilder::new(); 114 | 115 | let color = request::Color::create( 116 | &mut builder, 117 | &request::ColorArgs { 118 | data: 0x00100000, 119 | duration: 5000, 120 | }, 121 | ); 122 | 123 | let offset = request::Request::create( 124 | &mut builder, 125 | &request::RequestArgs { 126 | command_type: request::Command::Color, 127 | command: Some(color.as_union_value()), 128 | }, 129 | ); 130 | 131 | request::finish_request_buffer(&mut builder, offset); 132 | 133 | let dat = builder.finished_data(); 134 | socket.write_u32::(dat.len() as _)?; 135 | socket.write_all(dat)?; 136 | socket.flush()?; 137 | 138 | read_reply(socket, verbose)?; 139 | 140 | Ok(()) 141 | } 142 | -------------------------------------------------------------------------------- /src/hyperion_reply_generated.rs: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | extern crate flatbuffers; 3 | 4 | #[allow(unused_imports, dead_code)] 5 | pub mod hyperionnet { 6 | 7 | use std::mem; 8 | use std::cmp::Ordering; 9 | 10 | extern crate flatbuffers; 11 | use self::flatbuffers::{EndianScalar, Follow}; 12 | 13 | pub enum ReplyOffset {} 14 | #[derive(Copy, Clone, PartialEq)] 15 | 16 | pub struct Reply<'a> { 17 | pub _tab: flatbuffers::Table<'a>, 18 | } 19 | 20 | impl<'a> flatbuffers::Follow<'a> for Reply<'a> { 21 | type Inner = Reply<'a>; 22 | #[inline] 23 | fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { 24 | Self { _tab: flatbuffers::Table { buf, loc } } 25 | } 26 | } 27 | 28 | impl<'a> Reply<'a> { 29 | #[inline] 30 | pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self { 31 | Reply { _tab: table } 32 | } 33 | #[allow(unused_mut)] 34 | pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>( 35 | _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>, 36 | args: &'args ReplyArgs<'args>) -> flatbuffers::WIPOffset> { 37 | let mut builder = ReplyBuilder::new(_fbb); 38 | builder.add_registered(args.registered); 39 | builder.add_video(args.video); 40 | if let Some(x) = args.error { builder.add_error(x); } 41 | builder.finish() 42 | } 43 | 44 | pub const VT_ERROR: flatbuffers::VOffsetT = 4; 45 | pub const VT_VIDEO: flatbuffers::VOffsetT = 6; 46 | pub const VT_REGISTERED: flatbuffers::VOffsetT = 8; 47 | 48 | #[inline] 49 | pub fn error(&self) -> Option<&'a str> { 50 | self._tab.get::>(Reply::VT_ERROR, None) 51 | } 52 | #[inline] 53 | pub fn video(&self) -> i32 { 54 | self._tab.get::(Reply::VT_VIDEO, Some(-1)).unwrap() 55 | } 56 | #[inline] 57 | pub fn registered(&self) -> i32 { 58 | self._tab.get::(Reply::VT_REGISTERED, Some(-1)).unwrap() 59 | } 60 | } 61 | 62 | impl flatbuffers::Verifiable for Reply<'_> { 63 | #[inline] 64 | fn run_verifier( 65 | v: &mut flatbuffers::Verifier, pos: usize 66 | ) -> Result<(), flatbuffers::InvalidFlatbuffer> { 67 | use self::flatbuffers::Verifiable; 68 | v.visit_table(pos)? 69 | .visit_field::>(&"error", Self::VT_ERROR, false)? 70 | .visit_field::(&"video", Self::VT_VIDEO, false)? 71 | .visit_field::(&"registered", Self::VT_REGISTERED, false)? 72 | .finish(); 73 | Ok(()) 74 | } 75 | } 76 | pub struct ReplyArgs<'a> { 77 | pub error: Option>, 78 | pub video: i32, 79 | pub registered: i32, 80 | } 81 | impl<'a> Default for ReplyArgs<'a> { 82 | #[inline] 83 | fn default() -> Self { 84 | ReplyArgs { 85 | error: None, 86 | video: -1, 87 | registered: -1, 88 | } 89 | } 90 | } 91 | pub struct ReplyBuilder<'a: 'b, 'b> { 92 | fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>, 93 | start_: flatbuffers::WIPOffset, 94 | } 95 | impl<'a: 'b, 'b> ReplyBuilder<'a, 'b> { 96 | #[inline] 97 | pub fn add_error(&mut self, error: flatbuffers::WIPOffset<&'b str>) { 98 | self.fbb_.push_slot_always::>(Reply::VT_ERROR, error); 99 | } 100 | #[inline] 101 | pub fn add_video(&mut self, video: i32) { 102 | self.fbb_.push_slot::(Reply::VT_VIDEO, video, -1); 103 | } 104 | #[inline] 105 | pub fn add_registered(&mut self, registered: i32) { 106 | self.fbb_.push_slot::(Reply::VT_REGISTERED, registered, -1); 107 | } 108 | #[inline] 109 | pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> ReplyBuilder<'a, 'b> { 110 | let start = _fbb.start_table(); 111 | ReplyBuilder { 112 | fbb_: _fbb, 113 | start_: start, 114 | } 115 | } 116 | #[inline] 117 | pub fn finish(self) -> flatbuffers::WIPOffset> { 118 | let o = self.fbb_.end_table(self.start_); 119 | flatbuffers::WIPOffset::new(o.value()) 120 | } 121 | } 122 | 123 | impl std::fmt::Debug for Reply<'_> { 124 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 125 | let mut ds = f.debug_struct("Reply"); 126 | ds.field("error", &self.error()); 127 | ds.field("video", &self.video()); 128 | ds.field("registered", &self.registered()); 129 | ds.finish() 130 | } 131 | } 132 | #[inline] 133 | #[deprecated(since="2.0.0", note="Deprecated in favor of `root_as...` methods.")] 134 | pub fn get_root_as_reply<'a>(buf: &'a [u8]) -> Reply<'a> { 135 | unsafe { flatbuffers::root_unchecked::>(buf) } 136 | } 137 | 138 | #[inline] 139 | #[deprecated(since="2.0.0", note="Deprecated in favor of `root_as...` methods.")] 140 | pub fn get_size_prefixed_root_as_reply<'a>(buf: &'a [u8]) -> Reply<'a> { 141 | unsafe { flatbuffers::size_prefixed_root_unchecked::>(buf) } 142 | } 143 | 144 | #[inline] 145 | /// Verifies that a buffer of bytes contains a `Reply` 146 | /// and returns it. 147 | /// Note that verification is still experimental and may not 148 | /// catch every error, or be maximally performant. For the 149 | /// previous, unchecked, behavior use 150 | /// `root_as_reply_unchecked`. 151 | pub fn root_as_reply(buf: &[u8]) -> Result { 152 | flatbuffers::root::(buf) 153 | } 154 | #[inline] 155 | /// Verifies that a buffer of bytes contains a size prefixed 156 | /// `Reply` and returns it. 157 | /// Note that verification is still experimental and may not 158 | /// catch every error, or be maximally performant. For the 159 | /// previous, unchecked, behavior use 160 | /// `size_prefixed_root_as_reply_unchecked`. 161 | pub fn size_prefixed_root_as_reply(buf: &[u8]) -> Result { 162 | flatbuffers::size_prefixed_root::(buf) 163 | } 164 | #[inline] 165 | /// Verifies, with the given options, that a buffer of bytes 166 | /// contains a `Reply` and returns it. 167 | /// Note that verification is still experimental and may not 168 | /// catch every error, or be maximally performant. For the 169 | /// previous, unchecked, behavior use 170 | /// `root_as_reply_unchecked`. 171 | pub fn root_as_reply_with_opts<'b, 'o>( 172 | opts: &'o flatbuffers::VerifierOptions, 173 | buf: &'b [u8], 174 | ) -> Result, flatbuffers::InvalidFlatbuffer> { 175 | flatbuffers::root_with_opts::>(opts, buf) 176 | } 177 | #[inline] 178 | /// Verifies, with the given verifier options, that a buffer of 179 | /// bytes contains a size prefixed `Reply` and returns 180 | /// it. Note that verification is still experimental and may not 181 | /// catch every error, or be maximally performant. For the 182 | /// previous, unchecked, behavior use 183 | /// `root_as_reply_unchecked`. 184 | pub fn size_prefixed_root_as_reply_with_opts<'b, 'o>( 185 | opts: &'o flatbuffers::VerifierOptions, 186 | buf: &'b [u8], 187 | ) -> Result, flatbuffers::InvalidFlatbuffer> { 188 | flatbuffers::size_prefixed_root_with_opts::>(opts, buf) 189 | } 190 | #[inline] 191 | /// Assumes, without verification, that a buffer of bytes contains a Reply and returns it. 192 | /// # Safety 193 | /// Callers must trust the given bytes do indeed contain a valid `Reply`. 194 | pub unsafe fn root_as_reply_unchecked(buf: &[u8]) -> Reply { 195 | flatbuffers::root_unchecked::(buf) 196 | } 197 | #[inline] 198 | /// Assumes, without verification, that a buffer of bytes contains a size prefixed Reply and returns it. 199 | /// # Safety 200 | /// Callers must trust the given bytes do indeed contain a valid size prefixed `Reply`. 201 | pub unsafe fn size_prefixed_root_as_reply_unchecked(buf: &[u8]) -> Reply { 202 | flatbuffers::size_prefixed_root_unchecked::(buf) 203 | } 204 | #[inline] 205 | pub fn finish_reply_buffer<'a, 'b>( 206 | fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>, 207 | root: flatbuffers::WIPOffset>) { 208 | fbb.finish(root, None); 209 | } 210 | 211 | #[inline] 212 | pub fn finish_size_prefixed_reply_buffer<'a, 'b>(fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>, root: flatbuffers::WIPOffset>) { 213 | fbb.finish_size_prefixed(root, None); 214 | } 215 | } // pub mod hyperionnet 216 | 217 | -------------------------------------------------------------------------------- /src/hyperion_request_generated.rs: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | extern crate flatbuffers; 3 | 4 | #[allow(unused_imports, dead_code)] 5 | pub mod hyperionnet { 6 | 7 | use std::mem; 8 | use std::cmp::Ordering; 9 | 10 | extern crate flatbuffers; 11 | use self::flatbuffers::{EndianScalar, Follow}; 12 | 13 | #[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] 14 | pub const ENUM_MIN_IMAGE_TYPE: u8 = 0; 15 | #[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] 16 | pub const ENUM_MAX_IMAGE_TYPE: u8 = 1; 17 | #[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] 18 | #[allow(non_camel_case_types)] 19 | pub const ENUM_VALUES_IMAGE_TYPE: [ImageType; 2] = [ 20 | ImageType::NONE, 21 | ImageType::RawImage, 22 | ]; 23 | 24 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 25 | #[repr(transparent)] 26 | pub struct ImageType(pub u8); 27 | #[allow(non_upper_case_globals)] 28 | impl ImageType { 29 | pub const NONE: Self = Self(0); 30 | pub const RawImage: Self = Self(1); 31 | 32 | pub const ENUM_MIN: u8 = 0; 33 | pub const ENUM_MAX: u8 = 1; 34 | pub const ENUM_VALUES: &'static [Self] = &[ 35 | Self::NONE, 36 | Self::RawImage, 37 | ]; 38 | /// Returns the variant's name or "" if unknown. 39 | pub fn variant_name(self) -> Option<&'static str> { 40 | match self { 41 | Self::NONE => Some("NONE"), 42 | Self::RawImage => Some("RawImage"), 43 | _ => None, 44 | } 45 | } 46 | } 47 | impl std::fmt::Debug for ImageType { 48 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 49 | if let Some(name) = self.variant_name() { 50 | f.write_str(name) 51 | } else { 52 | f.write_fmt(format_args!("", self.0)) 53 | } 54 | } 55 | } 56 | impl<'a> flatbuffers::Follow<'a> for ImageType { 57 | type Inner = Self; 58 | #[inline] 59 | fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { 60 | let b = unsafe { 61 | flatbuffers::read_scalar_at::(buf, loc) 62 | }; 63 | Self(b) 64 | } 65 | } 66 | 67 | impl flatbuffers::Push for ImageType { 68 | type Output = ImageType; 69 | #[inline] 70 | fn push(&self, dst: &mut [u8], _rest: &[u8]) { 71 | unsafe { flatbuffers::emplace_scalar::(dst, self.0); } 72 | } 73 | } 74 | 75 | impl flatbuffers::EndianScalar for ImageType { 76 | #[inline] 77 | fn to_little_endian(self) -> Self { 78 | let b = u8::to_le(self.0); 79 | Self(b) 80 | } 81 | #[inline] 82 | #[allow(clippy::wrong_self_convention)] 83 | fn from_little_endian(self) -> Self { 84 | let b = u8::from_le(self.0); 85 | Self(b) 86 | } 87 | } 88 | 89 | impl<'a> flatbuffers::Verifiable for ImageType { 90 | #[inline] 91 | fn run_verifier( 92 | v: &mut flatbuffers::Verifier, pos: usize 93 | ) -> Result<(), flatbuffers::InvalidFlatbuffer> { 94 | use self::flatbuffers::Verifiable; 95 | u8::run_verifier(v, pos) 96 | } 97 | } 98 | 99 | impl flatbuffers::SimpleToVerifyInSlice for ImageType {} 100 | pub struct ImageTypeUnionTableOffset {} 101 | 102 | #[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] 103 | pub const ENUM_MIN_COMMAND: u8 = 0; 104 | #[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] 105 | pub const ENUM_MAX_COMMAND: u8 = 4; 106 | #[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] 107 | #[allow(non_camel_case_types)] 108 | pub const ENUM_VALUES_COMMAND: [Command; 5] = [ 109 | Command::NONE, 110 | Command::Color, 111 | Command::Image, 112 | Command::Clear, 113 | Command::Register, 114 | ]; 115 | 116 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 117 | #[repr(transparent)] 118 | pub struct Command(pub u8); 119 | #[allow(non_upper_case_globals)] 120 | impl Command { 121 | pub const NONE: Self = Self(0); 122 | pub const Color: Self = Self(1); 123 | pub const Image: Self = Self(2); 124 | pub const Clear: Self = Self(3); 125 | pub const Register: Self = Self(4); 126 | 127 | pub const ENUM_MIN: u8 = 0; 128 | pub const ENUM_MAX: u8 = 4; 129 | pub const ENUM_VALUES: &'static [Self] = &[ 130 | Self::NONE, 131 | Self::Color, 132 | Self::Image, 133 | Self::Clear, 134 | Self::Register, 135 | ]; 136 | /// Returns the variant's name or "" if unknown. 137 | pub fn variant_name(self) -> Option<&'static str> { 138 | match self { 139 | Self::NONE => Some("NONE"), 140 | Self::Color => Some("Color"), 141 | Self::Image => Some("Image"), 142 | Self::Clear => Some("Clear"), 143 | Self::Register => Some("Register"), 144 | _ => None, 145 | } 146 | } 147 | } 148 | impl std::fmt::Debug for Command { 149 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 150 | if let Some(name) = self.variant_name() { 151 | f.write_str(name) 152 | } else { 153 | f.write_fmt(format_args!("", self.0)) 154 | } 155 | } 156 | } 157 | impl<'a> flatbuffers::Follow<'a> for Command { 158 | type Inner = Self; 159 | #[inline] 160 | fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { 161 | let b = unsafe { 162 | flatbuffers::read_scalar_at::(buf, loc) 163 | }; 164 | Self(b) 165 | } 166 | } 167 | 168 | impl flatbuffers::Push for Command { 169 | type Output = Command; 170 | #[inline] 171 | fn push(&self, dst: &mut [u8], _rest: &[u8]) { 172 | unsafe { flatbuffers::emplace_scalar::(dst, self.0); } 173 | } 174 | } 175 | 176 | impl flatbuffers::EndianScalar for Command { 177 | #[inline] 178 | fn to_little_endian(self) -> Self { 179 | let b = u8::to_le(self.0); 180 | Self(b) 181 | } 182 | #[inline] 183 | #[allow(clippy::wrong_self_convention)] 184 | fn from_little_endian(self) -> Self { 185 | let b = u8::from_le(self.0); 186 | Self(b) 187 | } 188 | } 189 | 190 | impl<'a> flatbuffers::Verifiable for Command { 191 | #[inline] 192 | fn run_verifier( 193 | v: &mut flatbuffers::Verifier, pos: usize 194 | ) -> Result<(), flatbuffers::InvalidFlatbuffer> { 195 | use self::flatbuffers::Verifiable; 196 | u8::run_verifier(v, pos) 197 | } 198 | } 199 | 200 | impl flatbuffers::SimpleToVerifyInSlice for Command {} 201 | pub struct CommandUnionTableOffset {} 202 | 203 | pub enum RegisterOffset {} 204 | #[derive(Copy, Clone, PartialEq)] 205 | 206 | pub struct Register<'a> { 207 | pub _tab: flatbuffers::Table<'a>, 208 | } 209 | 210 | impl<'a> flatbuffers::Follow<'a> for Register<'a> { 211 | type Inner = Register<'a>; 212 | #[inline] 213 | fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { 214 | Self { _tab: flatbuffers::Table { buf, loc } } 215 | } 216 | } 217 | 218 | impl<'a> Register<'a> { 219 | #[inline] 220 | pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self { 221 | Register { _tab: table } 222 | } 223 | #[allow(unused_mut)] 224 | pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>( 225 | _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>, 226 | args: &'args RegisterArgs<'args>) -> flatbuffers::WIPOffset> { 227 | let mut builder = RegisterBuilder::new(_fbb); 228 | builder.add_priority(args.priority); 229 | if let Some(x) = args.origin { builder.add_origin(x); } 230 | builder.finish() 231 | } 232 | 233 | pub const VT_ORIGIN: flatbuffers::VOffsetT = 4; 234 | pub const VT_PRIORITY: flatbuffers::VOffsetT = 6; 235 | 236 | #[inline] 237 | pub fn origin(&self) -> &'a str { 238 | self._tab.get::>(Register::VT_ORIGIN, None).unwrap() 239 | } 240 | #[inline] 241 | pub fn priority(&self) -> i32 { 242 | self._tab.get::(Register::VT_PRIORITY, Some(0)).unwrap() 243 | } 244 | } 245 | 246 | impl flatbuffers::Verifiable for Register<'_> { 247 | #[inline] 248 | fn run_verifier( 249 | v: &mut flatbuffers::Verifier, pos: usize 250 | ) -> Result<(), flatbuffers::InvalidFlatbuffer> { 251 | use self::flatbuffers::Verifiable; 252 | v.visit_table(pos)? 253 | .visit_field::>(&"origin", Self::VT_ORIGIN, true)? 254 | .visit_field::(&"priority", Self::VT_PRIORITY, false)? 255 | .finish(); 256 | Ok(()) 257 | } 258 | } 259 | pub struct RegisterArgs<'a> { 260 | pub origin: Option>, 261 | pub priority: i32, 262 | } 263 | impl<'a> Default for RegisterArgs<'a> { 264 | #[inline] 265 | fn default() -> Self { 266 | RegisterArgs { 267 | origin: None, // required field 268 | priority: 0, 269 | } 270 | } 271 | } 272 | pub struct RegisterBuilder<'a: 'b, 'b> { 273 | fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>, 274 | start_: flatbuffers::WIPOffset, 275 | } 276 | impl<'a: 'b, 'b> RegisterBuilder<'a, 'b> { 277 | #[inline] 278 | pub fn add_origin(&mut self, origin: flatbuffers::WIPOffset<&'b str>) { 279 | self.fbb_.push_slot_always::>(Register::VT_ORIGIN, origin); 280 | } 281 | #[inline] 282 | pub fn add_priority(&mut self, priority: i32) { 283 | self.fbb_.push_slot::(Register::VT_PRIORITY, priority, 0); 284 | } 285 | #[inline] 286 | pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> RegisterBuilder<'a, 'b> { 287 | let start = _fbb.start_table(); 288 | RegisterBuilder { 289 | fbb_: _fbb, 290 | start_: start, 291 | } 292 | } 293 | #[inline] 294 | pub fn finish(self) -> flatbuffers::WIPOffset> { 295 | let o = self.fbb_.end_table(self.start_); 296 | self.fbb_.required(o, Register::VT_ORIGIN,"origin"); 297 | flatbuffers::WIPOffset::new(o.value()) 298 | } 299 | } 300 | 301 | impl std::fmt::Debug for Register<'_> { 302 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 303 | let mut ds = f.debug_struct("Register"); 304 | ds.field("origin", &self.origin()); 305 | ds.field("priority", &self.priority()); 306 | ds.finish() 307 | } 308 | } 309 | pub enum RawImageOffset {} 310 | #[derive(Copy, Clone, PartialEq)] 311 | 312 | pub struct RawImage<'a> { 313 | pub _tab: flatbuffers::Table<'a>, 314 | } 315 | 316 | impl<'a> flatbuffers::Follow<'a> for RawImage<'a> { 317 | type Inner = RawImage<'a>; 318 | #[inline] 319 | fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { 320 | Self { _tab: flatbuffers::Table { buf, loc } } 321 | } 322 | } 323 | 324 | impl<'a> RawImage<'a> { 325 | #[inline] 326 | pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self { 327 | RawImage { _tab: table } 328 | } 329 | #[allow(unused_mut)] 330 | pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>( 331 | _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>, 332 | args: &'args RawImageArgs<'args>) -> flatbuffers::WIPOffset> { 333 | let mut builder = RawImageBuilder::new(_fbb); 334 | builder.add_height(args.height); 335 | builder.add_width(args.width); 336 | if let Some(x) = args.data { builder.add_data(x); } 337 | builder.finish() 338 | } 339 | 340 | pub const VT_DATA: flatbuffers::VOffsetT = 4; 341 | pub const VT_WIDTH: flatbuffers::VOffsetT = 6; 342 | pub const VT_HEIGHT: flatbuffers::VOffsetT = 8; 343 | 344 | #[inline] 345 | pub fn data(&self) -> Option<&'a [u8]> { 346 | self._tab.get::>>(RawImage::VT_DATA, None).map(|v| v.safe_slice()) 347 | } 348 | #[inline] 349 | pub fn width(&self) -> i32 { 350 | self._tab.get::(RawImage::VT_WIDTH, Some(-1)).unwrap() 351 | } 352 | #[inline] 353 | pub fn height(&self) -> i32 { 354 | self._tab.get::(RawImage::VT_HEIGHT, Some(-1)).unwrap() 355 | } 356 | } 357 | 358 | impl flatbuffers::Verifiable for RawImage<'_> { 359 | #[inline] 360 | fn run_verifier( 361 | v: &mut flatbuffers::Verifier, pos: usize 362 | ) -> Result<(), flatbuffers::InvalidFlatbuffer> { 363 | use self::flatbuffers::Verifiable; 364 | v.visit_table(pos)? 365 | .visit_field::>>(&"data", Self::VT_DATA, false)? 366 | .visit_field::(&"width", Self::VT_WIDTH, false)? 367 | .visit_field::(&"height", Self::VT_HEIGHT, false)? 368 | .finish(); 369 | Ok(()) 370 | } 371 | } 372 | pub struct RawImageArgs<'a> { 373 | pub data: Option>>, 374 | pub width: i32, 375 | pub height: i32, 376 | } 377 | impl<'a> Default for RawImageArgs<'a> { 378 | #[inline] 379 | fn default() -> Self { 380 | RawImageArgs { 381 | data: None, 382 | width: -1, 383 | height: -1, 384 | } 385 | } 386 | } 387 | pub struct RawImageBuilder<'a: 'b, 'b> { 388 | fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>, 389 | start_: flatbuffers::WIPOffset, 390 | } 391 | impl<'a: 'b, 'b> RawImageBuilder<'a, 'b> { 392 | #[inline] 393 | pub fn add_data(&mut self, data: flatbuffers::WIPOffset>) { 394 | self.fbb_.push_slot_always::>(RawImage::VT_DATA, data); 395 | } 396 | #[inline] 397 | pub fn add_width(&mut self, width: i32) { 398 | self.fbb_.push_slot::(RawImage::VT_WIDTH, width, -1); 399 | } 400 | #[inline] 401 | pub fn add_height(&mut self, height: i32) { 402 | self.fbb_.push_slot::(RawImage::VT_HEIGHT, height, -1); 403 | } 404 | #[inline] 405 | pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> RawImageBuilder<'a, 'b> { 406 | let start = _fbb.start_table(); 407 | RawImageBuilder { 408 | fbb_: _fbb, 409 | start_: start, 410 | } 411 | } 412 | #[inline] 413 | pub fn finish(self) -> flatbuffers::WIPOffset> { 414 | let o = self.fbb_.end_table(self.start_); 415 | flatbuffers::WIPOffset::new(o.value()) 416 | } 417 | } 418 | 419 | impl std::fmt::Debug for RawImage<'_> { 420 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 421 | let mut ds = f.debug_struct("RawImage"); 422 | ds.field("data", &self.data()); 423 | ds.field("width", &self.width()); 424 | ds.field("height", &self.height()); 425 | ds.finish() 426 | } 427 | } 428 | pub enum ImageOffset {} 429 | #[derive(Copy, Clone, PartialEq)] 430 | 431 | pub struct Image<'a> { 432 | pub _tab: flatbuffers::Table<'a>, 433 | } 434 | 435 | impl<'a> flatbuffers::Follow<'a> for Image<'a> { 436 | type Inner = Image<'a>; 437 | #[inline] 438 | fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { 439 | Self { _tab: flatbuffers::Table { buf, loc } } 440 | } 441 | } 442 | 443 | impl<'a> Image<'a> { 444 | #[inline] 445 | pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self { 446 | Image { _tab: table } 447 | } 448 | #[allow(unused_mut)] 449 | pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>( 450 | _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>, 451 | args: &'args ImageArgs) -> flatbuffers::WIPOffset> { 452 | let mut builder = ImageBuilder::new(_fbb); 453 | builder.add_duration(args.duration); 454 | if let Some(x) = args.data { builder.add_data(x); } 455 | builder.add_data_type(args.data_type); 456 | builder.finish() 457 | } 458 | 459 | pub const VT_DATA_TYPE: flatbuffers::VOffsetT = 4; 460 | pub const VT_DATA: flatbuffers::VOffsetT = 6; 461 | pub const VT_DURATION: flatbuffers::VOffsetT = 8; 462 | 463 | #[inline] 464 | pub fn data_type(&self) -> ImageType { 465 | self._tab.get::(Image::VT_DATA_TYPE, Some(ImageType::NONE)).unwrap() 466 | } 467 | #[inline] 468 | pub fn data(&self) -> flatbuffers::Table<'a> { 469 | self._tab.get::>>(Image::VT_DATA, None).unwrap() 470 | } 471 | #[inline] 472 | pub fn duration(&self) -> i32 { 473 | self._tab.get::(Image::VT_DURATION, Some(-1)).unwrap() 474 | } 475 | #[inline] 476 | #[allow(non_snake_case)] 477 | pub fn data_as_raw_image(&self) -> Option> { 478 | if self.data_type() == ImageType::RawImage { 479 | let u = self.data(); 480 | Some(RawImage::init_from_table(u)) 481 | } else { 482 | None 483 | } 484 | } 485 | 486 | } 487 | 488 | impl flatbuffers::Verifiable for Image<'_> { 489 | #[inline] 490 | fn run_verifier( 491 | v: &mut flatbuffers::Verifier, pos: usize 492 | ) -> Result<(), flatbuffers::InvalidFlatbuffer> { 493 | use self::flatbuffers::Verifiable; 494 | v.visit_table(pos)? 495 | .visit_union::(&"data_type", Self::VT_DATA_TYPE, &"data", Self::VT_DATA, true, |key, v, pos| { 496 | match key { 497 | ImageType::RawImage => v.verify_union_variant::>("ImageType::RawImage", pos), 498 | _ => Ok(()), 499 | } 500 | })? 501 | .visit_field::(&"duration", Self::VT_DURATION, false)? 502 | .finish(); 503 | Ok(()) 504 | } 505 | } 506 | pub struct ImageArgs { 507 | pub data_type: ImageType, 508 | pub data: Option>, 509 | pub duration: i32, 510 | } 511 | impl<'a> Default for ImageArgs { 512 | #[inline] 513 | fn default() -> Self { 514 | ImageArgs { 515 | data_type: ImageType::NONE, 516 | data: None, // required field 517 | duration: -1, 518 | } 519 | } 520 | } 521 | pub struct ImageBuilder<'a: 'b, 'b> { 522 | fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>, 523 | start_: flatbuffers::WIPOffset, 524 | } 525 | impl<'a: 'b, 'b> ImageBuilder<'a, 'b> { 526 | #[inline] 527 | pub fn add_data_type(&mut self, data_type: ImageType) { 528 | self.fbb_.push_slot::(Image::VT_DATA_TYPE, data_type, ImageType::NONE); 529 | } 530 | #[inline] 531 | pub fn add_data(&mut self, data: flatbuffers::WIPOffset) { 532 | self.fbb_.push_slot_always::>(Image::VT_DATA, data); 533 | } 534 | #[inline] 535 | pub fn add_duration(&mut self, duration: i32) { 536 | self.fbb_.push_slot::(Image::VT_DURATION, duration, -1); 537 | } 538 | #[inline] 539 | pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> ImageBuilder<'a, 'b> { 540 | let start = _fbb.start_table(); 541 | ImageBuilder { 542 | fbb_: _fbb, 543 | start_: start, 544 | } 545 | } 546 | #[inline] 547 | pub fn finish(self) -> flatbuffers::WIPOffset> { 548 | let o = self.fbb_.end_table(self.start_); 549 | self.fbb_.required(o, Image::VT_DATA,"data"); 550 | flatbuffers::WIPOffset::new(o.value()) 551 | } 552 | } 553 | 554 | impl std::fmt::Debug for Image<'_> { 555 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 556 | let mut ds = f.debug_struct("Image"); 557 | ds.field("data_type", &self.data_type()); 558 | match self.data_type() { 559 | ImageType::RawImage => { 560 | if let Some(x) = self.data_as_raw_image() { 561 | ds.field("data", &x) 562 | } else { 563 | ds.field("data", &"InvalidFlatbuffer: Union discriminant does not match value.") 564 | } 565 | }, 566 | _ => { 567 | let x: Option<()> = None; 568 | ds.field("data", &x) 569 | }, 570 | }; 571 | ds.field("duration", &self.duration()); 572 | ds.finish() 573 | } 574 | } 575 | pub enum ClearOffset {} 576 | #[derive(Copy, Clone, PartialEq)] 577 | 578 | pub struct Clear<'a> { 579 | pub _tab: flatbuffers::Table<'a>, 580 | } 581 | 582 | impl<'a> flatbuffers::Follow<'a> for Clear<'a> { 583 | type Inner = Clear<'a>; 584 | #[inline] 585 | fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { 586 | Self { _tab: flatbuffers::Table { buf, loc } } 587 | } 588 | } 589 | 590 | impl<'a> Clear<'a> { 591 | #[inline] 592 | pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self { 593 | Clear { _tab: table } 594 | } 595 | #[allow(unused_mut)] 596 | pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>( 597 | _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>, 598 | args: &'args ClearArgs) -> flatbuffers::WIPOffset> { 599 | let mut builder = ClearBuilder::new(_fbb); 600 | builder.add_priority(args.priority); 601 | builder.finish() 602 | } 603 | 604 | pub const VT_PRIORITY: flatbuffers::VOffsetT = 4; 605 | 606 | #[inline] 607 | pub fn priority(&self) -> i32 { 608 | self._tab.get::(Clear::VT_PRIORITY, Some(0)).unwrap() 609 | } 610 | } 611 | 612 | impl flatbuffers::Verifiable for Clear<'_> { 613 | #[inline] 614 | fn run_verifier( 615 | v: &mut flatbuffers::Verifier, pos: usize 616 | ) -> Result<(), flatbuffers::InvalidFlatbuffer> { 617 | use self::flatbuffers::Verifiable; 618 | v.visit_table(pos)? 619 | .visit_field::(&"priority", Self::VT_PRIORITY, false)? 620 | .finish(); 621 | Ok(()) 622 | } 623 | } 624 | pub struct ClearArgs { 625 | pub priority: i32, 626 | } 627 | impl<'a> Default for ClearArgs { 628 | #[inline] 629 | fn default() -> Self { 630 | ClearArgs { 631 | priority: 0, 632 | } 633 | } 634 | } 635 | pub struct ClearBuilder<'a: 'b, 'b> { 636 | fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>, 637 | start_: flatbuffers::WIPOffset, 638 | } 639 | impl<'a: 'b, 'b> ClearBuilder<'a, 'b> { 640 | #[inline] 641 | pub fn add_priority(&mut self, priority: i32) { 642 | self.fbb_.push_slot::(Clear::VT_PRIORITY, priority, 0); 643 | } 644 | #[inline] 645 | pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> ClearBuilder<'a, 'b> { 646 | let start = _fbb.start_table(); 647 | ClearBuilder { 648 | fbb_: _fbb, 649 | start_: start, 650 | } 651 | } 652 | #[inline] 653 | pub fn finish(self) -> flatbuffers::WIPOffset> { 654 | let o = self.fbb_.end_table(self.start_); 655 | flatbuffers::WIPOffset::new(o.value()) 656 | } 657 | } 658 | 659 | impl std::fmt::Debug for Clear<'_> { 660 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 661 | let mut ds = f.debug_struct("Clear"); 662 | ds.field("priority", &self.priority()); 663 | ds.finish() 664 | } 665 | } 666 | pub enum ColorOffset {} 667 | #[derive(Copy, Clone, PartialEq)] 668 | 669 | pub struct Color<'a> { 670 | pub _tab: flatbuffers::Table<'a>, 671 | } 672 | 673 | impl<'a> flatbuffers::Follow<'a> for Color<'a> { 674 | type Inner = Color<'a>; 675 | #[inline] 676 | fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { 677 | Self { _tab: flatbuffers::Table { buf, loc } } 678 | } 679 | } 680 | 681 | impl<'a> Color<'a> { 682 | #[inline] 683 | pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self { 684 | Color { _tab: table } 685 | } 686 | #[allow(unused_mut)] 687 | pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>( 688 | _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>, 689 | args: &'args ColorArgs) -> flatbuffers::WIPOffset> { 690 | let mut builder = ColorBuilder::new(_fbb); 691 | builder.add_duration(args.duration); 692 | builder.add_data(args.data); 693 | builder.finish() 694 | } 695 | 696 | pub const VT_DATA: flatbuffers::VOffsetT = 4; 697 | pub const VT_DURATION: flatbuffers::VOffsetT = 6; 698 | 699 | #[inline] 700 | pub fn data(&self) -> i32 { 701 | self._tab.get::(Color::VT_DATA, Some(-1)).unwrap() 702 | } 703 | #[inline] 704 | pub fn duration(&self) -> i32 { 705 | self._tab.get::(Color::VT_DURATION, Some(-1)).unwrap() 706 | } 707 | } 708 | 709 | impl flatbuffers::Verifiable for Color<'_> { 710 | #[inline] 711 | fn run_verifier( 712 | v: &mut flatbuffers::Verifier, pos: usize 713 | ) -> Result<(), flatbuffers::InvalidFlatbuffer> { 714 | use self::flatbuffers::Verifiable; 715 | v.visit_table(pos)? 716 | .visit_field::(&"data", Self::VT_DATA, false)? 717 | .visit_field::(&"duration", Self::VT_DURATION, false)? 718 | .finish(); 719 | Ok(()) 720 | } 721 | } 722 | pub struct ColorArgs { 723 | pub data: i32, 724 | pub duration: i32, 725 | } 726 | impl<'a> Default for ColorArgs { 727 | #[inline] 728 | fn default() -> Self { 729 | ColorArgs { 730 | data: -1, 731 | duration: -1, 732 | } 733 | } 734 | } 735 | pub struct ColorBuilder<'a: 'b, 'b> { 736 | fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>, 737 | start_: flatbuffers::WIPOffset, 738 | } 739 | impl<'a: 'b, 'b> ColorBuilder<'a, 'b> { 740 | #[inline] 741 | pub fn add_data(&mut self, data: i32) { 742 | self.fbb_.push_slot::(Color::VT_DATA, data, -1); 743 | } 744 | #[inline] 745 | pub fn add_duration(&mut self, duration: i32) { 746 | self.fbb_.push_slot::(Color::VT_DURATION, duration, -1); 747 | } 748 | #[inline] 749 | pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> ColorBuilder<'a, 'b> { 750 | let start = _fbb.start_table(); 751 | ColorBuilder { 752 | fbb_: _fbb, 753 | start_: start, 754 | } 755 | } 756 | #[inline] 757 | pub fn finish(self) -> flatbuffers::WIPOffset> { 758 | let o = self.fbb_.end_table(self.start_); 759 | flatbuffers::WIPOffset::new(o.value()) 760 | } 761 | } 762 | 763 | impl std::fmt::Debug for Color<'_> { 764 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 765 | let mut ds = f.debug_struct("Color"); 766 | ds.field("data", &self.data()); 767 | ds.field("duration", &self.duration()); 768 | ds.finish() 769 | } 770 | } 771 | pub enum RequestOffset {} 772 | #[derive(Copy, Clone, PartialEq)] 773 | 774 | pub struct Request<'a> { 775 | pub _tab: flatbuffers::Table<'a>, 776 | } 777 | 778 | impl<'a> flatbuffers::Follow<'a> for Request<'a> { 779 | type Inner = Request<'a>; 780 | #[inline] 781 | fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { 782 | Self { _tab: flatbuffers::Table { buf, loc } } 783 | } 784 | } 785 | 786 | impl<'a> Request<'a> { 787 | #[inline] 788 | pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self { 789 | Request { _tab: table } 790 | } 791 | #[allow(unused_mut)] 792 | pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>( 793 | _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>, 794 | args: &'args RequestArgs) -> flatbuffers::WIPOffset> { 795 | let mut builder = RequestBuilder::new(_fbb); 796 | if let Some(x) = args.command { builder.add_command(x); } 797 | builder.add_command_type(args.command_type); 798 | builder.finish() 799 | } 800 | 801 | pub const VT_COMMAND_TYPE: flatbuffers::VOffsetT = 4; 802 | pub const VT_COMMAND: flatbuffers::VOffsetT = 6; 803 | 804 | #[inline] 805 | pub fn command_type(&self) -> Command { 806 | self._tab.get::(Request::VT_COMMAND_TYPE, Some(Command::NONE)).unwrap() 807 | } 808 | #[inline] 809 | pub fn command(&self) -> flatbuffers::Table<'a> { 810 | self._tab.get::>>(Request::VT_COMMAND, None).unwrap() 811 | } 812 | #[inline] 813 | #[allow(non_snake_case)] 814 | pub fn command_as_color(&self) -> Option> { 815 | if self.command_type() == Command::Color { 816 | let u = self.command(); 817 | Some(Color::init_from_table(u)) 818 | } else { 819 | None 820 | } 821 | } 822 | 823 | #[inline] 824 | #[allow(non_snake_case)] 825 | pub fn command_as_image(&self) -> Option> { 826 | if self.command_type() == Command::Image { 827 | let u = self.command(); 828 | Some(Image::init_from_table(u)) 829 | } else { 830 | None 831 | } 832 | } 833 | 834 | #[inline] 835 | #[allow(non_snake_case)] 836 | pub fn command_as_clear(&self) -> Option> { 837 | if self.command_type() == Command::Clear { 838 | let u = self.command(); 839 | Some(Clear::init_from_table(u)) 840 | } else { 841 | None 842 | } 843 | } 844 | 845 | #[inline] 846 | #[allow(non_snake_case)] 847 | pub fn command_as_register(&self) -> Option> { 848 | if self.command_type() == Command::Register { 849 | let u = self.command(); 850 | Some(Register::init_from_table(u)) 851 | } else { 852 | None 853 | } 854 | } 855 | 856 | } 857 | 858 | impl flatbuffers::Verifiable for Request<'_> { 859 | #[inline] 860 | fn run_verifier( 861 | v: &mut flatbuffers::Verifier, pos: usize 862 | ) -> Result<(), flatbuffers::InvalidFlatbuffer> { 863 | use self::flatbuffers::Verifiable; 864 | v.visit_table(pos)? 865 | .visit_union::(&"command_type", Self::VT_COMMAND_TYPE, &"command", Self::VT_COMMAND, true, |key, v, pos| { 866 | match key { 867 | Command::Color => v.verify_union_variant::>("Command::Color", pos), 868 | Command::Image => v.verify_union_variant::>("Command::Image", pos), 869 | Command::Clear => v.verify_union_variant::>("Command::Clear", pos), 870 | Command::Register => v.verify_union_variant::>("Command::Register", pos), 871 | _ => Ok(()), 872 | } 873 | })? 874 | .finish(); 875 | Ok(()) 876 | } 877 | } 878 | pub struct RequestArgs { 879 | pub command_type: Command, 880 | pub command: Option>, 881 | } 882 | impl<'a> Default for RequestArgs { 883 | #[inline] 884 | fn default() -> Self { 885 | RequestArgs { 886 | command_type: Command::NONE, 887 | command: None, // required field 888 | } 889 | } 890 | } 891 | pub struct RequestBuilder<'a: 'b, 'b> { 892 | fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>, 893 | start_: flatbuffers::WIPOffset, 894 | } 895 | impl<'a: 'b, 'b> RequestBuilder<'a, 'b> { 896 | #[inline] 897 | pub fn add_command_type(&mut self, command_type: Command) { 898 | self.fbb_.push_slot::(Request::VT_COMMAND_TYPE, command_type, Command::NONE); 899 | } 900 | #[inline] 901 | pub fn add_command(&mut self, command: flatbuffers::WIPOffset) { 902 | self.fbb_.push_slot_always::>(Request::VT_COMMAND, command); 903 | } 904 | #[inline] 905 | pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> RequestBuilder<'a, 'b> { 906 | let start = _fbb.start_table(); 907 | RequestBuilder { 908 | fbb_: _fbb, 909 | start_: start, 910 | } 911 | } 912 | #[inline] 913 | pub fn finish(self) -> flatbuffers::WIPOffset> { 914 | let o = self.fbb_.end_table(self.start_); 915 | self.fbb_.required(o, Request::VT_COMMAND,"command"); 916 | flatbuffers::WIPOffset::new(o.value()) 917 | } 918 | } 919 | 920 | impl std::fmt::Debug for Request<'_> { 921 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 922 | let mut ds = f.debug_struct("Request"); 923 | ds.field("command_type", &self.command_type()); 924 | match self.command_type() { 925 | Command::Color => { 926 | if let Some(x) = self.command_as_color() { 927 | ds.field("command", &x) 928 | } else { 929 | ds.field("command", &"InvalidFlatbuffer: Union discriminant does not match value.") 930 | } 931 | }, 932 | Command::Image => { 933 | if let Some(x) = self.command_as_image() { 934 | ds.field("command", &x) 935 | } else { 936 | ds.field("command", &"InvalidFlatbuffer: Union discriminant does not match value.") 937 | } 938 | }, 939 | Command::Clear => { 940 | if let Some(x) = self.command_as_clear() { 941 | ds.field("command", &x) 942 | } else { 943 | ds.field("command", &"InvalidFlatbuffer: Union discriminant does not match value.") 944 | } 945 | }, 946 | Command::Register => { 947 | if let Some(x) = self.command_as_register() { 948 | ds.field("command", &x) 949 | } else { 950 | ds.field("command", &"InvalidFlatbuffer: Union discriminant does not match value.") 951 | } 952 | }, 953 | _ => { 954 | let x: Option<()> = None; 955 | ds.field("command", &x) 956 | }, 957 | }; 958 | ds.finish() 959 | } 960 | } 961 | #[inline] 962 | #[deprecated(since="2.0.0", note="Deprecated in favor of `root_as...` methods.")] 963 | pub fn get_root_as_request<'a>(buf: &'a [u8]) -> Request<'a> { 964 | unsafe { flatbuffers::root_unchecked::>(buf) } 965 | } 966 | 967 | #[inline] 968 | #[deprecated(since="2.0.0", note="Deprecated in favor of `root_as...` methods.")] 969 | pub fn get_size_prefixed_root_as_request<'a>(buf: &'a [u8]) -> Request<'a> { 970 | unsafe { flatbuffers::size_prefixed_root_unchecked::>(buf) } 971 | } 972 | 973 | #[inline] 974 | /// Verifies that a buffer of bytes contains a `Request` 975 | /// and returns it. 976 | /// Note that verification is still experimental and may not 977 | /// catch every error, or be maximally performant. For the 978 | /// previous, unchecked, behavior use 979 | /// `root_as_request_unchecked`. 980 | pub fn root_as_request(buf: &[u8]) -> Result { 981 | flatbuffers::root::(buf) 982 | } 983 | #[inline] 984 | /// Verifies that a buffer of bytes contains a size prefixed 985 | /// `Request` and returns it. 986 | /// Note that verification is still experimental and may not 987 | /// catch every error, or be maximally performant. For the 988 | /// previous, unchecked, behavior use 989 | /// `size_prefixed_root_as_request_unchecked`. 990 | pub fn size_prefixed_root_as_request(buf: &[u8]) -> Result { 991 | flatbuffers::size_prefixed_root::(buf) 992 | } 993 | #[inline] 994 | /// Verifies, with the given options, that a buffer of bytes 995 | /// contains a `Request` and returns it. 996 | /// Note that verification is still experimental and may not 997 | /// catch every error, or be maximally performant. For the 998 | /// previous, unchecked, behavior use 999 | /// `root_as_request_unchecked`. 1000 | pub fn root_as_request_with_opts<'b, 'o>( 1001 | opts: &'o flatbuffers::VerifierOptions, 1002 | buf: &'b [u8], 1003 | ) -> Result, flatbuffers::InvalidFlatbuffer> { 1004 | flatbuffers::root_with_opts::>(opts, buf) 1005 | } 1006 | #[inline] 1007 | /// Verifies, with the given verifier options, that a buffer of 1008 | /// bytes contains a size prefixed `Request` and returns 1009 | /// it. Note that verification is still experimental and may not 1010 | /// catch every error, or be maximally performant. For the 1011 | /// previous, unchecked, behavior use 1012 | /// `root_as_request_unchecked`. 1013 | pub fn size_prefixed_root_as_request_with_opts<'b, 'o>( 1014 | opts: &'o flatbuffers::VerifierOptions, 1015 | buf: &'b [u8], 1016 | ) -> Result, flatbuffers::InvalidFlatbuffer> { 1017 | flatbuffers::size_prefixed_root_with_opts::>(opts, buf) 1018 | } 1019 | #[inline] 1020 | /// Assumes, without verification, that a buffer of bytes contains a Request and returns it. 1021 | /// # Safety 1022 | /// Callers must trust the given bytes do indeed contain a valid `Request`. 1023 | pub unsafe fn root_as_request_unchecked(buf: &[u8]) -> Request { 1024 | flatbuffers::root_unchecked::(buf) 1025 | } 1026 | #[inline] 1027 | /// Assumes, without verification, that a buffer of bytes contains a size prefixed Request and returns it. 1028 | /// # Safety 1029 | /// Callers must trust the given bytes do indeed contain a valid size prefixed `Request`. 1030 | pub unsafe fn size_prefixed_root_as_request_unchecked(buf: &[u8]) -> Request { 1031 | flatbuffers::size_prefixed_root_unchecked::(buf) 1032 | } 1033 | #[inline] 1034 | pub fn finish_request_buffer<'a, 'b>( 1035 | fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>, 1036 | root: flatbuffers::WIPOffset>) { 1037 | fbb.finish(root, None); 1038 | } 1039 | 1040 | #[inline] 1041 | pub fn finish_size_prefixed_request_buffer<'a, 'b>(fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>, root: flatbuffers::WIPOffset>) { 1042 | fbb.finish_size_prefixed(root, None); 1043 | } 1044 | } // pub mod hyperionnet 1045 | 1046 | -------------------------------------------------------------------------------- /src/image_decoder.rs: -------------------------------------------------------------------------------- 1 | use image::{GenericImage, Rgb, RgbImage}; 2 | 3 | struct PixelAverage { 4 | avg_rb: u32, 5 | avg_g: u32, 6 | } 7 | 8 | impl PixelAverage { 9 | pub fn new() -> PixelAverage { 10 | PixelAverage { 11 | avg_rb: 0, 12 | avg_g: 0, 13 | } 14 | } 15 | 16 | pub fn add(&mut self, rgb: u32) { 17 | let rb = rgb & 0x00FF00FF; 18 | let g = rgb & 0x0000FF00; 19 | self.avg_rb += rb; 20 | self.avg_g += g; 21 | } 22 | 23 | pub fn rgb(self) -> Rgb { 24 | let rb = self.avg_rb / 16; 25 | let g = (self.avg_g / 16) >> 8; 26 | let b = rb; 27 | let r = rb >> 16; 28 | 29 | Rgb([r as _, g as _, b as _]) 30 | } 31 | } 32 | 33 | pub trait ToRgb { 34 | fn rgb(&self) -> Rgb; 35 | } 36 | 37 | pub struct RgbPixel { 38 | dat: [u8; 3], 39 | } 40 | 41 | impl RgbPixel { 42 | pub fn new(r: u8, g: u8, b: u8) -> RgbPixel { 43 | RgbPixel { dat: [r, g, b] } 44 | } 45 | } 46 | 47 | impl ToRgb for RgbPixel { 48 | fn rgb(&self) -> Rgb { 49 | Rgb(self.dat) 50 | } 51 | } 52 | 53 | pub struct YUV420Pixel { 54 | dat: [u8; 3], 55 | } 56 | 57 | impl YUV420Pixel { 58 | pub fn new(c: u8, d: u8, e: u8) -> YUV420Pixel { 59 | YUV420Pixel { dat: [c, d, e] } 60 | } 61 | } 62 | 63 | fn clamp(v : i32) -> u8 { 64 | if v > 0xFF { 65 | 0xFF 66 | } else { 67 | if v < 0 { 68 | 0 69 | } else { 70 | v as u8 71 | } 72 | } 73 | } 74 | 75 | impl ToRgb for YUV420Pixel { 76 | fn rgb(&self) -> Rgb { 77 | let y = self.dat[0] as i32; 78 | let u = self.dat[1] as i32; 79 | let v = self.dat[2] as i32; 80 | let c = y - 16; 81 | let d = u - 128; 82 | let e = v - 128; 83 | 84 | let r = (298 * c + 409 * e + 128) >> 8; 85 | let g = (298 * c - 100 * d - 208 * e + 128) >> 8; 86 | let b = (298 * c + 516 * d + 128) >> 8; 87 | 88 | Rgb([clamp(r), clamp(g), clamp(b)]) 89 | } 90 | } 91 | 92 | pub struct Rgb565 { 93 | dat: u16, 94 | } 95 | 96 | impl Rgb565 { 97 | fn new(dat: u16) -> Self { 98 | Rgb565 { dat } 99 | } 100 | } 101 | 102 | impl ToRgb for Rgb565 { 103 | fn rgb(&self) -> Rgb { 104 | let byte = |i, c| (((1 << c) - 1) as u16 & (self.dat >> i)) as u8; 105 | 106 | let r8 = (byte(11, 5) as u16 * 527 + 23) >> 6; 107 | let g8 = (byte(5, 6) as u16 * 259 + 33) >> 6; 108 | let b8 = (byte(0, 5) as u16 * 527 + 23) >> 6; 109 | Rgb([r8 as u8, g8 as u8, b8 as u8]) 110 | } 111 | } 112 | 113 | pub fn rgb565_to_rgb888(mapping: &[u16], pitch: u32, size: (u32, u32)) -> RgbImage { 114 | let mut img = RgbImage::new(size.0, size.1); 115 | 116 | let bytepitch = pitch / 2; 117 | 118 | for y in 0..size.1 { 119 | for x in 0..size.0 { 120 | let offset = y * bytepitch + x; 121 | let v = Rgb565::new(mapping[offset as usize]); 122 | 123 | unsafe { img.unsafe_put_pixel(x, y, v.rgb()) }; 124 | } 125 | } 126 | img 127 | } 128 | 129 | pub fn decode_image(mapping: &[u32], pitch: u32, size: (u32, u32)) -> RgbImage { 130 | let mut img = RgbImage::new(size.0, size.1); 131 | 132 | let bytepitch = pitch / 4; 133 | 134 | for y in 0..size.1 { 135 | for x in 0..size.0 { 136 | let offset = y * bytepitch + x; 137 | let v = mapping[offset as usize]; 138 | let byte = |i| (v >> i * 8) as u8; 139 | 140 | let px = Rgb([byte(2), (byte(1)), (byte(0))]); 141 | 142 | unsafe { img.unsafe_put_pixel(x, y, px) }; 143 | } 144 | } 145 | 146 | img 147 | } 148 | 149 | pub fn decode_image_multichannel( 150 | mappings: [&[u8]; 3], 151 | size: (u32, u32), 152 | pitches: [u32; 3], 153 | ) -> RgbImage { 154 | let mut img = RgbImage::new(size.0, size.1); 155 | 156 | for y in 0..size.1 { 157 | for x in 0..size.0 { 158 | let offset: usize = (y * pitches[0] + x) as _; 159 | let offset1: usize = ((y / 2) * (pitches[1]) + x / 2) as _; 160 | let offset2: usize = ((y / 2) * (pitches[2]) + x / 2) as _; 161 | let yuv = YUV420Pixel::new( 162 | mappings[0][offset], 163 | mappings[1][offset1], 164 | mappings[2][offset2], 165 | ); 166 | 167 | unsafe { img.unsafe_put_pixel(x, y, yuv.rgb()) }; 168 | } 169 | } 170 | 171 | img 172 | } 173 | 174 | pub fn decode_small_image_multichannel( 175 | mappings: [&[u8]; 3], 176 | size: (u32, u32), 177 | pitches: [u32; 3], 178 | ) -> RgbImage { 179 | let halfsize = (size.0 / 2, size.1 / 2); 180 | let mut img = RgbImage::new(halfsize.0, halfsize.1); 181 | 182 | for y in 0..halfsize.1 { 183 | for x in 0..halfsize.0 { 184 | let offset: usize = (2 * y * pitches[0] + 2 * x) as _; 185 | let offset1: usize = (y * pitches[1] + x) as _; 186 | let offset2: usize = (y * pitches[2] + x) as _; 187 | let yat = |offset| mappings[0][offset] as u32; 188 | let yval = (yat(offset) 189 | + yat(offset + 1) 190 | + yat(offset + pitches[0] as usize) 191 | + yat(offset + pitches[0] as usize + 1)) 192 | / 4; 193 | let yuv = YUV420Pixel::new(yval as _, mappings[1][offset1], mappings[2][offset2]); 194 | 195 | unsafe { img.unsafe_put_pixel(x, y, yuv.rgb()) }; 196 | } 197 | } 198 | 199 | img 200 | } 201 | 202 | pub fn decode_tiled_small_image( 203 | mapping: &[u32], 204 | tilesize: u32, 205 | tiles: (u32, u32), 206 | size: (u32, u32), 207 | ) -> RgbImage { 208 | let mut img = RgbImage::new(tiles.0 * tilesize / 4, tiles.1 * tilesize / 4); 209 | 210 | let mut i = 0; 211 | 212 | let mut avg_16 = |x, y| { 213 | let mut avg = PixelAverage::new(); 214 | for n in 0..16 { 215 | avg.add(mapping[i + n]); 216 | } 217 | unsafe { 218 | img.unsafe_put_pixel(x, y, avg.rgb()); 219 | } 220 | i = i + 16; 221 | }; 222 | 223 | let mut copy_16x4_px = |x, y| { 224 | avg_16(x, y); 225 | avg_16(x + 1, y); 226 | avg_16(x + 2, y); 227 | avg_16(x + 3, y); 228 | }; 229 | 230 | let mut copy_16x16_px = |x, y| { 231 | copy_16x4_px(x, y); 232 | copy_16x4_px(x, y + 1); 233 | copy_16x4_px(x, y + 2); 234 | copy_16x4_px(x, y + 3); 235 | }; 236 | 237 | for ytile in 0..tiles.1 { 238 | if ytile % 2 == 0 { 239 | let mut copy_tile = |x, y| { 240 | copy_16x16_px(x, y); 241 | copy_16x16_px(x, y + 4); 242 | copy_16x16_px(x + 4, y + 4); 243 | copy_16x16_px(x + 4, y); 244 | }; 245 | 246 | for xtile in 0..tiles.0 { 247 | copy_tile(xtile * tilesize / 4, ytile * tilesize / 4); 248 | } 249 | } else { 250 | let mut copy_tile = |x, y| { 251 | copy_16x16_px(x + 4, y + 4); 252 | copy_16x16_px(x + 4, y); 253 | copy_16x16_px(x, y); 254 | copy_16x16_px(x, y + 4); 255 | }; 256 | 257 | for xtile in (0..tiles.0).rev() { 258 | copy_tile(xtile * tilesize / 4, ytile * tilesize / 4); 259 | } 260 | } 261 | } 262 | 263 | img.sub_image(0, 0, size.0 / 4, size.1 / 4).to_image() 264 | } 265 | 266 | pub fn to_image(mapping: &[u8], tilesize: u32, tiles: (u32, u32), size: (u32, u32)) -> RgbImage { 267 | let mut img = RgbImage::new(tiles.0 * tilesize, tiles.1 * tilesize); 268 | let mut i = 0; 269 | 270 | let mut copy_px = |x, y| { 271 | let color = Rgb([ 272 | mapping[(i + 2) as usize], 273 | mapping[(i + 1) as usize], 274 | mapping[(i + 0) as usize], 275 | ]); 276 | unsafe { 277 | img.unsafe_put_pixel(x, y, color); 278 | } 279 | i = i + 4; 280 | }; 281 | let mut copy_4_px = |x, y| { 282 | copy_px(x, y); 283 | copy_px(x + 1, y); 284 | copy_px(x + 2, y); 285 | copy_px(x + 3, y); 286 | }; 287 | 288 | let mut copy_4x4_px = |x, y| { 289 | copy_4_px(x, y); 290 | copy_4_px(x, y + 1); 291 | copy_4_px(x, y + 2); 292 | copy_4_px(x, y + 3); 293 | }; 294 | 295 | let mut copy_16x4_px = |x, y| { 296 | copy_4x4_px(x, y); 297 | copy_4x4_px(x + 4, y); 298 | copy_4x4_px(x + 8, y); 299 | copy_4x4_px(x + 12, y); 300 | }; 301 | 302 | let mut copy_16x16_px = |x, y| { 303 | copy_16x4_px(x, y); 304 | copy_16x4_px(x, y + 4); 305 | copy_16x4_px(x, y + 8); 306 | copy_16x4_px(x, y + 12); 307 | }; 308 | 309 | for ytile in 0..tiles.1 { 310 | if ytile % 2 == 0 { 311 | let mut copy_tile = |x, y| { 312 | copy_16x16_px(x, y); 313 | copy_16x16_px(x, y + 16); 314 | copy_16x16_px(x + 16, y + 16); 315 | copy_16x16_px(x + 16, y); 316 | }; 317 | 318 | for xtile in 0..tiles.0 { 319 | copy_tile(xtile * tilesize, ytile * tilesize); 320 | } 321 | } else { 322 | let mut copy_tile = |x, y| { 323 | copy_16x16_px(x + 16, y + 16); 324 | copy_16x16_px(x + 16, y); 325 | copy_16x16_px(x, y); 326 | copy_16x16_px(x, y + 16); 327 | }; 328 | 329 | for xtile in (0..tiles.0).rev() { 330 | copy_tile(xtile * tilesize, ytile * tilesize); 331 | } 332 | } 333 | } 334 | 335 | img.sub_image(0, 0, size.0, size.1).to_image() 336 | } 337 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate nix; 3 | 4 | use std::fs::{File, OpenOptions}; 5 | use std::net::TcpStream; 6 | use std::os::fd::AsFd; 7 | 8 | use clap::{App, Arg}; 9 | use drm::control::framebuffer::Handle; 10 | use drm::control::Device as ControlDevice; 11 | use drm::Device; 12 | use drm_ffi::drm_set_client_cap; 13 | 14 | use dump_image::dump_framebuffer_to_image; 15 | use image::{ImageError, RgbImage}; 16 | 17 | use std::os::unix::io::{AsRawFd, RawFd}; 18 | use std::{thread, time::Duration}; 19 | 20 | use std::io::Result as StdResult; 21 | 22 | pub mod ffi; 23 | pub mod framebuffer; 24 | pub mod hyperion; 25 | pub mod hyperion_reply_generated; 26 | pub mod hyperion_request_generated; 27 | pub mod image_decoder; 28 | pub mod dump_image; 29 | 30 | pub use hyperion_request_generated::hyperionnet::{Clear, Color, Command, Image, Register}; 31 | 32 | use hyperion::{read_reply, register_direct, send_color_red, send_image}; 33 | 34 | pub struct Card(File); 35 | 36 | impl AsRawFd for Card { 37 | fn as_raw_fd(&self) -> RawFd { 38 | self.0.as_raw_fd() 39 | } 40 | } 41 | 42 | impl AsFd for Card { 43 | fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { 44 | self.0.as_fd() 45 | } 46 | } 47 | 48 | impl Device for Card {} 49 | impl ControlDevice for Card {} 50 | 51 | impl Card { 52 | pub fn open(path: &str) -> Self { 53 | let mut options = OpenOptions::new(); 54 | options.read(true); 55 | options.write(false); 56 | Card(options.open(path).unwrap()) 57 | } 58 | } 59 | 60 | fn save_screenshot(img: &RgbImage) -> Result<(), ImageError> { 61 | img.save("screenshot.png") 62 | } 63 | 64 | fn send_dumped_image(socket: &mut TcpStream, img: &RgbImage, verbose : bool) -> StdResult<()> { 65 | register_direct(socket)?; 66 | read_reply(socket, verbose)?; 67 | 68 | send_image(socket, img, verbose)?; 69 | 70 | Ok(()) 71 | } 72 | 73 | fn dump_and_send_framebuffer( 74 | socket: &mut TcpStream, 75 | card: &Card, 76 | fb: Handle, 77 | verbose: bool, 78 | ) -> StdResult<()> { 79 | let img = dump_framebuffer_to_image(card, fb, verbose); 80 | if let Ok(img) = img { 81 | send_dumped_image(socket, &img, verbose)?; 82 | } else { 83 | println!("Error dumping framebuffer to image."); 84 | } 85 | 86 | Ok(()) 87 | } 88 | 89 | fn find_framebuffer(card: &Card, verbose: bool) -> Option { 90 | let resource_handles = card.resource_handles().unwrap(); 91 | 92 | for crtc in resource_handles.crtcs() { 93 | let info = card.get_crtc(*crtc).unwrap(); 94 | 95 | if verbose { 96 | println!("CRTC Info: {:?}", info); 97 | } 98 | 99 | if info.mode().is_some() { 100 | if let Some(fb) = info.framebuffer() { 101 | return Some(fb); 102 | } 103 | } 104 | } 105 | 106 | let plane_handles = card.plane_handles().unwrap(); 107 | 108 | for plane in plane_handles.planes() { 109 | let info = card.get_plane(*plane).unwrap(); 110 | 111 | if verbose { 112 | println!("Plane Info: {:?}", info); 113 | } 114 | 115 | if info.crtc().is_some() { 116 | let fb = info.framebuffer().unwrap(); 117 | 118 | return Some(fb); 119 | } 120 | } 121 | 122 | None 123 | } 124 | 125 | fn main() { 126 | let matches = App::new("DRM VC4 Screen Grabber for Hyperion") 127 | .version("0.1.0") 128 | .author("Rudi Horn ") 129 | .about("Captures a screenshot and sends it to the Hyperion server.") 130 | .arg( 131 | Arg::with_name("device") 132 | .short("d") 133 | .long("device") 134 | .default_value("/dev/dri/card0") 135 | .takes_value(true) 136 | .help("The device path of the DRM device to capture the image from."), 137 | ) 138 | .arg( 139 | Arg::with_name("address") 140 | .short("a") 141 | .long("address") 142 | .default_value("127.0.0.1:19400") 143 | .takes_value(true) 144 | .help("The Hyperion TCP socket address to send the captured screenshots to."), 145 | ) 146 | .arg( 147 | Arg::with_name("screenshot") 148 | .long("screenshot") 149 | .takes_value(false) 150 | .help("Capture a screenshot and save it to screenshot.png"), 151 | ) 152 | .arg( 153 | Arg::with_name("verbose") 154 | .short("v") 155 | .long("verbose") 156 | .help("Print verbose debugging information."), 157 | ) 158 | .get_matches(); 159 | 160 | let verbose = matches.is_present("verbose"); 161 | let screenshot = matches.is_present("screenshot"); 162 | let device_path = matches.value_of("device").unwrap(); 163 | let card = Card::open(device_path); 164 | let authenticated = card.authenticated().unwrap(); 165 | 166 | if verbose { 167 | let driver = card.get_driver().unwrap(); 168 | println!("Driver (auth={}): {:?}", authenticated, driver); 169 | } 170 | 171 | unsafe { 172 | let set_cap = drm_set_client_cap{ capability: drm_ffi::DRM_CLIENT_CAP_UNIVERSAL_PLANES as u64, value: 1 }; 173 | drm_ffi::ioctl::set_cap(card.as_raw_fd(), &set_cap).unwrap(); 174 | } 175 | 176 | let adress = matches.value_of("address").unwrap(); 177 | if screenshot { 178 | if let Some(fb) = find_framebuffer(&card, verbose) { 179 | let img = dump_framebuffer_to_image(&card, fb, verbose).unwrap(); 180 | save_screenshot(&img).unwrap(); 181 | } else { 182 | println!("No framebuffer found!"); 183 | } 184 | } else { 185 | let mut socket = TcpStream::connect(adress).unwrap(); 186 | register_direct(&mut socket).unwrap(); 187 | read_reply(&mut socket, verbose).unwrap(); 188 | 189 | send_color_red(&mut socket, verbose).unwrap(); 190 | thread::sleep(Duration::from_secs(1)); 191 | 192 | loop { 193 | if let Some(fb) = find_framebuffer(&card, verbose) { 194 | dump_and_send_framebuffer(&mut socket, &card, fb, verbose).unwrap(); 195 | thread::sleep(Duration::from_millis(1000/20)); 196 | } else { 197 | thread::sleep(Duration::from_secs(1)); 198 | } 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /systemd/drm-capture.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=DRM VC4 capture 3 | After=kodi.service 4 | 5 | [Service] 6 | ExecStartPre=/bin/sleep 10 7 | ExecStart=/storage/drm-vc4-grabber-v0.1.1-aarch64-linux/drm-vc4-grabber 8 | TimeoutStopSec=2 9 | Restart=always 10 | RestartSec=10 11 | 12 | [Install] 13 | WantedBy=default.target --------------------------------------------------------------------------------