diff options
| -rw-r--r-- | .rustfmt.toml | 3 | ||||
| -rw-r--r-- | src/main.rs | 710 | ||||
| -rw-r--r-- | src/wayland.rs | 282 |
3 files changed, 499 insertions, 496 deletions
diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..a0e81c4 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +reorder_imports = true +style_edition = "2024" +hard_tabs = true diff --git a/src/main.rs b/src/main.rs index 64dfdba..5509e47 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,373 +6,373 @@ use std::mem; use std::time::Instant; use wgpu::util::DeviceExt; use winit::{ - application::ApplicationHandler, - event::WindowEvent, - event_loop::{ActiveEventLoop, EventLoop}, - window::{Window, WindowAttributes, WindowId}, + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, EventLoop}, + window::{Window, WindowAttributes, WindowId}, }; #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Uniforms { - i_resolution: [f32; 3], - i_time: f32, - i_mouse: [f32; 4], + i_resolution: [f32; 3], + i_time: f32, + i_mouse: [f32; 4], } struct WallpaperApp { - window: Option<Window>, - surface: Option<wgpu::Surface<'static>>, - device: Option<wgpu::Device>, - queue: Option<wgpu::Queue>, - config: Option<wgpu::SurfaceConfiguration>, - render_pipeline: Option<wgpu::RenderPipeline>, - uniform_buffer: Option<wgpu::Buffer>, - bind_group: Option<wgpu::BindGroup>, - start_time: Instant, - shader_path: String, - hyprland_config: wayland::HyprlandConfig, - wayland_configured: bool, + window: Option<Window>, + surface: Option<wgpu::Surface<'static>>, + device: Option<wgpu::Device>, + queue: Option<wgpu::Queue>, + config: Option<wgpu::SurfaceConfiguration>, + render_pipeline: Option<wgpu::RenderPipeline>, + uniform_buffer: Option<wgpu::Buffer>, + bind_group: Option<wgpu::BindGroup>, + start_time: Instant, + shader_path: String, + hyprland_config: wayland::HyprlandConfig, + wayland_configured: bool, } impl WallpaperApp { - fn new(shader_path: String) -> Self { - Self { - window: None, - surface: None, - device: None, - queue: None, - config: None, - render_pipeline: None, - uniform_buffer: None, - bind_group: None, - start_time: Instant::now(), - shader_path, - hyprland_config: wayland::HyprlandConfig::default(), - wayland_configured: false, - } - } - fn init_graphics(&mut self) -> Result<()> { - let window = self - .window - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Window not initialized"))?; - let size = window.inner_size(); - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - memory_budget_thresholds: wgpu::MemoryBudgetThresholds { - for_resource_creation: Some(200), - for_device_loss: Some(100), - }, - backends: wgpu::Backends::VULKAN, - backend_options: wgpu::BackendOptions { - gl: wgpu::GlBackendOptions::default(), - dx12: wgpu::Dx12BackendOptions::default(), - noop: wgpu::NoopBackendOptions::default(), - }, - flags: wgpu::InstanceFlags::empty(), - }); - // Create surface - let surface = instance - .create_surface(window) - .map_err(|e| anyhow::anyhow!("Failed to create surface: {}", e))?; - let surface = - unsafe { mem::transmute::<wgpu::Surface<'_>, wgpu::Surface<'static>>(surface) }; - let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .unwrap(); - let (device, queue) = - pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - memory_hints: wgpu::MemoryHints::Performance, - label: None, - experimental_features: wgpu::ExperimentalFeatures::disabled(), - trace: wgpu::Trace::Off, - }))?; - let surface_caps = surface.get_capabilities(&adapter); - let surface_format = surface_caps - .formats - .iter() - .copied() - .find(|f| f.is_srgb()) - .unwrap_or(surface_caps.formats[0]); - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - surface.configure(&device, &config); - // Load and compile shader - let shader_source = std::fs::read_to_string(&self.shader_path).map_err(|e| { - anyhow::anyhow!("Failed to read shader file {}: {}", &self.shader_path, e) - })?; - let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Wallpaper Shader"), - source: wgpu::ShaderSource::Wgsl(shader_source.as_str().into()), - }); - // Create uniform buffer - let uniforms = Uniforms { - i_resolution: [size.width as f32, size.height as f32, 0.0], - i_time: 0.0, - i_mouse: [0.0, 0.0, 0.0, 0.0], - }; - let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Uniform Buffer"), - contents: bytemuck::cast_slice(&[uniforms]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }); - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("uniform_bind_group_layout"), - }); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: uniform_buffer.as_entire_binding(), - }], - label: Some("uniform_bind_group"), - }); - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader_module, - entry_point: Some("vs_main"), - compilation_options: wgpu::PipelineCompilationOptions::default(), - buffers: &[], - }, - fragment: Some(wgpu::FragmentState { - module: &shader_module, - entry_point: Some("fs_main"), - compilation_options: wgpu::PipelineCompilationOptions::default(), - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - self.surface = Some(surface); - self.device = Some(device); - self.queue = Some(queue); - self.config = Some(config); - self.render_pipeline = Some(render_pipeline); - self.uniform_buffer = Some(uniform_buffer); - self.bind_group = Some(bind_group); - Ok(()) - } - fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { - if new_size.width > 0 - && new_size.height > 0 - && let (Some(surface), Some(device), Some(config)) = - (&self.surface, &self.device, &mut self.config) - { - config.width = new_size.width; - config.height = new_size.height; - surface.configure(device, config); - } - } - fn render(&mut self) -> Result<()> { - let surface = self - .surface - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Surface not initialized"))?; - let device = self - .device - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Device not initialized"))?; - let queue = self - .queue - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Queue not initialized"))?; - let render_pipeline = self - .render_pipeline - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Render pipeline not initialized"))?; - let uniform_buffer = self - .uniform_buffer - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Uniform buffer not initialized"))?; - let bind_group = self - .bind_group - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Bind group not initialized"))?; - let config = self - .config - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Config not initialized"))?; - let frame = surface.get_current_texture()?; - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - // Update uniforms - let uniforms = Uniforms { - i_resolution: [config.width as f32, config.height as f32, 0.0], - i_time: self.start_time.elapsed().as_secs_f32(), - i_mouse: [0.0, 0.0, 0.0, 0.0], - }; - queue.write_buffer(uniform_buffer, 0, bytemuck::cast_slice(&[uniforms])); - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - depth_slice: None, - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - render_pass.set_pipeline(render_pipeline); - render_pass.set_bind_group(0, bind_group, &[]); - render_pass.draw(0..3, 0..1); - } - queue.submit(std::iter::once(encoder.finish())); - frame.present(); - Ok(()) - } + fn new(shader_path: String) -> Self { + Self { + window: None, + surface: None, + device: None, + queue: None, + config: None, + render_pipeline: None, + uniform_buffer: None, + bind_group: None, + start_time: Instant::now(), + shader_path, + hyprland_config: wayland::HyprlandConfig::default(), + wayland_configured: false, + } + } + fn init_graphics(&mut self) -> Result<()> { + let window = self + .window + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Window not initialized"))?; + let size = window.inner_size(); + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + memory_budget_thresholds: wgpu::MemoryBudgetThresholds { + for_resource_creation: Some(200), + for_device_loss: Some(100), + }, + backends: wgpu::Backends::VULKAN, + backend_options: wgpu::BackendOptions { + gl: wgpu::GlBackendOptions::default(), + dx12: wgpu::Dx12BackendOptions::default(), + noop: wgpu::NoopBackendOptions::default(), + }, + flags: wgpu::InstanceFlags::empty(), + }); + // Create surface + let surface = instance + .create_surface(window) + .map_err(|e| anyhow::anyhow!("Failed to create surface: {}", e))?; + let surface = + unsafe { mem::transmute::<wgpu::Surface<'_>, wgpu::Surface<'static>>(surface) }; + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + })) + .unwrap(); + let (device, queue) = + pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + memory_hints: wgpu::MemoryHints::Performance, + label: None, + experimental_features: wgpu::ExperimentalFeatures::disabled(), + trace: wgpu::Trace::Off, + }))?; + let surface_caps = surface.get_capabilities(&adapter); + let surface_format = surface_caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + surface.configure(&device, &config); + // Load and compile shader + let shader_source = std::fs::read_to_string(&self.shader_path).map_err(|e| { + anyhow::anyhow!("Failed to read shader file {}: {}", &self.shader_path, e) + })?; + let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Wallpaper Shader"), + source: wgpu::ShaderSource::Wgsl(shader_source.as_str().into()), + }); + // Create uniform buffer + let uniforms = Uniforms { + i_resolution: [size.width as f32, size.height as f32, 0.0], + i_time: 0.0, + i_mouse: [0.0, 0.0, 0.0, 0.0], + }; + let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Uniform Buffer"), + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("uniform_bind_group_layout"), + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buffer.as_entire_binding(), + }], + label: Some("uniform_bind_group"), + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader_module, + entry_point: Some("vs_main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader_module, + entry_point: Some("fs_main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + self.surface = Some(surface); + self.device = Some(device); + self.queue = Some(queue); + self.config = Some(config); + self.render_pipeline = Some(render_pipeline); + self.uniform_buffer = Some(uniform_buffer); + self.bind_group = Some(bind_group); + Ok(()) + } + fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { + if new_size.width > 0 + && new_size.height > 0 + && let (Some(surface), Some(device), Some(config)) = + (&self.surface, &self.device, &mut self.config) + { + config.width = new_size.width; + config.height = new_size.height; + surface.configure(device, config); + } + } + fn render(&mut self) -> Result<()> { + let surface = self + .surface + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Surface not initialized"))?; + let device = self + .device + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Device not initialized"))?; + let queue = self + .queue + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Queue not initialized"))?; + let render_pipeline = self + .render_pipeline + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Render pipeline not initialized"))?; + let uniform_buffer = self + .uniform_buffer + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Uniform buffer not initialized"))?; + let bind_group = self + .bind_group + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Bind group not initialized"))?; + let config = self + .config + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Config not initialized"))?; + let frame = surface.get_current_texture()?; + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + // Update uniforms + let uniforms = Uniforms { + i_resolution: [config.width as f32, config.height as f32, 0.0], + i_time: self.start_time.elapsed().as_secs_f32(), + i_mouse: [0.0, 0.0, 0.0, 0.0], + }; + queue.write_buffer(uniform_buffer, 0, bytemuck::cast_slice(&[uniforms])); + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + depth_slice: None, + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + render_pass.set_pipeline(render_pipeline); + render_pass.set_bind_group(0, bind_group, &[]); + render_pass.draw(0..3, 0..1); + } + queue.submit(std::iter::once(encoder.finish())); + frame.present(); + Ok(()) + } } impl ApplicationHandler for WallpaperApp { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - if self.window.is_none() { - let window_attributes = WindowAttributes::default() - .with_title("Hyprland Live Wallpaper") - .with_transparent(true) - .with_decorations(false) - .with_fullscreen(Some(winit::window::Fullscreen::Borderless(None))); - let window = match event_loop.create_window(window_attributes) { - Ok(window) => window, - Err(e) => { - error!("Failed to create window: {}", e); - return; - } - }; - // Configure window for Hyprland - if let Err(e) = wayland::configure_hyprland_window(&window, &self.hyprland_config) { - warn!("Failed to configure Hyprland window: {}", e); - } - // Store window first, then initialize graphics - self.window = Some(window); - match self.init_graphics() { - Ok(()) => { - info!("Wallpaper service running"); - } - Err(e) => { - error!("Failed to initialize graphics: {}", e); - } - } - } - } - fn window_event( - &mut self, - event_loop: &ActiveEventLoop, - _window_id: WindowId, - event: WindowEvent, - ) { - match event { - WindowEvent::CloseRequested => { - event_loop.exit(); - } - WindowEvent::Resized(size) => { - self.resize(size); - } - WindowEvent::RedrawRequested => { - // Apply Hyprland rules on first redraw (after window is mapped) - if !self.wayland_configured - && let Some(window) = &self.window - { - // Convert window ID to u64 for hyprctl - let window_id: u64 = window.id().into(); - if let Err(e) = wayland::apply_hyprland_rules(window_id, &self.hyprland_config) - { - warn!("Failed to apply Hyprland rules: {}", e); - } - self.wayland_configured = true; - } - if let Err(e) = self.render() { - error!("Render error: {}", e); - event_loop.exit(); - } - } - _ => {} - } - } - fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { - if let Some(window) = &self.window { - window.request_redraw(); - } - } + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + if self.window.is_none() { + let window_attributes = WindowAttributes::default() + .with_title("Hyprland Live Wallpaper") + .with_transparent(true) + .with_decorations(false) + .with_fullscreen(Some(winit::window::Fullscreen::Borderless(None))); + let window = match event_loop.create_window(window_attributes) { + Ok(window) => window, + Err(e) => { + error!("Failed to create window: {}", e); + return; + } + }; + // Configure window for Hyprland + if let Err(e) = wayland::configure_hyprland_window(&window, &self.hyprland_config) { + warn!("Failed to configure Hyprland window: {}", e); + } + // Store window first, then initialize graphics + self.window = Some(window); + match self.init_graphics() { + Ok(()) => { + info!("Wallpaper service running"); + } + Err(e) => { + error!("Failed to initialize graphics: {}", e); + } + } + } + } + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::CloseRequested => { + event_loop.exit(); + } + WindowEvent::Resized(size) => { + self.resize(size); + } + WindowEvent::RedrawRequested => { + // Apply Hyprland rules on first redraw (after window is mapped) + if !self.wayland_configured + && let Some(window) = &self.window + { + // Convert window ID to u64 for hyprctl + let window_id: u64 = window.id().into(); + if let Err(e) = wayland::apply_hyprland_rules(window_id, &self.hyprland_config) + { + warn!("Failed to apply Hyprland rules: {}", e); + } + self.wayland_configured = true; + } + if let Err(e) = self.render() { + error!("Render error: {}", e); + event_loop.exit(); + } + } + _ => {} + } + } + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + if let Some(window) = &self.window { + window.request_redraw(); + } + } } fn main() -> Result<()> { - env_logger::init(); - let matches = Command::new("hyprland-live-wallpaper") - .version("0.1.0") - .about("Live wallpaper service for Hyprland using wgpu") - .arg( - Arg::new("shader") - .short('s') - .long("shader") - .value_name("FILE") - .help("Path to WGSL shader file"), - ) - .get_matches(); - info!("Starting Hyprland Live Wallpaper"); - let shader_path = matches - .get_one::<String>("shader") - .map(|s| s.as_str()) - .unwrap_or("shaders/red.wgsl") - .to_string(); - run_wallpaper(shader_path)?; - Ok(()) + env_logger::init(); + let matches = Command::new("hyprland-live-wallpaper") + .version("0.1.0") + .about("Live wallpaper service for Hyprland using wgpu") + .arg( + Arg::new("shader") + .short('s') + .long("shader") + .value_name("FILE") + .help("Path to WGSL shader file"), + ) + .get_matches(); + info!("Starting Hyprland Live Wallpaper"); + let shader_path = matches + .get_one::<String>("shader") + .map(|s| s.as_str()) + .unwrap_or("shaders/red.wgsl") + .to_string(); + run_wallpaper(shader_path)?; + Ok(()) } fn run_wallpaper(shader_path: String) -> Result<()> { - let event_loop = EventLoop::new()?; - let mut app = WallpaperApp::new(shader_path); - event_loop.run_app(&mut app)?; - Ok(()) + let event_loop = EventLoop::new()?; + let mut app = WallpaperApp::new(shader_path); + event_loop.run_app(&mut app)?; + Ok(()) } diff --git a/src/wayland.rs b/src/wayland.rs index 54699ec..da775e2 100644 --- a/src/wayland.rs +++ b/src/wayland.rs @@ -4,173 +4,173 @@ use std::process::Command; /// Hyprland-specific window configuration for better integration pub struct HyprlandConfig { - pub floating: bool, - pub pin: bool, - pub no_focus: bool, + pub floating: bool, + pub pin: bool, + pub no_focus: bool, } impl Default for HyprlandConfig { - fn default() -> Self { - Self { - floating: true, - pin: true, - no_focus: true, - } - } + fn default() -> Self { + Self { + floating: true, + pin: true, + no_focus: true, + } + } } /// Configure a window for optimal Hyprland wallpaper behavior pub fn configure_hyprland_window( - _window: &winit::window::Window, - _config: &HyprlandConfig, + _window: &winit::window::Window, + _config: &HyprlandConfig, ) -> Result<()> { - // On Wayland, we can set window properties for better integration - // For now, skip advanced Wayland protocol setup as it's not implemented - - // For Hyprland, we can use hyprctl to configure the window - // This requires the window to be created first, so we'll do this after creation - if let Ok(output) = Command::new("which").arg("hyprctl").output() - && output.status.success() - { - info!("Hyprland detected, will configure window properties"); - // We'll configure the window after it's mapped in the event loop - } - - Ok(()) + // On Wayland, we can set window properties for better integration + // For now, skip advanced Wayland protocol setup as it's not implemented + + // For Hyprland, we can use hyprctl to configure the window + // This requires the window to be created first, so we'll do this after creation + if let Ok(output) = Command::new("which").arg("hyprctl").output() + && output.status.success() + { + info!("Hyprland detected, will configure window properties"); + // We'll configure the window after it's mapped in the event loop + } + + Ok(()) } /// Apply Hyprland-specific window rules after window creation pub fn apply_hyprland_rules(window_id: u64, config: &HyprlandConfig) -> Result<()> { - #[cfg(target_os = "linux")] - { - use std::process::Command; - use std::thread; - use std::time::Duration; - - // Wait a bit for the window to be mapped - thread::sleep(Duration::from_millis(100)); - - // Use hyprctl to configure the window - let mut rules = Vec::new(); - - if config.floating { - rules.push("float"); - } - - if config.pin { - rules.push("pin"); - } - - if config.no_focus { - rules.push("nofocus"); - } - - if !rules.is_empty() { - let rules_str = rules.join(","); - let window_selector = format!("address:0x{:x}", window_id); - - let output = Command::new("hyprctl") - .args([ - "keyword", - "windowrule", - &format!("{},{}", rules_str, window_selector), - ]) - .output() - .map_err(|e| anyhow!("Failed to execute hyprctl: {}", e))?; - - if output.status.success() { - info!( - "Applied Hyprland rules: {} to window {}", - rules_str, window_selector - ); - } else { - let error_msg = String::from_utf8_lossy(&output.stderr); - warn!("Failed to apply Hyprland rules: {}", error_msg); - } - - // Also try to move it to the background layer - let workspace = format!("special:background,{}", window_selector); - let output = Command::new("hyprctl") - .args(["dispatch", "movetoworkspace", &workspace]) - .output() - .map_err(|e| anyhow!("Failed to move window to background: {}", e))?; - - if output.status.success() { - info!("Moved wallpaper window to background workspace"); - } - } - } - - Ok(()) + #[cfg(target_os = "linux")] + { + use std::process::Command; + use std::thread; + use std::time::Duration; + + // Wait a bit for the window to be mapped + thread::sleep(Duration::from_millis(100)); + + // Use hyprctl to configure the window + let mut rules = Vec::new(); + + if config.floating { + rules.push("float"); + } + + if config.pin { + rules.push("pin"); + } + + if config.no_focus { + rules.push("nofocus"); + } + + if !rules.is_empty() { + let rules_str = rules.join(","); + let window_selector = format!("address:0x{:x}", window_id); + + let output = Command::new("hyprctl") + .args([ + "keyword", + "windowrule", + &format!("{},{}", rules_str, window_selector), + ]) + .output() + .map_err(|e| anyhow!("Failed to execute hyprctl: {}", e))?; + + if output.status.success() { + info!( + "Applied Hyprland rules: {} to window {}", + rules_str, window_selector + ); + } else { + let error_msg = String::from_utf8_lossy(&output.stderr); + warn!("Failed to apply Hyprland rules: {}", error_msg); + } + + // Also try to move it to the background layer + let workspace = format!("special:background,{}", window_selector); + let output = Command::new("hyprctl") + .args(["dispatch", "movetoworkspace", &workspace]) + .output() + .map_err(|e| anyhow!("Failed to move window to background: {}", e))?; + + if output.status.success() { + info!("Moved wallpaper window to background workspace"); + } + } + } + + Ok(()) } /// Get information about available outputs (monitors) pub fn get_output_info() -> Result<Vec<OutputInfo>> { - #[cfg(target_os = "linux")] - { - use std::process::Command; - - let output = Command::new("hyprctl") - .args(["monitors", "-j"]) - .output() - .map_err(|e| anyhow!("Failed to execute hyprctl: {}", e))?; - - if output.status.success() { - let output_str = String::from_utf8_lossy(&output.stdout); - let monitors: Vec<HyprlandMonitor> = serde_json::from_str(&output_str) - .map_err(|e| anyhow!("Failed to parse hyprctl output: {}", e))?; - - let output_info = monitors - .into_iter() - .map(|monitor| OutputInfo { - name: monitor.name, - width: monitor.width, - height: monitor.height, - x: monitor.x, - y: monitor.y, - }) - .collect(); - - Ok(output_info) - } else { - // Fallback to basic monitor detection - Ok(vec![OutputInfo::default()]) - } - } - - #[cfg(not(target_os = "linux"))] - { - Ok(vec![OutputInfo::default()]) - } + #[cfg(target_os = "linux")] + { + use std::process::Command; + + let output = Command::new("hyprctl") + .args(["monitors", "-j"]) + .output() + .map_err(|e| anyhow!("Failed to execute hyprctl: {}", e))?; + + if output.status.success() { + let output_str = String::from_utf8_lossy(&output.stdout); + let monitors: Vec<HyprlandMonitor> = serde_json::from_str(&output_str) + .map_err(|e| anyhow!("Failed to parse hyprctl output: {}", e))?; + + let output_info = monitors + .into_iter() + .map(|monitor| OutputInfo { + name: monitor.name, + width: monitor.width, + height: monitor.height, + x: monitor.x, + y: monitor.y, + }) + .collect(); + + Ok(output_info) + } else { + // Fallback to basic monitor detection + Ok(vec![OutputInfo::default()]) + } + } + + #[cfg(not(target_os = "linux"))] + { + Ok(vec![OutputInfo::default()]) + } } #[derive(Debug, Clone)] pub struct OutputInfo { - pub name: String, - pub width: i32, - pub height: i32, - pub x: i32, - pub y: i32, + pub name: String, + pub width: i32, + pub height: i32, + pub x: i32, + pub y: i32, } impl Default for OutputInfo { - fn default() -> Self { - Self { - name: "eDP-1".to_string(), - width: 1920, - height: 1080, - x: 0, - y: 0, - } - } + fn default() -> Self { + Self { + name: "eDP-1".to_string(), + width: 1920, + height: 1080, + x: 0, + y: 0, + } + } } #[cfg(target_os = "linux")] #[derive(serde::Deserialize)] struct HyprlandMonitor { - name: String, - width: i32, - height: i32, - x: i32, - y: i32, + name: String, + width: i32, + height: i32, + x: i32, + y: i32, } |
