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 }