diff --git a/Cargo.toml b/Cargo.toml index b2051bbe..5dd17831 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["daemon"] +members = [".", "daemon", "utils"] default-members = [".", "daemon"] [package] @@ -7,7 +7,7 @@ name = "swww" version = "0.9.5-master" authors = ["Leonardo Gibrowski FaƩ "] edition = "2021" -rust-version = "1.74" +rust-version = "1.75" # Enable some optimizations in debug mode. Otherwise, it is a pain to test it [profile.dev] diff --git a/README.md b/README.md index bff83475..2b98c912 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ ### Dependencies: - - Up to date stable rustc compiler and cargo (specifically, MSRV is 1.74.0) + - Up to date stable rustc compiler and cargo (specifically, MSRV is 1.75.0) To build, clone this repository and run: ``` diff --git a/daemon/src/animations/mod.rs b/daemon/src/animations/mod.rs index b28aade5..f2f8ebfd 100644 --- a/daemon/src/animations/mod.rs +++ b/daemon/src/animations/mod.rs @@ -7,7 +7,7 @@ use std::{ use utils::{ compression::Decompressor, - ipc::{self, Animation, Answer, BgImg, Img}, + ipc::{self, Animation, Answer, BgImg, ImgReq}, }; use crate::wallpaper::{AnimationToken, Wallpaper}; @@ -36,7 +36,7 @@ impl Animator { scope: &'a Scope<'b, '_>, transition: &'b ipc::Transition, img: &'b [u8], - path: &'b String, + path: &'b str, dim: (u32, u32), wallpapers: &'b mut Vec>, ) where @@ -68,7 +68,7 @@ impl Animator { pub(super) fn transition( &mut self, transition: ipc::Transition, - imgs: Box<[Img]>, + imgs: Box<[ImgReq]>, animations: Option>, mut wallpapers: Vec>>, ) -> Answer { @@ -78,10 +78,17 @@ impl Animator { .name("animation spawner".to_string()) .spawn(move || { thread::scope(|s| { - for (Img { img, path, dim, .. }, wallpapers) in + for (ImgReq { img, path, dim, .. }, wallpapers) in imgs.iter().zip(wallpapers.iter_mut()) { - Self::spawn_transition_thread(s, &transition, img, path, *dim, wallpapers); + Self::spawn_transition_thread( + s, + &transition, + img.bytes(), + path.str(), + *dim, + wallpapers, + ); } }); drop(imgs); diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 7dfaee85..09dbffa1 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -47,8 +47,8 @@ use wayland_client::{ }; use utils::ipc::{ - connect_to_socket, get_socket_path, read_socket, Answer, BgInfo, ImageRequest, PixelFormat, - Request, Scale, + connect_to_socket, get_socket_path, read_socket, Answer, BgInfo, ImageReq, MmappedStr, + PixelFormat, RequestRecv, RequestSend, Scale, }; use animations::Animator; @@ -363,9 +363,9 @@ impl Daemon { return; } }; - let request = Request::receive(bytes); + let request = RequestRecv::receive(bytes); let answer = match request { - Request::Clear(clear) => { + RequestRecv::Clear(clear) => { let wallpapers = self.find_wallpapers_by_names(&clear.outputs); std::thread::Builder::new() .stack_size(1 << 15) @@ -386,17 +386,17 @@ impl Daemon { .unwrap(); // builder only failed if the name contains null bytes Answer::Ok } - Request::Ping => Answer::Ping( + RequestRecv::Ping => Answer::Ping( self.wallpapers .iter() .all(|w| w.configured.load(std::sync::atomic::Ordering::Acquire)), ), - Request::Kill => { + RequestRecv::Kill => { exit_daemon(); Answer::Ok } - Request::Query => Answer::Info(self.wallpapers_info()), - Request::Img(ImageRequest { + RequestRecv::Query => Answer::Info(self.wallpapers_info()), + RequestRecv::Img(ImageReq { transition, imgs, outputs, @@ -426,11 +426,11 @@ impl Daemon { .collect() } - fn find_wallpapers_by_names(&self, names: &[String]) -> Vec> { + fn find_wallpapers_by_names(&self, names: &[MmappedStr]) -> Vec> { self.wallpapers .iter() .filter_map(|wallpaper| { - if names.is_empty() || names.iter().any(|n| wallpaper.has_name(n)) { + if names.is_empty() || names.iter().any(|n| wallpaper.has_name(n.str())) { return Some(Arc::clone(wallpaper)); } None @@ -864,7 +864,7 @@ pub fn is_daemon_running(addr: &PathBuf) -> Result { Err(_) => return Ok(false), }; - Request::Ping.send(&sock)?; + RequestSend::Ping.send(&sock)?; let answer = Answer::receive(read_socket(&sock)?); match answer { Answer::Ping(_) => Ok(true), diff --git a/src/main.rs b/src/main.rs index 02aee1af..1d713d63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::{path::PathBuf, process::Stdio, time::Duration}; use utils::{ cache, - ipc::{self, connect_to_socket, get_socket_path, read_socket, Answer, Request}, + ipc::{self, connect_to_socket, get_socket_path, read_socket, Answer, RequestSend}, }; mod imgproc; @@ -61,7 +61,7 @@ fn main() -> Result<(), String> { loop { let socket = connect_to_socket(&get_socket_path(), 5, 100)?; - Request::Ping.send(&socket)?; + RequestSend::Ping.send(&socket)?; let bytes = read_socket(&socket)?; let answer = Answer::receive(bytes); if let Answer::Ping(configured) = answer { @@ -114,7 +114,7 @@ fn process_swww_args(args: &Swww) -> Result<(), String> { Ok(()) } -fn make_request(args: &Swww) -> Result, String> { +fn make_request(args: &Swww) -> Result, String> { match args { Swww::Clear(c) => { let (format, _, _) = get_format_dims_and_outputs(&[])?; @@ -122,10 +122,11 @@ fn make_request(args: &Swww) -> Result, String> { if format.must_swap_r_and_b_channels() { color.swap(0, 2); } - Ok(Some(Request::Clear(ipc::Clear { + let clear = ipc::ClearSend { color, outputs: split_cmdline_outputs(&c.outputs), - }))) + }; + Ok(Some(RequestSend::Clear(clear.create_request()))) } Swww::Restore(restore) => { let requested_outputs = split_cmdline_outputs(&restore.outputs); @@ -137,9 +138,8 @@ fn make_request(args: &Swww) -> Result, String> { let requested_outputs = split_cmdline_outputs(&img.outputs); let (format, dims, outputs) = get_format_dims_and_outputs(&requested_outputs)?; let imgbuf = ImgBuf::new(&img.path)?; - Ok(Some(Request::Img(make_img_request( - img, &imgbuf, &dims, format, &outputs, - )?))) + let img_request = make_img_request(img, &imgbuf, &dims, format, &outputs)?; + Ok(Some(RequestSend::Img(img_request))) } Swww::Init { no_cache, .. } => { if !*no_cache { @@ -147,8 +147,8 @@ fn make_request(args: &Swww) -> Result, String> { } Ok(None) } - Swww::Kill => Ok(Some(Request::Kill)), - Swww::Query => Ok(Some(Request::Query)), + Swww::Kill => Ok(Some(RequestSend::Kill)), + Swww::Query => Ok(Some(RequestSend::Query)), } } @@ -158,7 +158,7 @@ fn make_img_request( dims: &[(u32, u32)], pixel_format: ipc::PixelFormat, outputs: &[Vec], -) -> Result { +) -> Result { let img_raw = imgbuf.decode(pixel_format)?; let transition = make_transition(img); let mut img_req_builder = ipc::ImageRequestBuilder::new(transition); @@ -212,13 +212,13 @@ fn make_img_request( }; img_req_builder.push( - ipc::Img { + ipc::ImgSend { img, path, dim, format: pixel_format, }, - outputs.to_owned().into_boxed_slice(), + outputs, animation, ); } @@ -235,7 +235,7 @@ fn get_format_dims_and_outputs( let mut imgs: Vec = Vec::new(); let socket = connect_to_socket(&get_socket_path(), 5, 100)?; - Request::Query.send(&socket)?; + RequestSend::Query.send(&socket)?; let bytes = read_socket(&socket)?; drop(socket); let answer = Answer::receive(bytes); @@ -316,7 +316,7 @@ fn is_daemon_running() -> Result { Err(_) => return Ok(false), }; - Request::Ping.send(&socket)?; + RequestSend::Ping.send(&socket)?; let answer = Answer::receive(read_socket(&socket)?); match answer { Answer::Ping(_) => Ok(true), diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 57bcd75d..a95750dd 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license-file = "../LICENSE" [dependencies] -rustix = { version = "0.38", default-features = false, features = [ "net", "shm", "mm" ] } +rustix = { version = "0.38", default-features = false, features = [ "std", "net", "shm", "mm", "param" ] } [build-dependencies] pkg-config = "0.3" diff --git a/utils/src/cache.rs b/utils/src/cache.rs index 9fbcd902..493e0de7 100644 --- a/utils/src/cache.rs +++ b/utils/src/cache.rs @@ -10,7 +10,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::ipc::{Animation, PixelFormat}; +use crate::ipc::{Animation, Mmap, PixelFormat}; pub(crate) fn store(output_name: &str, img_path: &str) -> io::Result<()> { let mut filepath = cache_dir()?; @@ -19,7 +19,7 @@ pub(crate) fn store(output_name: &str, img_path: &str) -> io::Result<()> { } pub(crate) fn store_animation_frames( - animation: &Animation, + animation: &[u8], path: &Path, dimensions: (u32, u32), pixel_format: PixelFormat, @@ -28,11 +28,8 @@ pub(crate) fn store_animation_frames( let mut filepath = cache_dir()?; filepath.push(&filename); - let mut bytes = Vec::new(); - animation.serialize(&mut bytes); - if !filepath.is_file() { - File::create(filepath)?.write_all(&bytes) + File::create(filepath)?.write_all(animation) } else { Ok(()) } @@ -52,10 +49,11 @@ pub fn load_animation_frames( for entry in read_dir.into_iter().flatten() { if entry.path() == filepath { - let mut buf = Vec::new(); - File::open(&filepath)?.read_to_end(&mut buf)?; + let fd = File::open(&filepath)?.into(); + let len = rustix::fs::seek(&fd, rustix::fs::SeekFrom::End(0))?; + let mmap = Mmap::from_fd(fd, len as usize); - match std::panic::catch_unwind(|| Animation::deserialize(&buf)) { + match std::panic::catch_unwind(|| Animation::deserialize(&mmap, mmap.slice())) { Ok((frames, _)) => return Ok(Some(frames)), Err(e) => eprintln!("Error loading animation frames: {e:?}"), } diff --git a/utils/src/compression/mod.rs b/utils/src/compression/mod.rs index 3730e23f..31ebef4f 100644 --- a/utils/src/compression/mod.rs +++ b/utils/src/compression/mod.rs @@ -6,7 +6,7 @@ use comp::pack_bytes; use decomp::{unpack_bytes_3channels, unpack_bytes_4channels}; use std::ffi::{c_char, c_int}; -use crate::ipc::PixelFormat; +use crate::ipc::{ImageRequestBuilder, Mmap, MmappedBytes, PixelFormat}; mod comp; mod cpu; mod decomp; @@ -42,9 +42,14 @@ extern "C" { fn LZ4_compressBound(input_size: c_int) -> c_int; } +enum Inner { + Boxed(Box<[u8]>), + Mmapped(MmappedBytes), +} + /// This struct represents the cached difference between the previous frame and the next pub struct BitPack { - inner: Box<[u8]>, + inner: Inner, /// This field will ensure we won't ever try to unpack the images on a buffer of the wrong size, /// which ultimately is what allows us to use unsafe in the unpack_bytes function expected_buf_size: u32, @@ -53,24 +58,25 @@ pub struct BitPack { } impl BitPack { - pub(crate) fn serialize(&self, buf: &mut Vec) { + pub(crate) fn serialize(&self, buf: &mut ImageRequestBuilder) { let Self { - inner, expected_buf_size, compressed_size, + .. } = self; - buf.extend((inner.len() as u32).to_ne_bytes()); - buf.extend((expected_buf_size).to_ne_bytes()); - buf.extend((compressed_size).to_ne_bytes()); - buf.extend(inner.iter()); + buf.extend(&(self.bytes().len() as u32).to_ne_bytes()); + buf.extend(&(expected_buf_size).to_ne_bytes()); + buf.extend(&(compressed_size).to_ne_bytes()); + buf.extend(self.bytes()); } - pub(crate) fn deserialize(bytes: &[u8]) -> (Self, usize) { + #[must_use] + pub(crate) fn deserialize(map: &Mmap, bytes: &[u8]) -> (Self, usize) { assert!(bytes.len() > 12); let len = u32::from_ne_bytes(bytes[0..4].try_into().unwrap()) as usize; let expected_buf_size = u32::from_ne_bytes(bytes[4..8].try_into().unwrap()); let compressed_size = i32::from_ne_bytes(bytes[8..12].try_into().unwrap()); - let inner = bytes[12..12 + len].into(); + let inner = Inner::Mmapped(MmappedBytes::new_with_len(map, &bytes[12..12 + len], len)); ( Self { inner, @@ -80,6 +86,15 @@ impl BitPack { 12 + len, ) } + + #[inline] + #[must_use] + fn bytes(&self) -> &[u8] { + match &self.inner { + Inner::Boxed(b) => b.as_ref(), + Inner::Mmapped(m) => m.bytes(), + } + } } /// Struct responsible for compressing our data. We use it to cache vector extensions that might @@ -156,7 +171,7 @@ impl Compressor { }; Some(BitPack { - inner: v.into_boxed_slice(), + inner: Inner::Boxed(v.into_boxed_slice()), expected_buf_size, compressed_size: self.buf.len() as i32, }) @@ -240,10 +255,11 @@ impl Decompressor { // SAFETY: errors will never happen because BitPacked is *always* only produced // with correct lz4 compression, and ptr has the necessary capacity let size = unsafe { + let bytes = bitpack.bytes(); LZ4_decompress_safe( - bitpack.inner.as_ptr() as _, + bytes.as_ptr() as _, self.ptr.as_ptr() as _, - bitpack.inner.len() as c_int, + bytes.len() as c_int, bitpack.compressed_size as c_int, ) }; diff --git a/utils/src/ipc.rs b/utils/src/ipc.rs deleted file mode 100644 index df31c490..00000000 --- a/utils/src/ipc.rs +++ /dev/null @@ -1,1164 +0,0 @@ -use std::{ - fmt, - num::{NonZeroI32, NonZeroU8}, - path::PathBuf, - time::Duration, -}; - -use rustix::{ - fd::{AsFd, BorrowedFd, OwnedFd}, - io::Errno, - mm::{mmap, munmap, MapFlags, ProtFlags}, - net::{self, RecvFlags}, - shm::{Mode, ShmOFlags}, -}; - -use crate::{cache, compression::BitPack}; - -#[derive(Clone, PartialEq)] -pub enum Coord { - Pixel(f32), - Percent(f32), -} - -#[derive(Clone, PartialEq)] -pub struct Position { - pub x: Coord, - pub y: Coord, -} - -impl Position { - #[must_use] - pub fn new(x: Coord, y: Coord) -> Self { - Self { x, y } - } - - #[must_use] - pub fn to_pixel(&self, dim: (u32, u32), invert_y: bool) -> (f32, f32) { - let x = match self.x { - Coord::Pixel(x) => x, - Coord::Percent(x) => x * dim.0 as f32, - }; - - let y = match self.y { - Coord::Pixel(y) => { - if invert_y { - y - } else { - dim.1 as f32 - y - } - } - Coord::Percent(y) => { - if invert_y { - y * dim.1 as f32 - } else { - (1.0 - y) * dim.1 as f32 - } - } - }; - - (x, y) - } - - #[must_use] - pub fn to_percent(&self, dim: (u32, u32)) -> (f32, f32) { - let x = match self.x { - Coord::Pixel(x) => x / dim.0 as f32, - Coord::Percent(x) => x, - }; - - let y = match self.y { - Coord::Pixel(y) => y / dim.1 as f32, - Coord::Percent(y) => y, - }; - - (x, y) - } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum BgImg { - Color([u8; 3]), - Img(String), -} - -impl fmt::Display for BgImg { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - BgImg::Color(color) => { - write!(f, "color: {:02X}{:02X}{:02X}", color[0], color[1], color[2]) - } - BgImg::Img(p) => write!(f, "image: {p}",), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -#[repr(u8)] -pub enum PixelFormat { - /// No swap, can copy directly onto WlBuffer - Bgr = 0, - /// Swap R and B channels at client, can copy directly onto WlBuffer - Rgb = 1, - /// No swap, must extend pixel with an extra byte when copying - Xbgr = 2, - /// Swap R and B channels at client, must extend pixel with an extra byte when copying - Xrgb = 3, -} - -impl PixelFormat { - #[inline] - #[must_use] - pub const fn channels(&self) -> u8 { - match self { - Self::Rgb => 3, - Self::Bgr => 3, - Self::Xbgr => 4, - Self::Xrgb => 4, - } - } - - #[inline] - #[must_use] - pub const fn must_swap_r_and_b_channels(&self) -> bool { - match self { - Self::Bgr => false, - Self::Rgb => true, - Self::Xbgr => false, - Self::Xrgb => true, - } - } - - #[inline] - #[must_use] - pub const fn can_copy_directly_onto_wl_buffer(&self) -> bool { - match self { - Self::Bgr => true, - Self::Rgb => true, - Self::Xbgr => false, - Self::Xrgb => false, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Scale { - Whole(NonZeroI32), - Fractional(NonZeroI32), -} - -impl Scale { - #[inline] - #[must_use] - pub fn mul_dim(&self, width: i32, height: i32) -> (i32, i32) { - match self { - Scale::Whole(i) => (width * i.get(), height * i.get()), - Scale::Fractional(f) => { - let scale = f.get() as f64 / 120.0; - let width = (width as f64 * scale).round() as i32; - let height = (height as f64 * scale).round() as i32; - (width, height) - } - } - } - - #[inline] - #[must_use] - pub fn div_dim(&self, width: i32, height: i32) -> (i32, i32) { - match self { - Scale::Whole(i) => (width / i.get(), height / i.get()), - Scale::Fractional(f) => { - let scale = 120.0 / f.get() as f64; - let width = (width as f64 * scale).round() as i32; - let height = (height as f64 * scale).round() as i32; - (width, height) - } - } - } -} - -impl fmt::Display for Scale { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Scale::Whole(i) => i.get() as f32, - Scale::Fractional(f) => f.get() as f32 / 120.0, - } - ) - } -} - -#[derive(Clone)] -pub struct BgInfo { - pub name: String, - pub dim: (u32, u32), - pub scale_factor: Scale, - pub img: BgImg, - pub pixel_format: PixelFormat, -} - -impl BgInfo { - #[inline] - #[must_use] - pub fn real_dim(&self) -> (u32, u32) { - let dim = self - .scale_factor - .mul_dim(self.dim.0 as i32, self.dim.1 as i32); - (dim.0 as u32, dim.1 as u32) - } - - fn serialize(&self, buf: &mut Vec) { - let Self { - name, - dim, - scale_factor, - img, - pixel_format, - } = self; - - serialize_bytes(name.as_bytes(), buf); - buf.extend(dim.0.to_ne_bytes()); - buf.extend(dim.1.to_ne_bytes()); - - match scale_factor { - Scale::Whole(value) => { - buf.push(0); - buf.extend(value.get().to_ne_bytes()); - } - Scale::Fractional(value) => { - buf.push(1); - buf.extend(value.get().to_ne_bytes()); - } - } - - match img { - BgImg::Color(color) => { - buf.push(0); - buf.extend(color); - } - BgImg::Img(path) => { - buf.push(1); - serialize_bytes(path.as_bytes(), buf); - } - } - - buf.push(*pixel_format as u8); - } - - fn deserialize(bytes: &[u8]) -> (Self, usize) { - let name = deserialize_string(bytes); - let mut i = name.len() + 4; - - assert!(bytes.len() > i + 17); - - let dim = ( - u32::from_ne_bytes(bytes[i..i + 4].try_into().unwrap()), - u32::from_ne_bytes(bytes[i + 4..i + 8].try_into().unwrap()), - ); - i += 8; - - let scale_factor = if bytes[i] == 0 { - Scale::Whole( - i32::from_ne_bytes(bytes[i + 1..i + 5].try_into().unwrap()) - .try_into() - .unwrap(), - ) - } else { - Scale::Fractional( - i32::from_ne_bytes(bytes[i + 1..i + 5].try_into().unwrap()) - .try_into() - .unwrap(), - ) - }; - i += 5; - - let img = if bytes[i] == 0 { - i += 4; - BgImg::Color([bytes[i - 3], bytes[i - 2], bytes[i - 1]]) - } else { - i += 1; - let path = deserialize_string(&bytes[i..]); - i += 4 + path.len(); - BgImg::Img(path) - }; - - let pixel_format = match bytes[i] { - 0 => PixelFormat::Bgr, - 1 => PixelFormat::Rgb, - 2 => PixelFormat::Xbgr, - _ => PixelFormat::Xrgb, - }; - i += 1; - - ( - Self { - name, - dim, - scale_factor, - img, - pixel_format, - }, - i, - ) - } -} - -impl fmt::Display for BgInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}: {}x{}, scale: {}, currently displaying: {}", - self.name, self.dim.0, self.dim.1, self.scale_factor, self.img - ) - } -} - -#[repr(u8)] -#[derive(Clone, Copy)] -pub enum TransitionType { - Simple = 0, - Fade = 1, - Outer = 2, - Wipe = 3, - Grow = 4, - Wave = 5, - None = 6, -} - -pub struct Transition { - pub transition_type: TransitionType, - pub duration: f32, - pub step: NonZeroU8, - pub fps: u16, - pub angle: f64, - pub pos: Position, - pub bezier: (f32, f32, f32, f32), - pub wave: (f32, f32), - pub invert_y: bool, -} - -impl Transition { - fn serialize(&self, buf: &mut Vec) { - let Self { - transition_type, - duration, - step, - fps, - angle, - pos, - bezier, - wave, - invert_y, - } = self; - - buf.push(*transition_type as u8); - buf.extend(duration.to_ne_bytes()); - buf.push(step.get()); - buf.extend(fps.to_ne_bytes()); - buf.extend(angle.to_ne_bytes()); - match pos.x { - Coord::Pixel(f) => { - buf.push(0); - buf.extend(f.to_ne_bytes()); - } - Coord::Percent(f) => { - buf.push(1); - buf.extend(f.to_ne_bytes()); - } - } - match pos.y { - Coord::Pixel(f) => { - buf.push(0); - buf.extend(f.to_ne_bytes()); - } - Coord::Percent(f) => { - buf.push(1); - buf.extend(f.to_ne_bytes()); - } - } - buf.extend(bezier.0.to_ne_bytes()); - buf.extend(bezier.1.to_ne_bytes()); - buf.extend(bezier.2.to_ne_bytes()); - buf.extend(bezier.3.to_ne_bytes()); - buf.extend(wave.0.to_ne_bytes()); - buf.extend(wave.1.to_ne_bytes()); - buf.push(*invert_y as u8); - } - - fn deserialize(bytes: &[u8]) -> Self { - assert!(bytes.len() > 50); - let transition_type = match bytes[0] { - 0 => TransitionType::Simple, - 1 => TransitionType::Fade, - 2 => TransitionType::Outer, - 3 => TransitionType::Wipe, - 4 => TransitionType::Grow, - 5 => TransitionType::Wave, - _ => TransitionType::None, - }; - let duration = f32::from_ne_bytes(bytes[1..5].try_into().unwrap()); - let step = NonZeroU8::new(bytes[5]).expect("received step of 0"); - let fps = u16::from_ne_bytes(bytes[6..8].try_into().unwrap()); - let angle = f64::from_ne_bytes(bytes[8..16].try_into().unwrap()); - let pos = { - let x = if bytes[16] == 0 { - Coord::Pixel(f32::from_ne_bytes(bytes[17..21].try_into().unwrap())) - } else { - Coord::Percent(f32::from_ne_bytes(bytes[17..21].try_into().unwrap())) - }; - let y = if bytes[21] == 0 { - Coord::Pixel(f32::from_ne_bytes(bytes[22..26].try_into().unwrap())) - } else { - Coord::Percent(f32::from_ne_bytes(bytes[22..26].try_into().unwrap())) - }; - Position { x, y } - }; - - let bezier = ( - f32::from_ne_bytes(bytes[26..30].try_into().unwrap()), - f32::from_ne_bytes(bytes[30..34].try_into().unwrap()), - f32::from_ne_bytes(bytes[34..38].try_into().unwrap()), - f32::from_ne_bytes(bytes[38..42].try_into().unwrap()), - ); - - let wave = ( - f32::from_ne_bytes(bytes[42..46].try_into().unwrap()), - f32::from_ne_bytes(bytes[46..50].try_into().unwrap()), - ); - - let invert_y = bytes[50] != 0; - - Self { - transition_type, - duration, - step, - fps, - angle, - pos, - bezier, - wave, - invert_y, - } - } -} - -pub struct Clear { - pub color: [u8; 3], - pub outputs: Box<[String]>, -} - -pub struct Img { - pub path: String, - pub dim: (u32, u32), - pub format: PixelFormat, - pub img: Box<[u8]>, -} - -pub struct Animation { - pub animation: Box<[(BitPack, Duration)]>, -} - -impl Animation { - pub(crate) fn serialize(&self, buf: &mut Vec) { - let Self { animation } = self; - - buf.extend((animation.len() as u32).to_ne_bytes()); - for (bitpack, duration) in animation.iter() { - bitpack.serialize(buf); - buf.extend(duration.as_secs_f64().to_ne_bytes()) - } - } - - pub(crate) fn deserialize(bytes: &[u8]) -> (Self, usize) { - let mut i = 0; - let animation_len = u32::from_ne_bytes(bytes[i..i + 4].try_into().unwrap()) as usize; - i += 4; - let mut animation = Vec::with_capacity(animation_len); - for _ in 0..animation_len { - let (anim, offset) = BitPack::deserialize(&bytes[i..]); - i += offset; - let duration = - Duration::from_secs_f64(f64::from_ne_bytes(bytes[i..i + 8].try_into().unwrap())); - i += 8; - animation.push((anim, duration)); - } - - ( - Self { - animation: animation.into(), - }, - i, - ) - } -} - -pub struct ImageRequest { - pub transition: Transition, - pub imgs: Box<[Img]>, - pub outputs: Box<[Box<[String]>]>, - pub animations: Option>, -} - -pub struct ImageRequestBuilder { - transition: Transition, - imgs: Vec, - outputs: Vec>, - animations: Vec, -} - -impl ImageRequestBuilder { - #[inline] - pub fn new(transition: Transition) -> Self { - Self { - transition, - imgs: Vec::new(), - outputs: Vec::new(), - animations: Vec::new(), - } - } - - #[inline] - pub fn push(&mut self, img: Img, outputs: Box<[String]>, animation: Option) { - self.imgs.push(img); - self.outputs.push(outputs); - if let Some(animation) = animation { - self.animations.push(animation); - } - } - - #[inline] - pub fn build(self) -> ImageRequest { - let animations = if self.animations.is_empty() { - None - } else { - assert_eq!(self.animations.len(), self.imgs.len()); - Some(self.animations.into_boxed_slice()) - }; - ImageRequest { - transition: self.transition, - imgs: self.imgs.into_boxed_slice(), - outputs: self.outputs.into_boxed_slice(), - animations, - } - } -} - -pub enum Request { - Ping, - Query, - Clear(Clear), - Img(ImageRequest), - Kill, -} - -impl Request { - pub fn send(&self, stream: &OwnedFd) -> Result<(), String> { - let mut socket_msg = [0u8; 16]; - socket_msg[0..8].copy_from_slice(&match self { - Request::Ping => 0u64.to_ne_bytes(), - Request::Query => 1u64.to_ne_bytes(), - Request::Clear(_) => 2u64.to_ne_bytes(), - Request::Img(_) => 3u64.to_ne_bytes(), - Request::Kill => 4u64.to_ne_bytes(), - }); - let bytes: Vec = match self { - Self::Clear(clear) => { - let mut buf = Vec::with_capacity(64); - buf.push(clear.outputs.len() as u8); // we assume someone does not have more than - // 255 monitors. Seems reasonable - for output in clear.outputs.iter() { - serialize_bytes(output.as_bytes(), &mut buf); - } - buf.extend(clear.color); - buf - } - Self::Img(img) => { - let ImageRequest { - transition, - imgs, - outputs, - animations, - } = img; - - let mut buf = Vec::with_capacity(imgs[0].img.len() + 1024); - transition.serialize(&mut buf); - buf.push(imgs.len() as u8); // we assume someone does not have more than 255 - // monitors - - for i in 0..outputs.len() { - let Img { - path, - img, - dim: dims, - format, - } = &imgs[i]; - serialize_bytes(path.as_bytes(), &mut buf); - serialize_bytes(img, &mut buf); - buf.extend(dims.0.to_ne_bytes()); - buf.extend(dims.1.to_ne_bytes()); - buf.push(*format as u8); - - let output = &outputs[i]; - buf.push(output.len() as u8); - for output in output.iter() { - serialize_bytes(output.as_bytes(), &mut buf); - } - - if let Some(animation) = animations { - buf.push(1); - animation[i].serialize(&mut buf); - } else { - buf.push(0); - } - } - - buf - } - _ => vec![], - }; - - match send_socket_msg(stream, &mut socket_msg, &bytes) { - Ok(true) => (), - Ok(false) => return Err("failed to send full length of message in socket!".to_string()), - Err(e) => return Err(format!("failed to write serialized request: {e}")), - } - - if let Self::Img(ImageRequest { - imgs, - outputs, - animations, - .. - }) = self - { - for i in 0..outputs.len() { - let Img { - path, - dim: dims, - format, - .. - } = &imgs[i]; - for output in outputs[i].iter() { - if let Err(e) = super::cache::store(output, path) { - eprintln!("ERROR: failed to store cache: {e}"); - } - } - if let Some(animations) = animations.as_ref() { - for animation in animations.iter() { - // only store the cache if we aren't reading from stdin - if path != "-" { - let p = PathBuf::from(&path); - if let Err(e) = - cache::store_animation_frames(animation, &p, *dims, *format) - { - eprintln!("Error storing cache for {}: {e}", path); - } - } - } - } - } - } - - Ok(()) - } - - #[must_use] - #[inline] - pub fn receive(socket_msg: SocketMsg) -> Self { - let ret = match socket_msg.code { - 0 => Self::Ping, - 1 => Self::Query, - 2 => { - let mut mmap = socket_msg.shm.unwrap(); - let bytes = mmap.slice_mut(); - let len = bytes[0] as usize; - let mut outputs = Vec::with_capacity(len); - let mut i = 1; - for _ in 0..len { - let output = deserialize_string(&bytes[i..]); - i += 4 + output.len(); - outputs.push(output); - } - let color = [bytes[i], bytes[i + 1], bytes[i + 2]]; - Self::Clear(Clear { - color, - outputs: outputs.into(), - }) - } - 3 => { - let mut mmap = socket_msg.shm.unwrap(); - let bytes = mmap.slice_mut(); - let transition = Transition::deserialize(&bytes[0..]); - let len = bytes[51] as usize; - - let mut imgs = Vec::with_capacity(len); - let mut outputs = Vec::with_capacity(len); - let mut animations = Vec::with_capacity(len); - - let mut i = 52; - for _ in 0..len { - let path = deserialize_string(&bytes[i..]); - i += 4 + path.len(); - - let img = deserialize_bytes(&bytes[i..]); - i += 4 + img.len(); - - let dims = ( - u32::from_ne_bytes(bytes[i..i + 4].try_into().unwrap()), - u32::from_ne_bytes(bytes[i + 4..i + 8].try_into().unwrap()), - ); - i += 8; - - let format = match bytes[i] { - 0 => PixelFormat::Bgr, - 1 => PixelFormat::Rgb, - 2 => PixelFormat::Xbgr, - _ => PixelFormat::Xrgb, - }; - i += 1; - - imgs.push(Img { - path, - img, - dim: dims, - format, - }); - - let n_outputs = bytes[i] as usize; - i += 1; - let mut out = Vec::with_capacity(n_outputs); - for _ in 0..n_outputs { - let output = deserialize_string(&bytes[i..]); - i += 4 + output.len(); - out.push(output); - } - outputs.push(out.into()); - - if bytes[i] == 1 { - i += 1; - let (animation, offset) = Animation::deserialize(&bytes[i..]); - i += offset; - animations.push(animation); - } else { - i += 1; - } - } - - Self::Img(ImageRequest { - transition, - imgs: imgs.into(), - outputs: outputs.into(), - animations: if animations.is_empty() { - None - } else { - Some(animations.into()) - }, - }) - } - _ => Self::Kill, - }; - ret - } -} - -pub enum Answer { - Ok, - Ping(bool), - Info(Box<[BgInfo]>), - Err(String), -} - -impl Answer { - pub fn send(&self, stream: &OwnedFd) -> Result<(), String> { - let mut socket_msg = [0u8; 16]; - socket_msg[0..8].copy_from_slice(&match self { - Self::Ok => 0u64.to_ne_bytes(), - Self::Ping(true) => 1u64.to_ne_bytes(), - Self::Ping(false) => 2u64.to_ne_bytes(), - Self::Info(_) => 3u64.to_ne_bytes(), - Self::Err(_) => 4u64.to_ne_bytes(), - }); - - let bytes = match self { - Self::Info(infos) => { - let mut buf = Vec::with_capacity(1024); - - buf.push(infos.len() as u8); - for info in infos.iter() { - info.serialize(&mut buf); - } - - buf - } - Self::Err(s) => { - let mut buf = Vec::with_capacity(128); - serialize_bytes(s.as_bytes(), &mut buf); - buf - } - _ => vec![], - }; - - match send_socket_msg(stream, &mut socket_msg, &bytes) { - Ok(true) => Ok(()), - Ok(false) => Err("failed to send full length of message in socket!".to_string()), - Err(e) => Err(format!("failed to write serialized request: {e}")), - } - } - - #[must_use] - #[inline] - pub fn receive(socket_msg: SocketMsg) -> Self { - match socket_msg.code { - 0 => Self::Ok, - 1 => Self::Ping(true), - 2 => Self::Ping(false), - 3 => { - let mut mmap = socket_msg.shm.unwrap(); - let bytes = mmap.slice_mut(); - let len = bytes[0] as usize; - let mut bg_infos = Vec::with_capacity(len); - - let mut i = 1; - for _ in 0..len { - let (info, offset) = BgInfo::deserialize(&bytes[i..]); - i += offset; - bg_infos.push(info); - } - - Self::Info(bg_infos.into()) - } - 4 => { - let mut mmap = socket_msg.shm.unwrap(); - let bytes = mmap.slice_mut(); - Self::Err(deserialize_string(bytes)) - } - _ => panic!("Received malformed answer from daemon"), - } - } -} - -fn send_socket_msg( - stream: &OwnedFd, - socket_msg: &mut [u8; 16], - bytes: &[u8], -) -> rustix::io::Result { - let mut ancillary_buf = [0u8; rustix::cmsg_space!(ScmRights(1))]; - let mut ancillary = net::SendAncillaryBuffer::new(&mut ancillary_buf); - - let mmap = if !bytes.is_empty() { - socket_msg[8..].copy_from_slice(&(bytes.len() as u64).to_ne_bytes()); - let mut mmap = Mmap::create(bytes.len()); - mmap.slice_mut().copy_from_slice(bytes); - Some(mmap) - } else { - None - }; - - let msg_buf; - if let Some(mmap) = mmap.as_ref() { - msg_buf = [mmap.fd.as_fd()]; - let msg = net::SendAncillaryMessage::ScmRights(&msg_buf); - ancillary.push(msg); - } - - let iov = rustix::io::IoSlice::new(&socket_msg[..]); - net::sendmsg(stream, &[iov], &mut ancillary, net::SendFlags::empty()) - .map(|written| written == socket_msg.len()) -} - -pub struct SocketMsg { - code: u8, - shm: Option, -} - -pub fn read_socket(stream: &OwnedFd) -> Result { - let mut buf = [0u8; 16]; - let mut ancillary_buf = [0u8; rustix::cmsg_space!(ScmRights(1))]; - - let mut control = net::RecvAncillaryBuffer::new(&mut ancillary_buf); - - let mut tries = 0; - loop { - let iov = rustix::io::IoSliceMut::new(&mut buf); - match net::recvmsg(stream, &mut [iov], &mut control, RecvFlags::WAITALL) { - Ok(_) => break, - Err(e) => { - if e.kind() == std::io::ErrorKind::WouldBlock && tries < 5 { - std::thread::sleep(Duration::from_millis(1)); - } else { - return Err(format!("failed to read serialized length: {e}")); - } - } - } - tries += 1; - } - - let code = u64::from_ne_bytes(buf[0..8].try_into().unwrap()) as u8; - let len = u64::from_ne_bytes(buf[8..16].try_into().unwrap()) as usize; - - let shm = if len == 0 { - None - } else { - let shm_file = match control.drain().next().unwrap() { - net::RecvAncillaryMessage::ScmRights(mut iter) => iter.next().unwrap(), - _ => panic!("malformed ancillary message"), - }; - Some(Mmap::from_fd(shm_file, len)) - }; - Ok(SocketMsg { code, shm }) -} - -#[must_use] -pub fn get_socket_path() -> PathBuf { - let runtime_dir = if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") { - dir - } else { - "/tmp/swww".to_string() - }; - - let mut socket_path = PathBuf::new(); - socket_path.push(runtime_dir); - - let mut socket_name = String::new(); - socket_name.push_str("swww-"); - if let Ok(socket) = std::env::var("WAYLAND_DISPLAY") { - socket_name.push_str(socket.as_str()); - } else { - socket_name.push_str("wayland-0") - } - socket_name.push_str(".socket"); - - socket_path.push(socket_name); - - socket_path -} - -/// We make sure the Stream is always set to blocking mode -/// -/// * `tries` - how many times to attempt the connection -/// * `interval` - how long to wait between attempts, in milliseconds -pub fn connect_to_socket(addr: &PathBuf, tries: u8, interval: u64) -> Result { - let socket = rustix::net::socket_with( - rustix::net::AddressFamily::UNIX, - rustix::net::SocketType::STREAM, - rustix::net::SocketFlags::CLOEXEC, - None, - ) - .expect("failed to create socket file descriptor"); - let addr = net::SocketAddrUnix::new(addr).unwrap(); - //Make sure we try at least once - let tries = if tries == 0 { 1 } else { tries }; - let mut error = None; - for _ in 0..tries { - match net::connect_unix(&socket, &addr) { - Ok(()) => { - #[cfg(debug_assertions)] - let timeout = Duration::from_secs(30); //Some operations take a while to respond in debug mode - #[cfg(not(debug_assertions))] - let timeout = Duration::from_secs(5); - if let Err(e) = net::sockopt::set_socket_timeout( - &socket, - net::sockopt::Timeout::Recv, - Some(timeout), - ) { - return Err(format!("failed to set read timeout for socket: {e}")); - } - - return Ok(socket); - } - Err(e) => error = Some(e), - } - std::thread::sleep(Duration::from_millis(interval)); - } - let error = error.unwrap(); - if error.kind() == std::io::ErrorKind::NotFound { - return Err("Socket file not found. Are you sure swww-daemon is running?".to_string()); - } - - Err(format!("Failed to connect to socket: {error}")) -} - -#[derive(Debug)] -pub struct Mmap { - fd: OwnedFd, - ptr: *mut std::ffi::c_void, - len: usize, -} - -impl Mmap { - const PROT: ProtFlags = ProtFlags::WRITE.union(ProtFlags::READ); - const FLAGS: MapFlags = MapFlags::SHARED; - - #[inline] - #[must_use] - pub fn create(len: usize) -> Self { - let fd = create_shm_fd().unwrap(); - rustix::io::retry_on_intr(|| rustix::fs::ftruncate(&fd, len as u64)).unwrap(); - - let ptr = - unsafe { mmap(std::ptr::null_mut(), len, Self::PROT, Self::FLAGS, &fd, 0).unwrap() }; - Self { fd, ptr, len } - } - - #[inline] - pub fn remap(&mut self, new_len: usize) { - rustix::io::retry_on_intr(|| rustix::fs::ftruncate(&self.fd, new_len as u64)).unwrap(); - - #[cfg(target_os = "linux")] - { - let result = unsafe { - rustix::mm::mremap( - self.ptr, - self.len, - new_len, - rustix::mm::MremapFlags::MAYMOVE, - ) - }; - - if let Ok(ptr) = result { - self.ptr = ptr; - self.len = new_len; - return; - } - } - - if let Err(e) = unsafe { munmap(self.ptr, self.len) } { - eprintln!("ERROR WHEN UNMAPPING MEMORY: {e}"); - } - - self.len = new_len; - self.ptr = unsafe { - mmap( - std::ptr::null_mut(), - self.len, - Self::PROT, - Self::FLAGS, - &self.fd, - 0, - ) - .unwrap() - }; - } - - fn from_fd(fd: OwnedFd, len: usize) -> Self { - let ptr = - unsafe { mmap(std::ptr::null_mut(), len, Self::PROT, Self::FLAGS, &fd, 0).unwrap() }; - Self { fd, ptr, len } - } - - #[inline] - #[must_use] - pub fn slice_mut(&mut self) -> &mut [u8] { - unsafe { std::slice::from_raw_parts_mut(self.ptr.cast(), self.len) } - } - - #[inline] - #[must_use] - #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.len - } - - #[inline] - #[must_use] - pub fn fd(&self) -> BorrowedFd { - self.fd.as_fd() - } -} - -impl Drop for Mmap { - #[inline] - fn drop(&mut self) { - if let Err(e) = unsafe { munmap(self.ptr, self.len) } { - eprintln!("ERROR WHEN UNMAPPING MEMORY: {e}"); - } - } -} - -fn create_shm_fd() -> std::io::Result { - #[cfg(target_os = "linux")] - { - match create_memfd() { - Ok(fd) => return Ok(fd), - // Not supported, use fallback. - Err(Errno::NOSYS) => (), - Err(err) => return Err(err.into()), - }; - } - - let time = std::time::SystemTime::now(); - let mut mem_file_handle = format!( - "/swww-ipc-{}", - time.duration_since(std::time::UNIX_EPOCH) - .unwrap() - .subsec_nanos() - ); - - let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; - let mode = Mode::RUSR | Mode::WUSR; - loop { - match rustix::shm::shm_open(mem_file_handle.as_str(), flags, mode) { - Ok(fd) => match rustix::shm::shm_unlink(mem_file_handle.as_str()) { - Ok(_) => return Ok(fd), - - Err(errno) => { - return Err(errno.into()); - } - }, - Err(Errno::EXIST) => { - // Change the handle if we happen to be duplicate. - let time = std::time::SystemTime::now(); - - mem_file_handle = format!( - "/swww-ipc-{}", - time.duration_since(std::time::UNIX_EPOCH) - .unwrap() - .subsec_nanos() - ); - - continue; - } - Err(Errno::INTR) => continue, - Err(err) => return Err(err.into()), - } - } -} - -#[cfg(target_os = "linux")] -fn create_memfd() -> rustix::io::Result { - use rustix::fs::{MemfdFlags, SealFlags}; - use std::ffi::CStr; - - let name = CStr::from_bytes_with_nul(b"swww-ipc\0").unwrap(); - let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC; - - loop { - match rustix::fs::memfd_create(name, flags) { - Ok(fd) => { - // We only need to seal for the purposes of optimization, ignore the errors. - let _ = rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL); - return Ok(fd); - } - Err(Errno::INTR) => continue, - Err(err) => return Err(err), - } - } -} - -fn serialize_bytes(bytes: &[u8], buf: &mut Vec) { - buf.extend((bytes.len() as u32).to_ne_bytes()); - buf.extend(bytes); -} - -fn deserialize_bytes(bytes: &[u8]) -> Box<[u8]> { - let size = u32::from_ne_bytes(bytes[0..4].try_into().unwrap()) as usize; - bytes[4..4 + size].into() -} - -fn deserialize_string(bytes: &[u8]) -> String { - let size = u32::from_ne_bytes(bytes[0..4].try_into().unwrap()) as usize; - std::str::from_utf8(&bytes[4..4 + size]) - .expect("received a non utf8 string from socket") - .to_string() -} diff --git a/utils/src/ipc/mmap.rs b/utils/src/ipc/mmap.rs new file mode 100644 index 00000000..ece971ec --- /dev/null +++ b/utils/src/ipc/mmap.rs @@ -0,0 +1,345 @@ +use std::ptr::NonNull; + +use rustix::{ + fd::{AsFd, BorrowedFd, OwnedFd}, + io::Errno, + mm::{mmap, munmap, MapFlags, ProtFlags}, + shm::{Mode, ShmOFlags}, +}; + +#[derive(Debug)] +pub struct Mmap { + fd: OwnedFd, + ptr: NonNull, + len: usize, +} + +impl Mmap { + const PROT: ProtFlags = ProtFlags::WRITE.union(ProtFlags::READ); + const FLAGS: MapFlags = MapFlags::SHARED; + + #[inline] + #[must_use] + pub fn create(len: usize) -> Self { + let fd = create_shm_fd().unwrap(); + rustix::io::retry_on_intr(|| rustix::fs::ftruncate(&fd, len as u64)).unwrap(); + + let ptr = unsafe { + let ptr = mmap(std::ptr::null_mut(), len, Self::PROT, Self::FLAGS, &fd, 0).unwrap(); + // SAFETY: the function above will never return a null pointer if it succeeds + // POSIX says that the implementation will never select an address at 0 + NonNull::new_unchecked(ptr) + }; + Self { fd, ptr, len } + } + + #[inline] + pub fn remap(&mut self, new_len: usize) { + rustix::io::retry_on_intr(|| rustix::fs::ftruncate(&self.fd, new_len as u64)).unwrap(); + + #[cfg(target_os = "linux")] + { + let result = unsafe { + rustix::mm::mremap( + self.ptr.as_ptr(), + self.len, + new_len, + rustix::mm::MremapFlags::MAYMOVE, + ) + }; + + if let Ok(ptr) = result { + // SAFETY: the mremap above will never return a null pointer if it succeeds + let ptr = unsafe { NonNull::new_unchecked(ptr) }; + self.ptr = ptr; + self.len = new_len; + return; + } + } + + if let Err(e) = unsafe { munmap(self.ptr.as_ptr(), self.len) } { + eprintln!("ERROR WHEN UNMAPPING MEMORY: {e}"); + } + + self.len = new_len; + self.ptr = unsafe { + let ptr = mmap( + std::ptr::null_mut(), + self.len, + Self::PROT, + Self::FLAGS, + &self.fd, + 0, + ) + .unwrap(); + // SAFETY: the function above will never return a null pointer if it succeeds + // POSIX says that the implementation will never select an address at 0 + NonNull::new_unchecked(ptr) + }; + } + + #[must_use] + pub(crate) fn from_fd(fd: OwnedFd, len: usize) -> Self { + let ptr = unsafe { + let ptr = mmap( + std::ptr::null_mut(), + len, + ProtFlags::READ, + Self::FLAGS, + &fd, + 0, + ) + .unwrap(); + // SAFETY: the function above will never return a null pointer if it succeeds + // POSIX says that the implementation will never select an address at 0 + NonNull::new_unchecked(ptr) + }; + Self { fd, ptr, len } + } + + #[inline] + #[must_use] + pub fn slice_mut(&mut self) -> &mut [u8] { + unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr().cast(), self.len) } + } + + #[inline] + #[must_use] + pub fn slice(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(self.ptr.as_ptr().cast(), self.len) } + } + + #[inline] + #[must_use] + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.len + } + + #[inline] + #[must_use] + pub fn fd(&self) -> BorrowedFd { + self.fd.as_fd() + } +} + +impl Drop for Mmap { + #[inline] + fn drop(&mut self) { + if let Err(e) = unsafe { munmap(self.ptr.as_ptr(), self.len) } { + eprintln!("ERROR WHEN UNMAPPING MEMORY: {e}"); + } + } +} + +fn create_shm_fd() -> std::io::Result { + #[cfg(target_os = "linux")] + { + match create_memfd() { + Ok(fd) => return Ok(fd), + // Not supported, use fallback. + Err(Errno::NOSYS) => (), + Err(err) => return Err(err.into()), + }; + } + + let time = std::time::SystemTime::now(); + let mut mem_file_handle = format!( + "/swww-ipc-{}", + time.duration_since(std::time::UNIX_EPOCH) + .unwrap() + .subsec_nanos() + ); + + let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; + let mode = Mode::RUSR | Mode::WUSR; + loop { + match rustix::shm::shm_open(mem_file_handle.as_str(), flags, mode) { + Ok(fd) => match rustix::shm::shm_unlink(mem_file_handle.as_str()) { + Ok(_) => return Ok(fd), + + Err(errno) => { + return Err(errno.into()); + } + }, + Err(Errno::EXIST) => { + // Change the handle if we happen to be duplicate. + let time = std::time::SystemTime::now(); + + mem_file_handle = format!( + "/swww-ipc-{}", + time.duration_since(std::time::UNIX_EPOCH) + .unwrap() + .subsec_nanos() + ); + + continue; + } + Err(Errno::INTR) => continue, + Err(err) => return Err(err.into()), + } + } +} + +#[cfg(target_os = "linux")] +fn create_memfd() -> rustix::io::Result { + use rustix::fs::{MemfdFlags, SealFlags}; + use std::ffi::CStr; + + let name = CStr::from_bytes_with_nul(b"swww-ipc\0").unwrap(); + let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC; + + loop { + match rustix::fs::memfd_create(name, flags) { + Ok(fd) => { + // We only need to seal for the purposes of optimization, ignore the errors. + let _ = rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL); + return Ok(fd); + } + Err(Errno::INTR) => continue, + Err(err) => return Err(err), + } + } +} + +pub struct MmappedBytes { + base_ptr: NonNull, + ptr: NonNull, + len: usize, +} + +impl MmappedBytes { + const PROT: ProtFlags = ProtFlags::READ; + const FLAGS: MapFlags = MapFlags::SHARED; + + pub(crate) fn new(map: &Mmap, bytes: &[u8]) -> Self { + let len = u32::from_ne_bytes(bytes[0..4].try_into().unwrap()) as usize; + let offset = 4 + bytes.as_ptr() as usize - map.ptr.as_ptr() as usize; + let page_size = rustix::param::page_size(); + let page_offset = offset - offset % page_size; + + let base_ptr = unsafe { + let ptr = mmap( + std::ptr::null_mut(), + len + (offset - page_offset), + Self::PROT, + Self::FLAGS, + &map.fd, + page_offset as u64, + ) + .unwrap(); + // SAFETY: the function above will never return a null pointer if it succeeds + // POSIX says that the implementation will never select an address at 0 + NonNull::new_unchecked(ptr) + }; + let ptr = + unsafe { NonNull::new_unchecked(base_ptr.as_ptr().byte_add(offset - page_offset)) }; + + Self { base_ptr, ptr, len } + } + + pub(crate) fn new_with_len(map: &Mmap, bytes: &[u8], len: usize) -> Self { + let offset = bytes.as_ptr() as usize - map.ptr.as_ptr() as usize; + let page_size = rustix::param::page_size(); + let page_offset = offset - offset % page_size; + + let base_ptr = unsafe { + let ptr = mmap( + std::ptr::null_mut(), + len + (offset - page_offset), + Self::PROT, + Self::FLAGS, + &map.fd, + page_offset as u64, + ) + .unwrap(); + // SAFETY: the function above will never return a null pointer if it succeeds + // POSIX says that the implementation will never select an address at 0 + NonNull::new_unchecked(ptr) + }; + let ptr = + unsafe { NonNull::new_unchecked(base_ptr.as_ptr().byte_add(offset - page_offset)) }; + + Self { base_ptr, ptr, len } + } + + #[inline] + #[must_use] + pub fn bytes(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(self.ptr.as_ptr().cast(), self.len) } + } +} + +impl Drop for MmappedBytes { + #[inline] + fn drop(&mut self) { + let len = self.len + self.ptr.as_ptr() as usize - self.base_ptr.as_ptr() as usize; + if let Err(e) = unsafe { munmap(self.base_ptr.as_ptr(), len) } { + eprintln!("ERROR WHEN UNMAPPING MEMORY: {e}"); + } + } +} + +unsafe impl Send for MmappedBytes {} +unsafe impl Sync for MmappedBytes {} + +pub struct MmappedStr { + base_ptr: NonNull, + ptr: NonNull, + len: usize, +} + +impl MmappedStr { + const PROT: ProtFlags = ProtFlags::READ; + const FLAGS: MapFlags = MapFlags::SHARED; + + pub(crate) fn new(map: &Mmap, bytes: &[u8]) -> Self { + let len = u32::from_ne_bytes(bytes[0..4].try_into().unwrap()) as usize; + let offset = 4 + bytes.as_ptr() as usize - map.ptr.as_ptr() as usize; + let page_size = rustix::param::page_size(); + let page_offset = offset - offset % page_size; + + let base_ptr = unsafe { + let ptr = mmap( + std::ptr::null_mut(), + len + (offset - page_offset), + Self::PROT, + Self::FLAGS, + &map.fd, + page_offset as u64, + ) + .unwrap(); + // SAFETY: the function above will never return a null pointer if it succeeds + // POSIX says that the implementation will never select an address at 0 + NonNull::new_unchecked(ptr) + }; + let ptr = + unsafe { NonNull::new_unchecked(base_ptr.as_ptr().byte_add(offset - page_offset)) }; + + // try to parse, panicking if we fail + let s = unsafe { std::slice::from_raw_parts(ptr.as_ptr().cast(), len) }; + let _s = std::str::from_utf8(s).expect("received a non utf8 string from socket"); + + Self { base_ptr, ptr, len } + } + + #[inline] + #[must_use] + pub fn str(&self) -> &str { + let s = unsafe { std::slice::from_raw_parts(self.ptr.as_ptr().cast(), self.len) }; + unsafe { std::str::from_utf8_unchecked(s) } + } +} + +unsafe impl Send for MmappedStr {} +unsafe impl Sync for MmappedStr {} + +impl Drop for MmappedStr { + #[inline] + fn drop(&mut self) { + let len = self.len + self.ptr.as_ptr() as usize - self.base_ptr.as_ptr() as usize; + if let Err(e) = unsafe { munmap(self.base_ptr.as_ptr(), len) } { + eprintln!("ERROR WHEN UNMAPPING MEMORY: {e}"); + } + } +} diff --git a/utils/src/ipc/mod.rs b/utils/src/ipc/mod.rs new file mode 100644 index 00000000..12054d65 --- /dev/null +++ b/utils/src/ipc/mod.rs @@ -0,0 +1,321 @@ +use std::path::PathBuf; + +use rustix::fd::OwnedFd; + +mod mmap; +mod socket; +mod types; + +use crate::cache; +pub use {mmap::*, socket::*, types::*}; + +pub struct ImageRequestBuilder { + memory: Mmap, + len: usize, + img_count: u8, + img_count_index: usize, +} + +impl ImageRequestBuilder { + #[inline] + pub fn new(transition: Transition) -> Self { + let memory = Mmap::create(1 << (20 + 3)); // start with 8 MB + let len = 0; + let mut builder = Self { + memory, + len, + img_count: 0, + img_count_index: 0, + }; + transition.serialize(&mut builder); + builder.img_count_index = builder.len; + builder.len += 1; + assert_eq!(builder.len, 52); + builder + } + + fn push_byte(&mut self, byte: u8) { + if self.len >= self.memory.len() { + self.grow(); + } + self.memory.slice_mut()[self.len] = byte; + self.len += 1; + } + + pub(crate) fn extend(&mut self, bytes: &[u8]) { + if self.len + bytes.len() >= self.memory.len() { + self.memory.remap(self.memory.len() + bytes.len() * 2); + } + self.memory.slice_mut()[self.len..self.len + bytes.len()].copy_from_slice(bytes); + self.len += bytes.len() + } + + fn grow(&mut self) { + self.memory.remap((self.memory.len() * 3) / 2); + } + + #[inline] + pub fn push(&mut self, img: ImgSend, outputs: &[String], animation: Option) { + self.img_count += 1; + + let ImgSend { + path, + img, + dim: dims, + format, + } = &img; + self.serialize_bytes(path.as_bytes()); + self.serialize_bytes(img); + self.extend(&dims.0.to_ne_bytes()); + self.extend(&dims.1.to_ne_bytes()); + self.push_byte(*format as u8); + + self.push_byte(outputs.len() as u8); + for output in outputs.iter() { + self.serialize_bytes(output.as_bytes()); + } + + let animation_start = self.len + 1; + if let Some(animation) = animation.as_ref() { + self.push_byte(1); + animation.serialize(self); + } else { + self.push_byte(0); + } + + // cache the request + for output in outputs.iter() { + if let Err(e) = super::cache::store(output, path) { + eprintln!("ERROR: failed to store cache: {e}"); + } + } + + if animation.is_some() && path != "-" { + let p = PathBuf::from(&path); + if let Err(e) = cache::store_animation_frames( + &self.memory.slice()[animation_start..], + &p, + *dims, + *format, + ) { + eprintln!("Error storing cache for {}: {e}", path); + } + } + } + + #[inline] + pub fn build(mut self) -> Mmap { + self.memory.slice_mut()[self.img_count_index] = self.img_count; + self.memory + } + + fn serialize_bytes(&mut self, bytes: &[u8]) { + self.extend(&(bytes.len() as u32).to_ne_bytes()); + self.extend(bytes); + } +} + +pub enum RequestSend { + Ping, + Query, + Clear(Mmap), + Img(Mmap), + Kill, +} + +pub enum RequestRecv { + Ping, + Query, + Clear(ClearReq), + Img(ImageReq), + Kill, +} + +impl RequestSend { + pub fn send(&self, stream: &OwnedFd) -> Result<(), String> { + let mut socket_msg = [0u8; 16]; + socket_msg[0..8].copy_from_slice(&match self { + Self::Ping => 0u64.to_ne_bytes(), + Self::Query => 1u64.to_ne_bytes(), + Self::Clear(_) => 2u64.to_ne_bytes(), + Self::Img(_) => 3u64.to_ne_bytes(), + Self::Kill => 4u64.to_ne_bytes(), + }); + + let mmap = match self { + Self::Clear(clear) => Some(clear), + Self::Img(img) => Some(img), + _ => None, + }; + + match send_socket_msg(stream, &mut socket_msg, mmap) { + Ok(true) => (), + Ok(false) => return Err("failed to send full length of message in socket!".to_string()), + Err(e) => return Err(format!("failed to write serialized request: {e}")), + } + + Ok(()) + } +} + +impl RequestRecv { + #[must_use] + #[inline] + pub fn receive(socket_msg: SocketMsg) -> Self { + let ret = match socket_msg.code { + 0 => Self::Ping, + 1 => Self::Query, + 2 => { + let mmap = socket_msg.shm.unwrap(); + let bytes = mmap.slice(); + let len = bytes[0] as usize; + let mut outputs = Vec::with_capacity(len); + let mut i = 1; + for _ in 0..len { + let output = MmappedStr::new(&mmap, &bytes[i..]); + i += 4 + output.str().len(); + outputs.push(output); + } + let color = [bytes[i], bytes[i + 1], bytes[i + 2]]; + Self::Clear(ClearReq { + color, + outputs: outputs.into(), + }) + } + 3 => { + let mmap = socket_msg.shm.unwrap(); + let bytes = mmap.slice(); + let transition = Transition::deserialize(&bytes[0..]); + let len = bytes[51] as usize; + + let mut imgs = Vec::with_capacity(len); + let mut outputs = Vec::with_capacity(len); + let mut animations = Vec::with_capacity(len); + + let mut i = 52; + for _ in 0..len { + let (img, offset) = ImgReq::deserialize(&mmap, &bytes[i..]); + i += offset; + imgs.push(img); + + let n_outputs = bytes[i] as usize; + i += 1; + let mut out = Vec::with_capacity(n_outputs); + for _ in 0..n_outputs { + let output = MmappedStr::new(&mmap, &bytes[i..]); + i += 4 + output.str().len(); + out.push(output); + } + outputs.push(out.into()); + + if bytes[i] == 1 { + let (animation, offset) = Animation::deserialize(&mmap, &bytes[i + 1..]); + i += offset; + animations.push(animation); + } + i += 1; + } + + Self::Img(ImageReq { + transition, + imgs: imgs.into(), + outputs: outputs.into(), + animations: if animations.is_empty() { + None + } else { + Some(animations.into()) + }, + }) + } + _ => Self::Kill, + }; + ret + } +} + +pub enum Answer { + Ok, + Ping(bool), + Info(Box<[BgInfo]>), + Err(String), +} + +impl Answer { + pub fn send(&self, stream: &OwnedFd) -> Result<(), String> { + let mut socket_msg = [0u8; 16]; + socket_msg[0..8].copy_from_slice(&match self { + Self::Ok => 0u64.to_ne_bytes(), + Self::Ping(true) => 1u64.to_ne_bytes(), + Self::Ping(false) => 2u64.to_ne_bytes(), + Self::Info(_) => 3u64.to_ne_bytes(), + Self::Err(_) => 4u64.to_ne_bytes(), + }); + + let mmap = match self { + Self::Info(infos) => { + let len = 1 + infos.iter().map(|i| i.serialized_size()).sum::(); + let mut mmap = Mmap::create(len); + let bytes = mmap.slice_mut(); + + bytes[0] = infos.len() as u8; + let mut i = 1; + + for info in infos.iter() { + i += info.serialize(&mut bytes[i..]); + } + + Some(mmap) + } + Self::Err(s) => { + let len = 4 + s.len(); + let mut mmap = Mmap::create(len); + let bytes = mmap.slice_mut(); + bytes[0..4].copy_from_slice(&(s.as_bytes().len() as u32).to_ne_bytes()); + bytes[4..len].copy_from_slice(s.as_bytes()); + Some(mmap) + } + _ => None, + }; + + match send_socket_msg(stream, &mut socket_msg, mmap.as_ref()) { + Ok(true) => Ok(()), + Ok(false) => Err("failed to send full length of message in socket!".to_string()), + Err(e) => Err(format!("failed to write serialized request: {e}")), + } + } + + #[must_use] + #[inline] + pub fn receive(socket_msg: SocketMsg) -> Self { + match socket_msg.code { + 0 => Self::Ok, + 1 => Self::Ping(true), + 2 => Self::Ping(false), + 3 => { + let mmap = socket_msg.shm.unwrap(); + let bytes = mmap.slice(); + let len = bytes[0] as usize; + let mut bg_infos = Vec::with_capacity(len); + + let mut i = 1; + for _ in 0..len { + let (info, offset) = BgInfo::deserialize(&bytes[i..]); + i += offset; + bg_infos.push(info); + } + + Self::Info(bg_infos.into()) + } + 4 => { + let mmap = socket_msg.shm.unwrap(); + let bytes = mmap.slice(); + let size = u32::from_ne_bytes(bytes[0..4].try_into().unwrap()) as usize; + let s = std::str::from_utf8(&bytes[4..4 + size]) + .expect("received a non utf8 string from socket") + .to_string(); + Self::Err(s) + } + _ => panic!("Received malformed answer from daemon"), + } + } +} diff --git a/utils/src/ipc/socket.rs b/utils/src/ipc/socket.rs new file mode 100644 index 00000000..79352185 --- /dev/null +++ b/utils/src/ipc/socket.rs @@ -0,0 +1,141 @@ +use std::{path::PathBuf, time::Duration}; + +use rustix::{ + fd::OwnedFd, + net::{self, RecvFlags}, +}; + +use super::Mmap; + +pub struct SocketMsg { + pub(super) code: u8, + pub(super) shm: Option, +} + +pub fn read_socket(stream: &OwnedFd) -> Result { + let mut buf = [0u8; 16]; + let mut ancillary_buf = [0u8; rustix::cmsg_space!(ScmRights(1))]; + + let mut control = net::RecvAncillaryBuffer::new(&mut ancillary_buf); + + let mut tries = 0; + loop { + let iov = rustix::io::IoSliceMut::new(&mut buf); + match net::recvmsg(stream, &mut [iov], &mut control, RecvFlags::WAITALL) { + Ok(_) => break, + Err(e) => { + if e.kind() == std::io::ErrorKind::WouldBlock && tries < 5 { + std::thread::sleep(Duration::from_millis(1)); + } else { + return Err(format!("failed to read serialized length: {e}")); + } + } + } + tries += 1; + } + + let code = u64::from_ne_bytes(buf[0..8].try_into().unwrap()) as u8; + let len = u64::from_ne_bytes(buf[8..16].try_into().unwrap()) as usize; + + let shm = if len == 0 { + None + } else { + let shm_file = match control.drain().next().unwrap() { + net::RecvAncillaryMessage::ScmRights(mut iter) => iter.next().unwrap(), + _ => panic!("malformed ancillary message"), + }; + Some(Mmap::from_fd(shm_file, len)) + }; + Ok(SocketMsg { code, shm }) +} + +pub(super) fn send_socket_msg( + stream: &OwnedFd, + socket_msg: &mut [u8; 16], + mmap: Option<&Mmap>, +) -> rustix::io::Result { + let mut ancillary_buf = [0u8; rustix::cmsg_space!(ScmRights(1))]; + let mut ancillary = net::SendAncillaryBuffer::new(&mut ancillary_buf); + + let msg_buf; + if let Some(mmap) = mmap.as_ref() { + socket_msg[8..].copy_from_slice(&(mmap.len() as u64).to_ne_bytes()); + msg_buf = [mmap.fd()]; + let msg = net::SendAncillaryMessage::ScmRights(&msg_buf); + ancillary.push(msg); + } + + let iov = rustix::io::IoSlice::new(&socket_msg[..]); + net::sendmsg(stream, &[iov], &mut ancillary, net::SendFlags::empty()) + .map(|written| written == socket_msg.len()) +} + +#[must_use] +pub fn get_socket_path() -> PathBuf { + let runtime_dir = if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") { + dir + } else { + "/tmp/swww".to_string() + }; + + let mut socket_path = PathBuf::new(); + socket_path.push(runtime_dir); + + let mut socket_name = String::new(); + socket_name.push_str("swww-"); + if let Ok(socket) = std::env::var("WAYLAND_DISPLAY") { + socket_name.push_str(socket.as_str()); + } else { + socket_name.push_str("wayland-0") + } + socket_name.push_str(".socket"); + + socket_path.push(socket_name); + + socket_path +} + +/// We make sure the Stream is always set to blocking mode +/// +/// * `tries` - how many times to attempt the connection +/// * `interval` - how long to wait between attempts, in milliseconds +pub fn connect_to_socket(addr: &PathBuf, tries: u8, interval: u64) -> Result { + let socket = rustix::net::socket_with( + rustix::net::AddressFamily::UNIX, + rustix::net::SocketType::STREAM, + rustix::net::SocketFlags::CLOEXEC, + None, + ) + .expect("failed to create socket file descriptor"); + let addr = net::SocketAddrUnix::new(addr).unwrap(); + //Make sure we try at least once + let tries = if tries == 0 { 1 } else { tries }; + let mut error = None; + for _ in 0..tries { + match net::connect_unix(&socket, &addr) { + Ok(()) => { + #[cfg(debug_assertions)] + let timeout = Duration::from_secs(30); //Some operations take a while to respond in debug mode + #[cfg(not(debug_assertions))] + let timeout = Duration::from_secs(5); + if let Err(e) = net::sockopt::set_socket_timeout( + &socket, + net::sockopt::Timeout::Recv, + Some(timeout), + ) { + return Err(format!("failed to set read timeout for socket: {e}")); + } + + return Ok(socket); + } + Err(e) => error = Some(e), + } + std::thread::sleep(Duration::from_millis(interval)); + } + let error = error.unwrap(); + if error.kind() == std::io::ErrorKind::NotFound { + return Err("Socket file not found. Are you sure swww-daemon is running?".to_string()); + } + + Err(format!("Failed to connect to socket: {error}")) +} diff --git a/utils/src/ipc/types.rs b/utils/src/ipc/types.rs new file mode 100644 index 00000000..9de514af --- /dev/null +++ b/utils/src/ipc/types.rs @@ -0,0 +1,601 @@ +use std::{ + fmt, + num::{NonZeroI32, NonZeroU8}, + time::Duration, +}; + +use crate::compression::BitPack; + +use super::{ImageRequestBuilder, Mmap, MmappedBytes, MmappedStr}; + +#[derive(Clone, PartialEq)] +pub enum Coord { + Pixel(f32), + Percent(f32), +} + +#[derive(Clone, PartialEq)] +pub struct Position { + pub x: Coord, + pub y: Coord, +} + +impl Position { + #[must_use] + pub fn new(x: Coord, y: Coord) -> Self { + Self { x, y } + } + + #[must_use] + pub fn to_pixel(&self, dim: (u32, u32), invert_y: bool) -> (f32, f32) { + let x = match self.x { + Coord::Pixel(x) => x, + Coord::Percent(x) => x * dim.0 as f32, + }; + + let y = match self.y { + Coord::Pixel(y) => { + if invert_y { + y + } else { + dim.1 as f32 - y + } + } + Coord::Percent(y) => { + if invert_y { + y * dim.1 as f32 + } else { + (1.0 - y) * dim.1 as f32 + } + } + }; + + (x, y) + } + + #[must_use] + pub fn to_percent(&self, dim: (u32, u32)) -> (f32, f32) { + let x = match self.x { + Coord::Pixel(x) => x / dim.0 as f32, + Coord::Percent(x) => x, + }; + + let y = match self.y { + Coord::Pixel(y) => y / dim.1 as f32, + Coord::Percent(y) => y, + }; + + (x, y) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum BgImg { + Color([u8; 3]), + Img(String), +} + +impl BgImg { + fn serialized_size(&self) -> usize { + 1 //discriminant + + match self { + Self::Color(_) => 3, + Self::Img(s) => 4 + s.len() + } + } +} + +impl fmt::Display for BgImg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BgImg::Color(color) => { + write!(f, "color: {:02X}{:02X}{:02X}", color[0], color[1], color[2]) + } + BgImg::Img(p) => write!(f, "image: {p}",), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +pub enum PixelFormat { + /// No swap, can copy directly onto WlBuffer + Bgr = 0, + /// Swap R and B channels at client, can copy directly onto WlBuffer + Rgb = 1, + /// No swap, must extend pixel with an extra byte when copying + Xbgr = 2, + /// Swap R and B channels at client, must extend pixel with an extra byte when copying + Xrgb = 3, +} + +impl PixelFormat { + #[inline] + #[must_use] + pub const fn channels(&self) -> u8 { + match self { + Self::Rgb => 3, + Self::Bgr => 3, + Self::Xbgr => 4, + Self::Xrgb => 4, + } + } + + #[inline] + #[must_use] + pub const fn must_swap_r_and_b_channels(&self) -> bool { + match self { + Self::Bgr => false, + Self::Rgb => true, + Self::Xbgr => false, + Self::Xrgb => true, + } + } + + #[inline] + #[must_use] + pub const fn can_copy_directly_onto_wl_buffer(&self) -> bool { + match self { + Self::Bgr => true, + Self::Rgb => true, + Self::Xbgr => false, + Self::Xrgb => false, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Scale { + Whole(NonZeroI32), + Fractional(NonZeroI32), +} + +impl Scale { + #[inline] + #[must_use] + pub fn mul_dim(&self, width: i32, height: i32) -> (i32, i32) { + match self { + Scale::Whole(i) => (width * i.get(), height * i.get()), + Scale::Fractional(f) => { + let scale = f.get() as f64 / 120.0; + let width = (width as f64 * scale).round() as i32; + let height = (height as f64 * scale).round() as i32; + (width, height) + } + } + } + + #[inline] + #[must_use] + pub fn div_dim(&self, width: i32, height: i32) -> (i32, i32) { + match self { + Scale::Whole(i) => (width / i.get(), height / i.get()), + Scale::Fractional(f) => { + let scale = 120.0 / f.get() as f64; + let width = (width as f64 * scale).round() as i32; + let height = (height as f64 * scale).round() as i32; + (width, height) + } + } + } +} + +impl fmt::Display for Scale { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Scale::Whole(i) => i.get() as f32, + Scale::Fractional(f) => f.get() as f32 / 120.0, + } + ) + } +} + +#[derive(Clone)] +pub struct BgInfo { + pub name: String, + pub dim: (u32, u32), + pub scale_factor: Scale, + pub img: BgImg, + pub pixel_format: PixelFormat, +} + +impl BgInfo { + #[inline] + #[must_use] + pub fn real_dim(&self) -> (u32, u32) { + let dim = self + .scale_factor + .mul_dim(self.dim.0 as i32, self.dim.1 as i32); + (dim.0 as u32, dim.1 as u32) + } + + pub(super) fn serialized_size(&self) -> usize { + 4 // name len + + self.name.len() + + 8 //dim + + 5 //scale_factor (discriminant + value) + + self.img.serialized_size() + + 1 //pixel_format + } + + pub(super) fn serialize(&self, buf: &mut [u8]) -> usize { + let Self { + name, + dim, + scale_factor, + img, + pixel_format, + } = self; + + let len = name.as_bytes().len(); + buf[0..4].copy_from_slice(&(len as u32).to_ne_bytes()); + buf[4..4 + len].copy_from_slice(name.as_bytes()); + let mut i = 4 + len; + buf[i..i + 4].copy_from_slice(&dim.0.to_ne_bytes()); + buf[i + 4..i + 8].copy_from_slice(&dim.1.to_ne_bytes()); + i += 8; + + match scale_factor { + Scale::Whole(value) => { + buf[i] = 0; + buf[i + 1..i + 5].copy_from_slice(&value.get().to_ne_bytes()); + } + Scale::Fractional(value) => { + buf[i] = 1; + buf[i + 1..i + 5].copy_from_slice(&value.get().to_ne_bytes()); + } + } + i += 5; + + match img { + BgImg::Color(color) => { + buf[i] = 0; + buf[i + 1..i + 4].copy_from_slice(color); + i += 4; + } + BgImg::Img(path) => { + buf[i] = 1; + i += 1; + let len = path.as_bytes().len(); + buf[i..i + 4].copy_from_slice(&(len as u32).to_ne_bytes()); + buf[i + 4..i + 4 + len].copy_from_slice(path.as_bytes()); + i += 4 + len; + } + } + + buf[i] = *pixel_format as u8; + i + 1 + } + + pub(super) fn deserialize(bytes: &[u8]) -> (Self, usize) { + let name = deserialize_string(bytes); + let mut i = name.len() + 4; + + assert!(bytes.len() > i + 17); + + let dim = ( + u32::from_ne_bytes(bytes[i..i + 4].try_into().unwrap()), + u32::from_ne_bytes(bytes[i + 4..i + 8].try_into().unwrap()), + ); + i += 8; + + let scale_factor = if bytes[i] == 0 { + Scale::Whole( + i32::from_ne_bytes(bytes[i + 1..i + 5].try_into().unwrap()) + .try_into() + .unwrap(), + ) + } else { + Scale::Fractional( + i32::from_ne_bytes(bytes[i + 1..i + 5].try_into().unwrap()) + .try_into() + .unwrap(), + ) + }; + i += 5; + + let img = if bytes[i] == 0 { + i += 4; + BgImg::Color([bytes[i - 3], bytes[i - 2], bytes[i - 1]]) + } else { + i += 1; + let path = deserialize_string(&bytes[i..]); + i += 4 + path.len(); + BgImg::Img(path) + }; + + let pixel_format = match bytes[i] { + 0 => PixelFormat::Bgr, + 1 => PixelFormat::Rgb, + 2 => PixelFormat::Xbgr, + _ => PixelFormat::Xrgb, + }; + i += 1; + + ( + Self { + name, + dim, + scale_factor, + img, + pixel_format, + }, + i, + ) + } +} + +impl fmt::Display for BgInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}: {}x{}, scale: {}, currently displaying: {}", + self.name, self.dim.0, self.dim.1, self.scale_factor, self.img + ) + } +} + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum TransitionType { + Simple = 0, + Fade = 1, + Outer = 2, + Wipe = 3, + Grow = 4, + Wave = 5, + None = 6, +} + +pub struct Transition { + pub transition_type: TransitionType, + pub duration: f32, + pub step: NonZeroU8, + pub fps: u16, + pub angle: f64, + pub pos: Position, + pub bezier: (f32, f32, f32, f32), + pub wave: (f32, f32), + pub invert_y: bool, +} + +impl Transition { + pub(super) fn serialize(&self, buf: &mut ImageRequestBuilder) { + let Self { + transition_type, + duration, + step, + fps, + angle, + pos, + bezier, + wave, + invert_y, + } = self; + + buf.push_byte(*transition_type as u8); + buf.extend(&duration.to_ne_bytes()); + buf.push_byte(step.get()); + buf.extend(&fps.to_ne_bytes()); + buf.extend(&angle.to_ne_bytes()); + match pos.x { + Coord::Pixel(f) => { + buf.push_byte(0); + buf.extend(&f.to_ne_bytes()); + } + Coord::Percent(f) => { + buf.push_byte(1); + buf.extend(&f.to_ne_bytes()); + } + } + match pos.y { + Coord::Pixel(f) => { + buf.push_byte(0); + buf.extend(&f.to_ne_bytes()); + } + Coord::Percent(f) => { + buf.push_byte(1); + buf.extend(&f.to_ne_bytes()); + } + } + buf.extend(&bezier.0.to_ne_bytes()); + buf.extend(&bezier.1.to_ne_bytes()); + buf.extend(&bezier.2.to_ne_bytes()); + buf.extend(&bezier.3.to_ne_bytes()); + buf.extend(&wave.0.to_ne_bytes()); + buf.extend(&wave.1.to_ne_bytes()); + buf.push_byte(*invert_y as u8); + } + + pub(super) fn deserialize(bytes: &[u8]) -> Self { + assert!(bytes.len() > 50); + let transition_type = match bytes[0] { + 0 => TransitionType::Simple, + 1 => TransitionType::Fade, + 2 => TransitionType::Outer, + 3 => TransitionType::Wipe, + 4 => TransitionType::Grow, + 5 => TransitionType::Wave, + _ => TransitionType::None, + }; + let duration = f32::from_ne_bytes(bytes[1..5].try_into().unwrap()); + let step = NonZeroU8::new(bytes[5]).expect("received step of 0"); + let fps = u16::from_ne_bytes(bytes[6..8].try_into().unwrap()); + let angle = f64::from_ne_bytes(bytes[8..16].try_into().unwrap()); + let pos = { + let x = if bytes[16] == 0 { + Coord::Pixel(f32::from_ne_bytes(bytes[17..21].try_into().unwrap())) + } else { + Coord::Percent(f32::from_ne_bytes(bytes[17..21].try_into().unwrap())) + }; + let y = if bytes[21] == 0 { + Coord::Pixel(f32::from_ne_bytes(bytes[22..26].try_into().unwrap())) + } else { + Coord::Percent(f32::from_ne_bytes(bytes[22..26].try_into().unwrap())) + }; + Position { x, y } + }; + + let bezier = ( + f32::from_ne_bytes(bytes[26..30].try_into().unwrap()), + f32::from_ne_bytes(bytes[30..34].try_into().unwrap()), + f32::from_ne_bytes(bytes[34..38].try_into().unwrap()), + f32::from_ne_bytes(bytes[38..42].try_into().unwrap()), + ); + + let wave = ( + f32::from_ne_bytes(bytes[42..46].try_into().unwrap()), + f32::from_ne_bytes(bytes[46..50].try_into().unwrap()), + ); + + let invert_y = bytes[50] != 0; + + Self { + transition_type, + duration, + step, + fps, + angle, + pos, + bezier, + wave, + invert_y, + } + } +} + +pub struct ClearSend { + pub color: [u8; 3], + pub outputs: Box<[String]>, +} + +impl ClearSend { + pub fn create_request(self) -> Mmap { + // 1 - output length + // 3 - color bytes + // 4 + output.len() - output len + bytes + let len = 4 + self.outputs.iter().map(|o| 4 + o.len()).sum::(); + let mut mmap = Mmap::create(len); + let bytes = mmap.slice_mut(); + bytes[0] = self.outputs.len() as u8; // we assume someone does not have more than + // 255 monitors. Seems reasonable + let mut i = 1; + for output in self.outputs.iter() { + let len = output.len() as u32; + bytes[i..i + 4].copy_from_slice(&len.to_ne_bytes()); + bytes[i + 4..i + 4 + len as usize].copy_from_slice(output.as_bytes()); + i += 4 + len as usize; + } + bytes[i..i + 3].copy_from_slice(&self.color); + mmap + } +} + +pub struct ClearReq { + pub color: [u8; 3], + pub outputs: Box<[MmappedStr]>, +} + +pub struct ImgSend { + pub path: String, + pub dim: (u32, u32), + pub format: PixelFormat, + pub img: Box<[u8]>, +} + +pub struct ImgReq { + pub path: MmappedStr, + pub dim: (u32, u32), + pub format: PixelFormat, + pub img: MmappedBytes, +} + +impl ImgReq { + pub(super) fn deserialize(mmap: &Mmap, bytes: &[u8]) -> (Self, usize) { + let mut i = 0; + let path = MmappedStr::new(mmap, &bytes[i..]); + i += 4 + path.str().len(); + + let img = MmappedBytes::new(mmap, &bytes[i..]); + i += 4 + img.bytes().len(); + + let dim = ( + u32::from_ne_bytes(bytes[i..i + 4].try_into().unwrap()), + u32::from_ne_bytes(bytes[i + 4..i + 8].try_into().unwrap()), + ); + i += 8; + + let format = match bytes[i] { + 0 => PixelFormat::Bgr, + 1 => PixelFormat::Rgb, + 2 => PixelFormat::Xbgr, + _ => PixelFormat::Xrgb, + }; + i += 1; + + ( + Self { + path, + dim, + format, + img, + }, + i, + ) + } +} + +pub struct Animation { + pub animation: Box<[(BitPack, Duration)]>, +} + +impl Animation { + pub(crate) fn serialize(&self, buf: &mut ImageRequestBuilder) { + let Self { animation } = self; + + buf.extend(&(animation.len() as u32).to_ne_bytes()); + for (bitpack, duration) in animation.iter() { + bitpack.serialize(buf); + buf.extend(&duration.as_secs_f64().to_ne_bytes()) + } + } + + pub(crate) fn deserialize(mmap: &Mmap, bytes: &[u8]) -> (Self, usize) { + let mut i = 0; + let animation_len = u32::from_ne_bytes(bytes[i..i + 4].try_into().unwrap()) as usize; + i += 4; + let mut animation = Vec::with_capacity(animation_len); + for _ in 0..animation_len { + let (anim, offset) = BitPack::deserialize(mmap, &bytes[i..]); + i += offset; + let duration = + Duration::from_secs_f64(f64::from_ne_bytes(bytes[i..i + 8].try_into().unwrap())); + i += 8; + animation.push((anim, duration)); + } + + ( + Self { + animation: animation.into(), + }, + i, + ) + } +} + +pub struct ImageReq { + pub transition: Transition, + pub imgs: Box<[ImgReq]>, + pub outputs: Box<[Box<[MmappedStr]>]>, + pub animations: Option>, +} + +fn deserialize_string(bytes: &[u8]) -> String { + let size = u32::from_ne_bytes(bytes[0..4].try_into().unwrap()) as usize; + std::str::from_utf8(&bytes[4..4 + size]) + .expect("received a non utf8 string from socket") + .to_string() +}