aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2025-10-18 19:32:34 +0300
committerPetri Hienonen <petri.hienonen@gmail.com>2025-10-18 19:32:34 +0300
commit2fb4913277734dccab54479e5b65c6031de6e9ab (patch)
tree1b497f1156f7a2d568f60723e0e22d979f07ef72
parent60505314d8bc86b33ba84bdfccdb87bf5dd7f9af (diff)
downloadwallpaper-2fb4913277734dccab54479e5b65c6031de6e9ab.tar.zst
Use tabs
-rw-r--r--.rustfmt.toml3
-rw-r--r--src/main.rs710
-rw-r--r--src/wayland.rs282
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,
}