Compare commits

...

7 commits
0.1.0 ... main

Author SHA1 Message Date
Leonardo Eugênio 39b716cf41
Corrigir rodrigo 2024-05-08 22:07:18 -03:00
lelgenio a4a5c1f49b Update README.md 2024-05-08 00:59:21 -03:00
lelgenio d642e72c48 Add support for loading any image, add some default cursors 2024-05-08 00:56:49 -03:00
Leonardo Eugênio 081f6bed69
Update README.md
Add more items to the todo-list, remove mention of focus limitation.
2024-04-26 14:42:16 -03:00
lelgenio effbf9ebc8 update flake.lock 2024-04-26 14:35:55 -03:00
Leonardo Eugênio 2f8e211f25
Merge pull request #1 from DeltaTimo/main
Don't close the window on mouse over and allow clicking through window.
2024-04-26 14:33:41 -03:00
Timo Zuccarello 1f13ba2b94 Don't close the window on mouse over and allow clicking through window.
This patch sets an empty input region allowing clicking through the
window. It also removes the pointer event handler and no longer removes
the window on mouse over.
2024-04-19 21:25:30 +02:00
10 changed files with 1033 additions and 83 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target
.direnv/
cursors/*~

944
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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):
![image](https://github.com/lelgenio/wl-crosshair/assets/31388299/6e0aaa16-837b-40a8-9a13-ed808ea5db86)
### 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

View file

@ -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": {

View file

@ -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
'';
};
};

View file

@ -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(&region));
}
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 {