halley-wl 0.3.1

Wayland backend and rendering implementation for the Halley Wayland compositor.
use smithay::{
    backend::renderer::{Color32F, Frame},
    input::pointer::{CursorImageStatus, CursorImageSurfaceData},
    reexports::wayland_server::protocol::wl_surface::WlSurface,
    utils::{Physical, Rectangle},
    wayland::compositor::{SurfaceAttributes, get_parent, with_states},
};

use super::cursor_theme::SoftwareCursorSprite;
use super::draw_primitives::draw_rect;

// ---------------------------------------------------------------------------
// Hotspot
// ---------------------------------------------------------------------------

/// Extract the cursor hotspot advertised by a client-side cursor surface.
pub(crate) fn cursor_surface_hotspot(surface: &WlSurface) -> (i32, i32) {
    with_states(surface, |states| {
        states
            .data_map
            .get::<CursorImageSurfaceData>()
            .and_then(|attrs| {
                attrs
                    .lock()
                    .ok()
                    .map(|attr| (attr.hotspot.x, attr.hotspot.y))
            })
            .unwrap_or((0, 0))
    })
}

pub(crate) fn handle_cursor_surface_commit(
    cursor_image: &CursorImageStatus,
    surface: &WlSurface,
) -> bool {
    let CursorImageStatus::Surface(cursor_surface) = cursor_image else {
        return false;
    };
    let root = surface_tree_root(surface);
    if cursor_surface != &root {
        return false;
    }

    if surface == &root {
        with_states(surface, |states| {
            let cursor_image_attributes = states.data_map.get::<CursorImageSurfaceData>();
            if let Some(mut cursor_image_attributes) =
                cursor_image_attributes.map(|attrs| attrs.lock().unwrap())
            {
                let buffer_delta = states
                    .cached_state
                    .get::<SurfaceAttributes>()
                    .current()
                    .buffer_delta
                    .take();
                if let Some(buffer_delta) = buffer_delta {
                    cursor_image_attributes.hotspot -= buffer_delta;
                }
            }
        });
    }

    true
}

fn surface_tree_root(surface: &WlSurface) -> WlSurface {
    let mut root = surface.clone();
    while let Some(parent) = get_parent(&root) {
        root = parent;
    }
    root
}

// ---------------------------------------------------------------------------
// Software sprite rasterisation
// ---------------------------------------------------------------------------

/// Blit a software cursor sprite to `frame` using run-length compressed rows.
///
/// Coordinates are in the same screen-space used for hit-testing so the
/// rendered cursor stays aligned with pointer events on every backend.
pub(crate) fn draw_cursor_sprite<F: Frame>(
    frame: &mut F,
    damage: Rectangle<i32, Physical>,
    cursor_screen: (f32, f32),
    sprite: &SoftwareCursorSprite,
) -> Result<(), F::Error> {
    let (sx, sy) = cursor_screen;
    let x0 = sx.round() as i32 - sprite.hotspot_x;
    let y0 = sy.round() as i32 - sprite.hotspot_y;
    let w = sprite.width;
    let h = sprite.height;

    for y in 0..h {
        let mut x = 0usize;
        while x < w {
            let base = (y * w + x) * 4;
            let a = sprite.pixels_bgra[base + 3];
            if a == 0 {
                x += 1;
                continue;
            }

            let b = sprite.pixels_bgra[base];
            let g = sprite.pixels_bgra[base + 1];
            let r = sprite.pixels_bgra[base + 2];

            // Merge identical neighbouring pixels into a single rect call.
            let mut run_end = x + 1;
            while run_end < w {
                let i = (y * w + run_end) * 4;
                if sprite.pixels_bgra[i] != b
                    || sprite.pixels_bgra[i + 1] != g
                    || sprite.pixels_bgra[i + 2] != r
                    || sprite.pixels_bgra[i + 3] != a
                {
                    break;
                }
                run_end += 1;
            }

            draw_rect(
                frame,
                x0 + x as i32,
                y0 + y as i32,
                (run_end - x) as i32,
                1,
                Color32F::new(
                    r as f32 / 255.0,
                    g as f32 / 255.0,
                    b as f32 / 255.0,
                    a as f32 / 255.0,
                ),
                damage,
            )?;
            x = run_end;
        }
    }
    Ok(())
}