1#[cfg(not(target_os = "windows"))]
4use crate::Result;
5
6#[cfg(not(target_os = "windows"))]
7pub struct Overlay;
8
9#[cfg(not(target_os = "windows"))]
10impl Overlay {
11 pub fn new() -> Result<Self> {
12 anyhow::bail!("Windows only")
13 }
14 pub fn show_at(&self, _x: i32, _y: i32, _w: i32, _h: i32) {}
15}
16
17#[cfg(target_os = "windows")]
20pub use win::Overlay;
21
22#[cfg(target_os = "windows")]
23mod win {
24 use std::sync::{Arc, Mutex};
25 use std::thread;
26
27 use windows::Win32::Foundation::{COLORREF, HWND, LPARAM, LRESULT, WPARAM};
28 use windows::Win32::Graphics::Gdi::{
29 CreatePen, CreateSolidBrush, DeleteObject, FillRect, GetDC, HGDIOBJ, PS_SOLID, Rectangle,
30 ReleaseDC, SelectObject,
31 };
32 use windows::Win32::System::LibraryLoader::GetModuleHandleW;
33 use windows::Win32::UI::WindowsAndMessaging::{
34 CreateWindowExW, DefWindowProcW, DispatchMessageW, IDC_ARROW, LWA_COLORKEY, LoadCursorW,
35 MSG, MoveWindow, PM_REMOVE, PeekMessageW, PostQuitMessage, RegisterClassExW,
36 SW_SHOWNOACTIVATE, SetLayeredWindowAttributes, ShowWindow, WM_DESTROY, WNDCLASSEXW,
37 WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TOPMOST, WS_EX_TRANSPARENT,
38 WS_POPUP,
39 };
40 use windows::core::PCWSTR;
41
42 use crate::Result;
43
44 unsafe extern "system" fn wnd_proc(
45 hwnd: HWND,
46 msg: u32,
47 wparam: WPARAM,
48 lparam: LPARAM,
49 ) -> LRESULT {
50 match msg {
51 WM_DESTROY => {
52 unsafe { PostQuitMessage(0) };
53 LRESULT(0)
54 }
55 _ => unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) },
56 }
57 }
58
59 fn draw_border(hwnd: HWND, width: i32, height: i32) {
60 const BORDER: i32 = 3;
61 const COLOR: u32 = 0x00FF00; unsafe {
64 let hdc = GetDC(Some(hwnd));
65 if hdc.is_invalid() {
66 return;
67 }
68
69 let black_brush = CreateSolidBrush(COLORREF(0x000000));
70 FillRect(
71 hdc,
72 &windows::Win32::Foundation::RECT {
73 left: 0,
74 top: 0,
75 right: width,
76 bottom: height,
77 },
78 black_brush,
79 );
80 let _ = DeleteObject(black_brush.into());
81
82 let pen = CreatePen(PS_SOLID, BORDER, COLORREF(COLOR));
83 let old_pen = SelectObject(hdc, HGDIOBJ(pen.0));
84 let fill = CreateSolidBrush(COLORREF(0x000000));
85 let old_brush = SelectObject(hdc, HGDIOBJ(fill.0));
86
87 let _ = Rectangle(hdc, 1, 1, width - 1, height - 1);
88
89 SelectObject(hdc, old_pen);
90 SelectObject(hdc, old_brush);
91 let _ = DeleteObject(HGDIOBJ(pen.0));
92 let _ = DeleteObject(fill.into());
93
94 ReleaseDC(Some(hwnd), hdc);
95 }
96 }
97
98 pub struct Overlay {
101 bounds: Arc<Mutex<Option<(i32, i32, i32, i32)>>>,
102 _thread: thread::JoinHandle<()>,
103 }
104
105 impl Overlay {
106 pub fn new() -> Result<Self> {
107 let bounds: Arc<Mutex<Option<(i32, i32, i32, i32)>>> = Arc::new(Mutex::new(None));
108 let bounds_clone = bounds.clone();
109
110 let handle = thread::spawn(move || unsafe {
111 use windows::Win32::System::Com::{COINIT_MULTITHREADED, CoInitializeEx};
112 let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
113
114 const CLASS: PCWSTR = windows::core::w!("UiCoreOverlay");
115 let instance = GetModuleHandleW(None).expect("GetModuleHandleW");
116
117 let wc = WNDCLASSEXW {
118 cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
119 lpfnWndProc: Some(wnd_proc),
120 hInstance: instance.into(),
121 hCursor: LoadCursorW(None, IDC_ARROW).unwrap_or_default(),
122 lpszClassName: CLASS,
123 ..Default::default()
124 };
125 let _ = RegisterClassExW(&wc);
126
127 let hwnd = CreateWindowExW(
128 WS_EX_LAYERED
129 | WS_EX_TRANSPARENT
130 | WS_EX_TOPMOST
131 | WS_EX_TOOLWINDOW
132 | WS_EX_NOACTIVATE,
133 CLASS,
134 windows::core::w!("overlay"),
135 WS_POPUP,
136 0,
137 0,
138 1,
139 1,
140 None,
141 None,
142 Some(instance.into()),
143 None,
144 )
145 .expect("CreateWindowExW");
146
147 SetLayeredWindowAttributes(hwnd, COLORREF(0x000000), 255, LWA_COLORKEY)
148 .expect("SetLayeredWindowAttributes");
149 let _ = ShowWindow(hwnd, SW_SHOWNOACTIVATE);
150
151 let mut msg = MSG::default();
152 let mut last: Option<(i32, i32, i32, i32)> = None;
153
154 loop {
155 while PeekMessageW(&mut msg, Some(hwnd), 0, 0, PM_REMOVE).as_bool() {
156 if msg.message == WM_DESTROY {
157 return;
158 }
159 let _ = DispatchMessageW(&msg);
160 }
161
162 let current = *bounds_clone.lock().unwrap();
163 if current != last {
164 if let Some((x, y, w, h)) = current {
165 if w > 0 && h > 0 {
166 let _ = MoveWindow(hwnd, x, y, w, h, true);
167 draw_border(hwnd, w, h);
168 }
169 }
170 last = current;
171 }
172
173 thread::sleep(std::time::Duration::from_millis(33));
174 }
175 });
176
177 Ok(Self {
178 bounds,
179 _thread: handle,
180 })
181 }
182
183 pub fn show_at(&self, x: i32, y: i32, w: i32, h: i32) {
185 *self.bounds.lock().unwrap() = Some((x, y, w, h));
186 }
187 }
188}