1use std::mem;
2use std::os::windows::prelude::AsRawHandle;
3use std::sync::atomic::{self, AtomicBool};
4use std::sync::{Arc, mpsc};
5use std::thread::{self, JoinHandle};
6
7use parking_lot::Mutex;
8use windows::Win32::Foundation::{ERROR_INVALID_THREAD_ID, HANDLE, LPARAM, WPARAM};
9use windows::Win32::Graphics::Direct3D11::{ID3D11Device, ID3D11DeviceContext};
10use windows::Win32::System::Threading::{GetCurrentThreadId, GetThreadId};
11use windows::Win32::System::WinRT::{
12 CreateDispatcherQueueController, DQTAT_COM_NONE, DQTYPE_THREAD_CURRENT, DispatcherQueueOptions,
13};
14use windows::Win32::UI::WindowsAndMessaging::{
15 DispatchMessageW, GetMessageW, MSG, PostQuitMessage, PostThreadMessageW, TranslateMessage, WM_QUIT,
16};
17use windows::core::Result as WindowsResult;
18use windows_future::AsyncActionCompletedHandler;
19
20use crate::d3d11::{self, create_d3d_device};
21use crate::frame::Frame;
22use crate::graphics_capture_api::{self, GraphicsCaptureApi, InternalCaptureControl};
23use crate::settings::{GraphicsCaptureItemType, Settings};
24use crate::winrt::WinRT;
25
26const fn dispatcher_queue_options() -> DispatcherQueueOptions {
27 DispatcherQueueOptions {
28 dwSize: mem::size_of::<DispatcherQueueOptions>() as u32,
29 threadType: DQTYPE_THREAD_CURRENT,
30 apartmentType: DQTAT_COM_NONE,
31 }
32}
33
34fn run_message_loop<E>() -> Result<(), GraphicsCaptureApiError<E>> {
35 let mut message = MSG::default();
36
37 loop {
38 match unsafe { GetMessageW(&mut message, None, 0, 0).0 } {
39 -1 => return Err(GraphicsCaptureApiError::FailedToRunMessageLoop),
40 0 => return Ok(()),
41 _ => unsafe {
42 let _ = TranslateMessage(&message);
43 DispatchMessageW(&message);
44 },
45 }
46 }
47}
48
49fn join_capture_thread<E>(
50 thread_handle: JoinHandle<Result<(), GraphicsCaptureApiError<E>>>,
51) -> Result<(), CaptureControlError<E>> {
52 match thread_handle.join() {
53 Ok(result) => {
54 result?;
55 Ok(())
56 }
57 Err(_) => Err(CaptureControlError::FailedToJoinThread),
58 }
59}
60
61#[derive(thiserror::Error, Debug)]
62pub enum CaptureControlError<E> {
67 #[error("Failed to join thread")]
72 FailedToJoinThread,
73 #[error("Thread handle is taken out of the struct")]
76 ThreadHandleIsTaken,
77 #[error("Failed to post thread message")]
81 FailedToPostThreadMessage,
82 #[error("Stopped handler error: {0}")]
86 StoppedHandlerError(E),
87 #[error("Windows capture error: {0}")]
91 GraphicsCaptureApiError(#[from] GraphicsCaptureApiError<E>),
92}
93
94pub struct CaptureControl<T: GraphicsCaptureApiHandler + Send + 'static, E> {
96 thread_handle: Option<JoinHandle<Result<(), GraphicsCaptureApiError<E>>>>,
97 halt_handle: Arc<AtomicBool>,
98 callback: Arc<Mutex<T>>,
99}
100
101impl<T: GraphicsCaptureApiHandler + Send + 'static, E> CaptureControl<T, E> {
102 #[inline]
104 #[must_use]
105 pub const fn new(
106 thread_handle: JoinHandle<Result<(), GraphicsCaptureApiError<E>>>,
107 halt_handle: Arc<AtomicBool>,
108 callback: Arc<Mutex<T>>,
109 ) -> Self {
110 Self { thread_handle: Some(thread_handle), halt_handle, callback }
111 }
112
113 #[inline]
115 #[must_use]
116 pub fn is_finished(&self) -> bool {
117 self.thread_handle.as_ref().is_none_or(std::thread::JoinHandle::is_finished)
118 }
119
120 #[inline]
122 #[must_use]
123 pub fn into_thread_handle(self) -> JoinHandle<Result<(), GraphicsCaptureApiError<E>>> {
124 self.thread_handle.unwrap()
125 }
126
127 #[inline]
129 #[must_use]
130 pub fn halt_handle(&self) -> Arc<AtomicBool> {
131 self.halt_handle.clone()
132 }
133
134 #[inline]
136 #[must_use]
137 pub fn callback(&self) -> Arc<Mutex<T>> {
138 self.callback.clone()
139 }
140
141 #[inline]
149 pub fn wait(mut self) -> Result<(), CaptureControlError<E>> {
150 if let Some(thread_handle) = self.thread_handle.take() {
151 join_capture_thread(thread_handle)?;
152 } else {
153 return Err(CaptureControlError::ThreadHandleIsTaken);
154 }
155
156 Ok(())
157 }
158
159 #[inline]
171 pub fn stop(mut self) -> Result<(), CaptureControlError<E>> {
172 self.halt_handle.store(true, atomic::Ordering::Relaxed);
173
174 if let Some(thread_handle) = self.thread_handle.take() {
175 let handle = thread_handle.as_raw_handle();
176 let handle = HANDLE(handle);
177 let thread_id = unsafe { GetThreadId(handle) };
178
179 if thread_id == 0 {
180 if thread_handle.is_finished() {
181 join_capture_thread(thread_handle)?;
182 return Ok(());
183 }
184
185 return Err(CaptureControlError::FailedToPostThreadMessage);
186 }
187
188 loop {
189 match unsafe { PostThreadMessageW(thread_id, WM_QUIT, WPARAM::default(), LPARAM::default()) } {
190 Ok(()) => break,
191 Err(error) => {
192 if thread_handle.is_finished() {
193 break;
194 }
195
196 if error.code() != windows::core::HRESULT::from_win32(ERROR_INVALID_THREAD_ID.0) {
197 return Err(CaptureControlError::FailedToPostThreadMessage);
198 }
199
200 thread::yield_now();
201 }
202 }
203 }
204
205 join_capture_thread(thread_handle)?;
206 } else {
207 return Err(CaptureControlError::ThreadHandleIsTaken);
208 }
209
210 Ok(())
211 }
212}
213
214#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
215pub enum GraphicsCaptureApiError<E> {
217 #[error("Failed to join thread")]
219 FailedToJoinThread,
220 #[error("Failed to initialize WinRT")]
224 FailedToInitWinRT,
225 #[error("Failed to create dispatcher queue controller")]
227 FailedToCreateDispatcherQueueController,
228 #[error("Failed to shut down dispatcher queue")]
230 FailedToShutdownDispatcherQueue,
231 #[error("Failed to set dispatcher queue completed handler")]
233 FailedToSetDispatcherQueueCompletedHandler,
234 #[error("Failed to run the capture thread message loop")]
236 FailedToRunMessageLoop,
237 #[error("Failed to initialize the capture thread")]
239 FailedToStartCaptureThread,
240 #[error("Failed to convert item to `GraphicsCaptureItem`")]
246 ItemConvertFailed,
247 #[error("DirectX error: {0}")]
251 DirectXError(#[from] d3d11::Error),
252 #[error("Graphics capture error: {0}")]
256 GraphicsCaptureApiError(graphics_capture_api::Error),
257 #[error("New handler error: {0}")]
260 NewHandlerError(E),
261 #[error("Frame handler error: {0}")]
265 FrameHandlerError(E),
266}
267
268pub struct Context<Flags> {
270 pub flags: Flags,
272 pub device: ID3D11Device,
274 pub device_context: ID3D11DeviceContext,
276}
277
278pub trait GraphicsCaptureApiHandler: Sized {
280 type Flags;
282
283 type Error: Send + Sync;
286
287 #[inline]
289 fn start<T: TryInto<GraphicsCaptureItemType>>(
290 settings: Settings<Self::Flags, T>,
291 ) -> Result<(), GraphicsCaptureApiError<Self::Error>>
292 where
293 Self: Send + 'static,
294 <Self as GraphicsCaptureApiHandler>::Flags: Send,
295 {
296 let _winrt = WinRT::new().map_err(|_| GraphicsCaptureApiError::FailedToInitWinRT)?;
298
299 let controller = unsafe {
301 CreateDispatcherQueueController(dispatcher_queue_options())
302 .map_err(|_| GraphicsCaptureApiError::FailedToCreateDispatcherQueueController)?
303 };
304
305 let thread_id = unsafe { GetCurrentThreadId() };
307
308 let (d3d_device, d3d_device_context) = create_d3d_device()?;
310
311 let result = Arc::new(Mutex::new(None));
313
314 let ctx =
315 Context { flags: settings.flags, device: d3d_device.clone(), device_context: d3d_device_context.clone() };
316
317 let callback = Arc::new(Mutex::new(Self::new(ctx).map_err(GraphicsCaptureApiError::NewHandlerError)?));
318
319 let mut capture = GraphicsCaptureApi::new(
320 d3d_device,
321 d3d_device_context,
322 settings.item.try_into().map_err(|_| GraphicsCaptureApiError::ItemConvertFailed)?,
323 callback,
324 settings.cursor_capture_settings,
325 settings.draw_border_settings,
326 settings.secondary_window_settings,
327 settings.minimum_update_interval_settings,
328 settings.dirty_region_settings,
329 settings.color_format,
330 thread_id,
331 result.clone(),
332 )
333 .map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?;
334 capture.start_capture().map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?;
335
336 run_message_loop()?;
338
339 let async_action =
341 controller.ShutdownQueueAsync().map_err(|_| GraphicsCaptureApiError::FailedToShutdownDispatcherQueue)?;
342
343 async_action
344 .SetCompleted(&AsyncActionCompletedHandler::new(move |_, _| -> WindowsResult<()> {
345 unsafe { PostQuitMessage(0) };
346 Ok(())
347 }))
348 .map_err(|_| GraphicsCaptureApiError::FailedToSetDispatcherQueueCompletedHandler)?;
349
350 run_message_loop()?;
352
353 capture.stop_capture();
355
356 let result = result.lock().take();
358 if let Some(e) = result {
359 return Err(GraphicsCaptureApiError::FrameHandlerError(e));
360 }
361
362 Ok(())
363 }
364
365 #[inline]
367 fn start_free_threaded<T: TryInto<GraphicsCaptureItemType> + Send + 'static>(
368 settings: Settings<Self::Flags, T>,
369 ) -> Result<CaptureControl<Self, Self::Error>, GraphicsCaptureApiError<Self::Error>>
370 where
371 Self: Send + 'static,
372 <Self as GraphicsCaptureApiHandler>::Flags: Send,
373 {
374 let (halt_sender, halt_receiver) = mpsc::channel::<Arc<AtomicBool>>();
375 let (callback_sender, callback_receiver) = mpsc::channel::<Arc<Mutex<Self>>>();
376
377 let thread_handle = thread::spawn(move || -> Result<(), GraphicsCaptureApiError<Self::Error>> {
378 let _winrt = WinRT::new().map_err(|_| GraphicsCaptureApiError::FailedToInitWinRT)?;
380
381 let controller = unsafe {
383 CreateDispatcherQueueController(dispatcher_queue_options())
384 .map_err(|_| GraphicsCaptureApiError::FailedToCreateDispatcherQueueController)?
385 };
386
387 let thread_id = unsafe { GetCurrentThreadId() };
389
390 let (d3d_device, d3d_device_context) = create_d3d_device()?;
392
393 let result = Arc::new(Mutex::new(None));
395
396 let ctx = Context {
397 flags: settings.flags,
398 device: d3d_device.clone(),
399 device_context: d3d_device_context.clone(),
400 };
401
402 let callback = Arc::new(Mutex::new(Self::new(ctx).map_err(GraphicsCaptureApiError::NewHandlerError)?));
403
404 let mut capture = GraphicsCaptureApi::new(
405 d3d_device,
406 d3d_device_context,
407 settings.item.try_into().map_err(|_| GraphicsCaptureApiError::ItemConvertFailed)?,
408 callback.clone(),
409 settings.cursor_capture_settings,
410 settings.draw_border_settings,
411 settings.secondary_window_settings,
412 settings.minimum_update_interval_settings,
413 settings.dirty_region_settings,
414 settings.color_format,
415 thread_id,
416 result.clone(),
417 )
418 .map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?;
419
420 capture.start_capture().map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?;
421
422 let halt_handle = capture.halt_handle();
424 halt_sender.send(halt_handle).map_err(|_| GraphicsCaptureApiError::FailedToStartCaptureThread)?;
425
426 callback_sender.send(callback).map_err(|_| GraphicsCaptureApiError::FailedToStartCaptureThread)?;
428
429 run_message_loop()?;
431
432 let async_action = controller
434 .ShutdownQueueAsync()
435 .map_err(|_| GraphicsCaptureApiError::FailedToShutdownDispatcherQueue)?;
436
437 async_action
438 .SetCompleted(&AsyncActionCompletedHandler::new(move |_, _| -> Result<(), windows::core::Error> {
439 unsafe { PostQuitMessage(0) };
440 Ok(())
441 }))
442 .map_err(|_| GraphicsCaptureApiError::FailedToSetDispatcherQueueCompletedHandler)?;
443
444 run_message_loop()?;
446
447 capture.stop_capture();
449
450 let result = result.lock().take();
452 if let Some(e) = result {
453 return Err(GraphicsCaptureApiError::FrameHandlerError(e));
454 }
455
456 Ok(())
457 });
458
459 let Ok(halt_handle) = halt_receiver.recv() else {
460 match thread_handle.join() {
461 Ok(Err(error)) => return Err(error),
462 Ok(Ok(())) => return Err(GraphicsCaptureApiError::FailedToStartCaptureThread),
463 Err(_) => {
464 return Err(GraphicsCaptureApiError::FailedToJoinThread);
465 }
466 }
467 };
468
469 let Ok(callback) = callback_receiver.recv() else {
470 match thread_handle.join() {
471 Ok(Err(error)) => return Err(error),
472 Ok(Ok(())) => return Err(GraphicsCaptureApiError::FailedToStartCaptureThread),
473 Err(_) => {
474 return Err(GraphicsCaptureApiError::FailedToJoinThread);
475 }
476 }
477 };
478
479 Ok(CaptureControl::new(thread_handle, halt_handle, callback))
480 }
481
482 fn new(ctx: Context<Self::Flags>) -> Result<Self, Self::Error>;
485
486 fn on_frame_arrived(
488 &mut self,
489 frame: &mut Frame,
490 capture_control: InternalCaptureControl,
491 ) -> Result<(), Self::Error>;
492
493 #[inline]
495 fn on_closed(&mut self) -> Result<(), Self::Error> {
496 Ok(())
497 }
498}