Skip to content

Commit

Permalink
Merge pull request #13 from leexgone/dev
Browse files Browse the repository at this point in the history
v0.1.8
  • Loading branch information
leexgone authored Jun 29, 2022
2 parents 208df26 + 5e68a60 commit a66f96c
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 22 deletions.
114 changes: 114 additions & 0 deletions README.cn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Rust for windows uiautomation

`uiatomation-rs`包是一套windows自动化体系的封装包,可以辅助你快速进行Windows自动化程序的开发。

## 使用

使用时将下面的依赖行加入到Cargo.toml文件中:

``` toml
[dependencies]
uiautomation = "0.1.8"
```

你可以直接使用封装好的API进行操作。

## 示例程序

### 打印所有界面元素

``` rust
use uiautomation::Result;
use uiautomation::UIAutomation;
use uiautomation::UIElement;
use uiautomation::UITreeWalker;

fn main() {
let automation = UIAutomation::new().unwrap();
let walker = automation.get_control_view_walker().unwrap();
let root = automation.get_root_element().unwrap();

print_element(&walker, &root, 0).unwrap();
}

fn print_element(walker: &UITreeWalker, element: &UIElement, level: usize) -> Result<()> {
for _ in 0..level {
print!(" ")
}
println!("{} - {}", element.get_classname()?, element.get_name()?);

if let Ok(child) = walker.get_first_child(&element) {
print_element(walker, &child, level + 1)?;

let mut next = child;
while let Ok(sibing) = walker.get_next_sibling(&next) {
print_element(walker, &sibing, level + 1)?;

next = sibing;
}
}

Ok(())
}
```

### 打开记事本并模拟写入文本

``` rust
use uiautomation::core::UIAutomation;
use uiautomation::processes::Process;

fn main() {
Process::create("notepad.exe").unwrap();

let automation = UIAutomation::new().unwrap();
let root = automation.get_root_element().unwrap();
let matcher = automation.create_matcher().from(root).timeout(10000).classname("Notepad");
if let Ok(notepad) = matcher.find_first() {
println!("Found: {} - {}", notepad.get_name().unwrap(), notepad.get_classname().unwrap());

notepad.send_keys("Hello,Rust UIAutomation!{enter}", 10).unwrap();

let window: WindowControl = notepad.try_into().unwrap();
window.maximize().unwrap();
}
}
```

### 支持Variant类型属性操作

``` rust
use uiautomation::UIAutomation;
use uiautomation::variants::Variant;
use windows::Win32::UI::Accessibility::UIA_ControlTypePropertyId;
use windows::Win32::UI::Accessibility::UIA_IsEnabledPropertyId;
use windows::Win32::UI::Accessibility::UIA_NamePropertyId;

fn main() {
let automation = UIAutomation::new().unwrap();
let root = automation.get_root_element().unwrap();

let name: Variant = root.get_property_value(UIA_NamePropertyId).unwrap();
println!("name = {}", name.get_string().unwrap());

let ctrl_type: Variant = root.get_property_value(UIA_ControlTypePropertyId).unwrap();
let ctrl_type_id: i32 = ctrl_type.try_into().unwrap();
println!("control type = {}", ctrl_type_id);

let enabled: Variant = root.get_property_value(UIA_IsEnabledPropertyId).unwrap();
let enabled_str: String = enabled.try_into().unwrap();
println!("enabled = {}", enabled_str);
}
```

### 模拟键盘输入

``` rust
use uiautomation::core::UIAutomation;

fn main() {
let automation = UIAutomation::new().unwrap();
let root = automation.get_root_element().unwrap();
root.send_keys("{Win}D", 10).unwrap();
}
```
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ The `uiatomation-rs` crate is a wrapper for windows uiautomation. This crate can

## Usages

Start by adding the following to your Cargo.toml file:
Start by adding the following line to your Cargo.toml file:

``` toml
[dependencies]
uiautomation = "0.1.7"
uiautomation = "0.1.8"
```

Make use of any windows uiautomation calls as needed.
Expand All @@ -25,7 +25,7 @@ use uiautomation::UITreeWalker;

