Fork of https://github.com/lelgenio/wl-crosshair - A crosshair overlay for wlroots compositor
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(®ion));
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);