window_enumerator/
utils.rs

1use crate::errors::{Result, WindowError};
2use crate::types::WindowInfo;
3
4#[cfg(feature = "selection")]
5use crate::types::Selection;
6
7#[cfg(feature = "sorting")]
8use crate::types::PositionSort; // ← 只保留 PositionSort,删除 SortCriteria
9
10/// Parses a selection string into a [`Selection`] enum.
11///
12/// # Examples
13/// ```
14/// use window_enumerator::parse_selection;
15///
16/// let selection = parse_selection("1,2,3").unwrap();
17/// let all_selection = parse_selection("all").unwrap();
18/// let range_selection = parse_selection("1-3").unwrap();
19/// ```
20///
21/// # Errors
22/// Returns [`WindowError::InvalidSelectionFormat`] if the string cannot be parsed.
23#[cfg(feature = "selection")]
24#[allow(dead_code)] // ← 添加这个属性,因为函数可能被库用户使用
25pub fn parse_selection(selection_str: &str) -> Result<Selection> {
26    let selection_str = selection_str.trim().to_lowercase();
27
28    if selection_str == "all" {
29        return Ok(Selection::All);
30    }
31
32    let mut indices = Vec::new();
33    let parts: Vec<&str> = selection_str.split(',').collect();
34
35    for part in parts {
36        let part = part.trim();
37        if part.contains('-') {
38            // Handle ranges like "1-3"
39            let range_parts: Vec<&str> = part.split('-').collect();
40            if range_parts.len() == 2 {
41                let start = parse_index(range_parts[0].trim())?;
42                let end = parse_index(range_parts[1].trim())?;
43
44                for i in start..=end {
45                    indices.push(i);
46                }
47            } else {
48                return Err(WindowError::InvalidRange);
49            }
50        } else {
51            // Handle single indices
52            let index = parse_index(part)?;
53            indices.push(index);
54        }
55    }
56
57    // Remove duplicates and sort
58    indices.sort();
59    indices.dedup();
60
61    Ok(Selection::Indices(indices))
62}
63
64/// Parses a position sort string into a [`PositionSort`] enum.
65///
66/// # Examples
67/// ```
68/// use window_enumerator::parse_position_sort;
69///
70/// let x_sort = parse_position_sort("x1").unwrap();
71/// let y_sort = parse_position_sort("y-1").unwrap();
72/// let xy_sort = parse_position_sort("x1|y1").unwrap();
73/// ```
74///
75/// # Errors
76/// Returns [`WindowError::InvalidPositionSortFormat`] if the string cannot be parsed.
77#[cfg(feature = "sorting")]
78#[allow(dead_code)] // ← 添加这个属性,因为函数可能被库用户使用
79pub fn parse_position_sort(sort_str: &str) -> Result<Option<PositionSort>> {
80    let sort_str = sort_str.trim().to_lowercase();
81
82    if sort_str.is_empty() {
83        return Ok(None);
84    }
85
86    if sort_str.contains('|') {
87        // Handle "x1|y1" format
88        let parts: Vec<&str> = sort_str.split('|').collect();
89        if parts.len() != 2 {
90            return Err(WindowError::InvalidPositionSortFormat);
91        }
92
93        let x_part = parts[0].trim();
94        let y_part = parts[1].trim();
95
96        let x_order = parse_single_position_order(x_part, 'x')?;
97        let y_order = parse_single_position_order(y_part, 'y')?;
98
99        Ok(Some(PositionSort::XY(x_order, y_order)))
100    } else {
101        // Handle single coordinate sorts
102        if sort_str.starts_with('x') {
103            let order = parse_single_position_order(&sort_str, 'x')?;
104            Ok(Some(PositionSort::X(order)))
105        } else if sort_str.starts_with('y') {
106            let order = parse_single_position_order(&sort_str, 'y')?;
107            Ok(Some(PositionSort::Y(order)))
108        } else {
109            Err(WindowError::InvalidPositionSortFormat)
110        }
111    }
112}
113
114/// Parses a single position sort order (e.g., "x1" -> 1).
115#[cfg(feature = "sorting")]
116#[allow(dead_code)] // ← 添加这个属性,因为函数可能被库用户使用
117fn parse_single_position_order(part: &str, expected_prefix: char) -> Result<i8> {
118    if part.len() < 2 || !part.starts_with(expected_prefix) {
119        return Err(WindowError::InvalidPositionSortFormat);
120    }
121
122    let order_str = &part[1..];
123    match order_str {
124        "1" => Ok(1),
125        "-1" => Ok(-1),
126        _ => Err(WindowError::InvalidSortOrder),
127    }
128}
129
130/// Parses a string into a usize index.
131#[allow(dead_code)] // ← 添加这个属性,因为函数可能被库用户使用
132fn parse_index(s: &str) -> Result<usize> {
133    s.parse().map_err(|_| WindowError::InvalidIndex)
134}
135
136/// Checks if a window matches the given filter criteria.
137///
138/// # Arguments
139///
140/// * `window` - The window to check
141/// * `criteria` - The filter criteria to match against
142///
143/// # Returns
144///
145/// `true` if the window matches all criteria, `false` otherwise.
146pub fn matches_criteria(window: &WindowInfo, criteria: &crate::types::FilterCriteria) -> bool {
147    // PID filter (exact match)
148    if let Some(pid) = criteria.pid {
149        if window.pid != pid {
150            return false;
151        }
152    }
153
154    // Title filter (contains, case-insensitive)
155    if let Some(ref title_filter) = criteria.title_contains {
156        if !title_filter.is_empty()
157            && !window
158                .title
159                .to_lowercase()
160                .contains(&title_filter.to_lowercase())
161        {
162            return false;
163        }
164    }
165
166    // Class name filter (contains, case-insensitive)
167    if let Some(ref class_filter) = criteria.class_name_contains {
168        if !class_filter.is_empty()
169            && !window
170                .class_name
171                .to_lowercase()
172                .contains(&class_filter.to_lowercase())
173        {
174            return false;
175        }
176    }
177
178    // Process name filter (contains, case-insensitive)
179    if let Some(ref process_filter) = criteria.process_name_contains {
180        if !process_filter.is_empty()
181            && !window
182                .process_name
183                .to_lowercase()
184                .contains(&process_filter.to_lowercase())
185        {
186            return false;
187        }
188    }
189
190    // Process file filter (contains, case-insensitive)
191    if let Some(ref file_filter) = criteria.process_file_contains {
192        if !file_filter.is_empty() {
193            let file_str = window.process_file.to_string_lossy().to_lowercase();
194            if !file_str.contains(&file_filter.to_lowercase()) {
195                return false;
196            }
197        }
198    }
199
200    true
201}