fn main() {
let automation = UIAutomation::new().unwrap();
let walker = automation.create_tree_walker().unwrap();
let walker = automation.get_control_view_walker().unwrap();
let root = automation.get_root_element().unwrap();

print_element(&walker, &root, 0).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion crates/uiautomation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "uiautomation"
version = "0.1.7"
version = "0.1.8"
edition = "2021"
license = "Apache-2.0"
authors = ["Steven Lee <[email protected]>"]
Expand Down
117 changes: 102 additions & 15 deletions crates/uiautomation/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ use windows::core::Interface;
use crate::inputs::Mouse;
use crate::variants::SafeArray;

use super::conditions::AndCondition;
use super::conditions::ClassNameCondition;
use super::conditions::Condition;
use super::conditions::ControlTypeCondition;
Expand Down Expand Up @@ -142,6 +141,14 @@ impl UIAutomation {
Ok(walker.into())
}

/// Retrieves a predefined UICondition that selects control elements.
pub fn get_control_view_condition(&self) -> Result<UICondition> {
let condition = unsafe {
self.automation.ControlViewCondition()?
};
Ok(condition.into())
}

/// Retrieves a predefined UITreeWalker interface that selects control elements.
pub fn get_control_view_walker(&self) -> Result<UITreeWalker> {
let walker = unsafe {
Expand All @@ -150,6 +157,14 @@ impl UIAutomation {
Ok(walker.into())
}

/// Retrieves a predefined UICondition that selects content elements.
pub fn get_content_view_condition(&self) -> Result<UICondition> {
let condition = unsafe {
self.automation.ContentViewCondition()?
};
Ok(condition.into())
}

/// Retrieves a UITreeWalker interface used to discover content elements.
pub fn get_content_view_walker(&self) -> Result<UITreeWalker> {
let walker = unsafe {
Expand Down Expand Up @@ -836,14 +851,27 @@ impl AsRef<IUIAutomationTreeWalker> for UITreeWalker {
}
}

/// Defines the uielement mode when matcher is searching for.
#[derive(Debug)]
pub enum UIMatcherMode {
/// Searches all element.
Raw,
/// Searches control element only.
Control,
/// Searches content element only.
Content
}

/// Defines filter conditions to match specific UI Element.
///
/// `UIMatcher` can find first element or find all elements.
pub struct UIMatcher {
automation: UIAutomation,
mode: UIMatcherMode,
depth: u32,
from: Option<UIElement>,
condition: Option<Box<dyn Condition>>,
// condition: Option<Box<dyn Condition>>,
conditions: Vec<Box<dyn Condition>>,
timeout: u64,
interval: u64,
debug: bool
Expand All @@ -854,15 +882,22 @@ impl UIMatcher {
pub fn new(automation: UIAutomation) -> Self {
UIMatcher {
automation,
mode: UIMatcherMode::Control,
depth: 7,
from: None,
condition: None,
conditions: Vec::new(),
timeout: 3000,
interval: 100,
debug: false
}
}

/// Sets the searching mode. `UIMatcherMode::Control` is default mode.
pub fn mode(mut self, search_mode: UIMatcherMode) -> Self {
self.mode = search_mode;
self
}

/// Sets the root element of the UIAutomation tree whitch should be searched from.
///
/// The root element is desktop by default.
Expand Down Expand Up @@ -893,12 +928,13 @@ impl UIMatcher {

/// Appends a filter condition which is used as `and` logic.
pub fn filter(mut self, condition: Box<dyn Condition>) -> Self {
let filter = if let Some(raw) = self.condition {
Box::new(AndCondition::new(raw, condition))
} else {
condition
};
self.condition = Some(filter);
// let filter = if let Some(raw) = self.condition {
// Box::new(AndCondition::new(raw, condition))
// } else {
// condition
// };
// self.condition = Some(filter);
self.conditions.push(condition);
self
}

Expand Down Expand Up @@ -949,6 +985,13 @@ impl UIMatcher {
self.filter(Box::new(condition))
}

/// Clears all filters.
pub fn reset(mut self) -> Self {
// self.condition = None;
self.conditions.clear();
self
}

/// Set `debug` as `true` to enable debug mode. The debug mode is `false` by default.
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
Expand Down Expand Up @@ -1009,7 +1052,11 @@ impl UIMatcher {
} else {
self.automation.get_root_element()?
};
let walker = self.automation.create_tree_walker()?;
let walker = match self.mode {
UIMatcherMode::Raw => self.automation.create_tree_walker()?,
UIMatcherMode::Control => self.automation.filter_tree_walker(self.automation.get_control_view_condition()?)?,
UIMatcherMode::Content => self.automation.filter_tree_walker(self.automation.get_content_view_condition()?)?,
};

Ok((root, walker))
}
Expand Down Expand Up @@ -1039,11 +1086,25 @@ impl UIMatcher {
}

fn is_matched(&self, element: &UIElement) -> Result<bool> {
let ret = if let Some(ref condition) = self.condition {
condition.judge(element)?
} else {
true
};
if let Some(ref root) = self.from {
if self.automation.compare_elements(root, element)? {
return Ok(false);
}
}

// let ret = if let Some(ref condition) = self.condition {
// condition.judge(element)?
// } else {
// true
// };

let mut ret = true;
for condition in &self.conditions {
ret = condition.judge(element)?;
if !ret {
break;
}
}

if self.debug {
println!("{:?} -> {}", element, ret);
Expand All @@ -1053,6 +1114,21 @@ impl UIMatcher {
}
}

impl Debug for UIMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UIMatcher")
.field("automation", &self.automation)
.field("mode", &self.mode)
.field("depth", &self.depth)
.field("from", &self.from)
.field("conditions", &format!("({} filers)", self.conditions.len()))
.field("timeout", &self.timeout)
.field("interval", &self.interval)
.field("debug", &self.debug)
.finish()
}
}

/// This is the trait for conditions used in filtering when searching for elements in the UI Automation tree.
pub trait IUICondition<T: Interface>: Sized + From<T> + Into<T> + AsRef<T> {
}
Expand Down Expand Up @@ -1479,6 +1555,7 @@ mod tests {
use windows::Win32::UI::Accessibility::TreeScope_Children;
use windows::Win32::UI::Accessibility::UIA_MenuItemControlTypeId;
use windows::Win32::UI::Accessibility::UIA_PaneControlTypeId;
use windows::Win32::UI::Accessibility::UIA_WindowControlTypeId;

use crate::UIAutomation;

Expand Down Expand Up @@ -1529,4 +1606,14 @@ mod tests {
println!("{}, {}", menubar.get_framework_id().unwrap(), menubar.get_classname().unwrap());
}
}

#[test]
fn test_search_from() {
let automation = UIAutomation::new().unwrap();
let matcher = automation.create_matcher();
if let Ok(window) = matcher.classname("Notepad").timeout(0).find_first() {
let nothing = automation.create_matcher().from(window.clone()).control_type(UIA_WindowControlTypeId).find_first();
assert!(nothing.is_err());
}
}
}
4 changes: 2 additions & 2 deletions samples/uia_notepad/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ fn main() {

let automation = UIAutomation::new().unwrap();
let root = automation.get_root_element().unwrap();
let matcher = automation.create_matcher().from(root).timeout(10000).classname("Notepad").debug(true);
let matcher = automation.create_matcher().from(root).timeout(10000).classname("Notepad");
if let Ok(notepad) = matcher.find_first() {
println!("Found: {} - {}", notepad.get_name().unwrap(), notepad.get_classname().unwrap());

notepad.send_keys("Hello,Rust UIAutomation!{enter}", 10).unwrap();
notepad.send_keys("Hello, Rust UIAutomation!{enter}", 10).unwrap();

let window: WindowControl = notepad.try_into().unwrap();
window.maximize().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion samples/uia_print/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use uiautomation::UITreeWalker;

fn main() {
let automation = UIAutomation::new().unwrap();
let walker = automation.create_tree_walker().unwrap();
let walker = automation.get_control_view_walker().unwrap();
let root = automation.get_root_element().unwrap();

print_element(&walker, &root, 0).unwrap();
Expand Down

0 comments on commit a66f96c

Please sign in to comment.