diff --git a/Cargo.lock b/Cargo.lock index 134b8cd0..e605dc7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -802,15 +802,6 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1389,7 +1380,6 @@ dependencies = [ "keyframe", "libc", "log", - "memmap2", "rand", "rayon", "rustix", diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index 8e3231a7..926bcadb 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -11,13 +11,12 @@ log = { version = "0.4", features = ["max_level_debug", "release_max_level_info" simplelog = "0.12" wayland-client = { version = "0.31", default-features = false, features = [ "log" ]} -wayland-protocols = { version = "0.31", default-features = false, features = [ "client" ]} +wayland-protocols = { version = "0.31", default-features = false, features = [ "client", "staging" ]} wayland-protocols-wlr = { version = "0.2", default-features = false, features = [ "client" ]} # use specific git version for Duration implementation. We will do this until the next bitcode release bitcode = { git = "https://github.com/SoftbearStudios/bitcode.git", rev = "5f25a59", default-features = false } rustix = { version = "0.38", default-features = false, features = [ "event", "shm", "mm" ] } -memmap2 = "0.9" libc = "0.2" keyframe = "1.1" diff --git a/daemon/src/bump_pool.rs b/daemon/src/bump_pool.rs index 7e96c1aa..0053e15e 100644 --- a/daemon/src/bump_pool.rs +++ b/daemon/src/bump_pool.rs @@ -1,24 +1,90 @@ -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, +use std::{ + io, + os::unix::prelude::{AsFd, OwnedFd}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::{SystemTime, UNIX_EPOCH}, +}; + +use rustix::{ + io::Errno, + mm::{mmap, munmap, MapFlags, ProtFlags}, + shm::{Mode, ShmOFlags}, }; -use crate::raw_pool::RawPool; use wayland_client::{ - protocol::{wl_buffer::WlBuffer, wl_shm::WlShm}, - QueueHandle, + backend::ObjectData, + protocol::{ + wl_buffer::WlBuffer, + wl_shm::{self, WlShm}, + wl_shm_pool::{self, WlShmPool}, + }, + Proxy, WEnum, }; -use crate::Daemon; +#[derive(Debug)] +struct ReleaseFlag(AtomicBool); + +impl ReleaseFlag { + fn is_released(&self) -> bool { + self.0.load(Ordering::Acquire) + } + + fn set_released(&self) { + self.0.store(true, Ordering::Release) + } + + fn unset_released(&self) { + self.0.store(false, Ordering::Release) + } +} + +impl ObjectData for ReleaseFlag { + fn event( + self: Arc, + _: &wayland_client::backend::Backend, + msg: wayland_client::backend::protocol::Message, + ) -> Option> { + if msg.opcode == wayland_client::protocol::wl_buffer::Event::Release.opcode() { + self.set_released(); + } + + None + } + + fn destroyed(&self, _: wayland_client::backend::ObjectId) {} +} #[derive(Debug)] struct Buffer { inner: WlBuffer, - released: Arc, + released: Arc, } impl Buffer { - fn new(inner: WlBuffer, released: Arc) -> Self { + fn new( + pool: &WlShmPool, + offset: i32, + width: i32, + height: i32, + stride: i32, + format: wl_shm::Format, + ) -> Self { + let released = Arc::new(ReleaseFlag(AtomicBool::new(true))); + let inner = pool + .send_constructor( + wl_shm_pool::Request::CreateBuffer { + offset, + width, + height, + stride, + format: WEnum::Value(format), + }, + released.clone(), + ) + .expect("WlShmPool failed to create buffer"); Self { inner, released } } } @@ -29,12 +95,79 @@ impl Drop for Buffer { } } +#[derive(Debug)] +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; + + fn new(len: usize) -> Self { + let fd = create_shm_fd().unwrap(); + + loop { + match rustix::fs::ftruncate(&fd, len as u64) { + Err(Errno::INTR) => continue, + otherwise => break otherwise.unwrap(), + } + } + + let ptr = + unsafe { mmap(std::ptr::null_mut(), len, Self::PROT, Self::FLAGS, &fd, 0).unwrap() }; + Self { fd, ptr, len } + } + + fn remap(&mut self, new_len: usize) { + if let Err(e) = unsafe { munmap(self.ptr, self.len) } { + log::error!("ERROR WHEN UNMAPPING MEMORY: {e}"); + } + self.len = new_len; + + loop { + match rustix::fs::ftruncate(&self.fd, self.len as u64) { + Err(Errno::INTR) => continue, + otherwise => break otherwise.unwrap(), + } + } + + self.ptr = unsafe { + mmap( + std::ptr::null_mut(), + self.len, + Self::PROT, + Self::FLAGS, + &self.fd, + 0, + ) + .unwrap() + }; + } + + fn as_mut(&mut self) -> &mut [u8] { + unsafe { std::slice::from_raw_parts_mut(self.ptr.cast(), self.len) } + } +} + +impl Drop for Mmap { + fn drop(&mut self) { + if let Err(e) = unsafe { munmap(self.ptr, self.len) } { + log::error!("ERROR WHEN UNMAPPING MEMORY: {e}"); + } + } +} + #[derive(Debug)] /// A pool implementation that only gives buffers of a fixed size, creating new ones if none of /// them are freed. It also takes care of copying the previous buffer's content over to the new one /// for us pub(crate) struct BumpPool { - pool: RawPool, + pool: WlShmPool, + mmap: Mmap, + buffers: Vec, width: i32, height: i32, @@ -45,11 +178,12 @@ impl BumpPool { /// We assume `width` and `height` have already been multiplied by their scale factor pub(crate) fn new(width: i32, height: i32, shm: &WlShm) -> Self { let len = width as usize * height as usize * crate::pixel_format().channels() as usize; - let pool = RawPool::new(len, shm); + let (pool, mmap) = new_pool(len, shm); let buffers = vec![]; Self { pool, + mmap, buffers, width, height, @@ -73,63 +207,63 @@ impl BumpPool { } /// resizes the pool and creates a new WlBuffer at the next free offset - fn grow(&mut self, qh: &QueueHandle) { + fn grow(&mut self) { //TODO: CHECK IF WE HAVE SIZE let len = self.buffer_len(); - self.pool - .resize(self.occupied_bytes() + len) - .expect("failed to resize RawPool"); - let released = Arc::new(AtomicBool::new(true)); + + let new_len = self.occupied_bytes() + len; + if new_len > self.mmap.len { + self.mmap.remap(new_len); + self.pool.resize(new_len as i32); + } + let new_buffer_index = self.buffers.len(); self.buffers.push(Buffer::new( - self.pool.create_buffer( - self.buffer_offset(new_buffer_index).try_into().unwrap(), - self.width, - self.height, - self.width * crate::pixel_format().channels() as i32, - crate::wl_shm_format(), - released.clone(), - qh, - ), - released, + &self.pool, + self.buffer_offset(new_buffer_index).try_into().unwrap(), + self.width, + self.height, + self.width * crate::pixel_format().channels() as i32, + crate::wl_shm_format(), )); + log::info!( "BumpPool with: {} buffers. Size: {}Kb", self.buffers.len(), - self.pool.len() / 1024 + self.mmap.len / 1024 ); } /// Returns a drawable surface. If we can't find a free buffer, we request more memory /// /// This function automatically handles copying the previous buffer over onto the new one - pub(crate) fn get_drawable(&mut self, qh: &QueueHandle) -> &mut [u8] { + pub(crate) fn get_drawable(&mut self) -> &mut [u8] { let (i, buf) = match self .buffers .iter() .enumerate() - .find(|(_, b)| b.released.load(Ordering::Acquire)) + .find(|(_, b)| b.released.is_released()) { Some((i, buf)) => (i, buf), None => { - self.grow(qh); + self.grow(); (self.buffers.len() - 1, self.buffers.last().unwrap()) } }; let len = self.buffer_len(); let offset = self.buffer_offset(i); - buf.released.store(false, Ordering::Release); + buf.released.unset_released(); if let Some(i) = self.last_used_buffer { let last_offset = self.buffer_offset(i); - self.pool - .mmap() + self.mmap + .as_mut() .copy_within(last_offset..last_offset + len, offset); } self.last_used_buffer = Some(i); - &mut self.pool.mmap()[offset..offset + len] + &mut self.mmap.as_mut()[offset..offset + len] } /// gets the last buffer we've drawn to @@ -149,3 +283,106 @@ impl BumpPool { self.buffers.clear(); } } + +impl Drop for BumpPool { + fn drop(&mut self) { + self.pool.destroy(); + } +} + +fn new_pool(len: usize, shm: &WlShm) -> (WlShmPool, Mmap) { + let mmap = Mmap::new(len); + + let pool = shm + .send_constructor( + wl_shm::Request::CreatePool { + fd: mmap.fd.as_fd(), + size: len as i32, + }, + Arc::new(ShmPoolData), + ) + .expect("failed to create WlShmPool object"); + + (pool, mmap) +} + +fn create_shm_fd() -> 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(Into::::into(err)), + }; + } + + let time = SystemTime::now(); + let mut mem_file_handle = format!( + "/swww-daemon-{}", + time.duration_since(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 = SystemTime::now(); + + mem_file_handle = format!( + "/swww-daemon-{}", + time.duration_since(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-daemon\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), + } + } +} + +#[derive(Debug)] +struct ShmPoolData; + +impl ObjectData for ShmPoolData { + fn event( + self: Arc, + _: &wayland_client::backend::Backend, + _: wayland_client::backend::protocol::Message, + ) -> Option> { + unreachable!("wl_shm_pool has no events") + } + + fn destroyed(&self, _: wayland_client::backend::ObjectId) {} +} diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 01f785d1..a8e37088 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -5,7 +5,6 @@ mod animations; pub mod bump_pool; mod cli; -pub mod raw_pool; mod wallpaper; use log::{debug, error, info, warn, LevelFilter}; use rustix::{ @@ -14,9 +13,17 @@ use rustix::{ }; use simplelog::{ColorChoice, TermLogger, TerminalMode, ThreadLogMode}; use wallpaper::Wallpaper; +use wayland_protocols::wp::{ + fractional_scale::v1::client::{ + wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, + wp_fractional_scale_v1::WpFractionalScaleV1, + }, + viewporter::client::{wp_viewport::WpViewport, wp_viewporter::WpViewporter}, +}; use std::{ fs, + num::NonZeroI32, os::{ fd::OwnedFd, unix::net::{UnixListener, UnixStream}, @@ -31,7 +38,6 @@ use std::{ use wayland_client::{ globals::{registry_queue_init, GlobalList, GlobalListContents}, protocol::{ - wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output, @@ -45,7 +51,7 @@ use wayland_client::{ use utils::ipc::{ connect_to_socket, get_socket_path, read_socket, AnimationRequest, Answer, BgInfo, - ImageRequest, PixelFormat, Request, + ImageRequest, PixelFormat, Request, Scale, }; use animations::Animator; @@ -184,10 +190,24 @@ fn main() -> Result<(), String> { }; if !events[1].is_empty() { - read_guard.read().expect("failed to read the event queue"); - event_queue - .dispatch_pending(&mut daemon) - .expect("failed to dispatch events"); + match read_guard.read() { + Ok(_) => { + event_queue + .dispatch_pending(&mut daemon) + .expect("failed to dispatch events"); + } + Err(e) => match e { + wayland_client::backend::WaylandError::Io(io) => match io.kind() { + std::io::ErrorKind::WouldBlock => { + warn!("failed to read wayland events because it would block") + } + _ => panic!("Io error when reading wayland events: {io}"), + }, + wayland_client::backend::WaylandError::Protocol(e) => { + panic!("{e}") + } + }, + } } else { drop(read_guard); } @@ -309,6 +329,8 @@ struct Daemon { shm: WlShm, pixel_format: PixelFormat, shm_format: wl_shm::Format, + viewporter: WpViewporter, + fractional_scale_manager: Option, // swww stuff wallpapers: Vec>, @@ -334,12 +356,19 @@ impl Daemon { let pixel_format = PixelFormat::Xrgb; let shm_format = wl_shm::Format::Xrgb8888; + let viewporter = globals + .bind(qh, 1..=1, ()) + .expect("viewported not available"); + let fractional_scale_manager = globals.bind(qh, 1..=1, ()).ok(); + Self { layer_shell, compositor, shm, pixel_format, shm_format, + viewporter, + fractional_scale_manager, wallpapers: Vec::new(), animator: Animator::new(), @@ -443,6 +472,7 @@ impl Daemon { assert!(PIXEL_FORMAT.set(self.pixel_format).is_ok()); log::info!("Selected wl_shm format: {:?}", self.shm_format); } + let surface = self.compositor.create_surface(qh, ()); // Wayland clients are expected to render the cursor on their input region. @@ -460,10 +490,18 @@ impl Daemon { (), ); + let wp_viewport = self.viewporter.get_viewport(&surface, qh, ()); + let wp_fractional = self + .fractional_scale_manager + .as_ref() + .map(|f| f.get_fractional_scale(&surface, qh, surface.clone())); + debug!("New output: {}", output.id()); self.wallpapers.push(Arc::new(Wallpaper::new( output, surface, + wp_viewport, + wp_fractional, layer_surface, &self.shm, qh, @@ -493,7 +531,10 @@ impl Dispatch for Daemon { .. } => wallpaper.set_dimensions(width, height), wl_output::Event::Done => wallpaper.commit_surface_changes(), - wl_output::Event::Scale { factor } => wallpaper.set_scale(factor), + wl_output::Event::Scale { factor } => match NonZeroI32::new(factor) { + Some(factor) => wallpaper.set_scale(Scale::Whole(factor)), + None => error!("received scale factor of 0 from compositor"), + }, wl_output::Event::Name { name } => wallpaper.set_name(name), wl_output::Event::Description { description } => { wallpaper.set_desc(description) @@ -507,24 +548,6 @@ impl Dispatch for Daemon { } } -impl Dispatch> for Daemon { - fn event( - _state: &mut Self, - _proxy: &WlBuffer, - event: ::Event, - data: &Arc, - _conn: &Connection, - _qhandle: &QueueHandle, - ) { - match event { - wayland_client::protocol::wl_buffer::Event::Release => { - data.store(true, Ordering::Release); - } - e => error!("unrecognized WlBuffer event: {e:?}"), - } - } -} - impl Dispatch for Daemon { fn event( _state: &mut Self, @@ -553,8 +576,13 @@ impl Dispatch for Daemon { wl_surface::Event::PreferredBufferScale { factor } => { for wallpaper in state.wallpapers.iter_mut() { if wallpaper.has_surface(proxy) { - wallpaper.set_scale(factor); - wallpaper.commit_surface_changes(); + match NonZeroI32::new(factor) { + Some(factor) => { + wallpaper.set_scale(Scale::Whole(factor)); + wallpaper.commit_surface_changes(); + } + None => error!("received scale factor of 0 from compositor"), + } return; } } @@ -676,6 +704,76 @@ impl Dispatch for Daemon { } } +impl Dispatch for Daemon { + fn event( + _state: &mut Self, + _proxy: &WpViewporter, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + error!("WpViewporter has no events"); + } +} + +impl Dispatch for Daemon { + fn event( + _state: &mut Self, + _proxy: &WpViewport, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + error!("WpViewport has no events"); + } +} + +impl Dispatch for Daemon { + fn event( + _state: &mut Self, + _proxy: &WpFractionalScaleManagerV1, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + error!("WpFractionalScaleManagerV1 has no events"); + } +} + +impl Dispatch for Daemon { + fn event( + state: &mut Self, + _proxy: &WpFractionalScaleV1, + event: ::Event, + data: &WlSurface, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1; + match event { + wp_fractional_scale_v1::Event::PreferredScale { scale } => { + for wallpaper in state.wallpapers.iter_mut() { + if wallpaper.has_surface(data) { + match NonZeroI32::new(scale as i32) { + Some(factor) => { + wallpaper.set_scale(Scale::Fractional(factor)); + wallpaper.commit_surface_changes(); + } + None => error!("received scale factor of 0 from compositor"), + } + return; + } + } + warn!("received new fractional scale factor for non-existing surface") + } + e => error!("unrecognized WpFractionalScaleV1 event: {e:?}"), + } + } +} + impl Dispatch for Daemon { fn event( _state: &mut Self, diff --git a/daemon/src/raw_pool.rs b/daemon/src/raw_pool.rs deleted file mode 100644 index 5e08c6fc..00000000 --- a/daemon/src/raw_pool.rs +++ /dev/null @@ -1,226 +0,0 @@ -//! Literally copy-pasted from the smithay-client-toolkit repository -//! -//! A raw shared memory pool handler. -//! -//! This is intended as a safe building block for higher level shared memory pool abstractions and is not -//! encouraged for most library users. - -use rustix::{ - io::Errno, - shm::{Mode, ShmOFlags}, -}; -use std::{ - fs::File, - io, - os::unix::prelude::{AsFd, OwnedFd}, - sync::Arc, - time::{SystemTime, UNIX_EPOCH}, -}; - -use memmap2::MmapMut; -use wayland_client::{ - backend::ObjectData, - protocol::{wl_buffer, wl_shm, wl_shm_pool}, - Dispatch, Proxy, QueueHandle, -}; - -/// A raw handler for file backed shared memory pools. -/// -/// This type of pool will create the SHM memory pool and provide a way to resize the pool. -/// -/// This pool does not release buffers. If you need this, use one of the higher level pools. -#[derive(Debug)] -pub struct RawPool { - pool: wl_shm_pool::WlShmPool, - len: usize, - mem_file: File, - mmap: MmapMut, -} - -impl RawPool { - pub fn new(len: usize, shm: &wl_shm::WlShm) -> RawPool { - let shm_fd = RawPool::create_shm_fd().unwrap(); - let mem_file = File::from(shm_fd); - mem_file.set_len(len as u64).unwrap(); - - let pool = shm - .send_constructor( - wl_shm::Request::CreatePool { - fd: mem_file.as_fd(), - size: len as i32, - }, - Arc::new(ShmPoolData), - ) - .unwrap_or_else(|_| Proxy::inert(shm.backend().clone())); - let mmap = unsafe { MmapMut::map_mut(&mem_file).unwrap() }; - - RawPool { - pool, - len, - mem_file, - mmap, - } - } - - /// Resizes the memory pool, notifying the server the pool has changed in size. - /// - /// The wl_shm protocol only allows the pool to be made bigger. If the new size is smaller than the - /// current size of the pool, this function will do nothing. - pub fn resize(&mut self, size: usize) -> io::Result<()> { - if size > self.len { - self.len = size; - self.mem_file.set_len(size as u64)?; - self.pool.resize(size as i32); - self.mmap = unsafe { MmapMut::map_mut(&self.mem_file) }?; - } - - Ok(()) - } - - /// Returns a reference to the underlying shared memory file using the memmap2 crate. - pub fn mmap(&mut self) -> &mut MmapMut { - &mut self.mmap - } - - /// Returns the size of the mempool - #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.len - } - - /// Create a new buffer to this pool. - /// - /// ## Parameters - /// - `offset`: the offset (in bytes) from the beginning of the pool at which this buffer starts. - /// - `width` and `height`: the width and height of the buffer in pixels. - /// - `stride`: distance (in bytes) between the beginning of a row and the next one. - /// - `format`: the encoding format of the pixels. - /// - /// The encoding format of the pixels must be supported by the compositor or else a protocol error is - /// risen. You can ensure the format is supported by listening to [`Shm::formats`](crate::shm::Shm::formats). - /// - /// Note this function only creates the wl_buffer object, you will need to write to the pixels using the - /// [`io::Write`] implementation or [`RawPool::mmap`]. - #[allow(clippy::too_many_arguments)] - pub fn create_buffer( - &mut self, - offset: i32, - width: i32, - height: i32, - stride: i32, - format: wl_shm::Format, - udata: U, - qh: &QueueHandle, - ) -> wl_buffer::WlBuffer - where - D: Dispatch + 'static, - U: Send + Sync + 'static, - { - self.pool - .create_buffer(offset, width, height, stride, format, qh, udata) - } - - /// Returns the pool object used to communicate with the server. - pub fn pool(&self) -> &wl_shm_pool::WlShmPool { - &self.pool - } -} - -impl RawPool { - fn create_shm_fd() -> io::Result { - #[cfg(target_os = "linux")] - { - match RawPool::create_memfd() { - Ok(fd) => return Ok(fd), - - // Not supported, use fallback. - Err(Errno::NOSYS) => (), - - Err(err) => return Err(Into::::into(err)), - }; - } - - let time = SystemTime::now(); - let mut mem_file_handle = format!( - "/swww-daemon-{}", - time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() - ); - - loop { - let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; - - let mode = Mode::RUSR | Mode::WUSR; - - 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 = SystemTime::now(); - - mem_file_handle = format!( - "/swww-daemon-{}", - time.duration_since(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 std::ffi::CStr; - - use rustix::fs::{MemfdFlags, SealFlags}; - - loop { - let name = CStr::from_bytes_with_nul(b"swww-daemon\0").unwrap(); - let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC; - - 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), - } - } - } -} - -impl Drop for RawPool { - fn drop(&mut self) { - self.pool.destroy(); - } -} - -#[derive(Debug)] -struct ShmPoolData; - -impl ObjectData for ShmPoolData { - fn event( - self: Arc, - _: &wayland_client::backend::Backend, - _: wayland_client::backend::protocol::Message, - ) -> Option> { - unreachable!("wl_shm_pool has no events") - } - - fn destroyed(&self, _: wayland_client::backend::ObjectId) {} -} diff --git a/daemon/src/wallpaper.rs b/daemon/src/wallpaper.rs index 5f93a04a..25d62bd1 100644 --- a/daemon/src/wallpaper.rs +++ b/daemon/src/wallpaper.rs @@ -1,5 +1,9 @@ use log::{debug, error, warn}; -use utils::ipc::{BgImg, BgInfo}; +use utils::ipc::{BgImg, BgInfo, Scale}; +use wayland_protocols::wp::{ + fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, + viewporter::client::wp_viewport::WpViewport, +}; use std::{ num::NonZeroI32, @@ -58,7 +62,7 @@ struct WallpaperInner { desc: Option, width: NonZeroI32, height: NonZeroI32, - scale_factor: NonZeroI32, + scale_factor: Scale, } impl Default for WallpaperInner { @@ -68,7 +72,7 @@ impl Default for WallpaperInner { desc: None, width: unsafe { NonZeroI32::new_unchecked(4) }, height: unsafe { NonZeroI32::new_unchecked(4) }, - scale_factor: unsafe { NonZeroI32::new_unchecked(1) }, + scale_factor: Scale::Whole(unsafe { NonZeroI32::new_unchecked(1) }), } } } @@ -76,6 +80,9 @@ impl Default for WallpaperInner { pub(super) struct Wallpaper { output: WlOutput, wl_surface: WlSurface, + wp_viewport: WpViewport, + #[allow(unused)] + wp_fractional: Option, layer_surface: LayerSurface, inner: RwLock, @@ -94,6 +101,8 @@ impl Wallpaper { pub(crate) fn new( output: WlOutput, wl_surface: WlSurface, + wp_viewport: WpViewport, + wp_fractional: Option, layer_surface: LayerSurface, shm: &WlShm, qh: &QueueHandle, @@ -111,7 +120,7 @@ impl Wallpaper { layer_surface.set_exclusive_zone(-1); layer_surface.set_margin(0, 0, 0, 0); layer_surface.set_keyboard_interactivity(KeyboardInteractivity::None); - layer_surface.set_size(4, 4); + //layer_surface.set_size(4, 4); wl_surface.set_buffer_scale(1); // commit so that the compositor send the initial configuration @@ -123,6 +132,8 @@ impl Wallpaper { Self { output, wl_surface, + wp_viewport, + wp_fractional, layer_surface, inner, inner_staging, @@ -144,7 +155,7 @@ impl Wallpaper { BgInfo { name: inner.name.clone().unwrap_or("?".to_string()), dim: (inner.width.get() as u32, inner.height.get() as u32), - scale_factor: inner.scale_factor.get(), + scale_factor: inner.scale_factor, img: self.img.lock().unwrap().clone(), pixel_format: crate::pixel_format(), } @@ -165,48 +176,59 @@ impl Wallpaper { #[inline] pub fn set_dimensions(&self, width: i32, height: i32) { let mut lock = self.inner_staging.lock().unwrap(); - let scale = lock.scale_factor.get(); + let (width, height) = lock.scale_factor.div_dim(width as u32, height as u32); - match NonZeroI32::new(width / scale) { + match NonZeroI32::new(width as i32) { Some(width) => lock.width = width, None => { - error!("dividing width {width} by scale_factor {scale} results in width 0!") + error!( + "dividing width {width} by scale_factor {} results in width 0!", + lock.scale_factor + ) } } - match NonZeroI32::new(height / scale) { + match NonZeroI32::new(height as i32) { Some(height) => lock.height = height, None => { - error!("dividing height {height} by scale_factor {scale} results in height 0!") + error!( + "dividing height {height} by scale_factor {} results in height 0!", + lock.scale_factor + ) } } } #[inline] - pub fn set_scale(&self, scale: i32) { - if scale <= 0 { - error!("invalid scale ({scale}) for output: {:?}", self.output); + pub fn set_scale(&self, scale: Scale) { + let mut lock = self.inner_staging.lock().unwrap(); + if matches!(lock.scale_factor, Scale::Fractional(_)) && matches!(scale, Scale::Whole(_)) { return; } - let mut lock = self.inner_staging.lock().unwrap(); - let (width, height) = ( - lock.width.get() * lock.scale_factor.get(), - lock.height.get() * lock.scale_factor.get(), - ); - lock.scale_factor = unsafe { NonZeroI32::new_unchecked(scale) }; + let (old_width, old_height) = lock + .scale_factor + .mul_dim(lock.width.get() as u32, lock.height.get() as u32); - match NonZeroI32::new(width / scale) { + lock.scale_factor = scale; + let (width, height) = lock.scale_factor.div_dim(old_width, old_height); + match NonZeroI32::new(width as i32) { Some(width) => lock.width = width, None => { - error!("dividing width {width} by scale_factor {scale} results in width 0!") + error!( + "dividing width {width} by scale_factor {} results in width 0!", + lock.scale_factor + ) } } - match NonZeroI32::new(height / scale) { + match NonZeroI32::new(height as i32) { Some(height) => lock.height = height, None => { - error!("dividing height {height} by scale_factor {scale} results in height 0!") + error!( + "dividing height {height} by scale_factor {} results in height 0!", + lock.scale_factor + ) } } } @@ -251,17 +273,24 @@ impl Wallpaper { self.stop_animations(); - self.wl_surface.set_buffer_scale(scale_factor.get()); - - *self.img.lock().unwrap() = BgImg::Color([0, 0, 0]); + match scale_factor { + Scale::Whole(i) => { + // unset destination + self.wp_viewport.set_destination(-1, -1); + self.wl_surface.set_buffer_scale(i.get()); + } + Scale::Fractional(_) => { + self.wl_surface.set_buffer_scale(1); + self.wp_viewport.set_destination(width.get(), height.get()); + } + } + self.layer_surface + .set_size(width.get() as u32, height.get() as u32); - let w = width.get() * scale_factor.get(); - let h = height.get() * scale_factor.get(); - self.pool.lock().unwrap().resize(w, h); + let (w, h) = scale_factor.mul_dim(width.get() as u32, height.get() as u32); + self.pool.lock().unwrap().resize(w as i32, h as i32); *self.frame_callback_handler.time.lock().unwrap() = Some(0); - self.layer_surface - .set_size(width.get() as u32, height.get() as u32); self.wl_surface.commit(); self.wl_surface.frame(&self.qh, self.wl_surface.clone()); self.configured.store(false, Ordering::Release); @@ -300,10 +329,9 @@ impl Wallpaper { pub(super) fn get_dimensions(&self) -> (u32, u32) { let inner = self.inner.read().unwrap(); - let width = inner.width.get() as u32; - let height = inner.height.get() as u32; - let scale_factor = inner.scale_factor.get() as u32; - (width * scale_factor, height * scale_factor) + inner + .scale_factor + .mul_dim(inner.width.get() as u32, inner.height.get() as u32) } #[inline] @@ -311,7 +339,7 @@ impl Wallpaper { where F: FnOnce(&mut [u8]) -> T, { - f(self.pool.lock().unwrap().get_drawable(&self.qh)) + f(self.pool.lock().unwrap().get_drawable()) } #[inline] @@ -366,12 +394,13 @@ impl Wallpaper { } let inner = self.inner.read().unwrap(); if let Some(buf) = self.pool.lock().unwrap().get_commitable_buffer() { - let width = inner.width.get() * inner.scale_factor.get(); - let height = inner.height.get() * inner.scale_factor.get(); + let (width, height) = inner + .scale_factor + .mul_dim(inner.width.get() as u32, inner.height.get() as u32); let surface = &self.wl_surface; surface.attach(Some(buf), 0, 0); drop(inner); - surface.damage_buffer(0, 0, width, height); + surface.damage_buffer(0, 0, width as i32, height as i32); surface.commit(); surface.frame(&self.qh, surface.clone()); } else { @@ -389,3 +418,6 @@ impl Drop for Wallpaper { self.output.release() } } + +unsafe impl Sync for Wallpaper {} +unsafe impl Send for Wallpaper {} diff --git a/src/main.rs b/src/main.rs index 0f3d6b35..2d39c041 100644 --- a/src/main.rs +++ b/src/main.rs @@ -235,10 +235,7 @@ fn get_format_dims_and_outputs( if !requested_outputs.is_empty() && !requested_outputs.contains(&name) { continue; } - let real_dim = ( - info.dim.0 * info.scale_factor as u32, - info.dim.1 * info.scale_factor as u32, - ); + let real_dim = info.real_dim(); if let Some((_, output)) = dims .iter_mut() .zip(&imgs) diff --git a/utils/src/ipc.rs b/utils/src/ipc.rs index 3f261915..2891c1ee 100644 --- a/utils/src/ipc.rs +++ b/utils/src/ipc.rs @@ -2,6 +2,7 @@ use bitcode::{Decode, Encode}; use std::{ fmt, io::{BufReader, BufWriter, Read, Write}, + num::NonZeroI32, os::unix::net::UnixStream, path::PathBuf, time::Duration, @@ -134,11 +135,56 @@ impl PixelFormat { } } +#[derive(Clone, Copy, Debug, Decode, Encode, PartialEq)] +pub enum Scale { + Whole(NonZeroI32), + Fractional(NonZeroI32), +} + +impl Scale { + #[inline] + #[must_use] + pub fn mul_dim(&self, width: u32, height: u32) -> (u32, u32) { + match self { + Scale::Whole(i) => (width * i.get() as u32, height * i.get() as u32), + Scale::Fractional(f) => ( + (width * f.get() as u32 + 60) / 120, + (height * f.get() as u32 + 60) / 120, + ), + } + } + + #[inline] + #[must_use] + pub fn div_dim(&self, width: u32, height: u32) -> (u32, u32) { + match self { + Scale::Whole(i) => (width / i.get() as u32, height / i.get() as u32), + Scale::Fractional(f) => ( + (width * 120) / f.get() as u32, + (height * 120) / f.get() as u32, + ), + } + } +} + +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, Decode, Encode)] pub struct BgInfo { pub name: String, pub dim: (u32, u32), - pub scale_factor: i32, + pub scale_factor: Scale, pub img: BgImg, pub pixel_format: PixelFormat, } @@ -147,10 +193,7 @@ impl BgInfo { #[inline] #[must_use] pub fn real_dim(&self) -> (u32, u32) { - ( - self.dim.0 * self.scale_factor as u32, - self.dim.1 * self.scale_factor as u32, - ) + self.scale_factor.mul_dim(self.dim.0, self.dim.1) } }