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 }