Skip to main content

extract_browser_url/platform/
windows.rs

1use 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
91///
92/// extract the browser active tab url
93/// - timeout: Sets the time in millionseconds for matching url element. Recommended setting: 3000 milliseconds.
94/// - only_foreground: Search only for browser windows that are in the foreground. Otherwise, search for the browser window that was last used and is currently visible.
95/// ## Example:
96/// ```rust
97/// use extract_browser_url::extract_url;
98/// fn main(){
99///     let url=extract_url(3000,false).unwrap();
100///     println!("the url is: {}",url)
101/// }
102/// ```
103pub 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}