extract_browser_url/platform/
windows.rs1use crate::error::BrowserError;
2use std::ffi::OsString;
3use std::os::windows::ffi::OsStringExt;
4use uiautomation::UIAutomation;
5use uiautomation::controls::ControlType;
6use uiautomation::types::UIProperty;
7use windows::Win32::Foundation::{HWND, LPARAM};
8use windows::Win32::System::ProcessStatus::K32GetModuleBaseNameW;
9use windows::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
10use windows::Win32::UI::WindowsAndMessaging::{
11 EnumWindows, GetForegroundWindow, GetWindowThreadProcessId, IsIconic, IsWindowVisible,
12};
13use windows::core::BOOL;
14
15pub(crate) fn get_foreground_window() -> Result<HWND, BrowserError> {
16 unsafe {
17 let hwnd: HWND = GetForegroundWindow();
18 if hwnd.0 == std::ptr::null_mut() {
19 return Err(BrowserError::FailedFindBrowser);
20 }
21 Ok(hwnd)
22 }
23}
24
25pub(crate) fn get_visible_windows() -> Result<Vec<HWND>, BrowserError> {
26 let mut hwnds = Vec::new();
27 unsafe {
28 if let Err(_) = EnumWindows(
29 Some(enum_windows_callback),
30 LPARAM(&mut hwnds as *mut Vec<HWND> as isize),
31 ) {
32 return Err(BrowserError::FailedEnumWindow);
33 }
34 }
35 Ok(hwnds)
36}
37unsafe extern "system" fn enum_windows_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
38 unsafe {
39 let hwnds = &mut *(lparam.0 as *mut Vec<HWND>);
40 if IsWindowVisible(hwnd).as_bool() && !IsIconic(hwnd).as_bool() {
41 hwnds.push(hwnd);
42 }
43 BOOL::from(true)
44 }
45}
46
47pub enum BrowserType {
48 Firefox,
49 Chrome,
50 Edge,
51}
52
53pub(crate) fn classify_browser(hwnd: HWND) -> Result<(BrowserType, u32), BrowserError> {
54 unsafe {
55 let mut pid = 0u32;
56 GetWindowThreadProcessId(hwnd, Some(&mut pid));
57 let process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid)
58 .map_err(|_| BrowserError::FailedFindBrowser)?;
59 let mut name_buf = vec![0u16; 256];
60 let len = K32GetModuleBaseNameW(process_handle, None, &mut name_buf);
61 let name = OsString::from_wide(&name_buf[0..len as usize])
62 .to_string_lossy()
63 .into_owned();
64 if name == "msedge.exe" {
65 Ok((BrowserType::Edge, pid))
66 } else if name == "chrome.exe" {
67 Ok((BrowserType::Chrome, pid))
68 } else if name == "firefox.exe" {
69 Ok((BrowserType::Firefox, pid))
70 } else {
71 Err(BrowserError::FailedFindBrowser)
72 }
73 }
74}
75
76pub(crate) fn get_browser_window_info(
77 only_foreground: bool,
78) -> Result<(BrowserType, u32), BrowserError> {
79 let visible_windows = if only_foreground {
80 vec![get_foreground_window()?]
81 } else {
82 get_visible_windows()?
83 };
84 for hwnd in visible_windows {
85 return classify_browser(hwnd);
86 }
87 Err(BrowserError::FailedFindBrowser)
88}
89
90
91pub fn extract_url(timeout: u64, only_foreground: bool) -> Result<String, BrowserError> {
104 let (browser_type, pid) = get_browser_window_info(only_foreground)?;
105
106 if let Ok(automation) = UIAutomation::new()
107 && let Ok(root) = automation.get_root_element()
108 {
109 return match browser_type {
110 BrowserType::Firefox => {
111 if let Ok(browser) = automation
112 .create_matcher()
113 .from(root)
114 .timeout(timeout)
115 .process_id(pid)
116 .find_first()
117 && let Ok(ele) = automation
118 .create_matcher()
119 .from(browser)
120 .timeout(timeout)
121 .control_type(ControlType::Edit)
122 .find_first()
123 {
124 let url_variant = ele
125 .get_property_value(UIProperty::ValueValue)
126 .unwrap_or_default();
127 let url = url_variant.get_string().unwrap_or_default();
128 if url == "" {
129 return Err(BrowserError::FailedExtractUrl);
130 }
131 return Ok(url);
132 }
133 Err(BrowserError::FailedFindUrlUI)
134 }
135 BrowserType::Chrome => {
136 if let Ok(browser) = automation
137 .create_matcher()
138 .from(root)
139 .timeout(timeout)
140 .process_id(pid)
141 .find_first()
142 && let Ok(toolbar) = automation
143 .create_matcher()
144 .from(browser)
145 .timeout(timeout)
146 .classname("ToolbarView")
147 .find_first()
148 && let Ok(address_bar) = automation
149 .create_matcher()
150 .from(toolbar)
151 .timeout(timeout)
152 .classname("LocationBarView")
153 .find_first()
154 && let Ok(ele) = automation
155 .create_matcher()
156 .from(address_bar)
157 .timeout(timeout)
158 .control_type(ControlType::Edit)
159 .find_first()
160 {
161 let url_variant = ele
162 .get_property_value(UIProperty::ValueValue)
163 .unwrap_or_default();
164 let url = url_variant.get_string().unwrap_or_default();
165 if url == "" {
166 return Err(BrowserError::FailedExtractUrl);
167 }
168 return Ok(url);
169 }
170 Err(BrowserError::FailedFindUrlUI)
171 }
172 BrowserType::Edge => {
173 if let Ok(browser) = automation
174 .create_matcher()
175 .from(root)
176 .timeout(timeout)
177 .process_id(pid)
178 .find_first()
179 && let Ok(toolbar) = automation
180 .create_matcher()
181 .from(browser)
182 .timeout(timeout)
183 .classname("EdgeToolbarView")
184 .find_first()
185 && let Ok(address_bar) = automation
186 .create_matcher()
187 .from(toolbar)
188 .timeout(timeout)
189 .classname("LocationBarView")
190 .find_first()
191 && let Ok(ele) = automation
192 .create_matcher()
193 .from(address_bar)
194 .timeout(timeout)
195 .control_type(ControlType::Edit)
196 .find_first()
197 {
198 let url_variant = ele
199 .get_property_value(UIProperty::ValueValue)
200 .unwrap_or_default();
201 let url = url_variant.get_string().unwrap_or_default();
202 if url == "" {
203 return Err(BrowserError::FailedExtractUrl);
204 }
205 return Ok(url);
206 }
207 Err(BrowserError::FailedFindUrlUI)
208 }
209 };
210 }
211 Err(BrowserError::FailedFindUrlUI)
212}