use crate::{AutomationError, ClickType};
use std::thread;
use std::time::Duration;
use tracing::info;
use windows::core::BOOL;
use windows::Win32::Foundation::POINT;
use windows::Win32::System::Com::{
CoCreateInstance, CoInitializeEx, CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED,
};
use windows::Win32::UI::Accessibility::{
CUIAutomation, IUIAutomation, IUIAutomationElement, IUIAutomationTextPattern2,
IUIAutomationTextRange, UIA_TextPattern2Id,
};
use windows::Win32::UI::Input::KeyboardAndMouse::{
SendInput, INPUT, INPUT_0, INPUT_MOUSE, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_LEFTDOWN,
MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEINPUT,
};
use windows::Win32::UI::WindowsAndMessaging::{
GetCursorPos, GetSystemMetrics, SetCursorPos, SM_CXSCREEN, SM_CYSCREEN,
};
pub fn send_mouse_click(
x: f64,
y: f64,
click_type: ClickType,
restore_cursor: bool,
) -> Result<(), AutomationError> {
let original_pos = if restore_cursor {
let mut pos = POINT { x: 0, y: 0 };
unsafe {
let _ = GetCursorPos(&mut pos);
}
Some(pos)
} else {
None
};
unsafe {
let screen_width = GetSystemMetrics(SM_CXSCREEN) as f64;
let screen_height = GetSystemMetrics(SM_CYSCREEN) as f64;
let abs_x = ((x * 65535.0) / screen_width) as i32;
let abs_y = ((y * 65535.0) / screen_height) as i32;
let (down_flag, up_flag) = match click_type {
ClickType::Left | ClickType::Double => (MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP),
ClickType::Right => (MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP),
};
let move_input = INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx: abs_x,
dy: abs_y,
mouseData: 0,
dwFlags: MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE,
time: 0,
dwExtraInfo: 0,
},
},
};
let down_input = INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx: abs_x,
dy: abs_y,
mouseData: 0,
dwFlags: MOUSEEVENTF_ABSOLUTE | down_flag,
time: 0,
dwExtraInfo: 0,
},
},
};
let up_input = INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx: abs_x,
dy: abs_y,
mouseData: 0,
dwFlags: MOUSEEVENTF_ABSOLUTE | up_flag,
time: 0,
dwExtraInfo: 0,
},
},
};
SendInput(&[move_input], std::mem::size_of::<INPUT>() as i32);
SendInput(&[down_input], std::mem::size_of::<INPUT>() as i32);
SendInput(&[up_input], std::mem::size_of::<INPUT>() as i32);
if click_type == ClickType::Double {
std::thread::sleep(std::time::Duration::from_millis(50));
SendInput(&[down_input], std::mem::size_of::<INPUT>() as i32);
SendInput(&[up_input], std::mem::size_of::<INPUT>() as i32);
}
}
if let Some(pos) = original_pos {
unsafe {
let _ = SetCursorPos(pos.x, pos.y);
}
}
Ok(())
}
#[inline]
pub fn send_left_click(x: f64, y: f64, restore_cursor: bool) -> Result<(), AutomationError> {
send_mouse_click(x, y, ClickType::Left, restore_cursor)
}
pub struct FocusState {
#[allow(dead_code)]
automation: IUIAutomation,
focused_element: IUIAutomationElement,
caret_range: Option<IUIAutomationTextRange>,
}
unsafe impl Send for FocusState {}
unsafe impl Sync for FocusState {}
pub fn save_focus_state() -> Option<FocusState> {
unsafe {
info!("[FOCUS_RESTORE] save_focus_state() called");
let hr = CoInitializeEx(None, COINIT_MULTITHREADED);
if hr.is_err() && hr.0 != 0x80010106u32 as i32 {
info!("[FOCUS_RESTORE] COM init failed: {:?}", hr);
return None;
}
let automation: IUIAutomation =
match CoCreateInstance(&CUIAutomation, None, CLSCTX_INPROC_SERVER) {
Ok(a) => a,
Err(e) => {
info!("[FOCUS_RESTORE] Failed to create UIA: {:?}", e);
return None;
}
};
let focused_element = match automation.GetFocusedElement() {
Ok(el) => el,
Err(e) => {
info!("[FOCUS_RESTORE] GetFocusedElement failed: {:?}", e);
return None;
}
};
let element_name = focused_element
.CurrentName()
.ok()
.map(|s| s.to_string())
.unwrap_or_else(|| "<no name>".to_string());
let element_class = focused_element
.CurrentClassName()
.ok()
.map(|s| s.to_string())
.unwrap_or_else(|| "<no class>".to_string());
let caret_range = if let Ok(pattern) =
focused_element.GetCurrentPatternAs::<IUIAutomationTextPattern2>(UIA_TextPattern2Id)
{
let mut is_active = BOOL::default();
if let Ok(range) = pattern.GetCaretRange(&mut is_active) {
info!("[FOCUS_RESTORE] Got caret range, is_active={:?}", is_active);
range.Clone().ok()
} else {
info!("[FOCUS_RESTORE] GetCaretRange failed");
None
}
} else {
info!("[FOCUS_RESTORE] Element does not support TextPattern2");
None
};
info!(
"[FOCUS_RESTORE] Saved: element='{}' class='{}' has_caret={}",
element_name,
element_class,
caret_range.is_some()
);
Some(FocusState {
automation,
focused_element,
caret_range,
})
}
}
pub fn restore_focus_state(state: FocusState) {
unsafe {
info!("[FOCUS_RESTORE] restore_focus_state() called");
let hr = CoInitializeEx(None, COINIT_MULTITHREADED);
if hr.is_err() && hr.0 != 0x80010106u32 as i32 {
info!("[FOCUS_RESTORE] COM init failed in restore: {:?}", hr);
return;
}
let element_name = state
.focused_element
.CurrentName()
.ok()
.map(|s| s.to_string())
.unwrap_or_else(|| "<no name>".to_string());
match state.focused_element.SetFocus() {
Ok(_) => info!("[FOCUS_RESTORE] SetFocus succeeded for '{}'", element_name),
Err(e) => info!(
"[FOCUS_RESTORE] SetFocus failed for '{}': {:?}",
element_name, e
),
}
if let Some(ref range) = state.caret_range {
thread::sleep(Duration::from_millis(50));
match range.Select() {
Ok(_) => info!("[FOCUS_RESTORE] Caret Select() succeeded"),
Err(e) => info!("[FOCUS_RESTORE] Caret Select() failed: {:?}", e),
}
}
info!(
"[FOCUS_RESTORE] Restoration complete: element='{}' had_caret={}",
element_name,
state.caret_range.is_some()
);
}
}