-
Notifications
You must be signed in to change notification settings - Fork 873
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* capture url browser * use applescript and only support macOS for now * fix: conditionally get browser URL only on macOS * add changes * rewrite use accessibility api * Remove unused dependency * Add AppleScript support for Arc browser * move deps to macos
- Loading branch information
Showing
14 changed files
with
244 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
screenpipe-server/src/migrations/20250219212616_add-browser_url-column.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
-- Add migration script here | ||
ALTER TABLE frames ADD COLUMN browser_url TEXT DEFAULT NULL; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
use accessibility_sys::{ | ||
kAXChildrenAttribute, kAXFocusedWindowAttribute, kAXRoleAttribute, kAXTextFieldRole, | ||
kAXValueAttribute, AXUIElementCopyAttributeValue, AXUIElementCreateApplication, | ||
AXUIElementRef, | ||
}; | ||
use anyhow::Result; | ||
use core_foundation::{ | ||
base::{CFTypeRef, TCFType}, | ||
string::CFString, | ||
array::CFArray, | ||
}; | ||
use url::Url; | ||
|
||
use super::BrowserUrlDetector; | ||
|
||
pub struct MacOSUrlDetector; | ||
|
||
impl MacOSUrlDetector { | ||
pub fn new() -> Self { | ||
Self | ||
} | ||
|
||
unsafe fn find_url_field(&self, element: AXUIElementRef) -> Option<AXUIElementRef> { | ||
let mut role: CFTypeRef = std::ptr::null_mut(); | ||
let status = AXUIElementCopyAttributeValue( | ||
element, | ||
CFString::from_static_string(kAXRoleAttribute).as_concrete_TypeRef(), | ||
&mut role, | ||
); | ||
|
||
if status == accessibility_sys::kAXErrorSuccess { | ||
let cf_role = CFString::wrap_under_get_rule(role as _); | ||
let role_str = cf_role.to_string(); | ||
|
||
if role_str == kAXTextFieldRole { | ||
let mut value: CFTypeRef = std::ptr::null_mut(); | ||
let status = AXUIElementCopyAttributeValue( | ||
element, | ||
CFString::from_static_string(kAXValueAttribute).as_concrete_TypeRef(), | ||
&mut value, | ||
); | ||
|
||
if status == accessibility_sys::kAXErrorSuccess { | ||
let url_str = CFString::wrap_under_get_rule(value as _).to_string(); | ||
let url_to_parse = if !url_str.starts_with("http://") && !url_str.starts_with("https://") { | ||
format!("https://{}", url_str) | ||
} else { | ||
url_str | ||
}; | ||
|
||
if Url::parse(&url_to_parse).is_ok() { | ||
return Some(element); | ||
} | ||
} | ||
} | ||
} | ||
|
||
let mut children: CFTypeRef = std::ptr::null_mut(); | ||
let status = AXUIElementCopyAttributeValue( | ||
element, | ||
CFString::from_static_string(kAXChildrenAttribute).as_concrete_TypeRef(), | ||
&mut children, | ||
); | ||
|
||
if status == accessibility_sys::kAXErrorSuccess { | ||
let children_array = CFArray::<*const std::ffi::c_void>::wrap_under_get_rule(children as _); | ||
for child in children_array.iter() { | ||
if let Some(found) = self.find_url_field(*child as AXUIElementRef) { | ||
return Some(found); | ||
} | ||
} | ||
} | ||
|
||
None | ||
} | ||
|
||
fn get_url_via_applescript(&self, script: &str) -> Result<Option<String>> { | ||
let output = std::process::Command::new("osascript") | ||
.arg("-e") | ||
.arg(script) | ||
.output()?; | ||
|
||
if output.status.success() { | ||
let url = String::from_utf8(output.stdout)?.trim().to_string(); | ||
return Ok(Some(url)); | ||
} | ||
Ok(None) | ||
} | ||
|
||
fn get_url_via_accessibility(&self, process_id: i32) -> Result<Option<String>> { | ||
unsafe { | ||
let app_element = AXUIElementCreateApplication(process_id); | ||
|
||
let mut focused_window: CFTypeRef = std::ptr::null_mut(); | ||
let status = AXUIElementCopyAttributeValue( | ||
app_element, | ||
CFString::from_static_string(kAXFocusedWindowAttribute).as_concrete_TypeRef(), | ||
&mut focused_window, | ||
); | ||
|
||
if status != accessibility_sys::kAXErrorSuccess { | ||
return Ok(None); | ||
} | ||
|
||
let window_ref = focused_window as AXUIElementRef; | ||
let address_bar = match self.find_url_field(window_ref) { | ||
Some(bar) => bar, | ||
None => return Ok(None), | ||
}; | ||
|
||
let mut url_value: CFTypeRef = std::ptr::null_mut(); | ||
let status = AXUIElementCopyAttributeValue( | ||
address_bar, | ||
CFString::from_static_string(kAXValueAttribute).as_concrete_TypeRef(), | ||
&mut url_value, | ||
); | ||
|
||
if status == accessibility_sys::kAXErrorSuccess { | ||
let url = CFString::wrap_under_get_rule(url_value as _).to_string(); | ||
Ok(Some(url)) | ||
} else { | ||
Ok(None) | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl BrowserUrlDetector for MacOSUrlDetector { | ||
fn get_active_url(&self, app_name: &str, process_id: i32) -> Result<Option<String>> { | ||
if app_name == "Arc" { | ||
let script = r#"tell application "Arc" to return URL of active tab of front window"#; | ||
self.get_url_via_applescript(script) | ||
} else { | ||
self.get_url_via_accessibility(process_id) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use anyhow::Result; | ||
|
||
// Trait definition | ||
pub trait BrowserUrlDetector { | ||
fn get_active_url(&self, app_name: &str, process_id: i32) -> Result<Option<String>>; | ||
} | ||
|
||
// Factory function | ||
pub fn create_url_detector() -> Box<dyn BrowserUrlDetector> { | ||
#[cfg(target_os = "macos")] | ||
return Box::new(MacOSUrlDetector::new()); | ||
|
||
#[cfg(not(target_os = "macos"))] | ||
return Box::new(UnsupportedUrlDetector::new()); | ||
} | ||
|
||
// Unsupported implementation | ||
pub struct UnsupportedUrlDetector; | ||
|
||
impl UnsupportedUrlDetector { | ||
pub fn new() -> Self { | ||
Self | ||
} | ||
} | ||
|
||
impl BrowserUrlDetector for UnsupportedUrlDetector { | ||
fn get_active_url(&self, _app_name: &str, _process_id: i32) -> Result<Option<String>> { | ||
Ok(None) | ||
} | ||
} | ||
|
||
// Re-export MacOS implementation | ||
#[cfg(target_os = "macos")] | ||
mod macos; | ||
#[cfg(target_os = "macos")] | ||
pub use macos::MacOSUrlDetector; |
Oops, something went wrong.