use std::io::Write;
use windows::{core::*, Win32::UI::WindowsAndMessaging::*};
use super::{BaseCursor, Cursor};
use crate::{
graphics::{pixmap_to_buffer, rasterize_svg, u32_to_rgba_u8},
platform::{
mouse::{CursorResolution, LoadCursorError},
Buffer,
},
};
pub fn load_cursor(
#[cfg(feature = "cursor_show_hotspot")] image_data: &mut Buffer,
#[cfg(not(feature = "cursor_show_hotspot"))] image_data: &Buffer,
hotspot_x: u16,
hotspot_y: u16,
) -> core::result::Result<
windows::Win32::UI::WindowsAndMessaging::HCURSOR,
LoadCursorError,
> {
let Ok((file_path, temp_file)) =
create_temp_file(&create_cursor(hotspot_x, hotspot_y, image_data))
else {
return Err(LoadCursorError::UnableToCreateTempfile);
};
let _ = temp_file.keep();
let temp = load_cursor_file(&file_path);
if delete_temp_file(&file_path).is_err() {
return Err(LoadCursorError::UnableToDeleteTempfile);
}
temp.map_or(Err(LoadCursorError::OsError), Ok)
}
#[allow(clippy::needless_pass_by_value)]
pub fn load_base_cursor_with_file(
cursor: BaseCursor,
size: CursorResolution,
svg_data: String,
) -> core::result::Result<Cursor, LoadCursorError> {
let wanted_size: u32 = size.get_size();
let Ok(image_data) =
rasterize_svg(svg_data.as_bytes(), wanted_size, wanted_size)
else {
return Err(LoadCursorError::InvalidImageData(
"Unable to rasterize svg".to_string(),
));
};
match load_cursor(
#[cfg(feature = "cursor_show_hotspot")]
&mut pixmap_to_buffer(&image_data),
#[cfg(not(feature = "cursor_show_hotspot"))]
&pixmap_to_buffer(&image_data),
cursor.hot_spot_x,
cursor.hot_spot_y,
) {
Ok(v) => Ok(Cursor::Win(v)),
Err(e) => Err(e),
}
}
fn load_cursor_file(
file_path: &str,
) -> core::result::Result<
windows::Win32::UI::WindowsAndMessaging::HCURSOR,
&'static str,
> {
unsafe {
let Some(filename) = std::ffi::CString::new(file_path).ok() else {
return Err("Unable to create null-terminated C-style byte string from file path");
};
let Ok(cursor) =
LoadCursorFromFileA(PCSTR(filename.as_ptr().cast::<u8>()))
else {
return Err("why");
};
assert!(cursor.0 != 0, "Failed to load cursor");
Ok(cursor)
}
}
#[must_use]
pub fn create_cursor(
hotspot_x: u16,
hotspot_y: u16,
#[cfg(feature = "cursor_show_hotspot")] image: &mut Buffer,
#[cfg(not(feature = "cursor_show_hotspot"))] image: &Buffer,
) -> Vec<u8> {
let mut cursor_buffer: Vec<u8> = Vec::new();
#[cfg(feature = "cursor_show_hotspot")]
image.set_pixel_safe(
(hotspot_x as usize, hotspot_y as usize),
crate::graphics::colors::RED,
);
let width = image.width as u8;
let height = image.height as u8;
cursor_buffer.extend(&[0x00, 0x00]); cursor_buffer.extend(&[0x02, 0x00]); cursor_buffer.extend(&[0x01, 0x00]);
cursor_buffer.push(width); cursor_buffer.push(height); cursor_buffer.push(0); cursor_buffer.push(0); cursor_buffer.extend(&hotspot_x.to_le_bytes()); cursor_buffer.extend(&hotspot_y.to_le_bytes());
let image_data_offset = 6 + 16;
let row_stride = (u32::from(width) * 32).div_ceil(32) * 4;
let pixel_array_size = row_stride * u32::from(height);
let bmp_header_size = 40;
let and_mask_size = u32::from(height) * (u32::from(width).div_ceil(32) * 4);
let size_in_bytes = bmp_header_size + pixel_array_size + and_mask_size;
cursor_buffer.extend(&(size_in_bytes).to_le_bytes()); cursor_buffer.extend(&(image_data_offset as u32).to_le_bytes());
let mut bmp_data: Vec<u8> = Vec::with_capacity(size_in_bytes as usize);
bmp_data.extend(&(40u32.to_le_bytes())); bmp_data.extend(&i32::from(width).to_le_bytes()); bmp_data.extend(&(2 * i32::from(height)).to_le_bytes()); bmp_data.extend(&(1u16.to_le_bytes())); bmp_data.extend(&(32u16.to_le_bytes())); bmp_data.extend(&(0u32.to_le_bytes())); bmp_data.extend(&(0u32.to_le_bytes())); bmp_data.extend(&(0u32.to_le_bytes())); bmp_data.extend(&(0u32.to_le_bytes())); bmp_data.extend(&(0u32.to_le_bytes())); bmp_data.extend(&(0u32.to_le_bytes()));
for pixel in &image.flip_vertically().data {
let (r, g, b, a) = u32_to_rgba_u8(*pixel);
#[allow(clippy::tuple_array_conversions)]
bmp_data.extend(&[b, g, r, a]);
}
bmp_data.extend(vec![0u8; and_mask_size as usize]);
cursor_buffer.extend(bmp_data);
cursor_buffer
}
pub fn create_temp_file(
cursor_data: &[u8],
) -> std::io::Result<(String, tempfile::NamedTempFile)> {
let mut file = tempfile::NamedTempFile::new()?;
file.write_all(cursor_data)?;
let path_str = file
.path()
.to_str()
.ok_or_else(|| {
std::io::Error::other("Invalid UTF-8 in temp file path")
})?
.to_string();
Ok((path_str, file))
}
pub fn delete_temp_file(path: &str) -> std::io::Result<()> {
std::fs::remove_file(path) }
pub fn set_cursor(cursor: &windows::Win32::UI::WindowsAndMessaging::HCURSOR) {
unsafe {
SetCursor(*cursor);
}
}
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
static mut ORIGINAL_WNDPROC: Option<WNDPROC> = None;
static mut CURRENT_CURSOR: windows::Win32::UI::WindowsAndMessaging::HCURSOR =
windows::Win32::UI::WindowsAndMessaging::HCURSOR(0);
#[must_use]
pub unsafe extern "system" fn wndproc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
if msg == WM_SETCURSOR {
if (lparam.0 as u16) == HTCLIENT as u16 && CURRENT_CURSOR.0 != 0 {
SetCursor(CURRENT_CURSOR);
return LRESULT(1); }
}
ORIGINAL_WNDPROC.map_or_else(
|| DefWindowProcW(hwnd, msg, wparam, lparam),
|orig| CallWindowProcW(orig, hwnd, msg, wparam, lparam),
)
}
#[allow(clippy::fn_to_numeric_cast)]
pub unsafe fn subclass_window(
handle: raw_window_handle::RawWindowHandle,
cursor: &Cursor,
) {
if let raw_window_handle::RawWindowHandle::Win32(hwnd_handle) = handle {
if let Cursor::Win(actual_cursor) = cursor {
let hwnd = windows::Win32::Foundation::HWND(hwnd_handle.hwnd.get());
CURRENT_CURSOR = *actual_cursor;
let orig_proc = GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
ORIGINAL_WNDPROC = Some(std::mem::transmute::<isize, core::option::Option<unsafe extern "system" fn(windows::Win32::Foundation::HWND, u32, windows::Win32::Foundation::WPARAM, windows::Win32::Foundation::LPARAM) -> windows::Win32::Foundation::LRESULT>>(orig_proc));
#[allow(function_casts_as_integer)]
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, wndproc as isize);
}
}
}
pub unsafe fn update_cursor(
cursor: &windows::Win32::UI::WindowsAndMessaging::HCURSOR,
) {
CURRENT_CURSOR = *cursor;
}