git.lucas.co / cce-browser
web browser (Servo)
git clone https://git.lucas.co/cce-browser.git

src/bin/open.rs (2.9K)

 1 //! `cce-browser-open` — the launch-latency half of single-instance.
 2 //!
 3 //! The desktop entry's `Exec` points here, not at `cce-browser`: forwarding a
 4 //! link through the full browser binary costs ~20ms of dynamic-library loading
 5 //! (libWPEWebKit and friends) before `main()` runs, all on the click-to-tab
 6 //! path. This bin exists to link nothing, forward in a few ms, and only
 7 //! `exec` the real browser when no instance answers.
 8 //!
 9 //! It therefore deliberately duplicates the client half of the socket
10 //! protocol instead of importing it: `src/instance.rs` (same crate) is the
11 //! server side and the fallback client, `cce_ui::ipc::socket_path` is the
12 //! path convention. All three must agree on `/tmp/cce-browser-<display>.sock`
13 //! and the `open <arg>` / `new-tab` lines. The protocol is small on purpose;
14 //! change it in both files or not at all.
15 
16 use std::io::{BufRead, BufReader, Write};
17 use std::os::unix::net::UnixStream;
18 use std::os::unix::process::CommandExt;
19 
20 fn socket_path() -> String {
21     // Mirrors cce_ui::ipc::socket_path("cce-browser") — not imported, so this
22     // bin stays free of cce-ui's native link flags.
23     match std::env::var("WAYLAND_DISPLAY") {
24         Ok(d) if !d.is_empty() => format!("/tmp/cce-browser-{d}.sock"),
25         _ => "/tmp/cce-browser.sock".to_string(),
26     }
27 }
28 
29 /// One forwarding attempt; false on any failure. Mirrors
30 /// `instance::try_forward`, ack wait included — exiting on write alone races
31 /// the instance actually reading the line.
32 fn try_forward(arg: Option<&str>) -> bool {
33     let Ok(mut stream) = UnixStream::connect(socket_path()) else {
34         return false;
35     };
36     let command = match arg {
37         Some(a) => {
38             // A relative file path is resolved against *this* process's cwd —
39             // the instance's differs, so it must travel absolute.
40             let p = std::path::Path::new(a);
41             let abs = if p.exists() {
42                 std::fs::canonicalize(p)
43                     .ok()
44                     .and_then(|c| c.to_str().map(String::from))
45             } else {
46                 None
47             };
48             format!("open {}\n", abs.as_deref().unwrap_or(a))
49         }
50         None => "new-tab\n".to_string(),
51     };
52     if stream.write_all(command.as_bytes()).is_err() {
53         return false;
54     }
55     let mut reply = String::new();
56     BufReader::new(stream).read_line(&mut reply).is_ok()
57 }
58 
59 fn main() {
60     let args: Vec<String> = std::env::args().skip(1).collect();
61     if try_forward(args.first().map(String::as_str)) {
62         return;
63     }
64     // No instance answered: become one. PATH resolution matches the desktop
65     // entry convention (~/.local/bin first); the browser runs its own
66     // forward_or_claim, which settles any launch race from here on.
67     let err = std::process::Command::new("cce-browser").args(&args).exec();
68     eprintln!("cce-browser-open: could not exec cce-browser: {err}");
69     std::process::exit(1);
70 }