diff --git a/daemon/src/image_picker.rs b/daemon/src/image_picker.rs index 72a9629..f2adfe4 100644 --- a/daemon/src/image_picker.rs +++ b/daemon/src/image_picker.rs @@ -46,7 +46,7 @@ pub struct ImagePicker { impl ImagePicker { pub fn new(wallpaper_info: Arc) -> Self { Self { - current_img: PathBuf::new(), + current_img: PathBuf::from("/tmp/new.png"), image_changed_instant: Instant::now(), action: Some(ImagePickerAction::Next), sorting: match wallpaper_info.sorting { diff --git a/daemon/src/ipc_server.rs b/daemon/src/ipc_server.rs index 1e764a7..1504c3a 100644 --- a/daemon/src/ipc_server.rs +++ b/daemon/src/ipc_server.rs @@ -11,6 +11,7 @@ use color_eyre::eyre::Context; use color_eyre::Result; use log::error; use smithay_client_toolkit::reexports::calloop::LoopHandle; +use smithay_client_toolkit::reexports::client::QueueHandle; use wpaperd_ipc::{IpcError, IpcMessage, IpcResponse}; use crate::socket::SocketSource; @@ -18,7 +19,11 @@ use crate::surface::Surface; use crate::Wpaperd; /// Create an IPC socket. -pub fn spawn_ipc_socket(event_loop: &LoopHandle, socket_path: &Path) -> Result<()> { +pub fn spawn_ipc_socket( + socket_path: &Path, + event_loop: &LoopHandle, + qh: QueueHandle, +) -> Result<()> { // Try to delete the socket if it exists already. if socket_path.exists() { fs::remove_file(socket_path)?; @@ -31,7 +36,7 @@ pub fn spawn_ipc_socket(event_loop: &LoopHandle, socket_path: &Path) -> // Add source to calloop loop. let mut message_buffer = String::new(); event_loop.insert_source(socket, move |stream, _, wpaperd| { - if let Err(err) = handle_message(&mut message_buffer, stream, wpaperd) { + if let Err(err) = handle_message(&mut message_buffer, stream, qh.clone(), wpaperd) { error!("{}", err); } })?; @@ -69,7 +74,12 @@ fn collect_surfaces(wpaperd: &mut Wpaperd, monitors: Vec) -> Vec<&mut Su } /// Handle IPC socket messages. -fn handle_message(buffer: &mut String, ustream: UnixStream, wpaperd: &mut Wpaperd) -> Result<()> { +fn handle_message( + buffer: &mut String, + ustream: UnixStream, + qh: QueueHandle, + wpaperd: &mut Wpaperd, +) -> Result<()> { buffer.clear(); // Read new content to buffer. @@ -110,45 +120,23 @@ fn handle_message(buffer: &mut String, ustream: UnixStream, wpaperd: &mut Wpaper IpcMessage::PreviousWallpaper { monitors } => { check_monitors(wpaperd, &monitors).and_then(|_| { - let mut errors = Vec::new(); for surface in collect_surfaces(wpaperd, monitors) { surface.image_picker.previous_image(); - match surface.draw() { - Ok(_) => {} - Err(err) => { - log::error!("Error drawing surface: {}", err); - errors.push((surface.name().to_string(), err.to_string())) - } - } + surface.queue_draw(&qh); } - if errors.is_empty() { - Ok(IpcResponse::Ok) - } else { - Err(IpcError::DrawErrors(errors)) - } + Ok(IpcResponse::Ok) }) } IpcMessage::NextWallpaper { monitors } => { check_monitors(wpaperd, &monitors).and_then(|_| { - let mut errors = Vec::new(); for surface in collect_surfaces(wpaperd, monitors) { surface.image_picker.next_image(); - match surface.draw() { - Ok(_) => {} - Err(err) => { - log::error!("Error drawing surface: {}", err); - errors.push((surface.name().to_string(), err.to_string())) - } - } + surface.queue_draw(&qh); } - if errors.is_empty() { - Ok(IpcResponse::Ok) - } else { - Err(IpcError::DrawErrors(errors)) - } + Ok(IpcResponse::Ok) }) } diff --git a/daemon/src/main.rs b/daemon/src/main.rs index d8ea5eb..4ca47b2 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -100,7 +100,8 @@ fn run(config: Config, xdg_dirs: BaseDirectories) -> Result<()> { // loop we will always receive timeout events and create // them when that happens if surface.is_configured() { - surface.add_timer(event_loop.handle(), None); + surface.add_timer(None, event_loop.handle(), qh.clone()); + surface.draw(&qh, 0); true } else { false @@ -117,7 +118,7 @@ fn run(config: Config, xdg_dirs: BaseDirectories) -> Result<()> { .context("dispatching the event loop")?; } - ipc_server::spawn_ipc_socket(&event_loop.handle(), &socket_path()?).unwrap(); + ipc_server::spawn_ipc_socket(&socket_path()?, &event_loop.handle(), qh.clone()).unwrap(); if let Some(notify) = config.notify { let mut f = unsafe { File::from_raw_fd(notify as i32) }; if let Err(err) = writeln!(f) { @@ -128,10 +129,10 @@ fn run(config: Config, xdg_dirs: BaseDirectories) -> Result<()> { loop { let mut wallpaper_config = wallpaper_config.lock().unwrap(); if wallpaper_config.reloaded { - wpaperd.surfaces.iter_mut().for_each(|surface| { + for surface in &mut wpaperd.surfaces { let wallpaper_info = wallpaper_config.get_output_by_name(surface.name()); - surface.update_wallpaper_info(event_loop.handle(), wallpaper_info); - }); + surface.update_wallpaper_info(event_loop.handle(), &qh, wallpaper_info); + } wallpaper_config.reloaded = false; } drop(wallpaper_config); diff --git a/daemon/src/render.rs b/daemon/src/render.rs index b39b6c4..4620a89 100644 --- a/daemon/src/render.rs +++ b/daemon/src/render.rs @@ -24,6 +24,16 @@ pub struct Renderer { vao: gl::types::GLuint, vbo: gl::types::GLuint, gl: gl::Gl, + // reverse: false + // old gl texture0 + // new gl texture1 + // reverse: true + // new gl texture0 + // old texture1 + reverse: bool, + // milliseconds time for the animation + animation_time: u32, + pub time_started: u32, } // Macro that check the error code of the last OpenGL call and returns a Result. @@ -32,14 +42,17 @@ macro_rules! gl_check { let error = $gl.GetError(); if error != gl::NO_ERROR { let error_string = $gl.GetString(error); - if error_string.is_null() { - bail!("OpenGL error when {}: {}", $desc, error); - } else { - let error_string = CStr::from_ptr(error_string as _) - .to_string_lossy() - .into_owned(); - bail!("OpenGL error when {}: {} ({})", $desc, error, error_string); - } + ensure!( + !error_string.is_null(), + "OpenGL error when {}: {}", + $desc, + error + ); + + let error_string = CStr::from_ptr(error_string as _) + .to_string_lossy() + .into_owned(); + bail!("OpenGL error when {}: {} ({})", $desc, error, error_string); } }}; } @@ -143,7 +156,7 @@ impl EglContext { } impl Renderer { - pub unsafe fn new() -> Result { + pub unsafe fn new(image: DynamicImage) -> Result { let gl = gl::Gl::load_with(|name| { egl.get_proc_address(name).unwrap() as *const std::ffi::c_void }); @@ -221,12 +234,19 @@ impl Renderer { gl.BindBuffer(gl::ARRAY_BUFFER, vbo); gl_check!(gl, "binding the buffer"); - Ok(Self { + let mut renderer = Self { program, vao, vbo, gl, - }) + reverse: true, + time_started: 0, + animation_time: 3000, + }; + + renderer.load_texture(image)?; + + Ok(renderer) } pub fn check_error(&self, msg: &str) -> Result<()> { @@ -236,19 +256,35 @@ impl Renderer { Ok(()) } - pub unsafe fn draw(&self) -> Result<()> { + pub unsafe fn draw(&self, time: u32) -> Result<()> { + let elapsed = time - self.time_started; + let mut progress = (elapsed as f32 / self.animation_time as f32).min(1.0); + if self.reverse { + progress = 1.0 - progress; + } + + let loc = self + .gl + .GetUniformLocation(self.program, b"u_progress\0".as_ptr() as *const _); + self.check_error("getting the uniform location")?; + self.gl.Uniform1f(loc, progress); + self.check_error("calling Uniform1i")?; self.gl.DrawArrays(gl::TRIANGLES, 0, 6); self.check_error("drawing the triangles")?; Ok(()) } - pub fn load_texture(&self, image: DynamicImage) -> Result<()> { + pub fn load_texture(&mut self, image: DynamicImage) -> Result<()> { let mut texture = 0; Ok(unsafe { self.gl.GenTextures(1, &mut texture); self.check_error("generating textures")?; - self.gl.ActiveTexture(gl::TEXTURE0); + self.gl.ActiveTexture(if self.reverse { + gl::TEXTURE0 + } else { + gl::TEXTURE1 + }); self.check_error("activating textures")?; self.gl.BindTexture(gl::TEXTURE_2D, texture); self.check_error("binding textures")?; @@ -266,21 +302,30 @@ impl Renderer { self.check_error("defining the texture")?; self.gl.GenerateMipmap(gl::TEXTURE_2D); self.check_error("generating the mipmap")?; - let loc = self - .gl - .GetUniformLocation(self.program, b"u_texture\0".as_ptr() as *const _); - self.check_error("getting the uniform location")?; - self.gl.Uniform1i(loc, 0); - self.check_error("calling Uniform1i")?; self.gl .TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); self.check_error("defining the texture min filter")?; self.gl .TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); self.check_error("defining the texture mag filter")?; + // We assume that the we still have an active texture + if self.reverse { + self.gl.Uniform1i(0, 0); + } else { + self.gl.Uniform1i(1, 1); + } + self.reverse = !self.reverse; }) } + pub fn start_animation(&mut self, time: u32) { + self.time_started = time; + } + + pub fn assign_textures(&mut self) -> Result<()> { + Ok(()) + } + pub fn clear_after_draw(&self) -> Result<()> { unsafe { // Unbind the framebuffer and renderbuffer before deleting. @@ -301,6 +346,10 @@ impl Renderer { self.check_error("resizing the viewport") } } + + pub(crate) fn is_drawing_animation(&self, time: u32) -> bool { + time < (self.time_started + self.animation_time) + } } impl Deref for Renderer { @@ -393,9 +442,13 @@ out vec4 FragColor; in vec2 v_texcoord; -uniform sampler2D u_texture; +layout(location = 0) uniform sampler2D u_texture0; +layout(location = 1) uniform sampler2D u_texture1; + +layout(location = 2) uniform float u_progress; void main() { - FragColor = texture(u_texture, v_texcoord); + FragColor = mix(texture(u_texture1, v_texcoord), texture(u_texture0, v_texcoord), u_progress); + } \0"; diff --git a/daemon/src/surface.rs b/daemon/src/surface.rs index bf444ea..7e0a7d9 100644 --- a/daemon/src/surface.rs +++ b/daemon/src/surface.rs @@ -8,6 +8,7 @@ use smithay_client_toolkit::reexports::calloop::timer::{TimeoutAction, Timer}; use smithay_client_toolkit::reexports::calloop::{LoopHandle, RegistrationToken}; use smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput; use smithay_client_toolkit::reexports::client::protocol::wl_surface; +use smithay_client_toolkit::reexports::client::QueueHandle; use smithay_client_toolkit::shell::wlr_layer::{LayerSurface, LayerSurfaceConfigure}; use crate::image_picker::ImagePicker; @@ -47,6 +48,11 @@ impl Surface { // Commit the surface surface.commit(); + let mut image_picker = ImagePicker::new(wallpaper_info.clone()); + + let image = image_picker.get_image().unwrap().unwrap().into_rgba8(); + let renderer = unsafe { Renderer::new(image.into()).unwrap() }; + Self { name, output, @@ -56,15 +62,15 @@ impl Surface { scale: scale_factor, surface, egl_context, - renderer: unsafe { Renderer::new().unwrap() }, - image_picker: ImagePicker::new(wallpaper_info.clone()), + renderer, + image_picker, event_source: None, wallpaper_info, } } /// Returns true if something has been drawn to the surface - pub fn draw(&mut self) -> Result<()> { + pub fn draw(&mut self, qh: &QueueHandle, time: u32) -> Result<()> { debug_assert!(self.width != 0 || self.height != 0); let width = self.width as i32 * self.scale; @@ -76,11 +82,19 @@ impl Surface { if let Some(image) = self.image_picker.get_image()? { let image = image.into_rgba8(); self.renderer.load_texture(image.into())?; + self.renderer.start_animation(time); // self.apply_shadow(&mut image, width.try_into()?); } + if self.renderer.time_started == 0 { + self.renderer.start_animation(time); + } + + unsafe { self.renderer.draw(time)? }; - unsafe { self.renderer.draw()? }; + if self.is_drawing_animation(time) { + self.queue_draw(qh); + } self.renderer.clear_after_draw()?; self.egl_context.swap_buffers()?; @@ -144,9 +158,6 @@ impl Surface { // Resize the gl viewport self.egl_context.make_current().unwrap(); self.renderer.resize(width, height).unwrap(); - - // Draw the surface again - self.draw().unwrap(); } /// Check that the dimensions are valid @@ -159,6 +170,7 @@ impl Surface { pub fn update_wallpaper_info( &mut self, handle: LoopHandle, + qh: &QueueHandle, mut wallpaper_info: Arc, ) { if self.wallpaper_info != wallpaper_info { @@ -176,7 +188,7 @@ impl Surface { handle.remove(registration_token); } if path_changed { - self.draw().unwrap(); + self.queue_draw(qh); } } // There wasn't a duration before but now it has been added or it has changed @@ -187,24 +199,26 @@ impl Surface { // if the path has not changed or the duration has changed // and the remaining time is great than 0 - if let (false, Some(remaining_time)) = ( + let timer = if let (false, Some(remaining_time)) = ( path_changed, remaining_duration( new_duration, self.image_picker.image_changed_instant, ), ) { - self.add_timer(handle, Some(Timer::from_duration(remaining_time))); + Some(Timer::from_duration(remaining_time)) } else { // otherwise draw the image immediately, the next timer // will be set to the new duration - self.add_timer(handle, Some(Timer::immediate())); - } + Some(Timer::immediate()) + }; + + self.add_timer(timer, handle, qh.clone()); } } } else { if path_changed { - self.draw().unwrap(); + self.queue_draw(qh); } } } @@ -212,7 +226,12 @@ impl Surface { /// Add a new timer in the event_loop for the current duration /// Stop if there is already a timer added - pub fn add_timer(&mut self, handle: LoopHandle, timer: Option) { + pub fn add_timer( + &mut self, + timer: Option, + handle: LoopHandle, + qh: QueueHandle, + ) { if let Some(duration) = self.wallpaper_info.duration { let timer = timer.unwrap_or(Timer::from_duration(duration)); if self.event_source.is_some() { @@ -241,7 +260,7 @@ impl Surface { } else { // Change the drawn image surface.image_picker.next_image(); - surface.draw().unwrap(); + surface.queue_draw(&qh); TimeoutAction::ToDuration(duration) } } else { @@ -254,6 +273,15 @@ impl Surface { self.event_source = Some(registration_token); } } + + pub fn is_drawing_animation(&self, time: u32) -> bool { + self.renderer.is_drawing_animation(time) + } + + pub(crate) fn queue_draw(&self, qh: &QueueHandle) { + self.surface.frame(qh, self.surface.clone()); + self.surface.commit(); + } } fn remaining_duration(duration: Duration, image_changed: Instant) -> Option { diff --git a/daemon/src/wpaperd.rs b/daemon/src/wpaperd.rs index 1cd0e04..b6ecc2d 100644 --- a/daemon/src/wpaperd.rs +++ b/daemon/src/wpaperd.rs @@ -94,7 +94,7 @@ impl CompositorHandler for Wpaperd { fn scale_factor_changed( &mut self, _conn: &Connection, - _qh: &QueueHandle, + qh: &QueueHandle, surface: &wl_surface::WlSurface, new_factor: i32, ) { @@ -107,16 +107,25 @@ impl CompositorHandler for Wpaperd { surface.scale = new_factor; surface.surface.set_buffer_scale(new_factor); surface.resize(None); + surface.queue_draw(qh); } } fn frame( &mut self, _conn: &Connection, - _qh: &QueueHandle, - _surface: &wl_surface::WlSurface, - _time: u32, + qh: &QueueHandle, + surface: &wl_surface::WlSurface, + time: u32, ) { + let surface = self.surface_from_wl_surface(surface).unwrap(); + + match surface.draw(qh, time) { + Ok(_) => {} + Err(err) => { + log::error!("Error drawing surface: {}", err); + } + } } fn transform_changed( @@ -224,7 +233,7 @@ impl LayerShellHandler for Wpaperd { fn configure( &mut self, _conn: &Connection, - _qh: &QueueHandle, + qh: &QueueHandle, layer: &LayerSurface, configure: LayerSurfaceConfigure, _serial: u32, @@ -239,6 +248,7 @@ impl LayerShellHandler for Wpaperd { if (surface.width, surface.height) != configure.new_size { // Update dimensions surface.resize(Some(configure)); + surface.queue_draw(qh); } } }