summaryrefslogtreecommitdiff
path: root/src/cursor.rs
blob: 57db8bbf9c5cab136061e1414fb6b570fe7a8352 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use std::io;

use wayrs_client::connection::Connection;
use wayrs_client::protocol::wl_compositor::WlCompositor;
use wayrs_client::protocol::wl_pointer::WlPointer;
use wayrs_client::protocol::wl_shm::Format;
use wayrs_client::protocol::wl_surface::WlSurface;

use wayrs_shm_alloc::ShmAlloc;

use xcursor::parser::Image;

use crate::state::State;

#[derive(Debug, thiserror::Error)]
pub enum CursorError {
    #[error("theme not found")]
    ThemeNotFound,
    #[error("theme could not be parsed")]
    ThemeParseError,
    #[error(transparent)]
    ReadError(#[from] io::Error),
}

pub struct Cursor {
    pub surface: WlSurface,
    pub images: Vec<Image>,
}

impl Cursor {
    pub fn new(
        conn: &mut Connection<State>,
        compositor: WlCompositor,
    ) -> Result<Self, CursorError> {
        let theme_name = std::env::var("XCURSOR_THEME").ok();
        let theme_name = theme_name.as_deref().unwrap_or("default");

        let theme_path = xcursor::CursorTheme::load(theme_name)
            .load_icon("default")
            .ok_or(CursorError::ThemeNotFound)?;
        let raw_theme = std::fs::read(theme_path)?;

        let mut images =
            xcursor::parser::parse_xcursor(&raw_theme).ok_or(CursorError::ThemeParseError)?;
        images.sort_unstable_by_key(|img| img.size);

        let surface = compositor.create_surface(conn);

        Ok(Cursor { surface, images })
    }

    pub fn set(
        &self,
        conn: &mut Connection<State>,
        serial: u32,
        pointer: WlPointer,
        scale: u32,
        shm: &mut ShmAlloc,
    ) {
        let target_size = std::env::var("XCURSOR_SIZE")
            .ok()
            .and_then(|x| x.parse().ok())
            .unwrap_or(24)
            * scale;

        let image = match self
            .images
            .binary_search_by_key(&target_size, |img| img.size)
        {
            Ok(indx) => &self.images[indx],
            Err(indx) if indx == 0 => self.images.first().unwrap(),
            Err(indx) if indx >= self.images.len() => self.images.last().unwrap(),
            Err(indx) => {
                let a = &self.images[indx - 1];
                let b = &self.images[indx];
                if target_size - a.size < b.size - target_size {
                    a
                } else {
                    b
                }
            }
        };

        let (buffer, canvas) = shm.alloc_buffer(
            conn,
            image.width as i32,
            image.height as i32,
            (image.width * 4) as i32,
            Format::Argb8888,
        );

        assert_eq!(image.pixels_argb.len(), canvas.len(),);
        canvas.copy_from_slice(&image.pixels_rgba);

        self.surface.attach(conn, buffer, 0, 0);
        self.surface
            .damage_buffer(conn, 0, 0, image.width as i32, image.height as i32);
        self.surface.set_buffer_scale(conn, scale as i32);
        self.surface.commit(conn);

        pointer.set_cursor(
            conn,
            serial,
            self.surface,
            (image.xhot / scale) as i32,
            (image.yhot / scale) as i32,
        );
    }
}