use std::mem;
use std::os::windows::prelude::AsRawHandle;
use std::sync::atomic::{self, AtomicBool};
use std::sync::{Arc, mpsc};
use std::thread::{self, JoinHandle};
use parking_lot::Mutex;
use windows::Win32::Foundation::{ERROR_INVALID_THREAD_ID, HANDLE, LPARAM, WPARAM};
use windows::Win32::Graphics::Direct3D11::{ID3D11Device, ID3D11DeviceContext};
use windows::Win32::System::Threading::{GetCurrentThreadId, GetThreadId};
use windows::Win32::System::WinRT::{
CreateDispatcherQueueController, DQTAT_COM_NONE, DQTYPE_THREAD_CURRENT, DispatcherQueueOptions,
};
use windows::Win32::UI::WindowsAndMessaging::{
DispatchMessageW, GetMessageW, MSG, PostQuitMessage, PostThreadMessageW, TranslateMessage, WM_QUIT,
};
use windows::core::Result as WindowsResult;
use windows_future::AsyncActionCompletedHandler;
use crate::d3d11::{self, create_d3d_device};
use crate::frame::Frame;
use crate::graphics_capture_api::{self, GraphicsCaptureApi, InternalCaptureControl};
use crate::settings::{GraphicsCaptureItemType, Settings};
use crate::winrt::WinRT;
const fn dispatcher_queue_options() -> DispatcherQueueOptions {
DispatcherQueueOptions {
dwSize: mem::size_of::<DispatcherQueueOptions>() as u32,
threadType: DQTYPE_THREAD_CURRENT,
apartmentType: DQTAT_COM_NONE,
}
}
fn run_message_loop<E>() -> Result<(), GraphicsCaptureApiError<E>> {
let mut message = MSG::default();
loop {
match unsafe { GetMessageW(&mut message, None, 0, 0).0 } {
-1 => return Err(GraphicsCaptureApiError::FailedToRunMessageLoop),
0 => return Ok(()),
_ => unsafe {
let _ = TranslateMessage(&message);
DispatchMessageW(&message);
},
}
}
}
fn join_capture_thread<E>(
thread_handle: JoinHandle<Result<(), GraphicsCaptureApiError<E>>>,
) -> Result<(), CaptureControlError<E>> {
match thread_handle.join() {
Ok(result) => {
result?;
Ok(())
}
Err(_) => Err(CaptureControlError::FailedToJoinThread),
}
}
#[derive(thiserror::Error, Debug)]
pub enum CaptureControlError<E> {
#[error("Failed to join thread")]
FailedToJoinThread,
#[error("Thread handle is taken out of the struct")]
ThreadHandleIsTaken,
#[error("Failed to post thread message")]
FailedToPostThreadMessage,
#[error("Stopped handler error: {0}")]
StoppedHandlerError(E),
#[error("Windows capture error: {0}")]
GraphicsCaptureApiError(#[from] GraphicsCaptureApiError<E>),
}
pub struct CaptureControl<T: GraphicsCaptureApiHandler + Send + 'static, E> {
thread_handle: Option<JoinHandle<Result<(), GraphicsCaptureApiError<E>>>>,
halt_handle: Arc<AtomicBool>,
callback: Arc<Mutex<T>>,
}
impl<T: GraphicsCaptureApiHandler + Send + 'static, E> CaptureControl<T, E> {
#[inline]
#[must_use]
pub const fn new(
thread_handle: JoinHandle<Result<(), GraphicsCaptureApiError<E>>>,
halt_handle: Arc<AtomicBool>,
callback: Arc<Mutex<T>>,
) -> Self {
Self { thread_handle: Some(thread_handle), halt_handle, callback }
}
#[inline]
#[must_use]
pub fn is_finished(&self) -> bool {
self.thread_handle.as_ref().is_none_or(std::thread::JoinHandle::is_finished)
}
#[inline]
#[must_use]
pub fn into_thread_handle(self) -> JoinHandle<Result<(), GraphicsCaptureApiError<E>>> {
self.thread_handle.unwrap()
}
#[inline]
#[must_use]
pub fn halt_handle(&self) -> Arc<AtomicBool> {
self.halt_handle.clone()
}
#[inline]
#[must_use]
pub fn callback(&self) -> Arc<Mutex<T>> {
self.callback.clone()
}
#[inline]
pub fn wait(mut self) -> Result<(), CaptureControlError<E>> {
if let Some(thread_handle) = self.thread_handle.take() {
join_capture_thread(thread_handle)?;
} else {
return Err(CaptureControlError::ThreadHandleIsTaken);
}
Ok(())
}
#[inline]
pub fn stop(mut self) -> Result<(), CaptureControlError<E>> {
self.halt_handle.store(true, atomic::Ordering::Relaxed);
if let Some(thread_handle) = self.thread_handle.take() {
let handle = thread_handle.as_raw_handle();
let handle = HANDLE(handle);
let thread_id = unsafe { GetThreadId(handle) };
if thread_id == 0 {
if thread_handle.is_finished() {
join_capture_thread(thread_handle)?;
return Ok(());
}
return Err(CaptureControlError::FailedToPostThreadMessage);
}
loop {
match unsafe { PostThreadMessageW(thread_id, WM_QUIT, WPARAM::default(), LPARAM::default()) } {
Ok(()) => break,
Err(error) => {
if thread_handle.is_finished() {
break;
}
if error.code() != windows::core::HRESULT::from_win32(ERROR_INVALID_THREAD_ID.0) {
return Err(CaptureControlError::FailedToPostThreadMessage);
}
thread::yield_now();
}
}
}
join_capture_thread(thread_handle)?;
} else {
return Err(CaptureControlError::ThreadHandleIsTaken);
}
Ok(())
}
}
#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
pub enum GraphicsCaptureApiError<E> {
#[error("Failed to join thread")]
FailedToJoinThread,
#[error("Failed to initialize WinRT")]
FailedToInitWinRT,
#[error("Failed to create dispatcher queue controller")]
FailedToCreateDispatcherQueueController,
#[error("Failed to shut down dispatcher queue")]
FailedToShutdownDispatcherQueue,
#[error("Failed to set dispatcher queue completed handler")]
FailedToSetDispatcherQueueCompletedHandler,
#[error("Failed to run the capture thread message loop")]
FailedToRunMessageLoop,
#[error("Failed to initialize the capture thread")]
FailedToStartCaptureThread,
#[error("Failed to convert item to `GraphicsCaptureItem`")]
ItemConvertFailed,
#[error("DirectX error: {0}")]
DirectXError(#[from] d3d11::Error),
#[error("Graphics capture error: {0}")]
GraphicsCaptureApiError(graphics_capture_api::Error),
#[error("New handler error: {0}")]
NewHandlerError(E),
#[error("Frame handler error: {0}")]
FrameHandlerError(E),
}
pub struct Context<Flags> {
pub flags: Flags,
pub device: ID3D11Device,
pub device_context: ID3D11DeviceContext,
}
pub trait GraphicsCaptureApiHandler: Sized {
type Flags;
type Error: Send + Sync;
#[inline]
fn start<T: TryInto<GraphicsCaptureItemType>>(
settings: Settings<Self::Flags, T>,
) -> Result<(), GraphicsCaptureApiError<Self::Error>>
where
Self: Send + 'static,
<Self as GraphicsCaptureApiHandler>::Flags: Send,
{
let _winrt = WinRT::new().map_err(|_| GraphicsCaptureApiError::FailedToInitWinRT)?;
let controller = unsafe {
CreateDispatcherQueueController(dispatcher_queue_options())
.map_err(|_| GraphicsCaptureApiError::FailedToCreateDispatcherQueueController)?
};
let thread_id = unsafe { GetCurrentThreadId() };
let (d3d_device, d3d_device_context) = create_d3d_device()?;
let result = Arc::new(Mutex::new(None));
let ctx =
Context { flags: settings.flags, device: d3d_device.clone(), device_context: d3d_device_context.clone() };
let callback = Arc::new(Mutex::new(Self::new(ctx).map_err(GraphicsCaptureApiError::NewHandlerError)?));
let mut capture = GraphicsCaptureApi::new(
d3d_device,
d3d_device_context,
settings.item.try_into().map_err(|_| GraphicsCaptureApiError::ItemConvertFailed)?,
callback,
settings.cursor_capture_settings,
settings.draw_border_settings,
settings.secondary_window_settings,
settings.minimum_update_interval_settings,
settings.dirty_region_settings,
settings.color_format,
thread_id,
result.clone(),
)
.map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?;
capture.start_capture().map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?;
run_message_loop()?;
let async_action =
controller.ShutdownQueueAsync().map_err(|_| GraphicsCaptureApiError::FailedToShutdownDispatcherQueue)?;
async_action
.SetCompleted(&AsyncActionCompletedHandler::new(move |_, _| -> WindowsResult<()> {
unsafe { PostQuitMessage(0) };
Ok(())
}))
.map_err(|_| GraphicsCaptureApiError::FailedToSetDispatcherQueueCompletedHandler)?;
run_message_loop()?;
capture.stop_capture();
let result = result.lock().take();
if let Some(e) = result {
return Err(GraphicsCaptureApiError::FrameHandlerError(e));
}
Ok(())
}
#[inline]
fn start_free_threaded<T: TryInto<GraphicsCaptureItemType> + Send + 'static>(
settings: Settings<Self::Flags, T>,
) -> Result<CaptureControl<Self, Self::Error>, GraphicsCaptureApiError<Self::Error>>
where
Self: Send + 'static,
<Self as GraphicsCaptureApiHandler>::Flags: Send,
{
let (halt_sender, halt_receiver) = mpsc::channel::<Arc<AtomicBool>>();
let (callback_sender, callback_receiver) = mpsc::channel::<Arc<Mutex<Self>>>();
let thread_handle = thread::spawn(move || -> Result<(), GraphicsCaptureApiError<Self::Error>> {
let _winrt = WinRT::new().map_err(|_| GraphicsCaptureApiError::FailedToInitWinRT)?;
let controller = unsafe {
CreateDispatcherQueueController(dispatcher_queue_options())
.map_err(|_| GraphicsCaptureApiError::FailedToCreateDispatcherQueueController)?
};
let thread_id = unsafe { GetCurrentThreadId() };
let (d3d_device, d3d_device_context) = create_d3d_device()?;
let result = Arc::new(Mutex::new(None));
let ctx = Context {
flags: settings.flags,
device: d3d_device.clone(),
device_context: d3d_device_context.clone(),
};
let callback = Arc::new(Mutex::new(Self::new(ctx).map_err(GraphicsCaptureApiError::NewHandlerError)?));
let mut capture = GraphicsCaptureApi::new(
d3d_device,
d3d_device_context,
settings.item.try_into().map_err(|_| GraphicsCaptureApiError::ItemConvertFailed)?,
callback.clone(),
settings.cursor_capture_settings,
settings.draw_border_settings,
settings.secondary_window_settings,
settings.minimum_update_interval_settings,
settings.dirty_region_settings,
settings.color_format,
thread_id,
result.clone(),
)
.map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?;
capture.start_capture().map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?;
let halt_handle = capture.halt_handle();
halt_sender.send(halt_handle).map_err(|_| GraphicsCaptureApiError::FailedToStartCaptureThread)?;
callback_sender.send(callback).map_err(|_| GraphicsCaptureApiError::FailedToStartCaptureThread)?;
run_message_loop()?;
let async_action = controller
.ShutdownQueueAsync()
.map_err(|_| GraphicsCaptureApiError::FailedToShutdownDispatcherQueue)?;
async_action
.SetCompleted(&AsyncActionCompletedHandler::new(move |_, _| -> Result<(), windows::core::Error> {
unsafe { PostQuitMessage(0) };
Ok(())
}))
.map_err(|_| GraphicsCaptureApiError::FailedToSetDispatcherQueueCompletedHandler)?;
run_message_loop()?;
capture.stop_capture();
let result = result.lock().take();
if let Some(e) = result {
return Err(GraphicsCaptureApiError::FrameHandlerError(e));
}
Ok(())
});
let Ok(halt_handle) = halt_receiver.recv() else {
match thread_handle.join() {
Ok(Err(error)) => return Err(error),
Ok(Ok(())) => return Err(GraphicsCaptureApiError::FailedToStartCaptureThread),
Err(_) => {
return Err(GraphicsCaptureApiError::FailedToJoinThread);
}
}
};
let Ok(callback) = callback_receiver.recv() else {
match thread_handle.join() {
Ok(Err(error)) => return Err(error),
Ok(Ok(())) => return Err(GraphicsCaptureApiError::FailedToStartCaptureThread),
Err(_) => {
return Err(GraphicsCaptureApiError::FailedToJoinThread);
}
}
};
Ok(CaptureControl::new(thread_handle, halt_handle, callback))
}
fn new(ctx: Context<Self::Flags>) -> Result<Self, Self::Error>;
fn on_frame_arrived(
&mut self,
frame: &mut Frame,
capture_control: InternalCaptureControl,
) -> Result<(), Self::Error>;
#[inline]
fn on_closed(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}