mirror of
https://github.com/lelgenio/wl-crosshair.git
synced 2025-03-14 12:14:13 -03:00
Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
|
39b716cf41 | ||
|
a4a5c1f49b | ||
|
d642e72c48 | ||
|
081f6bed69 | ||
|
effbf9ebc8 | ||
|
2f8e211f25 | ||
|
1f13ba2b94 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
.direnv/
|
||||
cursors/*~
|
||||
|
|
944
Cargo.lock
generated
944
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -10,3 +10,4 @@ wayland-protocols-wlr = { version = "0.1.0", features = ["client"] }
|
|||
|
||||
log = { version = "0.4", optional = true }
|
||||
tempfile = "3.2"
|
||||
image = "0.25.1"
|
||||
|
|
14
README.md
14
README.md
|
@ -3,10 +3,16 @@ A crosshair overlay for wlroots compositors.
|
|||
|
||||
A extremely stripped down version of [crossover](https://github.com/lacymorrow/crossover).
|
||||
|
||||
Currently has no support for command line arguments or any customization.
|
||||
```sh
|
||||
wl-crosshair ./my-crosshair.png
|
||||
```
|
||||
|
||||
### Preview:
|
||||
### Preview (default cursor):
|
||||

|
||||
|
||||
### Why is it flickering when I put my cursor over it?
|
||||
In wayland, windows cannot be "click-through", so in order to still send events we "close" the window when you hover it and show it in the next frame.
|
||||
## TODO
|
||||
- [x] Make the crosshair Click-through https://github.com/lelgenio/wl-crosshair/pull/1
|
||||
- [ ] Option to control size of crosshair
|
||||
- [ ] Option to offset crosshair
|
||||
- [ ] Configuration file
|
||||
- [x] Support for loading custom crosshair images
|
||||
|
|
BIN
cursors/inverse-v.png
Normal file
BIN
cursors/inverse-v.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
BIN
cursors/test-colors.png
Normal file
BIN
cursors/test-colors.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
BIN
cursors/wojak-cursor.png
Normal file
BIN
cursors/wojak-cursor.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 204 KiB |
12
flake.lock
12
flake.lock
|
@ -5,11 +5,11 @@
|
|||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1684242266,
|
||||
"narHash": "sha256-uaCQ2k1bmojHKjWQngvnnnxQJMY8zi1zq527HdWgQf8=",
|
||||
"lastModified": 1714091391,
|
||||
"narHash": "sha256-68n3GBvlm1MIeJXadPzQ3v8Y9sIW3zmv8gI5w5sliC8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7e0743a5aea1dc755d4b761daf75b20aa486fdad",
|
||||
"rev": "4c86138ce486d601d956a165e2f7a0fc029a03c1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -19,6 +19,13 @@
|
|||
version = "0.1.0";
|
||||
src = ./.;
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
nativeBuildInputs = with pkgs; [ makeWrapper ];
|
||||
postInstall = ''
|
||||
mkdir -p $out/share
|
||||
cp -rv ${./cursors} $out/share/cursors
|
||||
wrapProgram $out/bin/* \
|
||||
--set WL_CROSSHAIR_IMAGE_PATH $out/share/cursors/inverse-v.png
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
|
|
137
src/main.rs
137
src/main.rs
|
@ -1,8 +1,9 @@
|
|||
use std::{fs::File, io::Write, os::unix::prelude::AsRawFd};
|
||||
|
||||
use image::{GenericImageView, Pixel};
|
||||
use wayland_client::{
|
||||
protocol::{
|
||||
wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm,
|
||||
wl_buffer, wl_compositor, wl_keyboard, wl_region::WlRegion, wl_registry, wl_seat, wl_shm,
|
||||
wl_shm_pool, wl_surface,
|
||||
},
|
||||
Connection, Dispatch, Proxy, QueueHandle,
|
||||
|
@ -18,7 +19,9 @@ use wayland_protocols::xdg::shell::client::xdg_wm_base;
|
|||
struct State {
|
||||
running: bool,
|
||||
|
||||
cursor_size: u32,
|
||||
cursor_width: u32,
|
||||
cursor_height: u32,
|
||||
image_path: String,
|
||||
|
||||
compositor: Option<wl_compositor::WlCompositor>,
|
||||
base_surface: Option<wl_surface::WlSurface>,
|
||||
|
@ -26,7 +29,30 @@ struct State {
|
|||
layer_surface: Option<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>,
|
||||
buffer: Option<wl_buffer::WlBuffer>,
|
||||
wm_base: Option<xdg_wm_base::XdgWmBase>,
|
||||
pointer: Option<wl_pointer::WlPointer>,
|
||||
}
|
||||
|
||||
fn get_cursor_image_path() -> String {
|
||||
if let Some(p) = std::env::args().skip(1).next() {
|
||||
return p;
|
||||
}
|
||||
|
||||
if let Ok(p) = std::env::var("WL_CROSSHAIR_IMAGE_PATH") {
|
||||
return p;
|
||||
}
|
||||
|
||||
[
|
||||
std::option_env!("WL_CROSSHAIR_IMAGE_PATH").map(String::from),
|
||||
Some("cursors/inverse-v.png".to_string()),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|p|
|
||||
std::fs::metadata(p)
|
||||
.map(|m| m.is_file())
|
||||
.unwrap_or(false)
|
||||
)
|
||||
.next()
|
||||
.expect("Could not find a crosshair image, pass it as a cli argument or set WL_CROSSHAIR_IMAGE_PATH environment variable")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -40,14 +66,15 @@ fn main() {
|
|||
|
||||
let mut state = State {
|
||||
running: true,
|
||||
cursor_size: 10,
|
||||
cursor_width: 10,
|
||||
cursor_height: 10,
|
||||
image_path: get_cursor_image_path(),
|
||||
compositor: None,
|
||||
base_surface: None,
|
||||
layer_shell: None,
|
||||
layer_surface: None,
|
||||
buffer: None,
|
||||
wm_base: None,
|
||||
pointer: None,
|
||||
};
|
||||
|
||||
event_queue.blocking_dispatch(&mut state).unwrap();
|
||||
|
@ -94,10 +121,11 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
|||
} else if interface == wl_shm::WlShm::interface().name {
|
||||
let shm = registry.bind::<wl_shm::WlShm, _, _>(name, version, qh, ());
|
||||
|
||||
let (init_w, init_h) = (state.cursor_size, state.cursor_size);
|
||||
|
||||
let mut file = tempfile::tempfile().unwrap();
|
||||
draw(&mut file, (init_w, init_h));
|
||||
state.draw(&mut file);
|
||||
|
||||
let (init_w, init_h) = (state.cursor_width, state.cursor_height);
|
||||
|
||||
let pool = shm.create_pool(file.as_raw_fd(), (init_w * init_h * 4) as i32, qh, ());
|
||||
let buffer = pool.create_buffer(
|
||||
0,
|
||||
|
@ -109,9 +137,6 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
|||
(),
|
||||
);
|
||||
state.buffer = Some(buffer);
|
||||
} else if interface == wl_seat::WlSeat::interface().name {
|
||||
let seat = registry.bind::<wl_seat::WlSeat, _, _>(name, version, qh, ());
|
||||
state.pointer = Some(seat.get_pointer(qh, ()));
|
||||
} else if interface == xdg_wm_base::XdgWmBase::interface().name {
|
||||
let wm_base = registry.bind::<xdg_wm_base::XdgWmBase, _, _>(name, 1, qh, ());
|
||||
state.wm_base = Some(wm_base);
|
||||
|
@ -120,66 +145,18 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
|||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
impl Dispatch<WlRegion, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &wl_pointer::WlPointer,
|
||||
event: wl_pointer::Event,
|
||||
_: &mut Self,
|
||||
_: &WlRegion,
|
||||
_: <WlRegion as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
eprintln!("WlPointer event {event:#?}");
|
||||
match event {
|
||||
wl_pointer::Event::Enter { .. } => {
|
||||
if let Some(surface) = &state.base_surface {
|
||||
surface.destroy();
|
||||
}
|
||||
state.base_surface = None;
|
||||
}
|
||||
wl_pointer::Event::Leave { .. } => {
|
||||
let surface = state.compositor.as_ref().unwrap().create_surface(qh, ());
|
||||
state.base_surface = Some(surface);
|
||||
|
||||
state.init_layer_surface(qh);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(tmp: &mut File, (buf_x, buf_y): (u32, u32)) {
|
||||
let mut buf = std::io::BufWriter::new(tmp);
|
||||
for y in 0..buf_y {
|
||||
for x in 0..buf_x {
|
||||
let ix = x as i32;
|
||||
let iy = y as i32;
|
||||
|
||||
let dist = if x <= (buf_x / 2) {
|
||||
ix + iy - (buf_y as i32)
|
||||
} else {
|
||||
iy - ix
|
||||
};
|
||||
|
||||
let a: u32 = match dist.abs() {
|
||||
0 => 0xFF,
|
||||
1 => 0x88,
|
||||
_ => 0x00,
|
||||
};
|
||||
|
||||
let c: u32 = match dist.abs() {
|
||||
0 => 0xFF,
|
||||
1 => 0x88,
|
||||
_ => 0x00,
|
||||
};
|
||||
|
||||
let color = (a << 24) + (c << 16) + (c << 8) + c;
|
||||
buf.write_all(&color.to_ne_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
buf.flush().unwrap();
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn init_layer_surface(&mut self, qh: &QueueHandle<State>) {
|
||||
let layer = self.layer_shell.as_ref().unwrap().get_layer_surface(
|
||||
|
@ -193,14 +170,44 @@ impl State {
|
|||
// Center the window
|
||||
layer.set_anchor(Anchor::Top | Anchor::Right | Anchor::Bottom | Anchor::Left);
|
||||
layer.set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::None);
|
||||
layer.set_size(self.cursor_size, self.cursor_size);
|
||||
layer.set_size(self.cursor_width, self.cursor_height);
|
||||
// A negative value means we will be centered on the screen
|
||||
// independently of any other xdg_layer_shell
|
||||
layer.set_exclusive_zone(-1);
|
||||
// Set empty input region to allow clicking through the window.
|
||||
if let Some(compositor) = &self.compositor {
|
||||
let region = compositor.create_region(qh, ());
|
||||
self.base_surface
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.set_input_region(Some(®ion));
|
||||
}
|
||||
self.base_surface.as_ref().unwrap().commit();
|
||||
|
||||
self.layer_surface = Some(layer);
|
||||
}
|
||||
|
||||
fn draw(&mut self, tmp: &mut File) {
|
||||
let mut buf = std::io::BufWriter::new(tmp);
|
||||
|
||||
let i = image::open(&self.image_path).unwrap();
|
||||
|
||||
self.cursor_width = i.width();
|
||||
self.cursor_height = i.height();
|
||||
|
||||
for y in 0..self.cursor_height {
|
||||
for x in 0..self.cursor_width {
|
||||
let px = i.get_pixel(x, y).to_rgba();
|
||||
|
||||
let [r, g, b, a] = px.channels().try_into().unwrap();
|
||||
|
||||
let color = u32::from_be_bytes([a, r, g, b]);
|
||||
|
||||
buf.write_all(&color.to_le_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
buf.flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ()> for State {
|
||||
|
|
Loading…
Reference in a new issue