Skip to main content

browser_control/cli/
start.rs

1//! `start` subcommand: launch a browser and register it.
2
3use anyhow::{anyhow, Context, Result};
4use serde::Serialize;
5use std::path::PathBuf;
6
7use crate::cli::output::print_json;
8use crate::detect::{self, Engine, Installed, Kind};
9use crate::launch::{self, LaunchOpts};
10use crate::paths;
11use crate::registry::{self, BrowserRow, Registry};
12
13#[derive(Debug, Serialize)]
14pub struct StartResult {
15    pub name: String,
16    pub kind: Kind,
17    pub pid: u32,
18    pub engine: Engine,
19    pub endpoint: String,
20    pub profile: PathBuf,
21    pub headless: bool,
22    pub started_at: String,
23    pub reused: bool,
24}
25
26pub async fn run(
27    browser: Option<String>,
28    headless: bool,
29    profile: Option<PathBuf>,
30    json: bool,
31) -> Result<()> {
32    let installed = detect::list_installed();
33    if installed.is_empty() {
34        anyhow::bail!("no supported browsers installed; run `browser-control list-installed`");
35    }
36
37    let resolved_kind: Kind = match browser.as_deref() {
38        None => first_chromium_or_first(&installed)
39            .ok_or_else(|| anyhow!("no chromium-based browser installed"))?,
40        Some(s) => Kind::parse(s).ok_or_else(|| {
41            anyhow!("unknown browser kind `{s}`; valid: chrome, edge, chromium, brave, firefox")
42        })?,
43    };
44
45    let installed_match = installed
46        .iter()
47        .find(|i| i.kind == resolved_kind)
48        .cloned()
49        .ok_or_else(|| {
50            anyhow!(
51                "browser `{}` is not installed on this machine",
52                resolved_kind.as_str()
53            )
54        })?;
55
56    let registry = Registry::open()?;
57    if let Some(row) = registry.first_alive_by_kind(resolved_kind)? {
58        let res = to_result(&row, true);
59        emit(&res, json)?;
60        return Ok(());
61    }
62
63    let name = registry::naming::generate_default(resolved_kind, &registry)?;
64    let profile_dir = match profile {
65        Some(p) => p,
66        None => paths::profiles_dir()?.join(&name),
67    };
68    std::fs::create_dir_all(&profile_dir).context("creating profile directory")?;
69    let opts = LaunchOpts {
70        headless,
71        profile_dir: profile_dir.clone(),
72    };
73    let handle = launch::launch(&installed_match, opts)
74        .await
75        .with_context(|| format!("launching {}", installed_match.executable.display()))?;
76
77    let row = BrowserRow {
78        name: name.clone(),
79        kind: resolved_kind,
80        engine: handle.engine,
81        pid: handle.pid,
82        endpoint: handle.endpoint.clone(),
83        port: handle.port,
84        profile_dir: handle.profile_dir.clone(),
85        executable: installed_match.executable.clone(),
86        headless,
87        started_at: registry::now_iso8601(),
88    };
89    registry.insert(&row)?;
90    let _pid = handle.forget();
91
92    let res = to_result(&row, false);
93    emit(&res, json)?;
94    Ok(())
95}
96
97fn first_chromium_or_first(installed: &[Installed]) -> Option<Kind> {
98    installed
99        .iter()
100        .find(|i| i.kind.is_chromium())
101        .map(|i| i.kind)
102        .or_else(|| installed.first().map(|i| i.kind))
103}
104
105fn to_result(row: &BrowserRow, reused: bool) -> StartResult {
106    StartResult {
107        name: row.name.clone(),
108        kind: row.kind,
109        pid: row.pid,
110        engine: row.engine,
111        endpoint: row.endpoint.clone(),
112        profile: row.profile_dir.clone(),
113        headless: row.headless,
114        started_at: row.started_at.clone(),
115        reused,
116    }
117}
118
119fn emit(res: &StartResult, json: bool) -> Result<()> {
120    if json {
121        print_json(&mut std::io::stdout(), res)?;
122    } else {
123        let reused = if res.reused { " (reused)" } else { "" };
124        println!("Started {}{}", res.name, reused);
125        println!("  kind:     {}", res.kind.as_str());
126        println!("  pid:      {}", res.pid);
127        println!("  engine:   {:?}", res.engine);
128        println!("  endpoint: {}", res.endpoint);
129        println!("  profile:  {}", res.profile.display());
130    }
131    Ok(())
132}