use crate::error::{Error, Result};
use crate::options::{Options, ShowState, Speed, WinState};
use crate::{Control, Keys, Point, Rect, Selector, Size};
use autoitx_sys::{AU3_INTDEFAULT, Au3, POINT, RECT};
use parking_lot::{ReentrantMutex, ReentrantMutexGuard};
use std::path::Path;
use std::time::Duration;
const SMALL_BUF: usize = 1024;
const LARGE_BUF: usize = 65_536;
const DEFAULT_MAX_CHARS: usize = 16 * 1024 * 1024;
const INVALID_PID: u32 = u32::MAX;
pub(crate) fn wide(s: &str, what: &'static str) -> Result<Vec<u16>> {
if let Some(at) = s.bytes().position(|b| b == 0) {
return Err(Error::InteriorNul { what, at });
}
Ok(s.encode_utf16().chain(std::iter::once(0)).collect())
}
pub(crate) fn from_wide(buf: &[u16]) -> String {
let end = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
String::from_utf16_lossy(&buf[..end])
}
static EMPTY_WIDE: [u16; 1] = [0];
pub(crate) struct Inner {
au3: Au3,
lock: ReentrantMutex<()>,
options: Options,
max_chars: usize,
}
unsafe impl Send for Inner {}
unsafe impl Sync for Inner {}
macro_rules! au3 {
($inner:expr, $name:ident ( $($arg:expr),* $(,)? )) => {{
let inner = $inner;
let _guard = inner.lock.lock();
#[cfg(feature = "tracing")]
let _span = ::tracing::trace_span!(stringify!($name)).entered();
let ret = unsafe { (inner.au3.$name)($($arg),*) };
let code = unsafe { (inner.au3.AU3_error)() };
(ret, code)
}};
}
impl Inner {
pub(crate) fn load(
dll: Option<&Path>,
options: Options,
max_chars: Option<usize>,
) -> Result<Self> {
let au3 = unsafe { Au3::load(dll) }?;
Ok(Self {
au3,
lock: ReentrantMutex::new(()),
options,
max_chars: max_chars.unwrap_or(DEFAULT_MAX_CHARS),
})
}
pub(crate) const fn options(&self) -> Options {
self.options
}
pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, ()> {
self.lock.lock()
}
pub(crate) const fn raw(&self) -> &Au3 {
&self.au3
}
pub(crate) fn raw_error(&self) -> i32 {
let (code, _) = au3!(self, AU3_error());
code
}
fn call_str(
&self,
func: &'static str,
initial: usize,
mut call: impl FnMut(*mut u16, i32) -> i32,
) -> Result<String> {
let mut cap = initial;
loop {
let mut buf = vec![0u16; cap];
let code = call(buf.as_mut_ptr(), cap as i32);
let len = buf.iter().position(|&c| c == 0).unwrap_or(cap);
if len + 1 < cap {
let _ = code;
return Ok(from_wide(&buf));
}
if cap >= self.max_chars {
return Err(Error::StringTooLarge {
func,
limit: self.max_chars,
});
}
cap = cap.saturating_mul(4).min(self.max_chars);
}
}
fn sel(&self, s: &Selector) -> Result<Vec<u16>> {
wide(&s.to_string(), "selector")
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum ControlState {
Focus,
Enable,
Disable,
Show,
Hide,
}
fn timeout_secs(t: Option<Duration>) -> i32 {
match t {
None => 0,
Some(d) => i32::try_from(d.as_secs()).unwrap_or(i32::MAX).max(1),
}
}
impl Inner {
pub(crate) fn send(&self, keys: &Keys) -> Result<()> {
let w = wide(keys.as_str(), "keys")?;
au3!(self, AU3_Send(w.as_ptr(), 0));
Ok(())
}
pub(crate) fn clip_get(&self) -> Result<String> {
self.call_str("AU3_ClipGet", LARGE_BUF, |buf, cap| {
let (_, code) = au3!(self, AU3_ClipGet(buf, cap));
code
})
}
pub(crate) fn clip_put(&self, s: &str) -> Result<()> {
let w = wide(s, "clipboard text")?;
au3!(self, AU3_ClipPut(w.as_ptr()));
Ok(())
}
pub(crate) fn mouse_click(
&self,
button: &str,
p: Point,
clicks: u32,
speed: Option<Speed>,
) -> Result<()> {
let b = wide(button, "mouse button")?;
let spd = speed.map_or(AU3_INTDEFAULT, Speed::get);
let (ok, code) = au3!(
self,
AU3_MouseClick(b.as_ptr(), p.x, p.y, clicks as i32, spd)
);
if ok == 0 {
return Err(Error::AutoItFailed {
func: "AU3_MouseClick",
code,
});
}
Ok(())
}
pub(crate) fn win_exists(&self, s: &Selector) -> Result<bool> {
let w = self.sel(s)?;
let (found, _) = au3!(self, AU3_WinExists(w.as_ptr(), EMPTY_WIDE.as_ptr()));
Ok(found != 0)
}
pub(crate) fn win_active(&self, s: &Selector) -> Result<bool> {
let w = self.sel(s)?;
let (active, _) = au3!(self, AU3_WinActive(w.as_ptr(), EMPTY_WIDE.as_ptr()));
Ok(active != 0)
}
pub(crate) fn win_activate(&self, s: &Selector) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(self, AU3_WinActivate(w.as_ptr(), EMPTY_WIDE.as_ptr()));
Ok(ok != 0)
}
pub(crate) fn win_wait_active(&self, s: &Selector, t: Option<Duration>) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(
self,
AU3_WinWaitActive(w.as_ptr(), EMPTY_WIDE.as_ptr(), timeout_secs(t))
);
Ok(ok != 0)
}
pub(crate) fn win_wait(&self, s: &Selector, t: Option<Duration>) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(
self,
AU3_WinWait(w.as_ptr(), EMPTY_WIDE.as_ptr(), timeout_secs(t))
);
Ok(ok != 0)
}
pub(crate) fn win_wait_close(&self, s: &Selector, t: Option<Duration>) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(
self,
AU3_WinWaitClose(w.as_ptr(), EMPTY_WIDE.as_ptr(), timeout_secs(t))
);
Ok(ok != 0)
}
pub(crate) fn win_close(&self, s: &Selector) -> Result<()> {
let w = self.sel(s)?;
au3!(self, AU3_WinClose(w.as_ptr(), EMPTY_WIDE.as_ptr()));
Ok(())
}
pub(crate) fn win_get_process(&self, s: &Selector) -> Result<u32> {
let w = self.sel(s)?;
let (pid, _) = au3!(self, AU3_WinGetProcess(w.as_ptr(), EMPTY_WIDE.as_ptr()));
if pid == INVALID_PID {
return Err(Error::window_not_found(s));
}
Ok(pid)
}
pub(crate) fn win_set_state(&self, s: &Selector, state: ShowState) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(
self,
AU3_WinSetState(w.as_ptr(), EMPTY_WIDE.as_ptr(), state as i32)
);
Ok(ok != 0)
}
pub(crate) fn win_get_state(&self, s: &Selector) -> Result<WinState> {
let w = self.sel(s)?;
let (bits, code) = au3!(self, AU3_WinGetState(w.as_ptr(), EMPTY_WIDE.as_ptr()));
if code != 0 {
return Err(Error::window_not_found(s));
}
Ok(WinState::from_bits_truncate(bits))
}
pub(crate) fn win_get_pos(&self, s: &Selector) -> Result<Rect> {
let w = self.sel(s)?;
let mut rect = RECT::default();
let (_, code) = au3!(
self,
AU3_WinGetPos(w.as_ptr(), EMPTY_WIDE.as_ptr(), &raw mut rect)
);
if code != 0 {
return Err(Error::window_not_found(s));
}
Ok(Rect::from(rect))
}
pub(crate) fn win_get_title(&self, s: &Selector) -> Result<String> {
let w = self.sel(s)?;
self.call_str("AU3_WinGetTitle", SMALL_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_WinGetTitle(w.as_ptr(), EMPTY_WIDE.as_ptr(), buf, cap)
);
code
})
}
pub(crate) fn win_get_handle(&self, s: &Selector) -> Result<u64> {
let w = self.sel(s)?;
let (h, _) = au3!(self, AU3_WinGetHandle(w.as_ptr(), EMPTY_WIDE.as_ptr()));
if h.is_null() {
return Err(Error::window_not_found(s));
}
Ok(h as usize as u64)
}
pub(crate) fn win_get_text(&self, s: &Selector) -> Result<String> {
let w = self.sel(s)?;
self.call_str("AU3_WinGetText", LARGE_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_WinGetText(w.as_ptr(), EMPTY_WIDE.as_ptr(), buf, cap)
);
code
})
}
pub(crate) fn win_get_class_list(&self, s: &Selector) -> Result<Vec<String>> {
let w = self.sel(s)?;
let joined = self.call_str("AU3_WinGetClassList", LARGE_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_WinGetClassList(w.as_ptr(), EMPTY_WIDE.as_ptr(), buf, cap)
);
code
})?;
if joined.is_empty() {
return Err(Error::window_not_found(s));
}
Ok(joined.lines().map(str::to_owned).collect())
}
pub(crate) fn process_id(&self, name: &str) -> Result<Option<u32>> {
let w = wide(name, "process name")?;
let (pid, _) = au3!(self, AU3_ProcessExists(w.as_ptr()));
Ok(if pid == 0 { None } else { Some(pid as u32) })
}
pub(crate) fn pixel_get_color(&self, p: Point) -> Result<u32> {
let (rgb, _) = au3!(self, AU3_PixelGetColor(p.x, p.y));
Ok(rgb as u32)
}
pub(crate) fn run(&self, command: &str, working_dir: Option<&str>) -> Result<u32> {
let cmd = wide(command, "command")?;
let dir = match working_dir {
Some(d) => wide(d, "working directory")?,
None => vec![0u16],
};
let (pid, code) = au3!(self, AU3_Run(cmd.as_ptr(), dir.as_ptr(), AU3_INTDEFAULT));
if pid == 0 {
return Err(Error::AutoItFailed {
func: "AU3_Run",
code,
});
}
Ok(pid as u32)
}
pub(crate) fn process_close(&self, name_or_pid: &str) -> Result<()> {
let w = wide(name_or_pid, "process")?;
au3!(self, AU3_ProcessClose(w.as_ptr()));
Ok(())
}
pub(crate) fn mouse_get_cursor(&self) -> Result<i32> {
let (code, _) = au3!(self, AU3_MouseGetCursor());
Ok(code)
}
pub(crate) fn is_idle(&self) -> Result<bool> {
let cursor = self.mouse_get_cursor()?;
Ok(cursor == 2 || cursor == 5)
}
pub(crate) fn clip_sequence(&self) -> Option<u32> {
crate::backend::win32::clipboard_sequence()
}
pub(crate) fn mouse_get_pos(&self) -> Result<Point> {
let mut p = POINT::default();
au3!(self, AU3_MouseGetPos(&raw mut p));
Ok(Point::new(p.x, p.y))
}
pub(crate) fn mouse_move(&self, p: Point, speed: Option<Speed>) -> Result<()> {
let spd = speed.map_or(AU3_INTDEFAULT, Speed::get);
au3!(self, AU3_MouseMove(p.x, p.y, spd));
Ok(())
}
pub(crate) fn mouse_down(&self, button: &str) -> Result<()> {
let b = wide(button, "mouse button")?;
au3!(self, AU3_MouseDown(b.as_ptr()));
Ok(())
}
pub(crate) fn mouse_up(&self, button: &str) -> Result<()> {
let b = wide(button, "mouse button")?;
au3!(self, AU3_MouseUp(b.as_ptr()));
Ok(())
}
pub(crate) fn mouse_wheel(&self, direction: &str, clicks: u32) -> Result<()> {
let d = wide(direction, "wheel direction")?;
au3!(self, AU3_MouseWheel(d.as_ptr(), clicks as i32));
Ok(())
}
pub(crate) fn mouse_click_drag(
&self,
button: &str,
from: Point,
to: Point,
speed: Option<Speed>,
) -> Result<()> {
let b = wide(button, "mouse button")?;
let spd = speed.map_or(AU3_INTDEFAULT, Speed::get);
let (ok, code) = au3!(
self,
AU3_MouseClickDrag(b.as_ptr(), from.x, from.y, to.x, to.y, spd)
);
if ok == 0 {
return Err(Error::AutoItFailed {
func: "AU3_MouseClickDrag",
code,
});
}
Ok(())
}
pub(crate) fn win_move(&self, s: &Selector, r: Rect) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(
self,
AU3_WinMove(w.as_ptr(), EMPTY_WIDE.as_ptr(), r.x, r.y, r.w, r.h)
);
Ok(ok != 0)
}
pub(crate) fn win_set_title(&self, s: &Selector, title: &str) -> Result<bool> {
let w = self.sel(s)?;
let t = wide(title, "new title")?;
let (ok, _) = au3!(
self,
AU3_WinSetTitle(w.as_ptr(), EMPTY_WIDE.as_ptr(), t.as_ptr())
);
Ok(ok != 0)
}
pub(crate) fn win_set_on_top(&self, s: &Selector, on_top: bool) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(
self,
AU3_WinSetOnTop(w.as_ptr(), EMPTY_WIDE.as_ptr(), i32::from(on_top))
);
Ok(ok != 0)
}
pub(crate) fn win_kill(&self, s: &Selector) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(self, AU3_WinKill(w.as_ptr(), EMPTY_WIDE.as_ptr()));
Ok(ok != 0)
}
pub(crate) fn win_wait_not_active(&self, s: &Selector, t: Option<Duration>) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(
self,
AU3_WinWaitNotActive(w.as_ptr(), EMPTY_WIDE.as_ptr(), timeout_secs(t))
);
Ok(ok != 0)
}
pub(crate) fn win_get_client_size(&self, s: &Selector) -> Result<Size> {
let w = self.sel(s)?;
let mut rect = RECT::default();
let (_, code) = au3!(
self,
AU3_WinGetClientSize(w.as_ptr(), EMPTY_WIDE.as_ptr(), &raw mut rect)
);
if code != 0 {
return Err(Error::window_not_found(s));
}
let r = Rect::from(rect);
Ok(Size::new(r.w, r.h))
}
pub(crate) fn win_minimize_all(&self, undo: bool) -> Result<()> {
if undo {
au3!(self, AU3_WinMinimizeAllUndo());
} else {
au3!(self, AU3_WinMinimizeAll());
}
Ok(())
}
pub(crate) fn process_wait(&self, name: &str, t: Option<Duration>) -> Result<bool> {
let w = wide(name, "process name")?;
let (ok, _) = au3!(self, AU3_ProcessWait(w.as_ptr(), timeout_secs(t)));
Ok(ok != 0)
}
pub(crate) fn process_wait_close(&self, name: &str, t: Option<Duration>) -> Result<bool> {
let w = wide(name, "process name")?;
let (ok, _) = au3!(self, AU3_ProcessWaitClose(w.as_ptr(), timeout_secs(t)));
Ok(ok != 0)
}
pub(crate) fn run_wait(&self, command: &str, working_dir: Option<&str>) -> Result<i32> {
let cmd = wide(command, "command")?;
let dir = match working_dir {
Some(d) => wide(d, "working directory")?,
None => vec![0u16],
};
let (exit, _) = au3!(
self,
AU3_RunWait(cmd.as_ptr(), dir.as_ptr(), AU3_INTDEFAULT)
);
Ok(exit)
}
pub(crate) fn is_admin(&self) -> bool {
let (yes, _) = au3!(self, AU3_IsAdmin());
yes != 0
}
pub(crate) fn tool_tip(&self, text: &str, at: Option<Point>) -> Result<()> {
let t = wide(text, "tooltip text")?;
let (x, y) = at.map_or((AU3_INTDEFAULT, AU3_INTDEFAULT), |p| (p.x, p.y));
au3!(self, AU3_ToolTip(t.as_ptr(), x, y));
Ok(())
}
pub(crate) fn pixel_search(
&self,
area: Rect,
colour: u32,
variation: u32,
step: u32,
) -> Result<Option<Point>> {
let mut r = RECT {
left: area.x,
top: area.y,
right: area.x + area.w,
bottom: area.y + area.h,
};
let mut found = POINT { x: -1, y: -1 };
let (_, code) = au3!(
self,
AU3_PixelSearch(
&raw mut r,
colour as i32,
variation as i32,
step.max(1) as i32,
&raw mut found,
)
);
Ok(if code == 0 {
Some(Point::new(found.x, found.y))
} else {
None
})
}
pub(crate) fn pixel_checksum(&self, area: Rect, step: u32) -> Result<u32> {
let mut r = RECT {
left: area.x,
top: area.y,
right: area.x + area.w,
bottom: area.y + area.h,
};
let (sum, _) = au3!(self, AU3_PixelChecksum(&raw mut r, step.max(1) as i32));
Ok(sum)
}
#[cfg(any(windows, docsrs))]
pub(crate) fn win_get_caret_pos(&self) -> Result<Point> {
let mut p = POINT::default();
let (_, code) = au3!(self, AU3_WinGetCaretPos(&raw mut p));
if code != 0 {
return Err(Error::AutoItFailed {
func: "AU3_WinGetCaretPos",
code,
});
}
Ok(Point::new(p.x, p.y))
}
pub(crate) fn process_set_priority(&self, name: &str, priority: i32) -> Result<bool> {
let w = wide(name, "process name")?;
let (ok, _) = au3!(self, AU3_ProcessSetPriority(w.as_ptr(), priority));
Ok(ok != 0)
}
pub(crate) fn set_option(&self, option: &str, value: i32) -> Result<i32> {
let o = wide(option, "option name")?;
let (previous, _) = au3!(self, AU3_AutoItSetOption(o.as_ptr(), value));
Ok(previous)
}
#[cfg(any(windows, docsrs))]
pub(crate) fn win_set_trans(&self, s: &Selector, alpha: u8) -> Result<bool> {
let w = self.sel(s)?;
let (ok, _) = au3!(
self,
AU3_WinSetTrans(w.as_ptr(), EMPTY_WIDE.as_ptr(), i32::from(alpha))
);
Ok(ok != 0)
}
#[cfg(any(windows, docsrs))]
pub(crate) fn win_menu_select_item(&self, s: &Selector, path: &[&str]) -> Result<bool> {
let w = self.sel(s)?;
let mut items = Vec::with_capacity(8);
for i in 0..8 {
items.push(match path.get(i) {
Some(t) => wide(t, "menu item")?,
None => vec![0u16],
});
}
let (ok, _) = au3!(
self,
AU3_WinMenuSelectItem(
w.as_ptr(),
EMPTY_WIDE.as_ptr(),
items[0].as_ptr(),
items[1].as_ptr(),
items[2].as_ptr(),
items[3].as_ptr(),
items[4].as_ptr(),
items[5].as_ptr(),
items[6].as_ptr(),
items[7].as_ptr(),
)
);
Ok(ok != 0)
}
#[cfg(any(windows, docsrs))]
pub(crate) fn statusbar_get_text(&self, s: &Selector, part: u32) -> Result<String> {
let w = self.sel(s)?;
self.call_str("AU3_StatusbarGetText", SMALL_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_StatusbarGetText(w.as_ptr(), EMPTY_WIDE.as_ptr(), part as i32, buf, cap)
);
code
})
}
#[cfg(any(windows, docsrs))]
pub(crate) fn drive_map_add(
&self,
device: &str,
share: &str,
user: &str,
password: &str,
) -> Result<String> {
let d = wide(device, "device")?;
let sh = wide(share, "share")?;
let u = wide(user, "user")?;
let p = wide(password, "password")?;
self.call_str("AU3_DriveMapAdd", SMALL_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_DriveMapAdd(d.as_ptr(), sh.as_ptr(), 0, u.as_ptr(), p.as_ptr(), buf, cap,)
);
code
})
}
#[cfg(any(windows, docsrs))]
pub(crate) fn drive_map_del(&self, device: &str) -> Result<bool> {
let d = wide(device, "device")?;
let (ok, _) = au3!(self, AU3_DriveMapDel(d.as_ptr()));
Ok(ok != 0)
}
#[cfg(any(windows, docsrs))]
pub(crate) fn drive_map_get(&self, device: &str) -> Result<String> {
let d = wide(device, "device")?;
self.call_str("AU3_DriveMapGet", SMALL_BUF, |buf, cap| {
let (_, code) = au3!(self, AU3_DriveMapGet(d.as_ptr(), buf, cap));
code
})
}
#[cfg(any(windows, docsrs))]
pub(crate) fn run_as(
&self,
user: &str,
domain: &str,
password: &str,
command: &str,
working_dir: Option<&str>,
wait: bool,
) -> Result<i32> {
let u = wide(user, "user")?;
let dom = wide(domain, "domain")?;
let p = wide(password, "password")?;
let cmd = wide(command, "command")?;
let dir = match working_dir {
Some(d) => wide(d, "working directory")?,
None => vec![0u16],
};
let (ret, code) = if wait {
au3!(
self,
AU3_RunAsWait(
u.as_ptr(),
dom.as_ptr(),
p.as_ptr(),
1,
cmd.as_ptr(),
dir.as_ptr(),
AU3_INTDEFAULT,
)
)
} else {
au3!(
self,
AU3_RunAs(
u.as_ptr(),
dom.as_ptr(),
p.as_ptr(),
1,
cmd.as_ptr(),
dir.as_ptr(),
AU3_INTDEFAULT,
)
)
};
if ret == 0 && !wait {
return Err(Error::AutoItFailed {
func: "AU3_RunAs",
code,
});
}
Ok(ret)
}
#[cfg(any(windows, docsrs))]
pub(crate) fn shutdown(&self, flags: i32) -> Result<bool> {
let (ok, _) = au3!(self, AU3_Shutdown(flags));
Ok(ok != 0)
}
pub(crate) fn control_click(
&self,
win: &Selector,
ctrl: &Control,
button: &str,
clicks: u32,
at: Option<Point>,
) -> Result<bool> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
let b = wide(button, "mouse button")?;
let (x, y) = at.map_or((AU3_INTDEFAULT, AU3_INTDEFAULT), |p| (p.x, p.y));
let (ok, _) = au3!(
self,
AU3_ControlClick(
w.as_ptr(),
EMPTY_WIDE.as_ptr(),
c.as_ptr(),
b.as_ptr(),
clicks as i32,
x,
y,
)
);
Ok(ok != 0)
}
pub(crate) fn control_send(
&self,
win: &Selector,
ctrl: &Control,
keys: &Keys,
raw: bool,
) -> Result<bool> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
let k = wide(keys.as_str(), "keys")?;
let (ok, _) = au3!(
self,
AU3_ControlSend(
w.as_ptr(),
EMPTY_WIDE.as_ptr(),
c.as_ptr(),
k.as_ptr(),
i32::from(raw),
)
);
Ok(ok != 0)
}
pub(crate) fn control_set_text(
&self,
win: &Selector,
ctrl: &Control,
text: &str,
) -> Result<bool> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
let t = wide(text, "control text")?;
let (ok, _) = au3!(
self,
AU3_ControlSetText(w.as_ptr(), EMPTY_WIDE.as_ptr(), c.as_ptr(), t.as_ptr())
);
Ok(ok != 0)
}
pub(crate) fn control_get_text(&self, win: &Selector, ctrl: &Control) -> Result<String> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
self.call_str("AU3_ControlGetText", LARGE_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_ControlGetText(w.as_ptr(), EMPTY_WIDE.as_ptr(), c.as_ptr(), buf, cap)
);
code
})
}
pub(crate) fn control_get_focus(&self, win: &Selector) -> Result<String> {
let w = self.sel(win)?;
self.call_str("AU3_ControlGetFocus", SMALL_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_ControlGetFocus(w.as_ptr(), EMPTY_WIDE.as_ptr(), buf, cap)
);
code
})
}
pub(crate) fn control_get_pos(&self, win: &Selector, ctrl: &Control) -> Result<Rect> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
let mut rect = RECT::default();
let (_, code) = au3!(
self,
AU3_ControlGetPos(w.as_ptr(), EMPTY_WIDE.as_ptr(), c.as_ptr(), &raw mut rect)
);
if code != 0 {
return Err(Error::ControlNotFound {
selector: Box::new(win.clone()),
control: ctrl.clone(),
});
}
Ok(Rect::from(rect))
}
pub(crate) fn control_get_handle(&self, win: &Selector, ctrl: &Control) -> Result<u64> {
let hwnd = self.win_get_handle(win)? as usize as autoitx_sys::HWND;
let c = wide(ctrl.as_str(), "control")?;
let (h, _) = au3!(self, AU3_ControlGetHandle(hwnd, c.as_ptr()));
if h.is_null() {
return Err(Error::ControlNotFound {
selector: Box::new(win.clone()),
control: ctrl.clone(),
});
}
Ok(h as usize as u64)
}
pub(crate) fn control_move(&self, win: &Selector, ctrl: &Control, r: Rect) -> Result<bool> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
let (ok, _) = au3!(
self,
AU3_ControlMove(
w.as_ptr(),
EMPTY_WIDE.as_ptr(),
c.as_ptr(),
r.x,
r.y,
r.w,
r.h
)
);
Ok(ok != 0)
}
pub(crate) fn control_set_state(
&self,
win: &Selector,
ctrl: &Control,
what: ControlState,
) -> Result<bool> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
let (ok, _) = match what {
ControlState::Focus => au3!(
self,
AU3_ControlFocus(w.as_ptr(), EMPTY_WIDE.as_ptr(), c.as_ptr())
),
ControlState::Enable => au3!(
self,
AU3_ControlEnable(w.as_ptr(), EMPTY_WIDE.as_ptr(), c.as_ptr())
),
ControlState::Disable => au3!(
self,
AU3_ControlDisable(w.as_ptr(), EMPTY_WIDE.as_ptr(), c.as_ptr())
),
ControlState::Show => au3!(
self,
AU3_ControlShow(w.as_ptr(), EMPTY_WIDE.as_ptr(), c.as_ptr())
),
ControlState::Hide => au3!(
self,
AU3_ControlHide(w.as_ptr(), EMPTY_WIDE.as_ptr(), c.as_ptr())
),
};
Ok(ok != 0)
}
pub(crate) fn control_command(
&self,
win: &Selector,
ctrl: &Control,
command: &str,
extra: &str,
) -> Result<String> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
let cmd = wide(command, "control command")?;
let ex = wide(extra, "control command argument")?;
self.call_str("AU3_ControlCommand", LARGE_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_ControlCommand(
w.as_ptr(),
EMPTY_WIDE.as_ptr(),
c.as_ptr(),
cmd.as_ptr(),
ex.as_ptr(),
buf,
cap,
)
);
code
})
}
pub(crate) fn control_list_view(
&self,
win: &Selector,
ctrl: &Control,
command: &str,
e1: &str,
e2: &str,
) -> Result<String> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
let cmd = wide(command, "list-view command")?;
let a = wide(e1, "list-view argument")?;
let b = wide(e2, "list-view argument")?;
self.call_str("AU3_ControlListView", LARGE_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_ControlListView(
w.as_ptr(),
EMPTY_WIDE.as_ptr(),
c.as_ptr(),
cmd.as_ptr(),
a.as_ptr(),
b.as_ptr(),
buf,
cap,
)
);
code
})
}
pub(crate) fn control_tree_view(
&self,
win: &Selector,
ctrl: &Control,
command: &str,
e1: &str,
e2: &str,
) -> Result<String> {
let w = self.sel(win)?;
let c = wide(ctrl.as_str(), "control")?;
let cmd = wide(command, "tree-view command")?;
let a = wide(e1, "tree-view argument")?;
let b = wide(e2, "tree-view argument")?;
self.call_str("AU3_ControlTreeView", LARGE_BUF, |buf, cap| {
let (_, code) = au3!(
self,
AU3_ControlTreeView(
w.as_ptr(),
EMPTY_WIDE.as_ptr(),
c.as_ptr(),
cmd.as_ptr(),
a.as_ptr(),
b.as_ptr(),
buf,
cap,
)
);
code
})
}
pub(crate) fn sleep(&self, d: Duration) {
std::thread::sleep(d);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wide_rejects_interior_nul_rather_than_truncating() {
let err = wide("abc\0def", "test").unwrap_err();
match err {
Error::InteriorNul { at, .. } => assert_eq!(at, 3),
other => panic!("expected InteriorNul, got {other:?}"),
}
}
#[test]
fn wide_round_trips_non_ascii() {
let w = wide("Order Entry", "test").unwrap();
assert_eq!(*w.last().unwrap(), 0, "must be NUL-terminated");
assert_eq!(from_wide(&w), "Order Entry");
}
#[test]
fn from_wide_stops_at_the_first_nul() {
let buf = [b'h' as u16, b'i' as u16, 0, b'x' as u16];
assert_eq!(from_wide(&buf), "hi");
}
#[test]
fn timeouts_convert_to_autoits_seconds() {
assert_eq!(timeout_secs(None), 0, "no timeout means wait forever");
assert_eq!(timeout_secs(Some(Duration::from_secs(30))), 30);
assert_eq!(timeout_secs(Some(Duration::from_millis(1))), 1);
assert_eq!(timeout_secs(Some(Duration::ZERO)), 1);
}
}