agentmux_launcher/
splash.rs1#![cfg(target_os = "windows")]
36
37use std::thread;
38use windows_sys::Win32::Foundation::*;
39use windows_sys::Win32::Graphics::Gdi::*;
40use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
41use windows_sys::Win32::System::Threading::*;
42use windows_sys::Win32::UI::WindowsAndMessaging::*;
43
44include!(concat!(env!("OUT_DIR"), "/brain_dims.rs"));
48
49const SPLASH_PADDING: i32 = 12;
51const SPLASH_SIZE: i32 = BRAIN_W + SPLASH_PADDING * 2;
52const BRAIN_X: i32 = SPLASH_PADDING;
53const BRAIN_Y: i32 = SPLASH_PADDING;
54
55const BG_B: u8 = 0x1F;
59const BG_G: u8 = 0x1A;
60const BG_R: u8 = 0x1A;
61
62static BRAIN_BGRA: &[u8] =
65 include_bytes!(concat!(env!("OUT_DIR"), "/brain_bgra.bin"));
66
67struct SendHandle(HANDLE);
72unsafe impl Send for SendHandle {}
73impl SendHandle {
74 fn take(self) -> HANDLE { self.0 }
75}
76
77pub fn spawn_splash(dir_hash: &str) -> Option<String> {
81 let event_name = format!("AgentMuxSplash-{}", dir_hash);
82 let nul_name: Vec<u16> = format!("{}\0", event_name)
83 .encode_utf16()
84 .collect();
85
86 let ev = unsafe {
87 CreateEventW(
88 std::ptr::null(), 1, 0, nul_name.as_ptr(),
92 )
93 };
94 if ev.is_null() {
95 crate::log("splash: CreateEventW failed — skipping splash");
96 return None;
97 }
98
99 let handle = SendHandle(ev);
100 thread::spawn(move || unsafe { run_splash(handle.take()) });
101 Some(event_name)
102}
103
104unsafe fn run_splash(dismiss_ev: HANDLE) {
105 let class: Vec<u16> = "AgentMuxSplash\0".encode_utf16().collect();
106 let hinst = GetModuleHandleW(std::ptr::null());
107
108 let wc = WNDCLASSEXW {
109 cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
110 style: 0,
111 lpfnWndProc: Some(DefWindowProcW),
112 cbClsExtra: 0,
113 cbWndExtra: 0,
114 hInstance: hinst,
115 hIcon: std::ptr::null_mut(),
116 hCursor: std::ptr::null_mut(),
117 hbrBackground: std::ptr::null_mut(),
118 lpszMenuName: std::ptr::null(),
119 lpszClassName: class.as_ptr(),
120 hIconSm: std::ptr::null_mut(),
121 };
122 RegisterClassExW(&wc);
124
125 let sw = GetSystemMetrics(SM_CXSCREEN);
126 let sh = GetSystemMetrics(SM_CYSCREEN);
127 let x = (sw - SPLASH_SIZE) / 2;
128 let y = (sh - SPLASH_SIZE) / 2;
129
130 let hwnd = CreateWindowExW(
131 WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,
132 class.as_ptr(),
133 std::ptr::null(), WS_POPUP,
135 x, y, SPLASH_SIZE, SPLASH_SIZE,
136 std::ptr::null_mut(), std::ptr::null_mut(), hinst,
139 std::ptr::null(), );
141 if hwnd.is_null() {
142 CloseHandle(dismiss_ev);
143 return;
144 }
145
146 let screen_dc = GetDC(std::ptr::null_mut());
151 let mem_dc = CreateCompatibleDC(screen_dc);
152 let mut bmi: BITMAPINFO = std::mem::zeroed();
153 bmi.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
154 bmi.bmiHeader.biWidth = SPLASH_SIZE;
155 bmi.bmiHeader.biHeight = -SPLASH_SIZE;
157 bmi.bmiHeader.biPlanes = 1;
158 bmi.bmiHeader.biBitCount = 32;
159 bmi.bmiHeader.biCompression = BI_RGB as u32;
160
161 let mut dib_pixels_raw: *mut core::ffi::c_void = std::ptr::null_mut();
162 let dib = CreateDIBSection(
163 mem_dc,
164 &bmi,
165 DIB_RGB_COLORS,
166 &mut dib_pixels_raw,
167 std::ptr::null_mut(),
168 0,
169 );
170 if dib.is_null() || dib_pixels_raw.is_null() {
171 ReleaseDC(std::ptr::null_mut(), screen_dc);
172 DeleteDC(mem_dc);
173 DestroyWindow(hwnd);
174 CloseHandle(dismiss_ev);
175 return;
176 }
177 let old_obj = SelectObject(mem_dc, dib as _);
178
179 let dib_pixels = std::slice::from_raw_parts_mut(
180 dib_pixels_raw as *mut u8,
181 (SPLASH_SIZE * SPLASH_SIZE * 4) as usize,
182 );
183
184 ShowWindow(hwnd, SW_SHOWNOACTIVATE);
185
186 let start = std::time::Instant::now();
187
188 loop {
191 if WaitForSingleObject(dismiss_ev, 0) == WAIT_OBJECT_0 {
193 fade_out(hwnd, mem_dc, dib_pixels);
194 break;
195 }
196
197 let t = start.elapsed().as_secs_f32();
198 let brain_alpha: u8 = if t < 0.2 {
199 (t / 0.2 * 220.0) as u8
200 } else {
201 let pulse = (((t - 0.2) * std::f32::consts::TAU * 1.1).sin() + 1.0) * 0.5;
202 (160.0 + pulse * 60.0) as u8
203 };
204
205 composite(dib_pixels, brain_alpha);
206 push_layered(hwnd, mem_dc, 255);
207
208 std::thread::sleep(std::time::Duration::from_millis(16)); }
210
211 SelectObject(mem_dc, old_obj);
212 DeleteObject(dib as _);
213 DeleteDC(mem_dc);
214 ReleaseDC(std::ptr::null_mut(), screen_dc);
215 DestroyWindow(hwnd);
216 CloseHandle(dismiss_ev);
217}
218
219fn composite(dib: &mut [u8], brain_alpha: u8) {
226 for px in dib.chunks_exact_mut(4) {
228 px[0] = BG_B;
229 px[1] = BG_G;
230 px[2] = BG_R;
231 px[3] = 0xFF;
232 }
233
234 let ba = brain_alpha as u16;
238 for y in 0..BRAIN_H {
239 let dib_row = ((BRAIN_Y + y) * SPLASH_SIZE * 4) as usize;
240 let src_row = (y * BRAIN_W * 4) as usize;
241 for x in 0..BRAIN_W {
242 let di = dib_row + ((BRAIN_X + x) * 4) as usize;
243 let si = src_row + (x * 4) as usize;
244 let sb = BRAIN_BGRA[si] as u16;
245 let sg = BRAIN_BGRA[si + 1] as u16;
246 let sr = BRAIN_BGRA[si + 2] as u16;
247 let sa = BRAIN_BGRA[si + 3] as u16;
248 if sa == 0 {
249 continue;
250 }
251 let mb = (sb * ba + 127) / 255;
253 let mg = (sg * ba + 127) / 255;
254 let mr = (sr * ba + 127) / 255;
255 let ma = (sa * ba + 127) / 255;
256 let inv = 255u16 - ma;
258 let bb = (dib[di] as u16) * inv / 255 + mb;
259 let bg = (dib[di + 1] as u16) * inv / 255 + mg;
260 let br = (dib[di + 2] as u16) * inv / 255 + mr;
261 dib[di] = bb.min(255) as u8;
262 dib[di + 1] = bg.min(255) as u8;
263 dib[di + 2] = br.min(255) as u8;
264 }
266 }
267}
268
269unsafe fn push_layered(hwnd: HWND, mem_dc: HDC, source_alpha: u8) {
270 let mut sz = SIZE {
271 cx: SPLASH_SIZE,
272 cy: SPLASH_SIZE,
273 };
274 let mut src_pt = POINT { x: 0, y: 0 };
275 let blend = BLENDFUNCTION {
276 BlendOp: AC_SRC_OVER as u8,
277 BlendFlags: 0,
278 SourceConstantAlpha: source_alpha,
279 AlphaFormat: AC_SRC_ALPHA as u8,
280 };
281 UpdateLayeredWindow(
282 hwnd,
283 std::ptr::null_mut(),
284 std::ptr::null(),
285 &mut sz,
286 mem_dc,
287 &mut src_pt,
288 0,
289 &blend,
290 ULW_ALPHA,
291 );
292}
293
294unsafe fn fade_out(hwnd: HWND, mem_dc: HDC, _dib: &mut [u8]) {
298 let mut alpha: i32 = 255;
299 while alpha > 0 {
300 alpha -= 25;
301 if alpha < 0 {
302 alpha = 0;
303 }
304 push_layered(hwnd, mem_dc, alpha as u8);
305 std::thread::sleep(std::time::Duration::from_millis(16));
306 }
307}