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(browser: Option<String>, headless: bool, json: bool) -> Result<()> {
27    let installed = detect::list_installed();
28    if installed.is_empty() {
29        anyhow::bail!("no supported browsers installed; run `browser-control list-installed`");
30    }
31
32    let resolved_kind: Kind = match browser.as_deref() {
33        None => first_chromium_or_first(&installed)
34            .ok_or_else(|| anyhow!("no chromium-based browser installed"))?,
35        Some(s) => Kind::parse(s).ok_or_else(|| {
36            anyhow!("unknown browser kind `{s}`; valid: chrome, edge, chromium, brave, firefox")
37        })?,
38    };
39
40    let installed_match = installed
41        .iter()
42        .find(|i| i.kind == resolved_kind)
43        .cloned()
44        .ok_or_else(|| {
45            anyhow!(
46                "browser `{}` is not installed on this machine",
47                resolved_kind.as_str()
48            )
49        })?;
50
51    let registry = Registry::open()?;
52    if let Some(row) = registry.first_alive_by_kind(resolved_kind)? {
53        let res = to_result(&row, true);
54        emit(&res, json)?;
55        return Ok(());
56    }
57
58    let name = registry::naming::generate_default(resolved_kind, &registry)?;
59    let profile_dir = paths::default_profile_dir(resolved_kind)?;
60    std::fs::create_dir_all(&profile_dir).context("creating profile directory")?;
61    let opts = LaunchOpts {
62        headless,
63        profile_dir: profile_dir.clone(),
64    };
65    let handle = launch::launch(&installed_match, opts)
66        .await
67        .with_context(|| format!("launching {}", installed_match.executable.display()))?;
68
69    let row = BrowserRow {
70        name: name.clone(),
71        kind: resolved_kind,
72        engine: handle.engine,
73        pid: handle.pid,
74        endpoint: handle.endpoint.clone(),
75        port: handle.port,
76        profile_dir: handle.profile_dir.clone(),
77        executable: installed_match.executable.clone(),
78        headless,
79        started_at: registry::now_iso8601(),
80    };
81    registry.insert(&row)?;
82    let _pid = handle.forget();
83
84    let res = to_result(&row, false);
85    emit(&res, json)?;
86    Ok(())
87}
88
89fn first_chromium_or_first(installed: &[Installed]) -> Option<Kind> {
90    installed
91        .iter()
92        .find(|i| i.kind.is_chromium())
93        .map(|i| i.kind)
94        .or_else(|| installed.first().map(|i| i.kind))
95}
96
97fn to_result(row: &BrowserRow, reused: bool) -> StartResult {
98    StartResult {
99        name: row.name.clone(),
100        kind: row.kind,
101        pid: row.pid,
102        engine: row.engine,
103        endpoint: row.endpoint.clone(),
104        profile: row.profile_dir.clone(),
105        headless: row.headless,
106        started_at: row.started_at.clone(),
107        reused,
108    }
109}
110
111fn emit(res: &StartResult, json: bool) -> Result<()> {
112    if json {
113        print_json(&mut std::io::stdout(), res)?;
114    } else {
115        let reused = if res.reused { " (reused)" } else { "" };
116        println!("Started {}{}", res.name, reused);
117        println!("  kind:     {}", res.kind.as_str());
118        println!("  pid:      {}", res.pid);
119        println!("  engine:   {:?}", res.engine);
120        println!("  endpoint: {}", res.endpoint);
121        println!("  profile:  {}", res.profile.display());
122    }
123    Ok(())
124}