Fork of https://github.com/lelgenio/wl-crosshair - A crosshair overlay for wlroots compositor
at main 8.3 kB view raw
1use std::{fs::File, io::Write, os::unix::prelude::AsRawFd}; 2 3use image::{GenericImageView, Pixel}; 4use wayland_client::{ 5 protocol::{ 6 wl_buffer, wl_compositor, wl_keyboard, wl_region::WlRegion, wl_registry, wl_seat, wl_shm, 7 wl_shm_pool, wl_surface, 8 }, 9 Connection, Dispatch, Proxy, QueueHandle, 10}; 11 12use wayland_protocols_wlr::layer_shell::v1::client::{ 13 zwlr_layer_shell_v1::{self, Layer}, 14 zwlr_layer_surface_v1::{self, KeyboardInteractivity}, 15}; 16 17use wayland_protocols::xdg::shell::client::xdg_wm_base; 18 19struct State { 20 running: bool, 21 22 cursor_width: u32, 23 cursor_height: u32, 24 image_path: String, 25 26 compositor: Option<wl_compositor::WlCompositor>, 27 base_surface: Option<wl_surface::WlSurface>, 28 layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>, 29 layer_surface: Option<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>, 30 buffer: Option<wl_buffer::WlBuffer>, 31 wm_base: Option<xdg_wm_base::XdgWmBase>, 32} 33 34fn get_cursor_image_path() -> String { 35 if let Some(p) = std::env::args().skip(1).next() { 36 return p; 37 } 38 39 if let Ok(p) = std::env::var("WL_CROSSHAIR_IMAGE_PATH") { 40 return p; 41 } 42 43 [ 44 std::option_env!("WL_CROSSHAIR_IMAGE_PATH").map(String::from), 45 Some("cursors/inverse-v.png".to_string()), 46 ] 47 .into_iter() 48 .flatten() 49 .filter(|p| 50 std::fs::metadata(p) 51 .map(|m| m.is_file()) 52 .unwrap_or(false) 53 ) 54 .next() 55 .expect("Could not find a crosshair image, pass it as a cli argument or set WL_CROSSHAIR_IMAGE_PATH environment variable") 56} 57 58fn main() { 59 let conn = Connection::connect_to_env().unwrap(); 60 61 let mut event_queue = conn.new_event_queue(); 62 let qhandle = event_queue.handle(); 63 64 let display = conn.display(); 65 display.get_registry(&qhandle, ()); 66 67 let mut state = State { 68 running: true, 69 cursor_width: 10, 70 cursor_height: 10, 71 image_path: get_cursor_image_path(), 72 compositor: None, 73 base_surface: None, 74 layer_shell: None, 75 layer_surface: None, 76 buffer: None, 77 wm_base: None, 78 }; 79 80 event_queue.blocking_dispatch(&mut state).unwrap(); 81 82 if state.layer_shell.is_some() && state.wm_base.is_some() { 83 state.init_layer_surface(&qhandle); 84 } 85 86 while state.running { 87 event_queue.blocking_dispatch(&mut state).unwrap(); 88 } 89} 90 91impl Dispatch<wl_registry::WlRegistry, ()> for State { 92 fn event( 93 state: &mut Self, 94 registry: &wl_registry::WlRegistry, 95 event: wl_registry::Event, 96 _: &(), 97 _: &Connection, 98 qh: &QueueHandle<Self>, 99 ) { 100 eprintln!("WlRegistry event {event:#?}"); 101 if let wl_registry::Event::Global { 102 name, 103 interface, 104 version, 105 } = event 106 { 107 if interface == zwlr_layer_shell_v1::ZwlrLayerShellV1::interface().name { 108 let wl_layer = registry.bind::<zwlr_layer_shell_v1::ZwlrLayerShellV1, _, _>( 109 name, 110 version, 111 qh, 112 (), 113 ); 114 state.layer_shell = Some(wl_layer); 115 } else if interface == wl_compositor::WlCompositor::interface().name { 116 let compositor = 117 registry.bind::<wl_compositor::WlCompositor, _, _>(name, version, qh, ()); 118 let surface = compositor.create_surface(qh, ()); 119 state.base_surface = Some(surface); 120 state.compositor = Some(compositor); 121 } else if interface == wl_shm::WlShm::interface().name { 122 let shm = registry.bind::<wl_shm::WlShm, _, _>(name, version, qh, ()); 123 124 let mut file = tempfile::tempfile().unwrap(); 125 state.draw(&mut file); 126 127 let (init_w, init_h) = (state.cursor_width, state.cursor_height); 128 129 let pool = shm.create_pool(file.as_raw_fd(), (init_w * init_h * 4) as i32, qh, ()); 130 let buffer = pool.create_buffer( 131 0, 132 init_w as i32, 133 init_h as i32, 134 (init_w * 4) as i32, 135 wl_shm::Format::Argb8888, 136 qh, 137 (), 138 ); 139 state.buffer = Some(buffer); 140 } else if interface == xdg_wm_base::XdgWmBase::interface().name { 141 let wm_base = registry.bind::<xdg_wm_base::XdgWmBase, _, _>(name, 1, qh, ()); 142 state.wm_base = Some(wm_base); 143 } 144 } 145 } 146} 147 148impl Dispatch<WlRegion, ()> for State { 149 fn event( 150 _: &mut Self, 151 _: &WlRegion, 152 _: <WlRegion as Proxy>::Event, 153 _: &(), 154 _: &Connection, 155 _: &QueueHandle<Self>, 156 ) { 157 } 158} 159 160impl State { 161 fn init_layer_surface(&mut self, qh: &QueueHandle<State>) { 162 let layer = self.layer_shell.as_ref().unwrap().get_layer_surface( 163 self.base_surface.as_ref().unwrap(), 164 None, 165 Layer::Overlay, 166 "crosshair".to_string(), 167 qh, 168 (), 169 ); 170 // Center the window - remove anchors to allow free positioning 171 // layer.set_anchor(Anchor::empty()); // No anchors for centering 172 layer.set_keyboard_interactivity(KeyboardInteractivity::None); 173 layer.set_size(self.cursor_width, self.cursor_height); 174 // A negative value means we will be centered on the screen 175 // independently of any other xdg_layer_shell 176 layer.set_exclusive_zone(-1); 177 // Set empty input region to allow clicking through the window. 178 if let Some(compositor) = &self.compositor { 179 let region = compositor.create_region(qh, ()); 180 self.base_surface 181 .as_ref() 182 .unwrap() 183 .set_input_region(Some(&region)); 184 } 185 self.base_surface.as_ref().unwrap().commit(); 186 187 self.layer_surface = Some(layer); 188 } 189 190 fn draw(&mut self, tmp: &mut File) { 191 let mut buf = std::io::BufWriter::new(tmp); 192 193 let i = image::open(&self.image_path).unwrap(); 194 195 self.cursor_width = i.width(); 196 self.cursor_height = i.height(); 197 198 for y in 0..self.cursor_height { 199 for x in 0..self.cursor_width { 200 let px = i.get_pixel(x, y).to_rgba(); 201 202 let [r, g, b, a] = px.channels().try_into().unwrap(); 203 204 let color = u32::from_be_bytes([a, r, g, b]); 205 206 buf.write_all(&color.to_le_bytes()).unwrap(); 207 } 208 } 209 buf.flush().unwrap(); 210 } 211} 212 213impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ()> for State { 214 fn event( 215 state: &mut Self, 216 surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, 217 event: <zwlr_layer_surface_v1::ZwlrLayerSurfaceV1 as Proxy>::Event, 218 _data: &(), 219 _conn: &Connection, 220 _qhandle: &QueueHandle<Self>, 221 ) { 222 eprintln!("ZwlrLayerSurfaceV1 event {event:#?}"); 223 if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event { 224 surface.ack_configure(serial); 225 if let (Some(surface), Some(buffer)) = (&state.base_surface, &state.buffer) { 226 surface.attach(Some(buffer), 0, 0); 227 surface.commit(); 228 } 229 } 230 } 231} 232 233// ignored 234 235macro_rules! impl_dispatch_log { 236 ($DispatchStruct: path) => { 237 impl Dispatch<$DispatchStruct, ()> for State { 238 fn event( 239 _: &mut Self, 240 _: &$DispatchStruct, 241 event: <$DispatchStruct as Proxy>::Event, 242 _: &(), 243 _: &Connection, 244 _: &QueueHandle<Self>, 245 ) { 246 eprintln!("{} event {:#?}", stringify!($DispatchStruct), event); 247 } 248 } 249 }; 250} 251 252impl_dispatch_log!(wl_buffer::WlBuffer); 253impl_dispatch_log!(wl_compositor::WlCompositor); 254impl_dispatch_log!(wl_keyboard::WlKeyboard); 255impl_dispatch_log!(wl_seat::WlSeat); 256impl_dispatch_log!(wl_shm_pool::WlShmPool); 257impl_dispatch_log!(wl_shm::WlShm); 258impl_dispatch_log!(wl_surface::WlSurface); 259impl_dispatch_log!(xdg_wm_base::XdgWmBase); 260impl_dispatch_log!(zwlr_layer_shell_v1::ZwlrLayerShellV1);