agentmux_launcher/splash_linux/
wayland.rs1use std::error::Error;
17use std::path::{Path, PathBuf};
18use std::time::{Duration, Instant};
19
20use smithay_client_toolkit::{
21 compositor::{CompositorHandler, CompositorState},
22 delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell,
23 delegate_xdg_window,
24 output::{OutputHandler, OutputState},
25 registry::{ProvidesRegistryState, RegistryState},
26 registry_handlers,
27 shell::{
28 xdg::{
29 window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
30 XdgShell,
31 },
32 WaylandSurface,
33 },
34 shm::{slot::SlotPool, Shm, ShmHandler},
35};
36use smithay_client_toolkit::reexports::client::{
37 globals::registry_queue_init,
38 protocol::{wl_output, wl_shm, wl_surface},
39 Connection, QueueHandle,
40};
41
42use super::{
43 fade_alpha, min_hold, pulse_alpha, render_frame, StageList, CARD_H, CARD_W, CORNER_RADIUS_PX,
44 DISMISS_TIMEOUT,
45};
46
47pub(super) fn run(
48 ready_file: &Path,
49 footer: Vec<String>,
50 startup_rx: std::sync::mpsc::Receiver<crate::startup_events::StartupEvent>,
51) -> Result<(), Box<dyn Error>> {
52 let conn = Connection::connect_to_env()?;
53 let (globals, mut event_queue) = registry_queue_init::<SplashState>(&conn)?;
54 let qh = event_queue.handle();
55
56 let compositor = CompositorState::bind(&globals, &qh)?;
57 let shm = Shm::bind(&globals, &qh)?;
58 let xdg_shell = XdgShell::bind(&globals, &qh)?;
59
60 let w = CARD_W as u32;
61 let h = CARD_H as u32;
62
63 let surface = compositor.create_surface(&qh);
64 let window = xdg_shell.create_window(surface, WindowDecorations::None, &qh);
65 window.set_title("AgentMux");
66 window.set_app_id("ai.agentmux.AgentMux");
68 window.set_min_size(Some((w, h)));
69 window.set_max_size(Some((w, h)));
70 window.commit();
71
72 let pool = SlotPool::new((w * h * 4) as usize, &shm)?;
73
74 let mut state = SplashState {
75 registry_state: RegistryState::new(&globals),
76 output_state: OutputState::new(&globals, &qh),
77 shm,
78 pool,
79 window,
80 width: w,
81 height: h,
82 start: Instant::now(),
83 min_hold: min_hold(),
84 ready_file: ready_file.to_path_buf(),
85 fade_start: None,
86 exit: false,
87 footer,
88 startup_rx,
89 stage_list: StageList::new(),
90 };
91
92 while !state.exit {
93 event_queue.blocking_dispatch(&mut state)?;
94 }
95 Ok(())
96}
97
98struct SplashState {
99 registry_state: RegistryState,
100 output_state: OutputState,
101 shm: Shm,
102 pool: SlotPool,
103 window: Window,
104 width: u32,
105 height: u32,
106 start: Instant,
107 min_hold: Duration,
108 ready_file: PathBuf,
109 fade_start: Option<Instant>,
110 exit: bool,
111 footer: Vec<String>,
112 startup_rx: std::sync::mpsc::Receiver<crate::startup_events::StartupEvent>,
113 stage_list: StageList,
114}
115
116impl SplashState {
117 fn should_dismiss(&self) -> bool {
118 let elapsed = self.start.elapsed();
119 (self.ready_file.exists() && elapsed >= self.min_hold) || elapsed >= DISMISS_TIMEOUT
120 }
121
122 fn draw(&mut self, qh: &QueueHandle<Self>) {
126 let mut fresh = Vec::new();
132 while let Ok(event) = self.startup_rx.try_recv() {
133 fresh.push(event);
134 }
135 self.stage_list.apply_tick(fresh);
136
137 let now = Instant::now();
138 if self.fade_start.is_none() && self.should_dismiss() {
141 self.stage_list.finalize_running(now);
146 self.fade_start = Some(now);
147 }
148 let window_alpha = fade_alpha(self.fade_start, now);
149 if self.fade_start.is_some() && window_alpha <= 0.0 {
150 self.exit = true;
151 return;
152 }
153
154 let (w, h) = (self.width as i32, self.height as i32);
155 let stride = w * 4;
156 let (buffer, canvas) = match self
157 .pool
158 .create_buffer(w, h, stride, wl_shm::Format::Argb8888)
159 {
160 Ok(b) => b,
161 Err(_) => {
162 self.exit = true;
163 return;
164 }
165 };
166
167 let brain_alpha = pulse_alpha(self.start.elapsed().as_secs_f32());
168 let stage_lines = self.stage_list.lines();
169 render_frame(
171 canvas,
172 w,
173 h,
174 brain_alpha,
175 window_alpha,
176 CORNER_RADIUS_PX,
177 true,
178 &self.footer,
179 &stage_lines,
180 );
181
182 let surface = self.window.wl_surface();
183 surface.damage_buffer(0, 0, w, h);
184 surface.frame(qh, surface.clone());
185 let _ = buffer.attach_to(surface);
186 self.window.commit();
187 }
188}
189
190impl CompositorHandler for SplashState {
191 fn scale_factor_changed(
192 &mut self,
193 _: &Connection,
194 _: &QueueHandle<Self>,
195 _: &wl_surface::WlSurface,
196 _: i32,
197 ) {
198 }
199 fn transform_changed(
200 &mut self,
201 _: &Connection,
202 _: &QueueHandle<Self>,
203 _: &wl_surface::WlSurface,
204 _: wl_output::Transform,
205 ) {
206 }
207 fn frame(
208 &mut self,
209 _: &Connection,
210 qh: &QueueHandle<Self>,
211 _: &wl_surface::WlSurface,
212 _: u32,
213 ) {
214 self.draw(qh);
215 }
216 fn surface_enter(
217 &mut self,
218 _: &Connection,
219 _: &QueueHandle<Self>,
220 _: &wl_surface::WlSurface,
221 _: &wl_output::WlOutput,
222 ) {
223 }
224 fn surface_leave(
225 &mut self,
226 _: &Connection,
227 _: &QueueHandle<Self>,
228 _: &wl_surface::WlSurface,
229 _: &wl_output::WlOutput,
230 ) {
231 }
232}
233
234impl WindowHandler for SplashState {
235 fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &Window) {
236 self.exit = true;
237 }
238 fn configure(
239 &mut self,
240 _: &Connection,
241 qh: &QueueHandle<Self>,
242 _: &Window,
243 _: WindowConfigure,
244 _: u32,
245 ) {
246 self.draw(qh);
248 }
249}
250
251impl OutputHandler for SplashState {
252 fn output_state(&mut self) -> &mut OutputState {
253 &mut self.output_state
254 }
255 fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
256 fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
257 fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
258}
259
260impl ShmHandler for SplashState {
261 fn shm_state(&mut self) -> &mut Shm {
262 &mut self.shm
263 }
264}
265
266impl ProvidesRegistryState for SplashState {
267 fn registry(&mut self) -> &mut RegistryState {
268 &mut self.registry_state
269 }
270 registry_handlers![OutputState];
271}
272
273delegate_compositor!(SplashState);
274delegate_output!(SplashState);
275delegate_xdg_shell!(SplashState);
276delegate_xdg_window!(SplashState);
277delegate_shm!(SplashState);
278delegate_registry!(SplashState);