browser_control/cli/
show.rs1use anyhow::{Context, Result};
5use serde::Serialize;
6
7use crate::cli::env_resolver::Source;
8use crate::cli::mcp::{acquire_bidi_lock_if_needed, resolve_browser};
9use crate::cli::output::print_json;
10use crate::detect::Engine;
11use crate::registry::Registry;
12use crate::session::backend::open_backend;
13
14#[derive(Debug, Serialize)]
15struct ShowResult {
16 name: String,
17 engine: Engine,
18 endpoint: String,
19 target_id: String,
20 os_activated: bool,
21}
22
23pub async fn run(browser: Option<String>, json: bool) -> Result<()> {
24 let registry = Registry::open()?;
25 let resolved = resolve_browser(browser).await?;
26 let name = match &resolved.source {
27 Source::Registered { name } => name.clone(),
28 Source::External => "<external>".to_string(),
29 };
30
31 let _bidi_lock = acquire_bidi_lock_if_needed(®istry, &resolved)?;
32 let backend = open_backend(&resolved.endpoint, resolved.engine).await?;
33 let target_id = backend.target_for_show().await?;
34
35 let os_activated = activate_resolved_app(®istry, &resolved.source)
36 .with_context(|| format!("activating browser app for {name}"))?;
37 backend.show_tab(&target_id).await?;
38
39 let result = ShowResult {
40 name,
41 engine: resolved.engine,
42 endpoint: resolved.endpoint,
43 target_id,
44 os_activated,
45 };
46 if json {
47 print_json(&mut std::io::stdout(), &result)?;
48 } else {
49 println!("Shown {}", result.name);
50 println!(" engine: {:?}", result.engine);
51 println!(" endpoint: {}", result.endpoint);
52 println!(" target_id: {}", result.target_id);
53 }
54 Ok(())
55}
56
57pub(crate) fn activate_resolved_app(registry: &Registry, source: &Source) -> Result<bool> {
58 let Source::Registered { name } = source else {
59 return Ok(false);
60 };
61 let Some(row) = registry.get_by_name(name)? else {
62 return Ok(false);
63 };
64 platform_activate(&row.executable)
65}
66
67#[cfg(target_os = "macos")]
68fn platform_activate(executable: &std::path::Path) -> Result<bool> {
69 let Some(app) = app_bundle_for_executable(executable) else {
70 return Ok(false);
71 };
72 let status = std::process::Command::new("open")
73 .arg(&app)
74 .status()
75 .with_context(|| format!("running `open {}`", app.display()))?;
76 if status.success() {
77 Ok(true)
78 } else {
79 anyhow::bail!("`open {}` exited with {status}", app.display())
80 }
81}
82
83#[cfg(not(target_os = "macos"))]
84fn platform_activate(_executable: &std::path::Path) -> Result<bool> {
85 Ok(false)
86}
87
88#[cfg(target_os = "macos")]
89fn app_bundle_for_executable(executable: &std::path::Path) -> Option<std::path::PathBuf> {
90 executable
91 .ancestors()
92 .find(|p| p.extension().and_then(|e| e.to_str()) == Some("app"))
93 .map(std::path::Path::to_path_buf)
94}
95
96#[cfg(test)]
97mod tests {
98 #[cfg(target_os = "macos")]
99 #[test]
100 fn finds_app_bundle_for_macos_executable() {
101 let path =
102 std::path::Path::new("/Applications/Brave Browser.app/Contents/MacOS/Brave Browser");
103 assert_eq!(
104 super::app_bundle_for_executable(path).as_deref(),
105 Some(std::path::Path::new("/Applications/Brave Browser.app"))
106 );
107 }
108}