From 678b48ccdad87269e669523810812cf336564555 Mon Sep 17 00:00:00 2001 From: rkuklik Date: Tue, 25 Jun 2024 09:40:29 +0200 Subject: [PATCH 1/5] refactor(daemon): extract `Daemon` into separate module --- daemon/src/daemon.rs | 259 +++++++++++++++ daemon/src/daemon/wayland.rs | 284 ++++++++++++++++ daemon/src/main.rs | 606 +++-------------------------------- 3 files changed, 594 insertions(+), 555 deletions(-) create mode 100644 daemon/src/daemon.rs create mode 100644 daemon/src/daemon/wayland.rs diff --git a/daemon/src/daemon.rs b/daemon/src/daemon.rs new file mode 100644 index 0000000..1ae189d --- /dev/null +++ b/daemon/src/daemon.rs @@ -0,0 +1,259 @@ +use std::mem; +use std::num::NonZeroU32; +use std::path::Path; +use std::ptr; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::thread; + +use common::ipc::Answer; +use common::ipc::BgInfo; +use common::ipc::ImageReq; +use common::ipc::IpcSocket; +use common::ipc::RequestRecv; +use common::ipc::RequestSend; +use common::ipc::Server; +use common::mmap::MmappedStr; + +use log::debug; +use log::error; +use log::info; + +use rustix::event; +use rustix::event::PollFd; +use rustix::event::PollFlags; +use rustix::io; +use rustix::io::Errno; +use rustix::net; + +use crate::animations::Animator; +use crate::wallpaper; +use crate::wallpaper::Wallpaper; +use crate::wayland::globals; +use crate::wayland::globals::Initializer; +use crate::wayland::wire; +use crate::wayland::ObjectId; + +mod wayland; + +// We need this because this might be set by signals, so we can't keep it in the self +static EXIT: AtomicBool = AtomicBool::new(false); + +pub struct Daemon { + pub wallpapers: Vec>, + pub animator: Animator, + pub cache: bool, + pub fractional_scale_manager: Option<(ObjectId, NonZeroU32)>, +} + +impl Daemon { + pub fn new(initializer: Initializer, cache: bool) -> Self { + log::info!("Selected wl_shm format: {:?}", globals::pixel_format()); + let fractional_scale_manager = initializer.fractional_scale().copied(); + + let mut daemon = Self { + wallpapers: Vec::new(), + animator: Animator::new(), + cache, + fractional_scale_manager, + }; + + for name in initializer.output_names().iter().copied() { + daemon.new_output(name); + } + + daemon + } + + fn wallpapers_info(&self) -> Box<[BgInfo]> { + self.wallpapers + .iter() + .map(|wallpaper| wallpaper.get_bg_info()) + .collect() + } + + 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.str())) { + return Some(Arc::clone(wallpaper)); + } + None + }) + .collect() + } + + pub fn main_loop(&mut self, socket: IpcSocket) -> Result<(), Errno> { + let wayland_fd = globals::wayland_fd(); + let mut fds = [ + PollFd::new(&wayland_fd, PollFlags::IN), + PollFd::new(socket.as_fd(), PollFlags::IN), + ]; + + // main loop + while !EXIT.load(Ordering::Acquire) { + if let Err(e) = event::poll(&mut fds, -1) { + match e { + rustix::io::Errno::INTR => continue, + _ => return Err(e), + } + } + + if !fds[0].revents().is_empty() { + match wire::WireMsg::recv() { + Ok((msg, payload)) => self.wayland_handler(msg, payload), + Err(io::Errno::INTR) => continue, + Err(err) => return Err(err), + }; + } + + if !fds[1].revents().is_empty() { + match net::accept(socket.as_fd()) { + // TODO: abstract away explicit socket creation + Ok(stream) => self.request_handler(IpcSocket::new(stream)), + Err(Errno::INTR | Errno::WOULDBLOCK) => continue, + Err(err) => return Err(err), + } + } + } + + Ok(()) + } + + fn request_handler(&mut self, socket: IpcSocket) { + let bytes = match socket.recv() { + Ok(bytes) => bytes, + Err(e) => { + error!("FATAL: cannot read socket: {e}. Exiting..."); + Self::exit(); + return; + } + }; + let request = RequestRecv::receive(bytes); + let answer = match request { + RequestRecv::Clear(clear) => { + let wallpapers = self.find_wallpapers_by_names(&clear.outputs); + thread::Builder::new() + .stack_size(1 << 15) + .name("clear".to_string()) + .spawn(move || { + wallpaper::stop_animations(&wallpapers); + for wallpaper in &wallpapers { + wallpaper.set_img_info(common::ipc::BgImg::Color(clear.color)); + wallpaper.clear(clear.color); + } + wallpaper::attach_buffers_and_damange_surfaces(&wallpapers); + wallpaper::commit_wallpapers(&wallpapers); + }) + .expect("builder only failed if the name contains null bytes"); + Answer::Ok + } + RequestRecv::Ping => Answer::Ping( + self.wallpapers + .iter() + .all(|w| w.configured.load(std::sync::atomic::Ordering::Acquire)), + ), + RequestRecv::Kill => { + Self::exit(); + Answer::Ok + } + RequestRecv::Query => Answer::Info(self.wallpapers_info()), + RequestRecv::Img(ImageReq { + transition, + imgs, + outputs, + animations, + }) => { + let mut used_wallpapers = Vec::new(); + for names in outputs.iter() { + let wallpapers = self.find_wallpapers_by_names(names); + wallpaper::stop_animations(&wallpapers); + used_wallpapers.push(wallpapers); + } + self.animator + .transition(transition, imgs, animations, used_wallpapers) + } + }; + if let Err(e) = answer.send(&socket) { + error!("error sending answer to client: {e}"); + } + } + + pub fn socket_occupied() -> bool { + let Ok(socket) = IpcSocket::connect() else { + return false; + }; + + let Answer::Ping(_) = RequestSend::Ping + .send(&socket) + .and_then(|()| socket.recv().map_err(|err| err.to_string())) + .map(Answer::receive) + .unwrap_or_else(|err| panic!("{err}")) + else { + unreachable!("Daemon did not return Answer::Ping, IPC is broken") + }; + true + } + + pub fn exit() { + EXIT.store(true, Ordering::Release); + } + + extern "C" fn handler(_: libc::c_int) { + Self::exit(); + } + + pub fn handle_signals() { + // C data structure, expected to be zeroed out. + let mut sigaction: libc::sigaction = unsafe { mem::zeroed() }; + unsafe { libc::sigemptyset(ptr::addr_of_mut!(sigaction.sa_mask)) }; + + // Is this necessary + #[cfg(not(target_os = "aix"))] + { + sigaction.sa_sigaction = Self::handler as usize; + } + #[cfg(target_os = "aix")] + { + sigaction.sa_union.__su_sigaction = Self::handler; + } + + for signal in [libc::SIGINT, libc::SIGQUIT, libc::SIGTERM, libc::SIGHUP] { + let ret = unsafe { libc::sigaction(signal, ptr::addr_of!(sigaction), ptr::null_mut()) }; + if ret != 0 { + error!("Failed to install signal handler!") + } + } + debug!("Finished setting up signal handlers") + } +} + +impl Drop for Daemon { + fn drop(&mut self) { + wallpaper::stop_animations(&self.wallpapers); + + // wait for the animation threads to finish. + while !self.wallpapers.is_empty() { + // When all animations finish, Arc's strong count will be exactly 1 + self.wallpapers + .retain(|wallpaper| Arc::strong_count(wallpaper) > 1); + + // set all frame callbacks as completed, otherwise the animation threads might deadlock on + // the conditional variable + for wallpaper in &self.wallpapers { + wallpaper.frame_callback_completed(); + } + + // yield to waste less cpu + thread::yield_now(); + } + + let addr = IpcSocket::::path(); + match std::fs::remove_file(Path::new(addr)) { + Err(err) => error!("Failed to remove socket at {addr}: {err}"), + Ok(()) => info!("Removed socket at {addr}"), + }; + } +} diff --git a/daemon/src/daemon/wayland.rs b/daemon/src/daemon/wayland.rs new file mode 100644 index 0000000..4fe5caf --- /dev/null +++ b/daemon/src/daemon/wayland.rs @@ -0,0 +1,284 @@ +use std::num::NonZeroI32; +use std::num::NonZeroU32; +use std::sync::Arc; + +use super::Daemon; + +use common::ipc::Scale; + +use log::debug; +use log::error; +use log::warn; + +use crate::wallpaper::Wallpaper; +use crate::wayland; +use crate::wayland::globals; +use crate::wayland::interfaces::*; +use crate::wayland::wire::WaylandPayload; +use crate::wayland::wire::WireMsg; +use crate::wayland::ObjectId; +use crate::wayland::WlDynObj; + +impl Daemon { + pub(super) fn new_output(&mut self, name: u32) { + let output = globals::object_create(wayland::WlDynObj::Output); + wl_registry::req::bind(name, output, "wl_output", 4).unwrap(); + + let surface = globals::object_create(wayland::WlDynObj::Surface); + wl_compositor::req::create_surface(surface).unwrap(); + + let region = globals::object_create(wayland::WlDynObj::Region); + wl_compositor::req::create_region(region).unwrap(); + + wl_surface::req::set_input_region(surface, Some(region)).unwrap(); + wl_region::req::destroy(region).unwrap(); + + let layer_surface = globals::object_create(wayland::WlDynObj::LayerSurface); + zwlr_layer_shell_v1::req::get_layer_surface( + layer_surface, + surface, + Some(output), + zwlr_layer_shell_v1::layer::BACKGROUND, + "swww-daemon", + ) + .unwrap(); + + let viewport = globals::object_create(wayland::WlDynObj::Viewport); + wp_viewporter::req::get_viewport(viewport, surface).unwrap(); + + let wp_fractional = if let Some((id, _)) = self.fractional_scale_manager.as_ref() { + let fractional = globals::object_create(wayland::WlDynObj::FractionalScale); + wp_fractional_scale_manager_v1::req::get_fractional_scale(*id, fractional, surface) + .unwrap(); + Some(fractional) + } else { + None + }; + + debug!("New output: {name}"); + self.wallpapers.push(Arc::new(Wallpaper::new( + output, + name, + surface, + viewport, + wp_fractional, + layer_surface, + ))); + } + + pub(super) fn wayland_handler(&mut self, msg: WireMsg, payload: WaylandPayload) { + match msg.sender_id() { + globals::WL_DISPLAY => wl_display::event(self, msg, payload), + globals::WL_REGISTRY => wl_registry::event(self, msg, payload), + globals::WL_COMPOSITOR => error!("wl_compositor has no events"), + globals::WL_SHM => wl_shm::event(self, msg, payload), + globals::WP_VIEWPORTER => error!("wp_viewporter has no events"), + globals::ZWLR_LAYER_SHELL_V1 => error!("zwlr_layer_shell_v1 has no events"), + other => match globals::object_type_get(other) { + Some(obj) => self.wayland_dyn_handler(obj, msg, payload), + None => error!("Received event for deleted object ({other:?})"), + }, + } + } + + fn wayland_dyn_handler(&mut self, obj: WlDynObj, msg: WireMsg, payload: WaylandPayload) { + match obj { + WlDynObj::Output => wl_output::event(self, msg, payload), + WlDynObj::Surface => wl_surface::event(self, msg, payload), + WlDynObj::Region => error!("wl_region has no events"), + WlDynObj::LayerSurface => zwlr_layer_surface_v1::event(self, msg, payload), + WlDynObj::Buffer => wl_buffer::event(self, msg, payload), + WlDynObj::ShmPool => error!("wl_shm_pool has no events"), + WlDynObj::Callback => wl_callback::event(self, msg, payload), + WlDynObj::Viewport => error!("wp_viewport has no events"), + WlDynObj::FractionalScale => wp_fractional_scale_v1::event(self, msg, payload), + } + } +} + +impl wl_display::EvHandler for Daemon { + fn delete_id(&mut self, id: u32) { + if let Some(id) = NonZeroU32::new(id) { + globals::object_remove(ObjectId::new(id)); + } + } +} + +impl wl_registry::EvHandler for Daemon { + fn global(&mut self, name: u32, interface: &str, version: u32) { + if interface == "wl_output" { + if version < 4 { + error!("your compositor must support at least version 4 of wl_output"); + } else { + self.new_output(name); + } + } + } + + fn global_remove(&mut self, name: u32) { + self.wallpapers.retain(|w| !w.has_output_name(name)); + } +} + +impl wl_shm::EvHandler for Daemon { + fn format(&mut self, format: u32) { + warn!( + "received a wl_shm format after initialization: {format}. This shouldn't be possible" + ); + } +} + +impl wl_output::EvHandler for Daemon { + fn geometry( + &mut self, + sender_id: ObjectId, + _x: i32, + _y: i32, + _physical_width: i32, + _physical_height: i32, + _subpixel: i32, + _make: &str, + _model: &str, + transform: i32, + ) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_output(sender_id) { + if transform as u32 > wayland::interfaces::wl_output::transform::FLIPPED_270 { + error!("received invalid transform value from compositor: {transform}") + } else { + wallpaper.set_transform(transform as u32); + } + break; + } + } + } + + fn mode(&mut self, sender_id: ObjectId, _flags: u32, width: i32, height: i32, _refresh: i32) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_output(sender_id) { + wallpaper.set_dimensions(width, height); + break; + } + } + } + + fn done(&mut self, sender_id: ObjectId) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_output(sender_id) { + wallpaper.commit_surface_changes(self.cache); + break; + } + } + } + + fn scale(&mut self, sender_id: ObjectId, factor: i32) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_output(sender_id) { + match NonZeroI32::new(factor) { + Some(factor) => wallpaper.set_scale(Scale::Whole(factor)), + None => error!("received scale factor of 0 from compositor"), + } + break; + } + } + } + + fn name(&mut self, sender_id: ObjectId, name: &str) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_output(sender_id) { + wallpaper.set_name(name.to_string()); + break; + } + } + } + + fn description(&mut self, sender_id: ObjectId, description: &str) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_output(sender_id) { + wallpaper.set_desc(description.to_string()); + break; + } + } + } +} + +impl wl_surface::EvHandler for Daemon { + fn enter(&mut self, _sender_id: ObjectId, output: ObjectId) { + debug!("Output {}: Surface Enter", output.get()); + } + + fn leave(&mut self, _sender_id: ObjectId, output: ObjectId) { + debug!("Output {}: Surface Leave", output.get()); + } + + fn preferred_buffer_scale(&mut self, sender_id: ObjectId, factor: i32) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_surface(sender_id) { + match NonZeroI32::new(factor) { + Some(factor) => wallpaper.set_scale(Scale::Whole(factor)), + None => error!("received scale factor of 0 from compositor"), + } + break; + } + } + } + + fn preferred_buffer_transform(&mut self, _sender_id: ObjectId, _transform: u32) { + warn!("Received PreferredBufferTransform. We currently ignore those") + } +} + +impl wl_buffer::EvHandler for Daemon { + fn release(&mut self, sender_id: ObjectId) { + for wallpaper in self.wallpapers.iter() { + let strong_count = Arc::strong_count(wallpaper); + if wallpaper.try_set_buffer_release_flag(sender_id, strong_count) { + break; + } + } + } +} + +impl wl_callback::EvHandler for Daemon { + fn done(&mut self, sender_id: ObjectId, _callback_data: u32) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_callback(sender_id) { + wallpaper.frame_callback_completed(); + break; + } + } + } +} + +impl zwlr_layer_surface_v1::EvHandler for Daemon { + fn configure(&mut self, sender_id: ObjectId, serial: u32, _width: u32, _height: u32) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_layer_surface(sender_id) { + wayland::interfaces::zwlr_layer_surface_v1::req::ack_configure(sender_id, serial) + .unwrap(); + break; + } + } + } + + fn closed(&mut self, sender_id: ObjectId) { + self.wallpapers.retain(|w| !w.has_layer_surface(sender_id)); + } +} + +impl wp_fractional_scale_v1::EvHandler for Daemon { + fn preferred_scale(&mut self, sender_id: ObjectId, scale: u32) { + for wallpaper in self.wallpapers.iter() { + if wallpaper.has_fractional_scale(sender_id) { + match NonZeroI32::new(scale as i32) { + Some(factor) => { + wallpaper.set_scale(Scale::Fractional(factor)); + wallpaper.commit_surface_changes(self.cache); + } + None => error!("received scale factor of 0 from compositor"), + } + break; + } + } + } +} diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 91575b7..9bdab92 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -2,410 +2,71 @@ //! them fail there is no point in continuing. All of the initialization code, for example, is full //! of `expects`, **on purpose**, because we **want** to unwind and exit when they happen +use std::error::Error; +use std::fs; +use std::io::IsTerminal; +use std::io::Write; +use std::mem; +use std::path::Path; +use std::ptr; + +use daemon::Daemon; + +use log::debug; +use log::error; +use log::info; +use log::warn; +use log::LevelFilter; + +use common::ipc::IpcSocket; +use common::ipc::Server; + mod animations; mod cli; +mod daemon; mod wallpaper; #[allow(dead_code)] mod wayland; -use log::{debug, error, info, warn, LevelFilter}; -use rustix::{ - event::{poll, PollFd, PollFlags}, - fd::OwnedFd, -}; - -use wallpaper::Wallpaper; -use wayland::{ - globals::{self, Initializer}, - ObjectId, -}; - -use std::{ - fs, - io::{IsTerminal, Write}, - num::{NonZeroI32, NonZeroU32}, - path::Path, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; - -use common::ipc::{Answer, BgInfo, ImageReq, IpcSocket, RequestRecv, RequestSend, Scale, Server}; -use common::mmap::MmappedStr; - -use animations::Animator; - -// We need this because this might be set by signals, so we can't keep it in the daemon -static EXIT: AtomicBool = AtomicBool::new(false); - -fn exit_daemon() { - EXIT.store(true, Ordering::Release); -} - -fn should_daemon_exit() -> bool { - EXIT.load(Ordering::Acquire) -} - -extern "C" fn signal_handler(_s: libc::c_int) { - exit_daemon(); -} - -struct Daemon { - wallpapers: Vec>, - animator: Animator, - use_cache: bool, - fractional_scale_manager: Option<(ObjectId, NonZeroU32)>, -} - -impl Daemon { - fn new(initializer: &Initializer, no_cache: bool) -> Self { - log::info!( - "Selected wl_shm format: {:?}", - wayland::globals::pixel_format() - ); - let fractional_scale_manager = initializer.fractional_scale().cloned(); - - let wallpapers = Vec::new(); - - Self { - wallpapers, - animator: Animator::new(), - use_cache: !no_cache, - fractional_scale_manager, - } - } - - fn new_output(&mut self, output_name: u32) { - use wayland::interfaces::*; - let output = globals::object_create(wayland::WlDynObj::Output); - wl_registry::req::bind(output_name, output, "wl_output", 4).unwrap(); - - let surface = globals::object_create(wayland::WlDynObj::Surface); - wl_compositor::req::create_surface(surface).unwrap(); - - let region = globals::object_create(wayland::WlDynObj::Region); - wl_compositor::req::create_region(region).unwrap(); - - wl_surface::req::set_input_region(surface, Some(region)).unwrap(); - wl_region::req::destroy(region).unwrap(); - - let layer_surface = globals::object_create(wayland::WlDynObj::LayerSurface); - zwlr_layer_shell_v1::req::get_layer_surface( - layer_surface, - surface, - Some(output), - zwlr_layer_shell_v1::layer::BACKGROUND, - "swww-daemon", - ) - .unwrap(); - - let viewport = globals::object_create(wayland::WlDynObj::Viewport); - wp_viewporter::req::get_viewport(viewport, surface).unwrap(); - - let wp_fractional = if let Some((id, _)) = self.fractional_scale_manager.as_ref() { - let fractional = globals::object_create(wayland::WlDynObj::FractionalScale); - wp_fractional_scale_manager_v1::req::get_fractional_scale(*id, fractional, surface) - .unwrap(); - Some(fractional) - } else { - None - }; - - debug!("New output: {output_name}"); - self.wallpapers.push(Arc::new(Wallpaper::new( - output, - output_name, - surface, - viewport, - wp_fractional, - layer_surface, - ))); - } - - fn recv_socket_msg(&mut self, stream: IpcSocket) { - let bytes = match stream.recv() { - Ok(bytes) => bytes, - Err(e) => { - error!("FATAL: cannot read socket: {e}. Exiting..."); - exit_daemon(); - return; - } - }; - let request = RequestRecv::receive(bytes); - let answer = match request { - RequestRecv::Clear(clear) => { - let wallpapers = self.find_wallpapers_by_names(&clear.outputs); - std::thread::Builder::new() - .stack_size(1 << 15) - .name("clear".to_string()) - .spawn(move || { - crate::wallpaper::stop_animations(&wallpapers); - for wallpaper in &wallpapers { - wallpaper.set_img_info(common::ipc::BgImg::Color(clear.color)); - wallpaper.clear(clear.color); - } - crate::wallpaper::attach_buffers_and_damange_surfaces(&wallpapers); - crate::wallpaper::commit_wallpapers(&wallpapers); - }) - .unwrap(); // builder only failed if the name contains null bytes - Answer::Ok - } - RequestRecv::Ping => Answer::Ping( - self.wallpapers - .iter() - .all(|w| w.configured.load(std::sync::atomic::Ordering::Acquire)), - ), - RequestRecv::Kill => { - exit_daemon(); - Answer::Ok - } - RequestRecv::Query => Answer::Info(self.wallpapers_info()), - RequestRecv::Img(ImageReq { - transition, - imgs, - outputs, - animations, - }) => { - let mut used_wallpapers = Vec::new(); - for names in outputs.iter() { - let wallpapers = self.find_wallpapers_by_names(names); - crate::wallpaper::stop_animations(&wallpapers); - used_wallpapers.push(wallpapers); - } - self.animator - .transition(transition, imgs, animations, used_wallpapers) - } - }; - if let Err(e) = answer.send(&stream) { - error!("error sending answer to client: {e}"); - } - } - - fn wallpapers_info(&self) -> Box<[BgInfo]> { - self.wallpapers - .iter() - .map(|wallpaper| wallpaper.get_bg_info()) - .collect() - } - - 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.str())) { - return Some(Arc::clone(wallpaper)); - } - None - }) - .collect() - } -} - -impl wayland::interfaces::wl_display::EvHandler for Daemon { - fn delete_id(&mut self, id: u32) { - if let Some(id) = NonZeroU32::new(id) { - globals::object_remove(ObjectId::new(id)); - } - } -} -impl wayland::interfaces::wl_registry::EvHandler for Daemon { - fn global(&mut self, name: u32, interface: &str, version: u32) { - if interface == "wl_output" { - if version < 4 { - error!("your compositor must support at least version 4 of wl_output"); - } else { - self.new_output(name); - } - } - } - - fn global_remove(&mut self, name: u32) { - self.wallpapers.retain(|w| !w.has_output_name(name)); - } -} - -impl wayland::interfaces::wl_shm::EvHandler for Daemon { - fn format(&mut self, format: u32) { - warn!( - "received a wl_shm format after initialization: {format}. This shouldn't be possible" - ); - } -} - -impl wayland::interfaces::wl_output::EvHandler for Daemon { - fn geometry( - &mut self, - sender_id: ObjectId, - _x: i32, - _y: i32, - _physical_width: i32, - _physical_height: i32, - _subpixel: i32, - _make: &str, - _model: &str, - transform: i32, - ) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_output(sender_id) { - if transform as u32 > wayland::interfaces::wl_output::transform::FLIPPED_270 { - error!("received invalid transform value from compositor: {transform}") - } else { - wallpaper.set_transform(transform as u32); - } - break; - } - } - } - - fn mode(&mut self, sender_id: ObjectId, _flags: u32, width: i32, height: i32, _refresh: i32) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_output(sender_id) { - wallpaper.set_dimensions(width, height); - break; - } - } - } - - fn done(&mut self, sender_id: ObjectId) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_output(sender_id) { - wallpaper.commit_surface_changes(self.use_cache); - break; - } - } - } - - fn scale(&mut self, sender_id: ObjectId, factor: i32) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_output(sender_id) { - match NonZeroI32::new(factor) { - Some(factor) => wallpaper.set_scale(Scale::Whole(factor)), - None => error!("received scale factor of 0 from compositor"), - } - break; - } - } - } - - fn name(&mut self, sender_id: ObjectId, name: &str) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_output(sender_id) { - wallpaper.set_name(name.to_string()); - break; - } - } - } - - fn description(&mut self, sender_id: ObjectId, description: &str) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_output(sender_id) { - wallpaper.set_desc(description.to_string()); - break; - } - } - } -} -impl wayland::interfaces::wl_surface::EvHandler for Daemon { - fn enter(&mut self, _sender_id: ObjectId, output: ObjectId) { - debug!("Output {}: Surface Enter", output.get()); - } - fn leave(&mut self, _sender_id: ObjectId, output: ObjectId) { - debug!("Output {}: Surface Leave", output.get()); - } - - fn preferred_buffer_scale(&mut self, sender_id: ObjectId, factor: i32) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_surface(sender_id) { - match NonZeroI32::new(factor) { - Some(factor) => wallpaper.set_scale(Scale::Whole(factor)), - None => error!("received scale factor of 0 from compositor"), - } - break; - } - } - } - - fn preferred_buffer_transform(&mut self, _sender_id: ObjectId, _transform: u32) { - warn!("Received PreferredBufferTransform. We currently ignore those") - } -} - -impl wayland::interfaces::wl_buffer::EvHandler for Daemon { - fn release(&mut self, sender_id: ObjectId) { - for wallpaper in self.wallpapers.iter() { - let strong_count = Arc::strong_count(wallpaper); - if wallpaper.try_set_buffer_release_flag(sender_id, strong_count) { - break; - } - } - } -} - -impl wayland::interfaces::wl_callback::EvHandler for Daemon { - fn done(&mut self, sender_id: ObjectId, _callback_data: u32) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_callback(sender_id) { - wallpaper.frame_callback_completed(); - break; - } - } - } -} - -impl wayland::interfaces::zwlr_layer_surface_v1::EvHandler for Daemon { - fn configure(&mut self, sender_id: ObjectId, serial: u32, _width: u32, _height: u32) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_layer_surface(sender_id) { - wayland::interfaces::zwlr_layer_surface_v1::req::ack_configure(sender_id, serial) - .unwrap(); - break; - } - } - } - - fn closed(&mut self, sender_id: ObjectId) { - self.wallpapers.retain(|w| !w.has_layer_surface(sender_id)); - } -} - -impl wayland::interfaces::wp_fractional_scale_v1::EvHandler for Daemon { - fn preferred_scale(&mut self, sender_id: ObjectId, scale: u32) { - for wallpaper in self.wallpapers.iter() { - if wallpaper.has_fractional_scale(sender_id) { - match NonZeroI32::new(scale as i32) { - Some(factor) => { - wallpaper.set_scale(Scale::Fractional(factor)); - wallpaper.commit_surface_changes(self.use_cache); - } - None => error!("received scale factor of 0 from compositor"), - } - break; - } - } - } -} - -fn main() -> Result<(), String> { +fn main() -> Result<(), Box> { // first, get the command line arguments and make the logger let cli = cli::Cli::new(); make_logger(cli.quiet); + Daemon::handle_signals(); // initialize the wayland connection, getting all the necessary globals let initializer = wayland::globals::init(cli.format); // create the socket listener and setup the signal handlers - // this will also return an error if there is an `swww-daemon` instance already - // running - let listener = SocketWrapper::new()?; - setup_signals(); + // this will also return an error if there is an `swww-daemon` instance already exists + // TODO: use `Daemon` constructor to do this + let addr = IpcSocket::::path(); + let path = Path::new(addr); + if path.exists() { + if Daemon::socket_occupied() { + Err("There is an swww-daemon instance already running on this socket!")?; + } else { + warn!("socket file '{addr}' was not deleted when the previous daemon exited",); + if let Err(err) = fs::remove_file(addr) { + Err(format!("failed to delete previous socket: {err}"))?; + } + } + } else { + let Some(parent) = path.parent() else { + Err("couldn't find a valid runtime directory")? + }; + if !parent.exists() { + if let Err(err) = fs::create_dir(parent) { + Err(format!("failed to create runtime dir: {err}"))? + }; + } + } + + let listener = IpcSocket::server()?; + debug!("Created socket in {:?}", addr); // use the initializer to create the Daemon, then drop it to free up the memory - let mut daemon = Daemon::new(&initializer, cli.no_cache); - for &output_name in initializer.output_names() { - daemon.new_output(output_name); - } - drop(initializer); + let mut daemon = Daemon::new(initializer, !cli.no_cache); if let Ok(true) = sd_notify::booted() { if let Err(e) = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]) { @@ -413,163 +74,14 @@ fn main() -> Result<(), String> { } } - let wayland_fd = wayland::globals::wayland_fd(); - let mut fds = [ - PollFd::new(&wayland_fd, PollFlags::IN), - PollFd::new(&listener.0, PollFlags::IN), - ]; - - // main loop - while !should_daemon_exit() { - use wayland::{interfaces::*, wire, WlDynObj}; + daemon.main_loop(listener)?; - if let Err(e) = poll(&mut fds, -1) { - match e { - rustix::io::Errno::INTR => continue, - _ => return Err(format!("failed to poll file descriptors: {e:?}")), - } - } - - if !fds[0].revents().is_empty() { - let (msg, payload) = match wire::WireMsg::recv() { - Ok((msg, payload)) => (msg, payload), - Err(rustix::io::Errno::INTR) => continue, - Err(e) => return Err(format!("failed to receive wire message: {e:?}")), - }; - - match msg.sender_id() { - globals::WL_DISPLAY => wl_display::event(&mut daemon, msg, payload), - globals::WL_REGISTRY => wl_registry::event(&mut daemon, msg, payload), - globals::WL_COMPOSITOR => error!("wl_compositor has no events"), - globals::WL_SHM => wl_shm::event(&mut daemon, msg, payload), - globals::WP_VIEWPORTER => error!("wp_viewporter has no events"), - globals::ZWLR_LAYER_SHELL_V1 => error!("zwlr_layer_shell_v1 has no events"), - other => { - let obj_id = globals::object_type_get(other); - match obj_id { - Some(WlDynObj::Output) => wl_output::event(&mut daemon, msg, payload), - Some(WlDynObj::Surface) => wl_surface::event(&mut daemon, msg, payload), - Some(WlDynObj::Region) => error!("wl_region has no events"), - Some(WlDynObj::LayerSurface) => { - zwlr_layer_surface_v1::event(&mut daemon, msg, payload) - } - Some(WlDynObj::Buffer) => wl_buffer::event(&mut daemon, msg, payload), - Some(WlDynObj::ShmPool) => error!("wl_shm_pool has no events"), - Some(WlDynObj::Callback) => wl_callback::event(&mut daemon, msg, payload), - Some(WlDynObj::Viewport) => error!("wp_viewport has no events"), - Some(WlDynObj::FractionalScale) => { - wp_fractional_scale_v1::event(&mut daemon, msg, payload) - } - None => error!("Received event for deleted object ({other:?})"), - } - } - } - } - - if !fds[1].revents().is_empty() { - match rustix::net::accept(&listener.0) { - // TODO: abstract away explicit socket creation - Ok(stream) => daemon.recv_socket_msg(IpcSocket::new(stream)), - Err(rustix::io::Errno::INTR | rustix::io::Errno::WOULDBLOCK) => continue, - Err(e) => return Err(format!("failed to accept incoming connection: {e}")), - } - } - } - crate::wallpaper::stop_animations(&daemon.wallpapers); - - // wait for the animation threads to finish. - while !daemon.wallpapers.is_empty() { - // When all animations finish, Arc's strong count will be exactly 1 - daemon.wallpapers.retain(|w| Arc::strong_count(w) > 1); - // set all frame callbacks as completed, otherwise the animation threads might deadlock on - // the conditional variable - for wallpaper in &daemon.wallpapers { - wallpaper.frame_callback_completed(); - } - // yield to waste less cpu - std::thread::yield_now(); - } - - drop(daemon); - drop(listener); info!("Goodbye!"); Ok(()) } fn setup_signals() { - // C data structure, expected to be zeroed out. - let mut sigaction: libc::sigaction = unsafe { std::mem::zeroed() }; - unsafe { libc::sigemptyset(std::ptr::addr_of_mut!(sigaction.sa_mask)) }; - - #[cfg(not(target_os = "aix"))] - { - sigaction.sa_sigaction = signal_handler as usize; - } - #[cfg(target_os = "aix")] - { - sigaction.sa_union.__su_sigaction = handler; - } - - for signal in [libc::SIGINT, libc::SIGQUIT, libc::SIGTERM, libc::SIGHUP] { - let ret = - unsafe { libc::sigaction(signal, std::ptr::addr_of!(sigaction), std::ptr::null_mut()) }; - if ret != 0 { - error!("Failed to install signal handler!") - } - } - debug!("Finished setting up signal handlers") -} - -/// This is a wrapper that makes sure to delete the socket when it is dropped -struct SocketWrapper(OwnedFd); -impl SocketWrapper { - fn new() -> Result { - let addr = IpcSocket::::path(); - let addr = Path::new(addr); - - if addr.exists() { - if is_daemon_running()? { - return Err( - "There is an swww-daemon instance already running on this socket!".to_string(), - ); - } else { - warn!( - "socket file {} was not deleted when the previous daemon exited", - addr.to_string_lossy() - ); - if let Err(e) = std::fs::remove_file(addr) { - return Err(format!("failed to delete previous socket: {e}")); - } - } - } - - let runtime_dir = match addr.parent() { - Some(path) => path, - None => return Err("couldn't find a valid runtime directory".to_owned()), - }; - - if !runtime_dir.exists() { - match fs::create_dir(runtime_dir) { - Ok(()) => (), - Err(e) => return Err(format!("failed to create runtime dir: {e}")), - } - } - - let socket = IpcSocket::server().map_err(|err| err.to_string())?; - - debug!("Created socket in {:?}", addr); - Ok(Self(socket.to_fd())) - } -} - -impl Drop for SocketWrapper { - fn drop(&mut self) { - let addr = IpcSocket::::path(); - if let Err(e) = fs::remove_file(Path::new(addr)) { - error!("Failed to remove socket at {addr}: {e}"); - } - info!("Removed socket at {addr}"); - } + Daemon::handle_signals() } struct Logger { @@ -634,22 +146,6 @@ fn make_logger(quiet: bool) { .unwrap(); } -pub fn is_daemon_running() -> Result { - let sock = match IpcSocket::connect() { - Ok(s) => s, - // likely a connection refused; either way, this is a reliable signal there's no surviving - // daemon. - Err(_) => return Ok(false), - }; - - RequestSend::Ping.send(&sock)?; - let answer = Answer::receive(sock.recv().map_err(|err| err.to_string())?); - match answer { - Answer::Ping(_) => Ok(true), - _ => Err("Daemon did not return Answer::Ping, as expected".to_string()), - } -} - /// copy-pasted from the `spin_sleep` crate on crates.io /// /// This will sleep for an amount of time we can roughly expected the OS to still be precise enough From 3ffb6a397b3987c205ded84084cb85f32f29b57d Mon Sep 17 00:00:00 2001 From: rkuklik Date: Tue, 25 Jun 2024 10:14:38 +0200 Subject: [PATCH 2/5] refactor(daemon): move init more to relevant structs --- daemon/src/daemon.rs | 52 ++++++++++++---- daemon/src/main.rs | 139 ++++++++++++++++++------------------------- 2 files changed, 100 insertions(+), 91 deletions(-) diff --git a/daemon/src/daemon.rs b/daemon/src/daemon.rs index 1ae189d..22be6bb 100644 --- a/daemon/src/daemon.rs +++ b/daemon/src/daemon.rs @@ -1,3 +1,5 @@ +use std::error::Error; +use std::fs; use std::mem; use std::num::NonZeroU32; use std::path::Path; @@ -19,13 +21,13 @@ use common::mmap::MmappedStr; use log::debug; use log::error; use log::info; +use log::warn; -use rustix::event; +use rustix::event::poll; use rustix::event::PollFd; use rustix::event::PollFlags; -use rustix::io; use rustix::io::Errno; -use rustix::net; +use rustix::net::accept; use crate::animations::Animator; use crate::wallpaper; @@ -48,8 +50,10 @@ pub struct Daemon { } impl Daemon { - pub fn new(initializer: Initializer, cache: bool) -> Self { - log::info!("Selected wl_shm format: {:?}", globals::pixel_format()); + pub fn new(initializer: Initializer, cache: bool) -> Result> { + Self::init_rt_files()?; + + info!("Selected wl_shm format: {:?}", globals::pixel_format()); let fractional_scale_manager = initializer.fractional_scale().copied(); let mut daemon = Self { @@ -63,7 +67,33 @@ impl Daemon { daemon.new_output(name); } - daemon + Ok(daemon) + } + + fn init_rt_files() -> Result<(), Box> { + let addr = IpcSocket::::path(); + let path = Path::new(addr); + if path.exists() { + if Daemon::socket_occupied() { + Err("There is an swww-daemon instance already running on this socket!")?; + } else { + warn!("socket file '{addr}' was not deleted when the previous daemon exited",); + if let Err(err) = fs::remove_file(addr) { + Err(format!("failed to delete previous socket: {err}"))?; + } + } + } else { + let Some(parent) = path.parent() else { + Err("couldn't find a valid runtime directory")? + }; + if !parent.exists() { + if let Err(err) = fs::create_dir(parent) { + Err(format!("failed to create runtime dir: {err}"))? + }; + } + } + debug!("Created socket in {addr}"); + Ok(()) } fn wallpapers_info(&self) -> Box<[BgInfo]> { @@ -94,9 +124,9 @@ impl Daemon { // main loop while !EXIT.load(Ordering::Acquire) { - if let Err(e) = event::poll(&mut fds, -1) { + if let Err(e) = poll(&mut fds, -1) { match e { - rustix::io::Errno::INTR => continue, + Errno::INTR => continue, _ => return Err(e), } } @@ -104,13 +134,13 @@ impl Daemon { if !fds[0].revents().is_empty() { match wire::WireMsg::recv() { Ok((msg, payload)) => self.wayland_handler(msg, payload), - Err(io::Errno::INTR) => continue, + Err(Errno::INTR) => continue, Err(err) => return Err(err), }; } if !fds[1].revents().is_empty() { - match net::accept(socket.as_fd()) { + match accept(socket.as_fd()) { // TODO: abstract away explicit socket creation Ok(stream) => self.request_handler(IpcSocket::new(stream)), Err(Errno::INTR | Errno::WOULDBLOCK) => continue, @@ -181,7 +211,7 @@ impl Daemon { } } - pub fn socket_occupied() -> bool { + fn socket_occupied() -> bool { let Ok(socket) = IpcSocket::connect() else { return false; }; diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 9bdab92..0fec3c1 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -4,22 +4,26 @@ use std::error::Error; use std::fs; +use std::io; use std::io::IsTerminal; use std::io::Write; -use std::mem; use std::path::Path; -use std::ptr; +use std::thread; +use std::time::Instant; +use cli::Cli; use daemon::Daemon; use log::debug; use log::error; use log::info; use log::warn; +use log::Level; use log::LevelFilter; use common::ipc::IpcSocket; use common::ipc::Server; +use log::SetLoggerError; mod animations; mod cli; @@ -30,43 +34,17 @@ mod wayland; fn main() -> Result<(), Box> { // first, get the command line arguments and make the logger - let cli = cli::Cli::new(); - make_logger(cli.quiet); + let cli = Cli::new(); + Logger::init(cli.quiet)?; Daemon::handle_signals(); // initialize the wayland connection, getting all the necessary globals let initializer = wayland::globals::init(cli.format); - // create the socket listener and setup the signal handlers - // this will also return an error if there is an `swww-daemon` instance already exists - // TODO: use `Daemon` constructor to do this - let addr = IpcSocket::::path(); - let path = Path::new(addr); - if path.exists() { - if Daemon::socket_occupied() { - Err("There is an swww-daemon instance already running on this socket!")?; - } else { - warn!("socket file '{addr}' was not deleted when the previous daemon exited",); - if let Err(err) = fs::remove_file(addr) { - Err(format!("failed to delete previous socket: {err}"))?; - } - } - } else { - let Some(parent) = path.parent() else { - Err("couldn't find a valid runtime directory")? - }; - if !parent.exists() { - if let Err(err) = fs::create_dir(parent) { - Err(format!("failed to create runtime dir: {err}"))? - }; - } - } - let listener = IpcSocket::server()?; - debug!("Created socket in {:?}", addr); // use the initializer to create the Daemon, then drop it to free up the memory - let mut daemon = Daemon::new(initializer, !cli.no_cache); + let mut daemon = Daemon::new(initializer, !cli.no_cache)?; if let Ok(true) = sd_notify::booted() { if let Err(e) = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]) { @@ -80,49 +58,66 @@ fn main() -> Result<(), Box> { Ok(()) } -fn setup_signals() { - Daemon::handle_signals() +struct Logger { + filter: LevelFilter, + start: Instant, + term: bool, } -struct Logger { - level_filter: LevelFilter, - start: std::time::Instant, - is_term: bool, +impl Logger { + fn init(quiet: bool) -> Result<(), SetLoggerError> { + let filter = if quiet { + LevelFilter::Error + } else { + LevelFilter::Debug + }; + + let logger = Self { + filter, + start: Instant::now(), + term: io::stderr().is_terminal(), + }; + + log::set_max_level(filter); + log::set_boxed_logger(Box::new(logger)) + } } impl log::Log for Logger { fn enabled(&self, metadata: &log::Metadata) -> bool { - metadata.level() <= self.level_filter + metadata.level() <= self.filter } fn log(&self, record: &log::Record) { - if self.enabled(record.metadata()) { - let time = self.start.elapsed().as_millis(); - - let level = if self.is_term { - match record.level() { - log::Level::Error => "\x1b[31m[ERROR]\x1b[0m", - log::Level::Warn => "\x1b[33m[WARN]\x1b[0m ", - log::Level::Info => "\x1b[32m[INFO]\x1b[0m ", - log::Level::Debug | log::Level::Trace => "\x1b[36m[DEBUG]\x1b[0m", - } - } else { - match record.level() { - log::Level::Error => "[ERROR]", - log::Level::Warn => "[WARN] ", - log::Level::Info => "[INFO] ", - log::Level::Debug | log::Level::Trace => "[DEBUG]", - } - }; - - let thread = std::thread::current(); - let thread_name = thread.name().unwrap_or("???"); - let msg = record.args(); - - let _ = std::io::stderr() - .lock() - .write_fmt(format_args!("{time:>8}ms {level} ({thread_name}) {msg}\n")); + if !self.enabled(record.metadata()) { + return; } + + let time = self.start.elapsed().as_millis(); + + let level = if self.term { + match record.level() { + Level::Error => "\x1b[31m[ERROR]\x1b[0m", + Level::Warn => "\x1b[33m[WARN]\x1b[0m ", + Level::Info => "\x1b[32m[INFO]\x1b[0m ", + Level::Debug | Level::Trace => "\x1b[36m[DEBUG]\x1b[0m", + } + } else { + match record.level() { + Level::Error => "[ERROR]", + Level::Warn => "[WARN] ", + Level::Info => "[INFO] ", + Level::Debug | Level::Trace => "[DEBUG]", + } + }; + + let thread = thread::current(); + let thread = thread.name().unwrap_or("???"); + let msg = record.args(); + + let _ = io::stderr() + .lock() + .write_fmt(format_args!("{time:>8}ms {level} ({thread}) {msg}\n")); } fn flush(&self) { @@ -130,22 +125,6 @@ impl log::Log for Logger { } } -fn make_logger(quiet: bool) { - let level_filter = if quiet { - LevelFilter::Error - } else { - LevelFilter::Debug - }; - - log::set_boxed_logger(Box::new(Logger { - level_filter, - start: std::time::Instant::now(), - is_term: std::io::stderr().is_terminal(), - })) - .map(|()| log::set_max_level(level_filter)) - .unwrap(); -} - /// copy-pasted from the `spin_sleep` crate on crates.io /// /// This will sleep for an amount of time we can roughly expected the OS to still be precise enough From 0b5ee0171aa7795eaab0fe30cbfe8b1c2e618f08 Mon Sep 17 00:00:00 2001 From: rkuklik Date: Tue, 25 Jun 2024 11:47:04 +0200 Subject: [PATCH 3/5] fix(daemon): unused imports --- daemon/src/main.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 0fec3c1..ab6c228 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -3,26 +3,20 @@ //! of `expects`, **on purpose**, because we **want** to unwind and exit when they happen use std::error::Error; -use std::fs; use std::io; use std::io::IsTerminal; use std::io::Write; -use std::path::Path; use std::thread; +use std::time::Duration; use std::time::Instant; use cli::Cli; +use common::ipc::IpcSocket; use daemon::Daemon; - -use log::debug; use log::error; use log::info; -use log::warn; use log::Level; use log::LevelFilter; - -use common::ipc::IpcSocket; -use common::ipc::Server; use log::SetLoggerError; mod animations; @@ -129,14 +123,14 @@ impl log::Log for Logger { /// /// This will sleep for an amount of time we can roughly expected the OS to still be precise enough /// for frame timing (125 us, currently). -fn spin_sleep(duration: std::time::Duration) { - const ACCURACY: std::time::Duration = std::time::Duration::new(0, 125_000); - let start = std::time::Instant::now(); +fn spin_sleep(duration: Duration) { + const ACCURACY: Duration = Duration::from_micros(125); + let start = Instant::now(); if duration > ACCURACY { - std::thread::sleep(duration - ACCURACY); + thread::sleep(duration - ACCURACY); } while start.elapsed() < duration { - std::thread::yield_now(); + thread::yield_now(); } } From b99825e2101ccce713652351a01907e0c7bb2eb8 Mon Sep 17 00:00:00 2001 From: rkuklik Date: Tue, 25 Jun 2024 11:55:34 +0200 Subject: [PATCH 4/5] refactor(client): consolidate IPC logic --- client/src/main.rs | 276 ++++++++++++++++++++++----------------------- 1 file changed, 136 insertions(+), 140 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index 4a525a0..9c67067 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,114 +1,151 @@ -use std::{path::Path, time::Duration}; +use std::error::Error; +use std::path::Path; +use std::thread; +use std::time::Duration; use clap::Parser; +use cli::Img; use common::cache; -use common::ipc::{self, Answer, Client, IpcSocket, RequestSend}; +use common::ipc; +use common::ipc::Answer; +use common::ipc::BgImg; +use common::ipc::Client; +use common::ipc::IpcSocket; +use common::ipc::PixelFormat; +use common::ipc::RequestSend; use common::mmap::Mmap; mod imgproc; use imgproc::*; mod cli; -use cli::{CliImage, ResizeStrategy, Swww}; +use cli::CliImage; +use cli::ResizeStrategy; +use cli::Swww; -fn main() -> Result<(), String> { +fn main() -> Result<(), Box> { let swww = Swww::parse(); if let Swww::ClearCache = &swww { - return cache::clean().map_err(|e| format!("failed to clean the cache: {e}")); + cache::clean().map_err(|e| format!("failed to clean the cache: {e}"))?; } - let socket = IpcSocket::connect().map_err(|err| err.to_string())?; + let socket = IpcSocket::connect()?; loop { RequestSend::Ping.send(&socket)?; - let bytes = socket.recv().map_err(|err| err.to_string())?; + let bytes = socket.recv()?; let answer = Answer::receive(bytes); if let Answer::Ping(configured) = answer { if configured { break; } } else { - return Err("Daemon did not return Answer::Ping, as expected".to_string()); + Err("Daemon did not return Answer::Ping, as expected")?; } - std::thread::sleep(Duration::from_millis(1)); + thread::sleep(Duration::from_millis(1)); } - process_swww_args(&swww) -} + match swww { + Swww::Clear(clear) => { + let (format, _, _) = get_format_dims_and_outputs(&[], &socket)?; + let mut color = clear.color; + if format.must_swap_r_and_b_channels() { + color.swap(0, 2); + } + let clear = ipc::ClearSend { + color, + outputs: split_cmdline_outputs(&clear.outputs) + .map(ToString::to_string) + .collect(), + }; + RequestSend::Clear(clear.create_request()).send(&socket)? + } + Swww::Restore(restore) => { + let requested_outputs: Box<_> = split_cmdline_outputs(&restore.outputs) + .map(ToString::to_string) + .collect(); -fn process_swww_args(args: &Swww) -> Result<(), String> { - let request = match make_request(args)? { - Some(request) => request, - None => return Ok(()), - }; - let socket = IpcSocket::connect().map_err(|err| err.to_string())?; - request.send(&socket)?; - let bytes = socket.recv().map_err(|err| err.to_string())?; - drop(socket); - match Answer::receive(bytes) { - Answer::Info(info) => info.iter().for_each(|i| println!("{}", i)), - Answer::Ok => { - if let Swww::Kill = args { + let (_, _, outputs) = get_format_dims_and_outputs(&requested_outputs, &socket)?; + + for output in outputs.iter().flatten().map(String::as_str) { + let path = cache::get_previous_image_path(output) + .map_err(|err| format!("failed to get previous image path: {err}"))?; + #[allow(deprecated)] + let img = Img { + image: cli::parse_image(&path)?, + outputs: output.to_string(), + no_resize: false, + resize: ResizeStrategy::Crop, + fill_color: [0, 0, 0], + filter: cli::Filter::Lanczos3, + transition_type: cli::TransitionType::None, + transition_step: std::num::NonZeroU8::MAX, + transition_duration: 0.0, + transition_fps: 30, + transition_angle: 0.0, + transition_pos: cli::CliPosition { + x: cli::CliCoord::Pixel(0.0), + y: cli::CliCoord::Pixel(0.0), + }, + invert_y: false, + transition_bezier: (0.0, 0.0, 0.0, 0.0), + transition_wave: (0.0, 0.0), + }; + img_request(img, &socket)?.send(&socket)?; + } + } + Swww::Img(img) => img_request(img, &socket)?.send(&socket)?, + Swww::Kill => match RequestSend::Kill + .send(&socket) + .map(|()| socket.recv())? + .map(Answer::receive)? + { + Answer::Ok => { #[cfg(debug_assertions)] let tries = 20; #[cfg(not(debug_assertions))] let tries = 10; - let path = IpcSocket::::path(); - let path = Path::new(path); + let addr = IpcSocket::::path(); + let path = Path::new(addr); for _ in 0..tries { if !path.exists() { return Ok(()); } - std::thread::sleep(Duration::from_millis(100)); + thread::sleep(Duration::from_millis(100)); } - return Err(format!("Could not confirm socket deletion at: {path:?}")); + Err(format!("Could not confirm socket deletion at: {addr}"))?; } - } - Answer::Ping(_) => { - return Ok(()); - } - } + _ => unreachable!("invalid IPC response"), + }, + Swww::Query => match RequestSend::Query + .send(&socket) + .map(|()| socket.recv())? + .map(Answer::receive)? + { + Answer::Info(info) => info.iter().for_each(|info| println!("{info}")), + _ => unreachable!("invalid IPC response"), + }, + Swww::ClearCache => unreachable!("handled at the start of `main`"), + }; + Ok(()) } -fn make_request(args: &Swww) -> Result, String> { - match args { - Swww::Clear(c) => { - let (format, _, _) = get_format_dims_and_outputs(&[])?; - let mut color = c.color; - if format.must_swap_r_and_b_channels() { - color.swap(0, 2); - } - 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); - restore_from_cache(&requested_outputs)?; - Ok(None) - } - Swww::ClearCache => unreachable!("there is no request for clear-cache"), - Swww::Img(img) => { - 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)?; - - let img_request = make_img_request(img, &dims, format, &outputs)?; +fn img_request(img: Img, socket: &IpcSocket) -> Result> { + let requested_outputs: Box<_> = split_cmdline_outputs(&img.outputs) + .map(ToString::to_string) + .collect(); + let (format, dims, outputs) = get_format_dims_and_outputs(&requested_outputs, socket)?; + // let imgbuf = ImgBuf::new(&img.path)?; - Ok(Some(RequestSend::Img(img_request))) - } - Swww::Kill => Ok(Some(RequestSend::Kill)), - Swww::Query => Ok(Some(RequestSend::Query)), - } + let img_request = make_img_request(&img, &dims, format, &outputs)?; + Ok(RequestSend::Img(img_request)) } fn make_img_request( img: &cli::Img, dims: &[(u32, u32)], - pixel_format: ipc::PixelFormat, + pixel_format: PixelFormat, outputs: &[Vec], ) -> Result { let transition = make_transition(img); @@ -206,88 +243,47 @@ fn make_img_request( #[allow(clippy::type_complexity)] fn get_format_dims_and_outputs( requested_outputs: &[String], -) -> Result<(ipc::PixelFormat, Vec<(u32, u32)>, Vec>), String> { + socket: &IpcSocket, +) -> Result<(PixelFormat, Vec<(u32, u32)>, Vec>), String> { let mut outputs: Vec> = Vec::new(); let mut dims: Vec<(u32, u32)> = Vec::new(); - let mut imgs: Vec = Vec::new(); + let mut imgs: Vec = Vec::new(); - let socket = IpcSocket::connect().map_err(|err| err.to_string())?; - RequestSend::Query.send(&socket)?; + RequestSend::Query.send(socket)?; let bytes = socket.recv().map_err(|err| err.to_string())?; - drop(socket); let answer = Answer::receive(bytes); - match answer { - Answer::Info(infos) => { - let mut format = ipc::PixelFormat::Xrgb; - for info in infos.iter() { - format = info.pixel_format; - let info_img = &info.img; - let name = info.name.to_string(); - if !requested_outputs.is_empty() && !requested_outputs.contains(&name) { - continue; - } - let real_dim = info.real_dim(); - if let Some((_, output)) = dims - .iter_mut() - .zip(&imgs) - .zip(&mut outputs) - .find(|((dim, img), _)| real_dim == **dim && info_img == *img) - { - output.push(name); - } else { - outputs.push(vec![name]); - dims.push(real_dim); - imgs.push(info_img.clone()); - } - } - if outputs.is_empty() { - Err("none of the requested outputs are valid".to_owned()) - } else { - Ok((format, dims, outputs)) - } + let Answer::Info(infos) = answer else { + unreachable!() + }; + let mut format = PixelFormat::Xrgb; + for info in infos.iter() { + format = info.pixel_format; + let info_img = &info.img; + let name = info.name.to_string(); + if !requested_outputs.is_empty() && !requested_outputs.contains(&name) { + continue; } - _ => unreachable!(), - } -} - -fn split_cmdline_outputs(outputs: &str) -> Box<[String]> { - outputs - .split(',') - .map(|s| s.to_owned()) - .filter(|s| !s.is_empty()) - .collect() -} - -fn restore_from_cache(requested_outputs: &[String]) -> Result<(), String> { - let (_, _, outputs) = get_format_dims_and_outputs(requested_outputs)?; - - for output in outputs.iter().flatten() { - let img_path = common::cache::get_previous_image_path(output) - .map_err(|e| format!("failed to get previous image path: {e}"))?; - #[allow(deprecated)] - if let Err(e) = process_swww_args(&Swww::Img(cli::Img { - image: cli::parse_image(&img_path)?, - outputs: output.to_string(), - no_resize: false, - resize: ResizeStrategy::Crop, - fill_color: [0, 0, 0], - filter: cli::Filter::Lanczos3, - transition_type: cli::TransitionType::None, - transition_step: std::num::NonZeroU8::MAX, - transition_duration: 0.0, - transition_fps: 30, - transition_angle: 0.0, - transition_pos: cli::CliPosition { - x: cli::CliCoord::Pixel(0.0), - y: cli::CliCoord::Pixel(0.0), - }, - invert_y: false, - transition_bezier: (0.0, 0.0, 0.0, 0.0), - transition_wave: (0.0, 0.0), - })) { - eprintln!("WARNING: failed to load cache for output {output}: {e}"); + let real_dim = info.real_dim(); + if let Some((_, output)) = dims + .iter_mut() + .zip(&imgs) + .zip(&mut outputs) + .find(|((dim, img), _)| real_dim == **dim && info_img == *img) + { + output.push(name); + } else { + outputs.push(vec![name]); + dims.push(real_dim); + imgs.push(info_img.clone()); } } + if outputs.is_empty() { + Err("none of the requested outputs are valid".to_owned()) + } else { + Ok((format, dims, outputs)) + } +} - Ok(()) +fn split_cmdline_outputs(outputs: &str) -> impl Iterator { + outputs.split(',').filter(|s| !s.is_empty()) } From db0bf1b32c5ea1b7d0725d7e7ff7fa3c8f4e89cd Mon Sep 17 00:00:00 2001 From: rkuklik Date: Tue, 25 Jun 2024 12:04:24 +0200 Subject: [PATCH 5/5] fix(daemon): rt dir creation --- daemon/src/daemon.rs | 1 - daemon/src/main.rs | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/daemon/src/daemon.rs b/daemon/src/daemon.rs index 22be6bb..f911c91 100644 --- a/daemon/src/daemon.rs +++ b/daemon/src/daemon.rs @@ -92,7 +92,6 @@ impl Daemon { }; } } - debug!("Created socket in {addr}"); Ok(()) } diff --git a/daemon/src/main.rs b/daemon/src/main.rs index ab6c228..a002847 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -12,7 +12,9 @@ use std::time::Instant; use cli::Cli; use common::ipc::IpcSocket; +use common::ipc::Server; use daemon::Daemon; +use log::debug; use log::error; use log::info; use log::Level; @@ -35,11 +37,13 @@ fn main() -> Result<(), Box> { // initialize the wayland connection, getting all the necessary globals let initializer = wayland::globals::init(cli.format); - let listener = IpcSocket::server()?; - // use the initializer to create the Daemon, then drop it to free up the memory let mut daemon = Daemon::new(initializer, !cli.no_cache)?; + // TODO: make socket part of daemon (beware of partial moves) + let listener = IpcSocket::server()?; + debug!("Created socket in {}", IpcSocket::::path()); + if let Ok(true) = sd_notify::booted() { if let Err(e) = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]) { error!("Error sending status update to systemd: {e}");