git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

src/ipc.rs (3.6K)

 1 //! Helpers for the CCE Unix-socket IPC convention: `/tmp/<prefix>-<WAYLAND_DISPLAY>.sock`.
 2 
 3 use std::io::{Read, Write};
 4 use std::os::unix::net::UnixStream;
 5 
 6 /// Path of a CCE IPC socket for `prefix`, keyed by `$WAYLAND_DISPLAY`.
 7 ///
 8 /// `socket_path("cce")` → `/tmp/cce-<display>.sock` (the compositor control socket);
 9 /// `socket_path("cce-status-interface")` → the status socket. Falls back to
10 /// `/tmp/<prefix>.sock` when `$WAYLAND_DISPLAY` is unset.
11 pub fn socket_path(prefix: &str) -> String {
12     match std::env::var("WAYLAND_DISPLAY") {
13         Ok(d) if !d.is_empty() => format!("/tmp/{}-{}.sock", prefix, d),
14         _ => format!("/tmp/{}.sock", prefix),
15     }
16 }
17 
18 /// Connect to the `prefix` socket, send `command` (newline-terminated), and
19 /// return the reply text. Errors if the socket can't be reached.
20 pub fn send_command(prefix: &str, command: &str) -> std::io::Result<String> {
21     let mut stream = UnixStream::connect(socket_path(prefix))?;
22     stream.write_all(command.as_bytes())?;
23     if !command.ends_with('\n') {
24         stream.write_all(b"\n")?;
25     }
26     let mut reply = String::new();
27     stream.read_to_string(&mut reply)?;
28     Ok(reply)
29 }
30 
31 /// Ask the compositor to dissolve this client's surfaces out, and return how
32 /// long it says that will take.
33 ///
34 /// The fade is the compositor's, not the app's: it ramps the opacity of the
35 /// scene subtree, which carries the backdrop blur, the drop shadow and the
36 /// bevel down with the window. A client fading its own pixels instead leaves
37 /// its surface fully present, so the blur behind it hangs at full strength
38 /// over a dissolving window — and any part of its drawing that is not plain
39 /// vertex alpha (shader-lit plate rims, specular) does not fade at all.
40 ///
41 /// The contract is that the caller keeps its surfaces mapped and its process
42 /// alive for the returned duration and only then exits. `window_runner` does
43 /// that for every [`Application`](crate::backend::window_runner::Application);
44 /// an app driving its own event loop calls this itself. Zero — no compositor,
45 /// nothing of ours on screen, or fading configured off — means exit now.
46 pub fn request_close_fade() -> std::time::Duration {
47     // This runs on the exit path of every cce-ui app, so it does its own
48     // socket call rather than `send_command`: that one reads to EOF with no
49     // deadline, and a compositor wedged mid-frame would hang the quit
50     // forever. A second is far longer than an IPC round trip and short
51     // enough that a user who hit Close still sees the window go.
52     const REPLY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
53     let Ok(mut stream) = UnixStream::connect(socket_path("cce")) else {
54         return std::time::Duration::ZERO;
55     };
56     let _ = stream.set_write_timeout(Some(REPLY_TIMEOUT));
57     let _ = stream.set_read_timeout(Some(REPLY_TIMEOUT));
58     if stream.write_all(b"fade-out\n").is_err() {
59         return std::time::Duration::ZERO;
60     }
61     // The duration is the compositor's to decide (`surface { fade out_ms }`),
62     // so it is read back rather than assumed: the two sides would otherwise
63     // drift apart the moment the config changed, and the visible failure —
64     // the window vanishing partway through its own dissolve — reads as a
65     // rendering bug rather than a disagreement about a number.
66     let mut reply = String::new();
67     if stream.read_to_string(&mut reply).is_err() {
68         return std::time::Duration::ZERO;
69     }
70     let ms: u64 = reply.trim().parse().unwrap_or(0);
71     // The compositor clamps this already; clamped again here because a
72     // client must never be made to hang on a number from the other side.
73     std::time::Duration::from_millis(ms.min(2000))
74 }