this definitely needed to be over 1k lines of code for our initial push
This commit is contained in:
+18
@@ -4,3 +4,21 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
iced = { version = "0.13.1", default-features = true, features = ["image", "advanced"] } # GUI, bundles wgpu + winit
|
||||
tray-icon = "0.20.1" # cross‑platform system tray
|
||||
image = "0.24.7" # Image processing
|
||||
walkdir = "2.4.0" # Directory traversal
|
||||
global-hotkey = "0.4.2" # Global hotkey registration
|
||||
lazy_static = "1.4.0" # Thread-safe static initialization
|
||||
|
||||
# Platform-specific dependencies
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["winuser", "wingdi", "consoleapi"] } # Windows API
|
||||
ctor = "0.2.4" # Constructor/destructor attributes
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2.7" # Objective-C bindings
|
||||
cocoa = "0.24.1" # macOS Cocoa bindings
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
x11 = { version = "2.21.0", features = ["xlib"] } # X11 bindings
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 184 B |
Binary file not shown.
|
After Width: | Height: | Size: 407 B |
+524
-2
@@ -1,3 +1,525 @@
|
||||
fn main() {
|
||||
println!("Hello, Cursor Rush!");
|
||||
use iced::{
|
||||
widget::{button, text, Column, Row, container, image as iced_image},
|
||||
Element, Length, Subscription, Task, Theme,
|
||||
};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
use ::image::{GenericImageView, RgbaImage, open as image_open};
|
||||
use walkdir::WalkDir;
|
||||
use tray_icon::{
|
||||
TrayIconBuilder,
|
||||
menu::{Menu, MenuEvent},
|
||||
};
|
||||
use global_hotkey::{
|
||||
GlobalHotKeyManager,
|
||||
hotkey::HotKey as GlobalHotKey,
|
||||
HotKeyState,
|
||||
};
|
||||
|
||||
// Import our platform-specific module
|
||||
mod platform;
|
||||
|
||||
// Define a struct to hold cursor information
|
||||
#[derive(Clone)]
|
||||
struct CursorInfo {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
// We'll use a simpler approach for the cursor overlay
|
||||
|
||||
// Define menu item IDs
|
||||
const NEXT_CURSOR_ID: &str = "next_cursor";
|
||||
const QUIT_ID: &str = "quit";
|
||||
|
||||
// Shared state between the UI and the tray icon
|
||||
struct SharedState {
|
||||
current_index: usize,
|
||||
cursors: Vec<CursorInfo>,
|
||||
current_cursor_image: Option<RgbaImage>,
|
||||
running: bool,
|
||||
}
|
||||
|
||||
// Our application state
|
||||
#[derive(Default)]
|
||||
struct CursorRush {
|
||||
cursors: Vec<CursorInfo>,
|
||||
current_index: usize,
|
||||
shared_state: Option<Arc<Mutex<SharedState>>>,
|
||||
cursor_position: (f32, f32), // Track the mouse position
|
||||
}
|
||||
|
||||
// Messages that can be sent in our application
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
NextCursor,
|
||||
SelectCursor(usize),
|
||||
Quit,
|
||||
TrayEvent,
|
||||
CursorMoved((f32, f32)), // New message for cursor movement
|
||||
}
|
||||
|
||||
// Function to load all PNG files from the assets directory
|
||||
fn load_cursor_files() -> Vec<CursorInfo> {
|
||||
let mut cursors = Vec::new();
|
||||
|
||||
println!("Looking for cursor PNG files in assets directory...");
|
||||
|
||||
// Check if assets directory exists
|
||||
if !std::path::Path::new("assets").exists() {
|
||||
println!("Assets directory not found!");
|
||||
return cursors;
|
||||
}
|
||||
|
||||
for entry in WalkDir::new("assets").into_iter().filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
println!("Found file: {}", path.display());
|
||||
if path.is_file() && path.extension().map_or(false, |ext| ext == "png") {
|
||||
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(CursorInfo {
|
||||
name,
|
||||
path: path.to_path_buf(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
println!("Found {} cursor PNG files", cursors.len());
|
||||
|
||||
// Sort cursors by name for consistent ordering
|
||||
cursors.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
cursors
|
||||
}
|
||||
|
||||
// Function to validate a cursor image
|
||||
fn validate_cursor_image(path: &Path) -> bool {
|
||||
// Load the image to validate it
|
||||
match image_open(path) {
|
||||
Ok(img) => {
|
||||
let (width, height) = img.dimensions();
|
||||
|
||||
// Ensure reasonable dimensions
|
||||
if width > 128 || height > 128 || width == 0 || height == 0 {
|
||||
eprintln!("Image dimensions not suitable for cursor: {}x{}", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Failed to open image {}: {}", path.display(), e);
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the cursor at the given path
|
||||
fn apply_cursor(path: &Path) -> bool {
|
||||
// Validate the cursor image
|
||||
if validate_cursor_image(path) {
|
||||
// Use platform-specific cursor setting
|
||||
if platform::set_cursor(path) {
|
||||
println!("Successfully set cursor");
|
||||
return true;
|
||||
} else {
|
||||
eprintln!("Failed to set cursor");
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// Top-level subscription function to listen for events
|
||||
fn subscription(_state: &CursorRush) -> Subscription<Message> {
|
||||
iced::event::listen().map(|event| {
|
||||
if let iced::Event::Mouse(iced::mouse::Event::CursorMoved { position }) = event {
|
||||
// Track cursor position for UI display purposes
|
||||
Message::CursorMoved((position.x, position.y))
|
||||
} else {
|
||||
// We need to return some message, so we'll use TrayEvent as a no-op
|
||||
Message::TrayEvent
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Boot function to initialize the application
|
||||
fn boot() -> (CursorRush, Task<Message>) {
|
||||
// Load cursor files from assets directory
|
||||
let cursors = load_cursor_files();
|
||||
|
||||
if cursors.is_empty() {
|
||||
eprintln!("No cursor PNG files found in assets directory!");
|
||||
return (CursorRush::default(), Task::none());
|
||||
}
|
||||
|
||||
// Load the first cursor image
|
||||
let current_cursor_image = if let Some(cursor) = cursors.first() {
|
||||
match image_open(&cursor.path) {
|
||||
Ok(img) => Some(img.to_rgba8()),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Apply the first cursor by default (this will hide the system cursor and start the render thread)
|
||||
if let Some(cursor) = cursors.first() {
|
||||
apply_cursor(&cursor.path);
|
||||
// Explicitly call render_cursor to ensure the render thread starts
|
||||
platform::render_cursor();
|
||||
}
|
||||
|
||||
// Create shared state
|
||||
let shared_state = Arc::new(Mutex::new(SharedState {
|
||||
current_index: 0,
|
||||
cursors: cursors.clone(),
|
||||
current_cursor_image,
|
||||
running: true,
|
||||
}));
|
||||
|
||||
// Create a channel to communicate with the UI
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// Set up the tray icon
|
||||
setup_tray_icon(&cursors, shared_state.clone(), tx.clone());
|
||||
|
||||
// Initialize the application state with loaded cursors
|
||||
let mut state = CursorRush::default();
|
||||
state.cursors = cursors;
|
||||
state.shared_state = Some(shared_state.clone());
|
||||
|
||||
// Set up global hotkey manager
|
||||
let hotkey_manager = GlobalHotKeyManager::new().expect("Failed to create hotkey manager");
|
||||
|
||||
// Register Ctrl+Shift+F12 as the exit hotkey (uncommon but easy to remember)
|
||||
use global_hotkey::hotkey::{Code, Modifiers};
|
||||
let hotkey = GlobalHotKey::new(
|
||||
Some(Modifiers::CONTROL | Modifiers::SHIFT),
|
||||
Code::F12,
|
||||
);
|
||||
|
||||
if let Err(e) = hotkey_manager.register(hotkey) {
|
||||
eprintln!("Failed to register hotkey: {}", e);
|
||||
} else {
|
||||
println!("Press Ctrl+Shift+F12 to exit the application");
|
||||
}
|
||||
|
||||
// We're using the event-driven approach with the subscription function for mouse tracking
|
||||
|
||||
// Spawn a thread to listen for tray events
|
||||
let tx_clone = tx.clone();
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
if let Ok(_) = rx.recv() {
|
||||
// Send a message to the UI
|
||||
let _ = tx_clone.send(Message::TrayEvent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Spawn a thread to listen for hotkeys
|
||||
let state_clone = shared_state.clone();
|
||||
thread::spawn(move || {
|
||||
let event_receiver = global_hotkey::GlobalHotKeyEvent::receiver();
|
||||
loop {
|
||||
if let Ok(event) = event_receiver.try_recv() {
|
||||
if event.state == HotKeyState::Pressed {
|
||||
println!("Exit hotkey pressed, shutting down...");
|
||||
|
||||
// Set running to false
|
||||
if let Ok(mut state) = state_clone.lock() {
|
||||
state.running = false;
|
||||
}
|
||||
|
||||
// Restore the system cursor
|
||||
platform::restore_cursor();
|
||||
|
||||
// Exit the application
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Small sleep to prevent high CPU usage
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
});
|
||||
|
||||
// Using the event-driven approach with the subscription function for cursor tracking
|
||||
|
||||
(state, Task::none())
|
||||
}
|
||||
|
||||
fn main() -> iced::Result {
|
||||
iced::application("Cursor Rush", update, view)
|
||||
.theme(|_| Theme::Dark)
|
||||
.subscription(subscription)
|
||||
.centered()
|
||||
.run_with(boot)
|
||||
}
|
||||
|
||||
// Set up the system tray icon
|
||||
fn setup_tray_icon(
|
||||
cursors: &[CursorInfo],
|
||||
shared_state: Arc<Mutex<SharedState>>,
|
||||
tx: mpsc::Sender<Message>,
|
||||
) {
|
||||
if cursors.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a menu
|
||||
let mut menu = Menu::new();
|
||||
|
||||
// Add menu items with IDs
|
||||
let next_item = tray_icon::menu::MenuItem::with_id(
|
||||
NEXT_CURSOR_ID,
|
||||
"Next cursor",
|
||||
true,
|
||||
None, // No accelerator
|
||||
);
|
||||
|
||||
let quit_item = tray_icon::menu::MenuItem::with_id(
|
||||
QUIT_ID,
|
||||
"Quit",
|
||||
true,
|
||||
None, // No accelerator
|
||||
);
|
||||
|
||||
// Add items to menu
|
||||
if let Ok(menu_with_items) = Menu::with_items(&[&next_item, &quit_item]) {
|
||||
menu = menu_with_items;
|
||||
}
|
||||
|
||||
// Use the first PNG as the tray icon
|
||||
let icon_path = &cursors[0].path;
|
||||
|
||||
// Load the image
|
||||
let img = match image_open(icon_path) {
|
||||
Ok(img) => img,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to open image {}: {}", icon_path.display(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Convert to RGBA
|
||||
let rgba_img = img.to_rgba8();
|
||||
let (width, height) = rgba_img.dimensions();
|
||||
|
||||
// Create icon from RGBA data
|
||||
let icon = match tray_icon::Icon::from_rgba(rgba_img.into_raw(), width, height) {
|
||||
Ok(icon) => icon,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create icon: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Build the tray icon
|
||||
let _tray = TrayIconBuilder::new()
|
||||
.with_tooltip("Cursor Rush")
|
||||
.with_icon(icon)
|
||||
.with_menu(Box::new(menu))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Listen for tray menu events in a separate thread
|
||||
let state_clone = shared_state.clone();
|
||||
thread::spawn(move || {
|
||||
let menu_channel = MenuEvent::receiver();
|
||||
while let Ok(event) = menu_channel.recv() {
|
||||
match event.id.as_ref() {
|
||||
NEXT_CURSOR_ID => {
|
||||
// Handle next cursor event
|
||||
let mut state = state_clone.lock().unwrap();
|
||||
if !state.cursors.is_empty() {
|
||||
state.current_index = (state.current_index + 1) % state.cursors.len();
|
||||
if let Some(cursor) = state.cursors.get(state.current_index) {
|
||||
apply_cursor(&cursor.path);
|
||||
// Ensure the render thread is running with the new cursor
|
||||
platform::render_cursor();
|
||||
|
||||
// Load the new cursor image
|
||||
let current_cursor_image = match image_open(&cursor.path) {
|
||||
Ok(img) => Some(img.to_rgba8()),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
// Update the cursor image in shared state
|
||||
state.current_cursor_image = current_cursor_image;
|
||||
}
|
||||
}
|
||||
// Notify the UI
|
||||
let _ = tx.send(Message::TrayEvent);
|
||||
},
|
||||
QUIT_ID => {
|
||||
// Restore the system cursor before exiting
|
||||
platform::restore_cursor();
|
||||
|
||||
// Exit the application
|
||||
std::process::exit(0);
|
||||
},
|
||||
_ => {} // any other menu items you add later
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update function to handle messages
|
||||
fn update(state: &mut CursorRush, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::NextCursor => {
|
||||
if !state.cursors.is_empty() {
|
||||
state.current_index = (state.current_index + 1) % state.cursors.len();
|
||||
|
||||
// Apply the new cursor
|
||||
if let Some(cursor) = state.cursors.get(state.current_index) {
|
||||
apply_cursor(&cursor.path);
|
||||
// Ensure the render thread is running with the new cursor
|
||||
platform::render_cursor();
|
||||
|
||||
// Load the new cursor image
|
||||
let current_cursor_image = match image::open(&cursor.path) {
|
||||
Ok(img) => Some(img.to_rgba8()),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
// Update shared state
|
||||
if let Some(shared) = &state.shared_state {
|
||||
if let Ok(mut shared_state) = shared.lock() {
|
||||
shared_state.current_index = state.current_index;
|
||||
shared_state.current_cursor_image = current_cursor_image;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
},
|
||||
Message::SelectCursor(idx) => {
|
||||
if idx < state.cursors.len() {
|
||||
state.current_index = idx;
|
||||
|
||||
// Apply the selected cursor
|
||||
if let Some(cursor) = state.cursors.get(state.current_index) {
|
||||
apply_cursor(&cursor.path);
|
||||
// Ensure the render thread is running with the new cursor
|
||||
platform::render_cursor();
|
||||
|
||||
// Load the new cursor image
|
||||
let current_cursor_image = match image_open(&cursor.path) {
|
||||
Ok(img) => Some(img.to_rgba8()),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
// Update shared state
|
||||
if let Some(shared) = &state.shared_state {
|
||||
if let Ok(mut shared_state) = shared.lock() {
|
||||
shared_state.current_index = state.current_index;
|
||||
shared_state.current_cursor_image = current_cursor_image;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
},
|
||||
Message::Quit => {
|
||||
// Restore the system cursor before exiting
|
||||
platform::restore_cursor();
|
||||
|
||||
// Exit the application
|
||||
std::process::exit(0);
|
||||
},
|
||||
Message::TrayEvent => {
|
||||
// Sync with shared state
|
||||
if let Some(shared) = &state.shared_state {
|
||||
if let Ok(shared_state) = shared.lock() {
|
||||
state.current_index = shared_state.current_index;
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
},
|
||||
Message::CursorMoved(position) => {
|
||||
// Update the cursor position
|
||||
state.cursor_position = position;
|
||||
Task::none()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// View function to render the UI
|
||||
fn view(state: &CursorRush) -> Element<Message> {
|
||||
let mut content = Column::new()
|
||||
.spacing(16)
|
||||
.push(text("Cursor Rush").size(24));
|
||||
|
||||
// Add a button for each cursor
|
||||
if !state.cursors.is_empty() {
|
||||
let current_cursor = &state.cursors[state.current_index];
|
||||
content = content.push(text(format!("Current cursor: {}", current_cursor.name)));
|
||||
|
||||
// Display the current cursor image as a preview
|
||||
let img_handle = iced_image::Handle::from_path(¤t_cursor.path);
|
||||
let cursor_image = iced_image::Image::new(img_handle)
|
||||
.width(Length::Fixed(64.0))
|
||||
.height(Length::Fixed(64.0));
|
||||
|
||||
content = content.push(cursor_image);
|
||||
|
||||
// Create a row of cursor buttons
|
||||
let mut cursor_row = Row::new().spacing(10);
|
||||
|
||||
for (i, cursor) in state.cursors.iter().enumerate() {
|
||||
// Create a small preview image for each cursor
|
||||
let preview_handle = iced_image::Handle::from_path(&cursor.path);
|
||||
let preview_image = iced_image::Image::new(preview_handle)
|
||||
.width(Length::Fixed(32.0))
|
||||
.height(Length::Fixed(32.0));
|
||||
|
||||
let btn = button(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(preview_image)
|
||||
.push(text(&cursor.name).size(16))
|
||||
)
|
||||
.on_press(Message::SelectCursor(i));
|
||||
|
||||
cursor_row = cursor_row.push(btn);
|
||||
}
|
||||
|
||||
content = content
|
||||
.push(cursor_row)
|
||||
.push(button("Next cursor").on_press(Message::NextCursor))
|
||||
.push(button("Quit").on_press(Message::Quit));
|
||||
|
||||
// Add instructions for the hotkey
|
||||
content = content.push(
|
||||
text("Press Ctrl+Shift+F12 to exit the application").size(14)
|
||||
);
|
||||
} else {
|
||||
content = content.push(text("No cursors found in assets directory"));
|
||||
}
|
||||
|
||||
// Add cursor position information for debugging
|
||||
if !state.cursors.is_empty() {
|
||||
let (x, y) = state.cursor_position;
|
||||
content = content.push(
|
||||
text(format!("Cursor position: ({:.1}, {:.1})", x, y)).size(12)
|
||||
);
|
||||
}
|
||||
|
||||
// Create the main container
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.padding(20)
|
||||
.into()
|
||||
}
|
||||
|
||||
+759
@@ -0,0 +1,759 @@
|
||||
// Platform-specific cursor handling
|
||||
|
||||
// Windows implementation
|
||||
#[cfg(windows)]
|
||||
mod windows {
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::sync::Once;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
use winapi::{
|
||||
um::winuser::{
|
||||
GetDC, ReleaseDC, GetCursorPos, LoadCursorW,
|
||||
SetSystemCursor, CopyIcon, IDC_ARROW,
|
||||
CreateCursor, SystemParametersInfoW, SPI_SETCURSORS,
|
||||
CreateWindowExW, DestroyWindow, UpdateLayeredWindow,
|
||||
SetLayeredWindowAttributes, ShowWindow, SetWindowPos,
|
||||
RegisterClassExW, DefWindowProcW,
|
||||
WS_EX_LAYERED, WS_EX_TRANSPARENT, WS_EX_TOPMOST, WS_EX_TOOLWINDOW,
|
||||
WS_POPUP, SW_SHOW, HWND_TOPMOST, SWP_NOSIZE, SWP_NOMOVE,
|
||||
LWA_ALPHA, LWA_COLORKEY, WM_DESTROY, WM_CLOSE,
|
||||
CS_HREDRAW, CS_VREDRAW,
|
||||
},
|
||||
um::wingdi::{
|
||||
SetPixel, CreateCompatibleDC, DeleteDC, SelectObject, DeleteObject,
|
||||
CreateCompatibleBitmap, RGB, BLENDFUNCTION, AC_SRC_OVER, AC_SRC_ALPHA,
|
||||
},
|
||||
um::consoleapi::SetConsoleCtrlHandler,
|
||||
shared::windef::{HWND, POINT, HCURSOR, SIZE},
|
||||
shared::minwindef::{DWORD, BOOL, TRUE, FALSE, UINT, WPARAM, LPARAM, LRESULT},
|
||||
};
|
||||
|
||||
// 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) -> BOOL;
|
||||
}
|
||||
|
||||
// Constants for SystemParametersInfo
|
||||
const SPI_SETCURSORS_A: u32 = 0x0057;
|
||||
const SPIF_UPDATEINIFILE: u32 = 0x01;
|
||||
const SPIF_SENDCHANGE: u32 = 0x02;
|
||||
use std::ptr::null_mut;
|
||||
use ::image::{RgbaImage, open as image_open};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
// Static variables for cursor state
|
||||
static mut CURSOR_HIDDEN: bool = false;
|
||||
|
||||
// Flag to control the rendering thread
|
||||
static RENDERING_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Define all system cursor IDs we'll replace
|
||||
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
|
||||
];
|
||||
|
||||
// Use lazy_static for thread-safe access to the cursor image
|
||||
lazy_static! {
|
||||
static ref CURRENT_CURSOR_IMAGE: Arc<Mutex<Option<RgbaImage>>> = Arc::new(Mutex::new(None));
|
||||
static ref RENDER_THREAD: Mutex<Option<thread::JoinHandle<()>>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
// We need to use a static variable for the window handle since HWND isn't Send
|
||||
static mut CURSOR_WINDOW: Option<HWND> = None;
|
||||
|
||||
// Window class name for our cursor window
|
||||
const CURSOR_WINDOW_CLASS: &str = "CursorRushOverlay";
|
||||
|
||||
// Window procedure for our cursor window
|
||||
unsafe extern "system" fn window_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||
match msg {
|
||||
WM_CLOSE | WM_DESTROY => {
|
||||
DestroyWindow(hwnd);
|
||||
0
|
||||
},
|
||||
_ => DefWindowProcW(hwnd, msg, wparam, lparam),
|
||||
}
|
||||
}
|
||||
|
||||
// Create a layered, topmost window for our cursor
|
||||
unsafe fn create_cursor_window() -> Option<HWND> {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
|
||||
// Convert class name to wide string
|
||||
let class_name: Vec<u16> = OsStr::new(CURSOR_WINDOW_CLASS)
|
||||
.encode_wide()
|
||||
.chain(std::iter::once(0))
|
||||
.collect();
|
||||
|
||||
// Register window class
|
||||
let mut wc = std::mem::zeroed::<winapi::um::winuser::WNDCLASSEXW>();
|
||||
wc.cbSize = std::mem::size_of::<winapi::um::winuser::WNDCLASSEXW>() as u32;
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wc.lpfnWndProc = Some(window_proc);
|
||||
wc.hInstance = std::ptr::null_mut();
|
||||
wc.lpszClassName = class_name.as_ptr();
|
||||
wc.hCursor = LoadCursorW(std::ptr::null_mut(), IDC_ARROW as _);
|
||||
|
||||
let atom = RegisterClassExW(&wc);
|
||||
if atom == 0 {
|
||||
eprintln!("Failed to register window class");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create window with layered, transparent, and topmost attributes
|
||||
// WS_EX_TRANSPARENT: Makes the window transparent to mouse clicks
|
||||
// WS_EX_LAYERED: Required for transparency and UpdateLayeredWindow
|
||||
// WS_EX_TOPMOST: Keeps the window above all other windows
|
||||
// WS_EX_TOOLWINDOW: Prevents the window from showing in the taskbar
|
||||
// WS_EX_NOACTIVATE: Prevents the window from becoming active when clicked
|
||||
let hwnd = CreateWindowExW(
|
||||
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_TOOLWINDOW | 0x08000000, // WS_EX_NOACTIVATE
|
||||
class_name.as_ptr(),
|
||||
std::ptr::null(), // Window title (none)
|
||||
WS_POPUP, // Popup window with no border
|
||||
0, 0, // Position (will be updated based on cursor position)
|
||||
128, 128, // Size (will be updated based on cursor image)
|
||||
std::ptr::null_mut(), // Parent window
|
||||
std::ptr::null_mut(), // Menu
|
||||
std::ptr::null_mut(), // Instance
|
||||
std::ptr::null_mut(), // Additional data
|
||||
);
|
||||
|
||||
if hwnd.is_null() {
|
||||
eprintln!("Failed to create cursor window");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Make the window transparent with click-through capability
|
||||
SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA | LWA_COLORKEY);
|
||||
|
||||
// Show the window
|
||||
ShowWindow(hwnd, SW_SHOW);
|
||||
|
||||
// Ensure it stays on top
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
HWND_TOPMOST,
|
||||
0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE
|
||||
);
|
||||
|
||||
Some(hwnd)
|
||||
}
|
||||
|
||||
// We'll use a static variable for original cursors since they're not thread-safe
|
||||
static mut ORIGINAL_CURSORS: Option<Vec<(DWORD, HCURSOR)>> = None;
|
||||
|
||||
// Flag to track if we've registered the control handler
|
||||
static mut CTRL_HANDLER_REGISTERED: bool = false;
|
||||
|
||||
// Use Once to ensure initialization happens only once
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
// Control handler function for console events (Ctrl+C, close, etc.)
|
||||
extern "system" fn ctrl_handler(_ctrl_type: DWORD) -> BOOL {
|
||||
println!("Control event received, restoring cursors...");
|
||||
// Call our cleanup function to restore cursors
|
||||
cleanup();
|
||||
// Return FALSE to allow other handlers to process the event
|
||||
FALSE
|
||||
}
|
||||
|
||||
// Cleanup function to restore cursors and clean up resources
|
||||
fn cleanup() {
|
||||
unsafe {
|
||||
// Only restore if we've hidden the cursor
|
||||
if CURSOR_HIDDEN {
|
||||
println!("Restoring system cursors...");
|
||||
|
||||
// First, force Windows to reload the cursors
|
||||
SystemParametersInfoW(SPI_SETCURSORS, 0, null_mut(), 0);
|
||||
|
||||
// Try multiple restoration methods and track success
|
||||
let mut restoration_success = false;
|
||||
|
||||
// Method 1: Try to restore the original system cursors if we have them
|
||||
if let Some(ref original_cursors) = ORIGINAL_CURSORS {
|
||||
if !original_cursors.is_empty() {
|
||||
let mut method1_success = true;
|
||||
println!("Attempting to restore {} saved original cursors", original_cursors.len());
|
||||
|
||||
for &(cursor_id, cursor) in original_cursors.iter() {
|
||||
// Make a copy of the original cursor
|
||||
let cursor_copy = CopyIcon(cursor as _) as HCURSOR;
|
||||
if !cursor_copy.is_null() {
|
||||
// Restore the system cursor
|
||||
if SetSystemCursor(cursor_copy, cursor_id) == 0 {
|
||||
eprintln!("Failed to restore system cursor {}", cursor_id);
|
||||
method1_success = false;
|
||||
}
|
||||
} else {
|
||||
method1_success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if method1_success {
|
||||
println!("Successfully restored original cursors");
|
||||
restoration_success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: If method 1 failed, try with the default arrow cursor
|
||||
if !restoration_success {
|
||||
println!("Using arrow cursor restoration method");
|
||||
let mut method2_success = true;
|
||||
|
||||
// Load the default arrow cursor
|
||||
let arrow_cursor = LoadCursorW(null_mut(), 32512 as _); // IDC_ARROW = 32512
|
||||
if !arrow_cursor.is_null() {
|
||||
// Make a copy for each system cursor
|
||||
for &cursor_id in SYSTEM_CURSORS.iter() {
|
||||
let cursor_copy = CopyIcon(arrow_cursor as _) as HCURSOR;
|
||||
if !cursor_copy.is_null() {
|
||||
if SetSystemCursor(cursor_copy, cursor_id) == 0 {
|
||||
eprintln!("Failed to set system cursor {} to arrow", cursor_id);
|
||||
method2_success = false;
|
||||
}
|
||||
} else {
|
||||
method2_success = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method2_success = false;
|
||||
}
|
||||
|
||||
if method2_success {
|
||||
println!("Successfully restored cursors using arrow cursor");
|
||||
restoration_success = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Reset all system cursors to their defaults using Windows API
|
||||
if !restoration_success {
|
||||
println!("Using system reset method");
|
||||
// Call our reset function which uses both W and A versions of SystemParametersInfo
|
||||
reset_system_cursors();
|
||||
|
||||
// We'll assume this worked unless we have evidence it didn't
|
||||
restoration_success = true;
|
||||
|
||||
// Try to verify by checking if we can load a cursor
|
||||
let test_cursor = LoadCursorW(null_mut(), 32512 as _); // IDC_ARROW
|
||||
if test_cursor.is_null() {
|
||||
restoration_success = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Method 4: Only create and run a batch file as a last resort if all other methods failed
|
||||
if !restoration_success {
|
||||
println!("All direct restoration methods failed, using batch file fallback");
|
||||
create_cursor_reset_file();
|
||||
}
|
||||
|
||||
CURSOR_HIDDEN = false;
|
||||
println!("System cursors restoration complete");
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the rendering thread
|
||||
RENDERING_ACTIVE.store(false, Ordering::SeqCst);
|
||||
|
||||
// Destroy the cursor window if it exists
|
||||
unsafe {
|
||||
if let Some(hwnd) = CURSOR_WINDOW.take() {
|
||||
DestroyWindow(hwnd);
|
||||
println!("Cursor window destroyed during cleanup");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the cleanup function with control handler
|
||||
fn register_cleanup() {
|
||||
// Use Once to ensure this only happens once
|
||||
INIT.call_once(|| {
|
||||
unsafe {
|
||||
// Register our control handler
|
||||
if SetConsoleCtrlHandler(Some(ctrl_handler), TRUE) == 0 {
|
||||
eprintln!("Failed to register control handler");
|
||||
} else {
|
||||
println!("Control handler registered for cursor restoration");
|
||||
CTRL_HANDLER_REGISTERED = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize module
|
||||
fn initialize() {
|
||||
// Register cleanup handler on module load
|
||||
register_cleanup();
|
||||
|
||||
// Reset system cursors on startup in case they were left in a bad state
|
||||
reset_system_cursors();
|
||||
}
|
||||
|
||||
// Call initialize when module is loaded
|
||||
#[ctor::ctor]
|
||||
fn module_init() {
|
||||
initialize();
|
||||
println!("Windows cursor module initialized with cleanup handlers");
|
||||
}
|
||||
|
||||
// Call cleanup when module is unloaded
|
||||
#[ctor::dtor]
|
||||
fn module_cleanup() {
|
||||
println!("Module unloading, restoring cursors...");
|
||||
// Just call cleanup which now handles all restoration methods
|
||||
cleanup();
|
||||
// No need to create a reset file here as cleanup() will do it if needed
|
||||
}
|
||||
|
||||
// Function to reset all system cursors to their defaults
|
||||
fn reset_system_cursors() {
|
||||
unsafe {
|
||||
// Try multiple approaches to reset cursors
|
||||
|
||||
// First try with the W version
|
||||
let result1 = SystemParametersInfoW(SPI_SETCURSORS, 0, null_mut(), 0);
|
||||
|
||||
// Then try with the A version with flags to update the system settings
|
||||
let result2 = SystemParametersInfoA(
|
||||
SPI_SETCURSORS_A,
|
||||
0,
|
||||
null_mut(),
|
||||
SPIF_UPDATEINIFILE | SPIF_SENDCHANGE
|
||||
);
|
||||
|
||||
// Try direct call to rundll32 as a more forceful approach
|
||||
if result1 == 0 && result2 == 0 {
|
||||
// Both API calls failed, try using Command
|
||||
match Command::new("rundll32.exe")
|
||||
.args(&["user32.dll,SystemParametersInfo", "0x0057", "0", "0", "0x01"])
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
println!("Reset system cursors using rundll32");
|
||||
} else {
|
||||
eprintln!("rundll32 cursor reset failed with status: {}", status);
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!("Failed to execute rundll32: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!("Reset system cursors to defaults via API");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a batch file that will reset the cursors and run it
|
||||
fn create_cursor_reset_file() {
|
||||
// Create a temporary batch file to reset cursors
|
||||
let batch_content = r#"@echo off
|
||||
echo Resetting system cursors...
|
||||
rundll32.exe user32.dll,SystemParametersInfo 0x0057 0 0 0x01 | 0x02
|
||||
echo Cursors reset.
|
||||
timeout /t 2
|
||||
del "%~f0"
|
||||
"#;
|
||||
|
||||
let file_path = "reset_cursors.bat";
|
||||
|
||||
// Write the batch file
|
||||
match File::create(file_path) {
|
||||
Ok(mut file) => {
|
||||
if let Err(e) = file.write_all(batch_content.as_bytes()) {
|
||||
eprintln!("Failed to write to batch file: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Created cursor reset batch file");
|
||||
|
||||
// Run the batch file in a separate process
|
||||
match Command::new("cmd")
|
||||
.args(&["/C", "start", "/min", file_path])
|
||||
.spawn()
|
||||
{
|
||||
Ok(_) => println!("Launched cursor reset batch file"),
|
||||
Err(e) => eprintln!("Failed to launch batch file: {}", e),
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!("Failed to create batch file: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
// Function to save the original system cursors
|
||||
fn save_original_cursors() {
|
||||
unsafe {
|
||||
// Only save if we haven't already
|
||||
if ORIGINAL_CURSORS.is_none() {
|
||||
let mut cursors = Vec::new();
|
||||
|
||||
for &cursor_id in SYSTEM_CURSORS.iter() {
|
||||
let cursor = LoadCursorW(null_mut(), cursor_id as _);
|
||||
if !cursor.is_null() {
|
||||
// Make a copy of the cursor
|
||||
let cursor_copy = CopyIcon(cursor as _) as HCURSOR;
|
||||
if !cursor_copy.is_null() {
|
||||
cursors.push((cursor_id, cursor_copy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ORIGINAL_CURSORS = Some(cursors);
|
||||
println!("Saved {} original system cursors", ORIGINAL_CURSORS.as_ref().unwrap().len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor(path: &Path) -> bool {
|
||||
// Save the original system cursors before hiding them
|
||||
save_original_cursors();
|
||||
|
||||
// Register cleanup handler to ensure cursor is restored on exit
|
||||
register_cleanup();
|
||||
|
||||
// Hide the system cursor
|
||||
unsafe {
|
||||
if !CURSOR_HIDDEN {
|
||||
// Create an invisible cursor (1x1 transparent cursor)
|
||||
// Create bitmap data for a 1x1 cursor
|
||||
let and_plane: [u8; 1] = [0xFF]; // All bits 1 = transparent
|
||||
let xor_plane: [u8; 1] = [0x00]; // All bits 0 = black
|
||||
|
||||
let empty_cursor = CreateCursor(
|
||||
null_mut(), // hInst
|
||||
0, // xHotSpot
|
||||
0, // yHotSpot
|
||||
1, // nWidth
|
||||
1, // nHeight
|
||||
and_plane.as_ptr() as *const _, // pvANDPlane
|
||||
xor_plane.as_ptr() as *const _, // pvXORPlane
|
||||
);
|
||||
|
||||
if empty_cursor.is_null() {
|
||||
eprintln!("Failed to create empty cursor");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace all system cursors with our empty cursor
|
||||
for &cursor_id in SYSTEM_CURSORS.iter() {
|
||||
// Make a copy of our empty cursor for each system cursor
|
||||
let cursor_copy = CopyIcon(empty_cursor as _) as HCURSOR;
|
||||
if !cursor_copy.is_null() {
|
||||
// Set the system cursor
|
||||
if SetSystemCursor(cursor_copy, cursor_id) == 0 {
|
||||
eprintln!("Failed to set system cursor {}", cursor_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CURSOR_HIDDEN = true;
|
||||
println!("System cursors hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// Load the cursor image
|
||||
match image_open(path) {
|
||||
Ok(img) => {
|
||||
let rgba_img = img.to_rgba8();
|
||||
*CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(rgba_img);
|
||||
println!("Using PNG cursor: {:?}", path);
|
||||
|
||||
// Start the rendering thread if it's not already running
|
||||
start_render_thread_if_needed();
|
||||
|
||||
true
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load cursor image: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to restore the cursor when the program exits
|
||||
pub fn restore_cursor() {
|
||||
println!("Explicit restore_cursor called");
|
||||
|
||||
// Call our cleanup function which handles cursor restoration
|
||||
cleanup();
|
||||
|
||||
// Join the thread if it exists
|
||||
if let Some(handle) = RENDER_THREAD.lock().unwrap().take() {
|
||||
// Use a timeout to avoid hanging if the thread is stuck
|
||||
let thread_handle = handle;
|
||||
match thread_handle.join() {
|
||||
Ok(_) => println!("Render thread joined successfully"),
|
||||
Err(_) => eprintln!("Error joining render thread"),
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy the cursor window if it exists
|
||||
unsafe {
|
||||
if let Some(hwnd) = CURSOR_WINDOW.take() {
|
||||
if DestroyWindow(hwnd) == 0 {
|
||||
eprintln!("Failed to destroy cursor window");
|
||||
} else {
|
||||
println!("Cursor window destroyed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the cursor image
|
||||
*CURRENT_CURSOR_IMAGE.lock().unwrap() = None;
|
||||
|
||||
// Unregister our control handler to avoid double cleanup
|
||||
unsafe {
|
||||
if CTRL_HANDLER_REGISTERED {
|
||||
if SetConsoleCtrlHandler(Some(ctrl_handler), FALSE) == 0 {
|
||||
eprintln!("Failed to unregister control handler");
|
||||
} else {
|
||||
CTRL_HANDLER_REGISTERED = false;
|
||||
println!("Control handler unregistered");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final verification that cursors are restored
|
||||
unsafe {
|
||||
if CURSOR_HIDDEN {
|
||||
println!("Cursor still marked as hidden after cleanup, forcing reset");
|
||||
reset_system_cursors();
|
||||
CURSOR_HIDDEN = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to start the rendering thread if it's not already running
|
||||
fn start_render_thread_if_needed() {
|
||||
let mut render_thread_guard = RENDER_THREAD.lock().unwrap();
|
||||
|
||||
// Only start a new thread if one isn't already running
|
||||
if render_thread_guard.is_none() {
|
||||
// Set the rendering active flag
|
||||
RENDERING_ACTIVE.store(true, Ordering::SeqCst);
|
||||
|
||||
// Clone the Arc for the thread
|
||||
let cursor_image_arc = CURRENT_CURSOR_IMAGE.clone();
|
||||
|
||||
// Create a new thread for continuous rendering
|
||||
let handle = thread::spawn(move || {
|
||||
println!("Starting cursor render thread");
|
||||
|
||||
// Render loop
|
||||
while RENDERING_ACTIVE.load(Ordering::SeqCst) {
|
||||
render_cursor_internal(&cursor_image_arc);
|
||||
|
||||
// Sleep to control frame rate (60 FPS)
|
||||
thread::sleep(Duration::from_millis(16));
|
||||
}
|
||||
|
||||
println!("Cursor render thread stopped");
|
||||
});
|
||||
|
||||
// Store the thread handle
|
||||
*render_thread_guard = Some(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal function to render the cursor (called from the render thread)
|
||||
fn render_cursor_internal(cursor_image_arc: &Arc<Mutex<Option<RgbaImage>>>) {
|
||||
// Get the cursor image
|
||||
let cursor_image_lock = cursor_image_arc.lock().unwrap();
|
||||
if let Some(ref cursor_image) = *cursor_image_lock {
|
||||
unsafe {
|
||||
// Get the current mouse position
|
||||
let mut point = POINT { x: 0, y: 0 };
|
||||
if GetCursorPos(&mut point) == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get image dimensions
|
||||
let (width, height) = cursor_image.dimensions();
|
||||
|
||||
// Get or create the cursor window
|
||||
let hwnd = unsafe {
|
||||
if CURSOR_WINDOW.is_none() {
|
||||
// Create a new window if we don't have one
|
||||
if let Some(window) = create_cursor_window() {
|
||||
CURSOR_WINDOW = Some(window);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap is safe because we just checked it's Some
|
||||
CURSOR_WINDOW.unwrap()
|
||||
};
|
||||
|
||||
// Move the window to the cursor position
|
||||
// Adjust position to center the cursor image on the actual cursor point
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
HWND_TOPMOST,
|
||||
point.x - (width as i32 / 2),
|
||||
point.y - (height as i32 / 2),
|
||||
width as i32,
|
||||
height as i32,
|
||||
0
|
||||
);
|
||||
|
||||
// Get the window DC
|
||||
let hwnd_dc = GetDC(hwnd);
|
||||
if hwnd_dc.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a compatible DC for double-buffering
|
||||
let mem_dc = CreateCompatibleDC(hwnd_dc);
|
||||
if mem_dc.is_null() {
|
||||
ReleaseDC(hwnd, hwnd_dc);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a compatible bitmap
|
||||
let bitmap = CreateCompatibleBitmap(hwnd_dc, width as i32, height as i32);
|
||||
if bitmap.is_null() {
|
||||
DeleteDC(mem_dc);
|
||||
ReleaseDC(hwnd, hwnd_dc);
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the bitmap into the memory DC
|
||||
let old_bitmap = SelectObject(mem_dc, bitmap as _);
|
||||
|
||||
// Draw the cursor image to the memory DC
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let pixel = cursor_image.get_pixel(x, y);
|
||||
|
||||
// Only draw non-transparent pixels
|
||||
if pixel[3] > 128 { // Alpha channel > 50%
|
||||
let color = RGB(pixel[0], pixel[1], pixel[2]);
|
||||
SetPixel(mem_dc, x as i32, y as i32, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the memory DC to the window DC using BitBlt
|
||||
use winapi::um::wingdi::BitBlt;
|
||||
use winapi::um::wingdi::SRCCOPY;
|
||||
|
||||
BitBlt(
|
||||
hwnd_dc,
|
||||
0, 0,
|
||||
width as i32, height as i32,
|
||||
mem_dc,
|
||||
0, 0,
|
||||
SRCCOPY
|
||||
);
|
||||
|
||||
// Clean up
|
||||
SelectObject(mem_dc, old_bitmap as _);
|
||||
DeleteObject(bitmap as _);
|
||||
DeleteDC(mem_dc);
|
||||
ReleaseDC(hwnd, hwnd_dc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to render the custom cursor (called from the application)
|
||||
pub fn render_cursor() {
|
||||
// This is now just a trigger to ensure the render thread is running
|
||||
start_render_thread_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
// macOS implementation
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos {
|
||||
use std::path::Path;
|
||||
|
||||
pub fn set_cursor(_path: &Path) -> bool {
|
||||
// macOS cursor implementation would go here
|
||||
// This is a placeholder - actual implementation would require
|
||||
// Objective-C/Cocoa bindings to NSCursor
|
||||
println!("macOS cursor swapping not yet implemented");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn restore_cursor() {
|
||||
// Placeholder for restoring cursor on macOS
|
||||
println!("macOS cursor restoration not yet implemented");
|
||||
}
|
||||
|
||||
pub fn render_cursor() {
|
||||
// On macOS, we would update the cursor on mouse movement
|
||||
// This is a placeholder
|
||||
}
|
||||
}
|
||||
|
||||
// Linux implementation
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux {
|
||||
use std::path::Path;
|
||||
|
||||
pub fn set_cursor(_path: &Path) -> bool {
|
||||
// Linux cursor implementation would go here
|
||||
// This is a placeholder - actual implementation would require
|
||||
// X11 or Wayland bindings
|
||||
println!("Linux cursor swapping not yet implemented");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn restore_cursor() {
|
||||
// Placeholder for restoring cursor on Linux
|
||||
println!("Linux cursor restoration not yet implemented");
|
||||
}
|
||||
|
||||
pub fn render_cursor() {
|
||||
// On Linux, we would update the cursor on mouse movement
|
||||
// This is a placeholder
|
||||
}
|
||||
}
|
||||
|
||||
// Export the appropriate implementation based on platform
|
||||
#[cfg(windows)]
|
||||
pub use windows::{set_cursor, restore_cursor, render_cursor};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::{set_cursor, restore_cursor, render_cursor};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::{set_cursor, restore_cursor, render_cursor};
|
||||
|
||||
// Default implementation for other platforms
|
||||
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
|
||||
pub fn set_cursor(_path: &Path) -> bool {
|
||||
println!("Cursor swapping not supported on this platform");
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
|
||||
pub fn restore_cursor() {
|
||||
println!("Cursor restoration not supported on this platform");
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
|
||||
pub fn render_cursor() {
|
||||
// No-op for unsupported platforms
|
||||
}
|
||||
Reference in New Issue
Block a user