tauri integration
@@ -0,0 +1,3 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "cursor-rush"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.5.2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1.6.0", features = [ "window-start-dragging", "shell-open"] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
image = "0.24"
|
||||
# Ensure "consoleapi" is in this features list
|
||||
winapi = { version = "0.3.9", features = ["winuser", "libloaderapi", "wingdi", "errhandlingapi", "consoleapi"] }
|
||||
once_cell = "1.19.0"
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
# DO NOT REMOVE!!
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
|
After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 407 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 407 B |
@@ -0,0 +1,294 @@
|
||||
use std::sync::Mutex;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use tauri::{Manager, AppHandle};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::env;
|
||||
|
||||
mod platform;
|
||||
|
||||
// Define cursor structure
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Cursor {
|
||||
name: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
// App state to track cursors
|
||||
pub struct AppState {
|
||||
cursors: Vec<Cursor>,
|
||||
current_index: usize,
|
||||
}
|
||||
|
||||
// Thread-safe app state wrapper
|
||||
pub struct AppStateWrapper(Mutex<AppState>);
|
||||
|
||||
// Command to get all cursors
|
||||
#[tauri::command]
|
||||
fn get_cursors(state: tauri::State<AppStateWrapper>) -> Vec<Cursor> {
|
||||
let app_state = state.0.lock().unwrap();
|
||||
app_state.cursors.clone()
|
||||
}
|
||||
|
||||
// Command to get current cursor index
|
||||
#[tauri::command]
|
||||
fn get_current_cursor_index(state: tauri::State<AppStateWrapper>) -> usize {
|
||||
let app_state = state.0.lock().unwrap();
|
||||
app_state.current_index
|
||||
}
|
||||
|
||||
// Command to select a cursor
|
||||
#[tauri::command]
|
||||
fn select_cursor(index: usize, state: tauri::State<AppStateWrapper>, app_handle: AppHandle) -> Result<(), String> {
|
||||
println!("=== RESOURCE DEBUG [select_cursor] ===");
|
||||
|
||||
let mut app_state = state.0.lock().unwrap();
|
||||
if index < app_state.cursors.len() {
|
||||
app_state.current_index = index;
|
||||
|
||||
let cursor = &app_state.cursors[index];
|
||||
println!("Cursor selected: {}", cursor.name);
|
||||
println!("Original path: {}", cursor.path);
|
||||
|
||||
// Get the resource directory
|
||||
let resource_dir = app_handle.path_resolver().resource_dir();
|
||||
println!("Resource dir: {:?}", resource_dir);
|
||||
|
||||
// List contents of resource dir
|
||||
if let Some(res_dir) = &resource_dir {
|
||||
println!("Listing contents of resource dir: {}", res_dir.display());
|
||||
if let Ok(entries) = std::fs::read_dir(res_dir) {
|
||||
for (i, entry) in entries.filter_map(Result::ok).enumerate() {
|
||||
println!(" {}: {}", i, entry.path().display());
|
||||
|
||||
// List subdirectories
|
||||
if entry.path().is_dir() {
|
||||
if let Ok(subentries) = std::fs::read_dir(entry.path()) {
|
||||
for (j, subentry) in subentries.filter_map(Result::ok).enumerate() {
|
||||
println!(" {}.{}: {}", i, j, subentry.path().display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Failed to read resource directory");
|
||||
}
|
||||
}
|
||||
|
||||
let filename = Path::new(&cursor.path)
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.ok_or_else(|| format!("Invalid cursor path: {}", cursor.path))?;
|
||||
|
||||
println!("Extracted filename: {}", filename);
|
||||
|
||||
// Try using the actual file path directly
|
||||
println!("Using direct file path: {}", cursor.path);
|
||||
|
||||
// Verify the file exists
|
||||
if Path::new(&cursor.path).exists() {
|
||||
println!("File exists, applying cursor...");
|
||||
|
||||
if let Err(e) = platform::set_system_cursor(&cursor.path) {
|
||||
println!("Error applying cursor: {}", e);
|
||||
return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e));
|
||||
}
|
||||
|
||||
// Emit an event to notify the frontend
|
||||
let _ = app_handle.emit_all("cursor_changed", index); // Ignore error
|
||||
|
||||
println!("Successfully applied cursor using actual file path");
|
||||
return Ok(());
|
||||
} else {
|
||||
println!("File doesn't exist: {}", cursor.path);
|
||||
return Err(format!("Cursor file does not exist: {}", cursor.path));
|
||||
}
|
||||
} else {
|
||||
Err("Index out of bounds".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// Command to go to the next cursor
|
||||
#[tauri::command]
|
||||
fn next_cursor(state: tauri::State<AppStateWrapper>, app_handle: AppHandle) -> Result<usize, String> {
|
||||
println!("=== RESOURCE DEBUG [next_cursor] ===");
|
||||
|
||||
let mut app_state = state.0.lock().unwrap();
|
||||
if !app_state.cursors.is_empty() {
|
||||
app_state.current_index = (app_state.current_index + 1) % app_state.cursors.len();
|
||||
|
||||
let cursor = &app_state.cursors[app_state.current_index];
|
||||
println!("Next cursor: {} ({})", cursor.name, cursor.path);
|
||||
|
||||
let filename = Path::new(&cursor.path)
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.ok_or_else(|| format!("Invalid cursor path: {}", cursor.path))?;
|
||||
|
||||
// Try both resource paths like in select_cursor
|
||||
println!("Extracted filename: {}", filename);
|
||||
|
||||
// Try using the actual file path directly
|
||||
println!("Using direct file path: {}", cursor.path);
|
||||
|
||||
// Verify the file exists
|
||||
if Path::new(&cursor.path).exists() {
|
||||
println!("File exists, applying cursor...");
|
||||
|
||||
if let Err(e) = platform::set_system_cursor(&cursor.path) {
|
||||
println!("Error applying cursor: {}", e);
|
||||
return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e));
|
||||
}
|
||||
|
||||
// Emit an event to notify the frontend
|
||||
let _ = app_handle.emit_all("cursor_changed", app_state.current_index); // Ignore error
|
||||
|
||||
println!("Successfully applied cursor using actual file path");
|
||||
return Ok(app_state.current_index);
|
||||
} else {
|
||||
println!("File doesn't exist: {}", cursor.path);
|
||||
return Err(format!("Cursor file does not exist: {}", cursor.path));
|
||||
}
|
||||
} else {
|
||||
Err("No cursors available".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// Command to quit the application
|
||||
#[tauri::command]
|
||||
fn quit_app(app_handle: AppHandle) {
|
||||
// Clean up and restore default cursor before exit
|
||||
match platform::restore_system_cursor() {
|
||||
Ok(_) => println!("Default cursor restored"),
|
||||
Err(e) => eprintln!("Failed to restore default cursor: {}", e),
|
||||
}
|
||||
|
||||
// Exit the application
|
||||
std::thread::spawn(move || {
|
||||
app_handle.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to load cursor files from src/cursors directory
|
||||
fn load_cursor_files() -> Vec<Cursor> {
|
||||
println!("=== RESOURCE DEBUG [load_cursor_files] ===");
|
||||
let mut cursors = Vec::new();
|
||||
|
||||
// Determine the path to src/cursors relative to src-tauri (CARGO_MANIFEST_DIR)
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
println!("CARGO_MANIFEST_DIR: {}", manifest_dir.display());
|
||||
|
||||
// Check multiple possible locations
|
||||
let possible_cursor_dirs = vec![
|
||||
manifest_dir.join("../src/cursors"),
|
||||
manifest_dir.join("src/cursors"),
|
||||
manifest_dir.join("cursors"),
|
||||
manifest_dir.clone(), // Check manifest dir itself
|
||||
];
|
||||
|
||||
println!("CHECKING FOR PNG/CUR FILES IN MULTIPLE DIRECTORIES:");
|
||||
for (i, cursors_dir) in possible_cursor_dirs.iter().enumerate() {
|
||||
println!("Checking dir {}: {}", i+1, cursors_dir.display());
|
||||
println!(" Directory exists: {}", cursors_dir.exists());
|
||||
|
||||
if cursors_dir.exists() {
|
||||
println!(" Looking for cursor files in this directory...");
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir(cursors_dir) {
|
||||
for entry in entries.filter_map(Result::ok) {
|
||||
let path = entry.path();
|
||||
println!(" Found: {}", path.display());
|
||||
|
||||
if path.is_file() && path.extension().map_or(false, |ext| ext == "png" || ext == "cur") {
|
||||
let name = path.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("Unknown")
|
||||
.to_string();
|
||||
|
||||
println!(" ADDING CURSOR: {} at path {}", name, path.display());
|
||||
cursors.push(Cursor {
|
||||
name,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !cursors.is_empty() {
|
||||
println!(" Found {} cursor files in this directory.", cursors.len());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found no cursors, use mock data with paths relative to what resolve_resource expects
|
||||
if cursors.is_empty() {
|
||||
println!("No cursors found in any directory. Using mock data instead.");
|
||||
println!("IMPORTANT: This is probably the source of your problem!");
|
||||
println!("The app could not find your cursor PNG files. Check that they exist in one of these directories:");
|
||||
for dir in &possible_cursor_dirs {
|
||||
println!(" {}", dir.display());
|
||||
}
|
||||
|
||||
// Mock data paths should reflect their eventual resource path structure
|
||||
cursors.push(Cursor {
|
||||
name: "Default".to_string(),
|
||||
path: "src/cursors/default.png".to_string(), // Filename will be 'default.png'
|
||||
});
|
||||
|
||||
cursors.push(Cursor {
|
||||
name: "Icon".to_string(),
|
||||
path: "src/cursors/icon.png".to_string(), // Filename will be 'icon.png'
|
||||
});
|
||||
|
||||
cursors.push(Cursor {
|
||||
name: "Red".to_string(),
|
||||
path: "src/cursors/red.png".to_string(), // Filename will be 'red.png'
|
||||
});
|
||||
|
||||
println!("Created {} mock cursor entries", cursors.len());
|
||||
}
|
||||
|
||||
println!("Loaded {} cursor files", cursors.len());
|
||||
|
||||
// Sort cursors by name for consistent ordering
|
||||
cursors.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
cursors
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
println!("Starting Cursor Rush application");
|
||||
|
||||
// Initialize platform-specific cursor handling
|
||||
if let Err(e) = platform::initialize() {
|
||||
eprintln!("Failed to initialize platform: {}", e);
|
||||
}
|
||||
|
||||
// Load cursors from assets directory
|
||||
let cursors = load_cursor_files();
|
||||
|
||||
// Create app state
|
||||
let app_state = AppState {
|
||||
cursors,
|
||||
current_index: 0,
|
||||
};
|
||||
|
||||
// This is one of the simplest ways to initialize Tauri
|
||||
tauri::Builder::default()
|
||||
.manage(AppStateWrapper(Mutex::new(app_state)))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_cursors,
|
||||
get_current_cursor_index,
|
||||
select_cursor,
|
||||
next_cursor,
|
||||
quit_app
|
||||
])
|
||||
.setup(|_app| {
|
||||
// Register for events or perform additional setup
|
||||
println!("Tauri application setup complete");
|
||||
Ok(())
|
||||
})
|
||||
// This matches exactly how it was before our changes
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
cursor_rush::run() // Corrected line
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Linux platform implementation for cursor handling
|
||||
use super::Result;
|
||||
|
||||
// Initialize cursor handling
|
||||
pub fn init() -> Result<()> {
|
||||
println!("Linux cursor initialization not yet implemented");
|
||||
Ok(()) // Return OK for now to not block platform-agnostic code
|
||||
}
|
||||
|
||||
pub fn set_cursor(path: &str) -> Result<()> {
|
||||
// Linux cursor implementation would go here
|
||||
println!("Linux cursor setting not yet implemented for path: {}", path);
|
||||
Ok(()) // Return OK for now to not block platform-agnostic code
|
||||
}
|
||||
|
||||
pub fn restore_default_cursor() -> Result<()> {
|
||||
// Linux cursor restoration would go here
|
||||
println!("Linux cursor restoration not yet implemented");
|
||||
Ok(()) // Return OK for now to not block platform-agnostic code
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// macOS platform implementation for cursor handling
|
||||
use super::Result;
|
||||
|
||||
// Initialize cursor handling
|
||||
pub fn init() -> Result<()> {
|
||||
println!("macOS cursor initialization not yet implemented");
|
||||
Ok(()) // Return OK for now to not block platform-agnostic code
|
||||
}
|
||||
|
||||
pub fn set_cursor(path: &str) -> Result<()> {
|
||||
// macOS cursor implementation would go here
|
||||
println!("macOS cursor setting not yet implemented for path: {}", path);
|
||||
Ok(()) // Return OK for now to not block platform-agnostic code
|
||||
}
|
||||
|
||||
pub fn restore_default_cursor() -> Result<()> {
|
||||
// macOS cursor restoration would go here
|
||||
println!("macOS cursor restoration not yet implemented");
|
||||
Ok(()) // Return OK for now to not block platform-agnostic code
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Platform-specific cursor handling
|
||||
|
||||
// Common types for cursor handling
|
||||
pub mod types;
|
||||
|
||||
// Windows implementation
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
// macOS implementation
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
// Linux implementation
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
|
||||
// Error type for platform operations
|
||||
pub type Result<T> = std::result::Result<T, String>;
|
||||
|
||||
// Platform-agnostic interface for cursor handling
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use self::windows::{set_cursor, restore_default_cursor, init};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use self::macos::{set_cursor, restore_default_cursor, init};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use self::linux::{set_cursor, restore_default_cursor, init};
|
||||
|
||||
// Initialize platform-specific cursor handling
|
||||
pub fn initialize() -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
init()
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-platform implementation for setting cursor
|
||||
pub fn set_system_cursor(path: &str) -> Result<()> {
|
||||
println!("Setting system cursor: {}", path);
|
||||
set_cursor(path)
|
||||
}
|
||||
|
||||
// Cross-platform implementation for restoring default cursor
|
||||
pub fn restore_system_cursor() -> Result<()> {
|
||||
println!("Restoring system cursor");
|
||||
restore_default_cursor()
|
||||
}
|
||||
|
||||
// Default implementations for non-supported platforms
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
pub fn set_cursor(_path: &str) -> Result<()> {
|
||||
Err("Cursor customization not supported on this platform".to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
pub fn restore_default_cursor() -> Result<()> {
|
||||
Err("Cursor restoration not supported on this platform".to_string())
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::path::PathBuf;
|
||||
use image::RgbaImage;
|
||||
|
||||
// Windows-specific cursor handle wrapper
|
||||
#[cfg(target_os = "windows")]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WinCursorWrapper {
|
||||
pub handle: winapi::shared::windef::HCURSOR,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe impl Send for WinCursorWrapper {}
|
||||
|
||||
// Common type for cursor information
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CursorInfo {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub image: Option<RgbaImage>,
|
||||
pub hotspot_x: i32,
|
||||
pub hotspot_y: i32,
|
||||
|
||||
// Platform-specific fields
|
||||
#[cfg(target_os = "windows")]
|
||||
pub win_cursor: Option<WinCursorWrapper>,
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
// Windows platform cursor management
|
||||
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::atomic::Ordering;
|
||||
use winapi::{
|
||||
um::winuser::{
|
||||
GetDC, ReleaseDC, LoadCursorW, SetSystemCursor, CopyIcon,
|
||||
SystemParametersInfoW, SPI_SETCURSORS, SetCursor,
|
||||
MAKEINTRESOURCEW, IDC_ARROW, CreateIconIndirect, ICONINFO,
|
||||
DestroyIcon,
|
||||
},
|
||||
um::wingdi::{
|
||||
CreateCompatibleDC, DeleteDC, SelectObject, DeleteObject,
|
||||
BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, BI_RGB, CreateDIBSection,
|
||||
},
|
||||
um::consoleapi::SetConsoleCtrlHandler,
|
||||
shared::minwindef::{TRUE, FALSE, DWORD},
|
||||
};
|
||||
use ::image::{RgbaImage, open as image_open};
|
||||
|
||||
use super::types::*;
|
||||
use super::state::*;
|
||||
|
||||
// Control handler function for console events (Ctrl+C, close, etc.)
|
||||
extern "system" fn ctrl_handler(_ctrl_type: u32) -> i32 {
|
||||
println!("Control event received, attempting cleanup...");
|
||||
cleanup(); // Call cleanup logic
|
||||
FALSE // Allow other handlers (like default exit) to run
|
||||
}
|
||||
|
||||
// Create a cursor from an RGBA image
|
||||
unsafe fn create_cursor_from_image(image: &RgbaImage) -> Option<SyncHCURSOR> {
|
||||
let (width, height) = image.dimensions();
|
||||
let width_i32 = width as i32;
|
||||
let height_i32 = height as i32;
|
||||
|
||||
// Create device context and bitmap for the color data
|
||||
let screen_dc = GetDC(null_mut());
|
||||
if screen_dc.is_null() {
|
||||
eprintln!("Failed to get screen DC");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create a compatible DC for our bitmap
|
||||
let color_dc = CreateCompatibleDC(screen_dc);
|
||||
if color_dc.is_null() {
|
||||
eprintln!("Failed to create compatible DC for color");
|
||||
ReleaseDC(null_mut(), screen_dc);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create a compatible DC for our mask bitmap
|
||||
let mask_dc = CreateCompatibleDC(screen_dc);
|
||||
if mask_dc.is_null() {
|
||||
eprintln!("Failed to create compatible DC for mask");
|
||||
DeleteDC(color_dc);
|
||||
ReleaseDC(null_mut(), screen_dc);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Set up BITMAPINFO structure for DIB
|
||||
let mut bitmap_info: BITMAPINFO = std::mem::zeroed();
|
||||
bitmap_info.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
|
||||
bitmap_info.bmiHeader.biWidth = width_i32;
|
||||
bitmap_info.bmiHeader.biHeight = -height_i32; // Negative for top-down DIB
|
||||
bitmap_info.bmiHeader.biPlanes = 1;
|
||||
bitmap_info.bmiHeader.biBitCount = 32; // 32 bits per pixel (BGRA)
|
||||
bitmap_info.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
// Create the color DIB section
|
||||
let mut color_bits_ptr: *mut winapi::ctypes::c_void = null_mut();
|
||||
let mut ppv_bits_color: *mut winapi::ctypes::c_void = color_bits_ptr;
|
||||
let color_bitmap = CreateDIBSection(
|
||||
color_dc,
|
||||
&bitmap_info,
|
||||
DIB_RGB_COLORS,
|
||||
&mut ppv_bits_color,
|
||||
null_mut(),
|
||||
0,
|
||||
);
|
||||
color_bits_ptr = ppv_bits_color;
|
||||
|
||||
if color_bitmap.is_null() || color_bits_ptr.is_null() {
|
||||
eprintln!("Failed to create color DIB section");
|
||||
DeleteDC(mask_dc);
|
||||
DeleteDC(color_dc);
|
||||
ReleaseDC(null_mut(), screen_dc);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create the mask DIB section (1-bit monochrome)
|
||||
bitmap_info.bmiHeader.biBitCount = 1; // 1 bit per pixel for mask
|
||||
let mut mask_bits_ptr: *mut winapi::ctypes::c_void = null_mut();
|
||||
let mut ppv_bits: *mut winapi::ctypes::c_void = mask_bits_ptr;
|
||||
let mask_bitmap = CreateDIBSection(
|
||||
mask_dc,
|
||||
&bitmap_info,
|
||||
DIB_RGB_COLORS,
|
||||
&mut ppv_bits,
|
||||
null_mut(),
|
||||
0,
|
||||
);
|
||||
mask_bits_ptr = ppv_bits;
|
||||
|
||||
if mask_bitmap.is_null() || mask_bits_ptr.is_null() {
|
||||
eprintln!("Failed to create mask DIB section");
|
||||
DeleteObject(color_bitmap as _);
|
||||
DeleteDC(mask_dc);
|
||||
DeleteDC(color_dc);
|
||||
ReleaseDC(null_mut(), screen_dc);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Select the bitmaps into the DCs
|
||||
let old_color_bitmap = SelectObject(color_dc, color_bitmap as _);
|
||||
let old_mask_bitmap = SelectObject(mask_dc, mask_bitmap as _);
|
||||
|
||||
if old_color_bitmap.is_null() || old_mask_bitmap.is_null() {
|
||||
eprintln!("Failed to select bitmaps into DCs");
|
||||
DeleteObject(mask_bitmap as _);
|
||||
DeleteObject(color_bitmap as _);
|
||||
DeleteDC(mask_dc);
|
||||
DeleteDC(color_dc);
|
||||
ReleaseDC(null_mut(), screen_dc);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Copy the image data to the color DIB, converting RGBA to BGRA
|
||||
let color_bits = std::slice::from_raw_parts_mut(color_bits_ptr as *mut u8, (width * height * 4) as usize);
|
||||
|
||||
// Calculate mask stride in bytes (1 bit per pixel, padded to DWORD)
|
||||
let mask_stride = ((width + 31) / 32) * 4;
|
||||
let mask_bits = std::slice::from_raw_parts_mut(mask_bits_ptr as *mut u8, (mask_stride * height) as usize);
|
||||
|
||||
// Clear mask bits (all transparent initially)
|
||||
for i in 0..(mask_stride * height) as usize {
|
||||
mask_bits[i] = 0xFF; // All bits set = transparent
|
||||
}
|
||||
|
||||
// Process image pixels
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let pixel = image.get_pixel(x, y);
|
||||
let color_idx = ((y * width + x) * 4) as usize;
|
||||
|
||||
// Get RGBA components
|
||||
let r = pixel[0];
|
||||
let g = pixel[1];
|
||||
let b = pixel[2];
|
||||
let a = pixel[3];
|
||||
|
||||
// Set color data (BGR order)
|
||||
color_bits[color_idx + 0] = b;
|
||||
color_bits[color_idx + 1] = g;
|
||||
color_bits[color_idx + 2] = r;
|
||||
color_bits[color_idx + 3] = a;
|
||||
|
||||
// Update mask bit if pixel is not transparent
|
||||
if a > 0 {
|
||||
// Calculate bit position in mask
|
||||
let byte_idx = (y * mask_stride + (x / 8)) as usize;
|
||||
let bit_idx = 7 - (x % 8); // Bits are stored MSB first
|
||||
|
||||
// Clear the bit (0 = opaque in AND mask)
|
||||
mask_bits[byte_idx] &= !(1 << bit_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create ICONINFO structure
|
||||
let mut icon_info: ICONINFO = std::mem::zeroed();
|
||||
icon_info.fIcon = FALSE; // This is a cursor, not an icon
|
||||
icon_info.xHotspot = width as DWORD / 2; // Center hotspot
|
||||
icon_info.yHotspot = height as DWORD / 2;
|
||||
icon_info.hbmMask = mask_bitmap;
|
||||
icon_info.hbmColor = color_bitmap;
|
||||
|
||||
// Create the cursor
|
||||
let cursor = CreateIconIndirect(&mut icon_info as *mut _);
|
||||
|
||||
// Clean up resources
|
||||
SelectObject(color_dc, old_color_bitmap);
|
||||
SelectObject(mask_dc, old_mask_bitmap);
|
||||
DeleteDC(color_dc);
|
||||
DeleteDC(mask_dc);
|
||||
ReleaseDC(null_mut(), screen_dc);
|
||||
|
||||
// Windows makes a copy of the bitmaps for the cursor, so we can delete them
|
||||
DeleteObject(color_bitmap as _);
|
||||
DeleteObject(mask_bitmap as _);
|
||||
|
||||
if cursor.is_null() {
|
||||
eprintln!("Failed to create cursor");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Return the cursor wrapped in our thread-safe type
|
||||
Some(SyncHCURSOR(cursor))
|
||||
}
|
||||
|
||||
// Cleanup function to restore cursors and clean up resources
|
||||
pub fn cleanup() {
|
||||
println!("Running cursor cleanup...");
|
||||
|
||||
// Restore system cursors if they were hidden
|
||||
// Use swap to ensure it runs only once
|
||||
if CURSOR_HIDDEN.swap(false, Ordering::SeqCst) {
|
||||
println!("Restoring system cursors...");
|
||||
|
||||
unsafe {
|
||||
// Try multiple approaches to ensure cursors are restored
|
||||
|
||||
// 1. First try to restore original cursors if we saved them
|
||||
let original_cursors_opt = ORIGINAL_CURSORS.lock().unwrap().take();
|
||||
if let Some(original_cursors) = original_cursors_opt {
|
||||
println!("Restoring {} saved original cursors", original_cursors.len());
|
||||
for (cursor_id, cursor) in original_cursors {
|
||||
if !cursor.0.is_null() {
|
||||
// Make a copy of the original cursor
|
||||
let cursor_copy = CopyIcon(cursor.0);
|
||||
if !cursor_copy.is_null() {
|
||||
if SetSystemCursor(cursor_copy, cursor_id) == 0 {
|
||||
eprintln!("Failed to restore original cursor for ID: {}", cursor_id);
|
||||
DestroyIcon(cursor_copy);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Failed to copy original cursor for ID: {}", cursor_id);
|
||||
}
|
||||
|
||||
// Destroy the original cursor we saved
|
||||
DestroyIcon(cursor.0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("No saved original cursors found");
|
||||
}
|
||||
|
||||
// 2. Force Windows to reload default cursor settings
|
||||
println!("Forcing Windows to reload default cursor settings");
|
||||
SystemParametersInfoW(SPI_SETCURSORS, 0, null_mut(), 0);
|
||||
|
||||
// 3. Also try the A version for maximum compatibility
|
||||
super::state::reset_system_cursors();
|
||||
|
||||
// 4. Reset to default arrow cursor
|
||||
let arrow_cursor = LoadCursorW(null_mut(), MAKEINTRESOURCEW(IDC_ARROW as u16));
|
||||
if !arrow_cursor.is_null() {
|
||||
SetCursor(arrow_cursor);
|
||||
println!("Reset to default arrow cursor.");
|
||||
}
|
||||
}
|
||||
|
||||
println!("System cursors restoration complete.");
|
||||
}
|
||||
|
||||
// Destroy the custom cursor if it exists
|
||||
let custom_cursor_to_destroy = CUSTOM_CURSOR.lock().unwrap().take();
|
||||
if let Some(cursor_wrapper) = custom_cursor_to_destroy {
|
||||
unsafe {
|
||||
if !cursor_wrapper.0.is_null() {
|
||||
DestroyIcon(cursor_wrapper.0);
|
||||
println!("Custom cursor destroyed during cleanup.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the last known cursor position
|
||||
*LAST_CURSOR_POS.lock().unwrap() = None;
|
||||
|
||||
// Clear the current cursor image
|
||||
*CURRENT_CURSOR_IMAGE.lock().unwrap() = None;
|
||||
|
||||
// Unregister control handler
|
||||
if CTRL_HANDLER_REGISTERED.load(Ordering::SeqCst) {
|
||||
unsafe {
|
||||
if SetConsoleCtrlHandler(Some(ctrl_handler), FALSE) != 0 {
|
||||
CTRL_HANDLER_REGISTERED.store(false, Ordering::SeqCst);
|
||||
println!("Control handler unregistered.");
|
||||
} else {
|
||||
eprintln!("Failed to unregister control handler.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Cursor cleanup complete.");
|
||||
}
|
||||
|
||||
// --- Public API Functions ---
|
||||
|
||||
// Hide the system cursor - now just initializes the control handler
|
||||
pub fn hide_system_cursor() -> bool {
|
||||
// Initialize once
|
||||
INIT.get_or_init(|| {
|
||||
// Register control handler for clean exit
|
||||
unsafe {
|
||||
if SetConsoleCtrlHandler(Some(ctrl_handler), TRUE) != 0 {
|
||||
CTRL_HANDLER_REGISTERED.store(true, Ordering::SeqCst);
|
||||
println!("Control handler registered for clean exit.");
|
||||
} else {
|
||||
eprintln!("Failed to register control handler: {}", std::io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check if already hidden
|
||||
if CURSOR_HIDDEN.load(Ordering::SeqCst) {
|
||||
return true; // Already hidden
|
||||
}
|
||||
|
||||
// Save original cursors for restoration
|
||||
let mut original_cursors = Vec::new();
|
||||
unsafe {
|
||||
for &cursor_id in SYSTEM_CURSORS.iter() {
|
||||
// Get the original cursor
|
||||
let original_cursor = CopyIcon(LoadCursorW(null_mut(), MAKEINTRESOURCEW(cursor_id as u16)));
|
||||
if !original_cursor.is_null() {
|
||||
// Save the original cursor for restoration
|
||||
original_cursors.push((cursor_id, SyncHCURSOR(original_cursor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store original cursors for restoration
|
||||
if !original_cursors.is_empty() {
|
||||
*ORIGINAL_CURSORS.lock().unwrap() = Some(original_cursors);
|
||||
}
|
||||
|
||||
// Mark as hidden - this is now just a flag to indicate we've saved the original cursors
|
||||
CURSOR_HIDDEN.store(true, Ordering::SeqCst);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Update the cursor image using CursorInfo (with caching)
|
||||
pub fn update_cursor_info(cursor_info: &mut crate::platform::types::CursorInfo) -> bool {
|
||||
println!("Updating cursor to: {}", cursor_info.name);
|
||||
|
||||
// Use cached image if available, otherwise load from disk
|
||||
let image: image::RgbaImage = if let Some(ref cached_image) = cursor_info.image {
|
||||
println!("Using cached image for cursor: {}", cursor_info.name);
|
||||
let img_clone: image::RgbaImage = cached_image.clone();
|
||||
img_clone
|
||||
} else {
|
||||
println!("Loading image from disk for cursor: {}", cursor_info.name);
|
||||
match image_open(&cursor_info.path) {
|
||||
Ok(img) => {
|
||||
let rgba = img.to_rgba8();
|
||||
// Cache the image for future use
|
||||
// Make sure to specify the type to avoid type inference issues
|
||||
let image_copy: image::RgbaImage = rgba.clone();
|
||||
cursor_info.image = Some(image_copy);
|
||||
rgba
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load cursor image: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Store the image in the global state for reference
|
||||
*CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(image.clone());
|
||||
|
||||
unsafe {
|
||||
// Create a new cursor first before destroying the old one
|
||||
// This minimizes the time when no custom cursor is active
|
||||
let new_cursor = if let Some(ref cached_image) = cursor_info.image {
|
||||
// Always create a fresh cursor from the image to avoid invalid handle issues
|
||||
create_cursor_from_image(cached_image)
|
||||
} else {
|
||||
create_cursor_from_image(&image)
|
||||
};
|
||||
|
||||
if let Some(new_cursor_wrapper) = new_cursor {
|
||||
// Apply the new cursor to all system cursors first
|
||||
let success = apply_cursor_to_all_system_cursors(new_cursor_wrapper.0);
|
||||
|
||||
// Now that the new cursor is applied, we can clean up the old one
|
||||
let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap();
|
||||
if let Some(old_cursor) = cursor_guard.take() {
|
||||
// Only destroy the old cursor after the new one is applied
|
||||
DestroyIcon(old_cursor.0);
|
||||
}
|
||||
|
||||
// Store the new cursor
|
||||
*cursor_guard = Some(new_cursor_wrapper);
|
||||
|
||||
// Update the cached cursor in CursorInfo
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Convert SyncHCURSOR to WinCursorWrapper for storage in cursor_info
|
||||
cursor_info.win_cursor = Some(crate::platform::types::WinCursorWrapper {
|
||||
handle: new_cursor_wrapper.0
|
||||
});
|
||||
}
|
||||
|
||||
if !success {
|
||||
eprintln!("Failed to apply cursor to all system cursors");
|
||||
// Even if we failed to set all system cursors, try to at least set the current cursor
|
||||
SetCursor(new_cursor_wrapper.0);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
eprintln!("Failed to create cursor from image");
|
||||
|
||||
// If we failed to create a new cursor, try to use the cached one as a fallback
|
||||
#[cfg(windows)]
|
||||
if let Some(cached_cursor) = &cursor_info.win_cursor {
|
||||
if !cached_cursor.handle.is_null() {
|
||||
println!("Falling back to cached cursor as a last resort");
|
||||
let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap();
|
||||
// Convert WinCursorWrapper to SyncHCURSOR
|
||||
let sync_cursor = super::types::SyncHCURSOR(cached_cursor.handle);
|
||||
*cursor_guard = Some(sync_cursor);
|
||||
SetCursor(cached_cursor.handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Apply a cursor to all system cursors
|
||||
unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCURSOR) -> bool {
|
||||
let mut success = true;
|
||||
|
||||
// First, ensure the cursor is valid
|
||||
if cursor.is_null() {
|
||||
eprintln!("Cannot apply null cursor to system cursors");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Immediately set the current cursor to minimize flashing
|
||||
SetCursor(cursor);
|
||||
|
||||
// Apply the cursor to all system cursor types
|
||||
// Start with the most commonly used cursors first
|
||||
let priority_cursors = [
|
||||
32512, // IDC_ARROW (most important)
|
||||
32513, // IDC_IBEAM (text cursor)
|
||||
32649, // IDC_HAND (links)
|
||||
];
|
||||
|
||||
// Apply to priority cursors first
|
||||
for &cursor_id in priority_cursors.iter() {
|
||||
// Make a copy of the cursor
|
||||
let cursor_copy = CopyIcon(cursor);
|
||||
if cursor_copy.is_null() {
|
||||
let error = std::io::Error::last_os_error();
|
||||
eprintln!("Failed to copy cursor for priority ID: {} - Error: {}", cursor_id, error);
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the system cursor
|
||||
if SetSystemCursor(cursor_copy, cursor_id) == 0 {
|
||||
let error = std::io::Error::last_os_error();
|
||||
eprintln!("Failed to set cursor for priority ID: {} - Error: {}", cursor_id, error);
|
||||
DestroyIcon(cursor_copy); // Clean up the copy if we failed to set it
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Now apply to the remaining cursors
|
||||
for &cursor_id in SYSTEM_CURSORS.iter() {
|
||||
// Skip the priority cursors we already processed
|
||||
if priority_cursors.contains(&cursor_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make a copy of the cursor
|
||||
let cursor_copy = CopyIcon(cursor);
|
||||
if cursor_copy.is_null() {
|
||||
// Don't log errors for non-priority cursors to reduce console spam
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the system cursor
|
||||
if SetSystemCursor(cursor_copy, cursor_id) == 0 {
|
||||
// Don't log errors for non-priority cursors to reduce console spam
|
||||
DestroyIcon(cursor_copy); // Clean up the copy if we failed to set it
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we had any failures, set the current cursor again as a fallback
|
||||
if !success {
|
||||
SetCursor(cursor);
|
||||
}
|
||||
|
||||
success
|
||||
}
|
||||
|
||||
// Restore the system cursor
|
||||
pub fn restore_cursor() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// Function to handle mouse movement and update the cursor
|
||||
// Kept for potential future use but currently not needed with SetSystemCursor approach
|
||||
#[allow(dead_code)]
|
||||
pub fn handle_mouse_move() {
|
||||
// With SetSystemCursor, we don't need to constantly update the cursor on mouse move
|
||||
// This function is kept for API compatibility, but it's now a no-op
|
||||
// System cursors are already set to our custom cursor
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Windows platform implementation module
|
||||
|
||||
pub mod types;
|
||||
mod cursor;
|
||||
mod state;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use crate::platform::Result;
|
||||
|
||||
// Functions expected by the platform-agnostic interface
|
||||
pub fn set_cursor(path: &str) -> Result<()> {
|
||||
println!("Windows platform: setting cursor to {}", path);
|
||||
|
||||
// Create a cursor info struct to pass to update_cursor_info
|
||||
let mut cursor_info = crate::platform::types::CursorInfo {
|
||||
name: path.split('\\').last().unwrap_or("unknown").to_string(),
|
||||
path: PathBuf::from(path),
|
||||
image: None,
|
||||
hotspot_x: 0, // Default hotspot at top-left
|
||||
hotspot_y: 0,
|
||||
#[cfg(target_os = "windows")]
|
||||
win_cursor: None,
|
||||
};
|
||||
|
||||
// Use the existing update_cursor_info function to handle loading and applying the cursor
|
||||
if !cursor::update_cursor_info(&mut cursor_info) {
|
||||
return Err(format!("Failed to update cursor from {}", path));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restore_default_cursor() -> Result<()> {
|
||||
println!("Windows platform: restoring default cursor");
|
||||
cursor::restore_cursor();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init() -> Result<()> {
|
||||
println!("Windows platform: initializing cursor system");
|
||||
if !cursor::hide_system_cursor() {
|
||||
return Err("Failed to initialize cursor system".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Windows platform state management
|
||||
|
||||
use std::sync::{Mutex, atomic::AtomicBool};
|
||||
use winapi::{
|
||||
shared::minwindef::DWORD,
|
||||
shared::windef::POINT,
|
||||
};
|
||||
use ::image::RgbaImage;
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
|
||||
use super::types::*;
|
||||
|
||||
// --- Thread-Safe Static Variables ---
|
||||
|
||||
// Use AtomicBool for simple flags
|
||||
pub static CURSOR_HIDDEN: AtomicBool = AtomicBool::new(false);
|
||||
pub static CTRL_HANDLER_REGISTERED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Use Lazy<Mutex<T>> for complex Option types
|
||||
pub static ORIGINAL_CURSORS: Lazy<Mutex<Option<Vec<(DWORD, SyncHCURSOR)>>>> = Lazy::new(|| Mutex::new(None));
|
||||
pub static CURRENT_CURSOR_IMAGE: Lazy<Mutex<Option<RgbaImage>>> = Lazy::new(|| Mutex::new(None));
|
||||
pub static LAST_CURSOR_POS: Lazy<Mutex<Option<POINT>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
// Use Lazy<Mutex<Option<Handle>>> for WinAPI handles with our thread-safe wrappers
|
||||
pub static CUSTOM_CURSOR: Lazy<Mutex<Option<SyncHCURSOR>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
// Note: Legacy variables for window-based cursor approach and mouse hook tracking
|
||||
// have been removed as part of code cleanup
|
||||
|
||||
// Use OnceCell for INIT to avoid `static mut`
|
||||
pub static INIT: OnceCell<()> = OnceCell::new();
|
||||
|
||||
// Reset system cursors using the Windows API
|
||||
pub fn reset_system_cursors() {
|
||||
unsafe {
|
||||
// Use both versions for maximum compatibility
|
||||
winapi::um::winuser::SystemParametersInfoW(
|
||||
winapi::um::winuser::SPI_SETCURSORS,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
0
|
||||
);
|
||||
|
||||
// Import the Windows API function to reset system cursors
|
||||
#[link(name = "user32")]
|
||||
extern "system" {
|
||||
fn SystemParametersInfoA(uiAction: u32, uiParam: u32, pvParam: *mut std::ffi::c_void, fWinIni: u32) -> i32;
|
||||
}
|
||||
|
||||
SystemParametersInfoA(
|
||||
SPI_SETCURSORS_A,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
SPIF_UPDATEINIFILE | SPIF_SENDCHANGE
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Windows platform types and constants
|
||||
|
||||
|
||||
use winapi::{
|
||||
shared::windef::HCURSOR,
|
||||
shared::minwindef::DWORD,
|
||||
};
|
||||
|
||||
// --- Thread-Safe Handle Wrappers ---
|
||||
// These wrappers make raw Windows handles safe to share between threads
|
||||
|
||||
// Wrapper for HCURSOR (cursor handle)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct SyncHCURSOR(pub HCURSOR);
|
||||
unsafe impl Send for SyncHCURSOR {}
|
||||
unsafe impl Sync for SyncHCURSOR {}
|
||||
|
||||
// --- Constants ---
|
||||
|
||||
// Constants for SystemParametersInfo
|
||||
pub const SPI_SETCURSORS_A: u32 = 0x0057;
|
||||
pub const SPIF_UPDATEINIFILE: u32 = 0x01;
|
||||
pub const SPIF_SENDCHANGE: u32 = 0x02;
|
||||
|
||||
// Note: Constants for window-based cursor approach have been removed as part of code cleanup
|
||||
|
||||
// Define all system cursor IDs we'll replace
|
||||
pub static SYSTEM_CURSORS: [DWORD; 11] = [
|
||||
32512, // IDC_ARROW
|
||||
32513, // IDC_IBEAM
|
||||
32515, // IDC_CROSS
|
||||
32514, // IDC_WAIT
|
||||
32649, // IDC_HAND
|
||||
32650, // IDC_APPSTARTING
|
||||
32648, // IDC_NO
|
||||
32646, // IDC_SIZEALL
|
||||
32645, // IDC_SIZENS
|
||||
32644, // IDC_SIZEWE
|
||||
32642, // IDC_SIZENWSE
|
||||
];
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"devPath": "http://localhost:5173",
|
||||
"distDir": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "cursor-rush",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"window": {
|
||||
"all": false,
|
||||
"startDragging": true
|
||||
},
|
||||
"shell": {
|
||||
"all": false,
|
||||
"open": true
|
||||
},
|
||||
"fs": {
|
||||
"all": false,
|
||||
"scope": [
|
||||
"$APP/cursors/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"identifier": "com.cursor-rush.dev",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"targets": "all"
|
||||
},
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "Cursor Rush",
|
||||
"width": 310,
|
||||
"height": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"decorations": false,
|
||||
"transparent": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||