diff --git a/Cargo.lock b/Cargo.lock index 81133fa..4611b2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,9 +206,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", @@ -247,9 +247,9 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "syn" -version = "2.0.50" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -296,17 +296,17 @@ checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "windows" -version = "0.53.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" dependencies = [ "windows-core", - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] name = "windows-capture" -version = "1.0.61" +version = "1.0.62" dependencies = [ "parking_lot", "rayon", @@ -316,7 +316,7 @@ dependencies = [ [[package]] name = "windows-capture-python" -version = "1.0.61" +version = "1.0.62" dependencies = [ "pyo3", "thiserror", @@ -325,12 +325,12 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.53.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" dependencies = [ "windows-result", - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] @@ -339,7 +339,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] @@ -359,17 +359,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -380,9 +380,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -392,9 +392,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -404,9 +404,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -416,9 +416,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -428,9 +428,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -440,9 +440,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -452,6 +452,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/Cargo.toml b/Cargo.toml index 4de78fd..8c2c431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windows-capture" -version = "1.0.61" +version = "1.0.62" authors = ["NiiightmareXD"] edition = "2021" description = "Fastest Windows Screen Capture Library For Rust 🔥" @@ -19,10 +19,8 @@ categories = [ resolver = "2" [dependencies] -parking_lot = "0.12.1" -rayon = "1.8.1" -thiserror = "1.0.57" -windows = { version = "0.53.0", features = [ +# Windows API +windows = { version = "0.54.0", features = [ "Win32_System_WinRT_Graphics_Capture", "Win32_Graphics_Direct3D11", "Win32_Foundation", @@ -46,6 +44,15 @@ windows = { version = "0.53.0", features = [ "Media_Transcoding", ] } +# Mutex optimization +parking_lot = "0.12.1" + +# Multithreading +rayon = "1.8.1" + +# Error handling +thiserror = "1.0.57" + [package.metadata.docs.rs] default-target = "x86_64-pc-windows-msvc" targets = ["x86_64-pc-windows-msvc"] diff --git a/README.md b/README.md index df83462..902206d 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Add this library to your `Cargo.toml`: ```toml [dependencies] -windows-capture = "1.0.61" +windows-capture = "1.0.62" ``` or run this command @@ -37,51 +37,88 @@ cargo add windows-capture ## Usage ```rust +use std::{ + io::{self, Write}, + time::Instant, +}; + use windows_capture::{ - capture::WindowsCaptureHandler, - frame::{Frame, ImageFormat}, + capture::GraphicsCaptureApiHandler, + encoder::{VideoEncoder, VideoEncoderQuality, VideoEncoderType}, + frame::Frame, graphics_capture_api::InternalCaptureControl, - settings::{ColorFormat, Settings}, - window::Window, + monitor::Monitor, + settings::{ColorFormat, CursorCaptuerSettings, DrawBorderSettings, Settings}, }; -// Struct To Implement The Trait For -struct Capture; +// This struct will be used to handle the capture events. +struct Capture { + // The video encoder that will be used to encode the frames. + encoder: Option, + // To measure the time the capture has been running + start: Instant, +} -impl WindowsCaptureHandler for Capture { - // Any Value To Get From The Settings +impl GraphicsCaptureApiHandler for Capture { + // The type of flags used to get the values from the settings. type Flags = String; - // To Redirect To `CaptureControl` Or Start Method + // The type of error that can occur during capture, the error will be returned from `CaptureControl` and `start` functions. type Error = Box; - // Function That Will Be Called To Create The Struct The Flags Can Be Passed - // From `WindowsCaptureSettings` + // Function that will be called to create the struct. The flags can be passed from settings. fn new(message: Self::Flags) -> Result { println!("Got The Flag: {message}"); - Ok(Self {}) + let encoder = VideoEncoder::new( + VideoEncoderType::Mp4, + VideoEncoderQuality::HD1080p, + 1920, + 1080, + "video.mp4", + )?; + + Ok(Self { + encoder: Some(encoder), + start: Instant::now(), + }) } - // Called Every Time A New Frame Is Available + // Called every time a new frame is available. fn on_frame_arrived( &mut self, frame: &mut Frame, capture_control: InternalCaptureControl, ) -> Result<(), Self::Error> { - println!("New Frame Arrived"); + print!( + "\rRecording for: {} seconds", + self.start.elapsed().as_secs() + ); + io::stdout().flush()?; + + // Send the frame to the video encoder + self.encoder.as_mut().unwrap().send_frame(frame)?; + + // Note: The frame has other uses too for example you can save a single for to a file like this: + // frame.save_as_image("frame.png", ImageFormat::Png)?; + // Or get the raw data like this so you have full control: + // let data = frame.buffer()?; + + // Stop the capture after 6 seconds + if self.start.elapsed().as_secs() >= 6 { + // Finish the encoder and save the video. + self.encoder.take().unwrap().finish()?; - // Save The Frame As An Image To The Specified Path - frame.save_as_image("image.png", ImageFormat::Png)?; + capture_control.stop(); - // Gracefully Stop The Capture Thread - capture_control.stop(); + // Because there wasn't any new lines in previous prints + println!(); + } Ok(()) } - // Called When The Capture Item Closes Usually When The Window Closes, Capture - // Session Will End After This Function Ends + // Optional handler called when the capture item (usually a window) closes. fn on_closed(&mut self) -> Result<(), Self::Error> { println!("Capture Session Closed"); @@ -91,23 +128,24 @@ impl WindowsCaptureHandler for Capture { fn main() { // Gets The Foreground Window, Checkout The Docs For Other Capture Items - let foreground_window = Window::foreground().expect("No Active Window Found"); + let primary_monitor = Monitor::primary().expect("There is no primary monitor"); let settings = Settings::new( // Item To Captue - foreground_window, + primary_monitor, // Capture Cursor - Some(true), + CursorCaptuerSettings::Default, // Draw Borders (None Means Default Api Configuration) - None, - // Kind Of Pixel Format For Frame To Have + DrawBorderSettings::Default, + // The desired color format for the captured frame. ColorFormat::Rgba8, - // Any Value To Pass To The New Function - "It Works".to_string(), + // Additional flags for the capture settings that will be passed to user defined `new` function. + "Yea This Works".to_string(), ) .unwrap(); - // Every Error From `new`, `on_frame_arrived` and `on_closed` Will End Up Here + // Starts the capture and takes control of the current thread. + // The errors from handler trait will end up here Capture::start(settings).expect("Screen Capture Failed"); } ``` diff --git a/examples/basic.rs b/examples/basic.rs index d60292f..af1a0fe 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,48 +1,85 @@ +use std::{ + io::{self, Write}, + time::Instant, +}; + use windows_capture::{ - capture::WindowsCaptureHandler, - frame::{Frame, ImageFormat}, + capture::GraphicsCaptureApiHandler, + encoder::{VideoEncoder, VideoEncoderQuality, VideoEncoderType}, + frame::Frame, graphics_capture_api::InternalCaptureControl, - settings::{ColorFormat, Settings}, - window::Window, + monitor::Monitor, + settings::{ColorFormat, CursorCaptuerSettings, DrawBorderSettings, Settings}, }; -// Struct To Implement The Trait For -struct Capture; +// This struct will be used to handle the capture events. +struct Capture { + // The video encoder that will be used to encode the frames. + encoder: Option, + // To measure the time the capture has been running + start: Instant, +} -impl WindowsCaptureHandler for Capture { - // Any Value To Get From The Settings +impl GraphicsCaptureApiHandler for Capture { + // The type of flags used to get the values from the settings. type Flags = String; - // To Redirect To `CaptureControl` Or Start Method + // The type of error that can occur during capture, the error will be returned from `CaptureControl` and `start` functions. type Error = Box; - // Function That Will Be Called To Create The Struct The Flags Can Be Passed - // From `WindowsCaptureSettings` + // Function that will be called to create the struct. The flags can be passed from settings. fn new(message: Self::Flags) -> Result { println!("Got The Flag: {message}"); - Ok(Self {}) + let encoder = VideoEncoder::new( + VideoEncoderType::Mp4, + VideoEncoderQuality::HD1080p, + 1920, + 1080, + "video.mp4", + )?; + + Ok(Self { + encoder: Some(encoder), + start: Instant::now(), + }) } - // Called Every Time A New Frame Is Available + // Called every time a new frame is available. fn on_frame_arrived( &mut self, frame: &mut Frame, capture_control: InternalCaptureControl, ) -> Result<(), Self::Error> { - println!("New Frame Arrived"); + print!( + "\rRecording for: {} seconds", + self.start.elapsed().as_secs() + ); + io::stdout().flush()?; + + // Send the frame to the video encoder + self.encoder.as_mut().unwrap().send_frame(frame)?; + + // Note: The frame has other uses too for example you can save a single for to a file like this: + // frame.save_as_image("frame.png", ImageFormat::Png)?; + // Or get the raw data like this so you have full control: + // let data = frame.buffer()?; + + // Stop the capture after 6 seconds + if self.start.elapsed().as_secs() >= 6 { + // Finish the encoder and save the video. + self.encoder.take().unwrap().finish()?; - // Save The Frame As An Image To The Specified Path - frame.save_as_image("image.png", ImageFormat::Png)?; + capture_control.stop(); - // Gracefully Stop The Capture Thread - capture_control.stop(); + // Because there wasn't any new lines in previous prints + println!(); + } Ok(()) } - // Called When The Capture Item Closes Usually When The Window Closes, Capture - // Session Will End After This Function Ends + // Optional handler called when the capture item (usually a window) closes. fn on_closed(&mut self) -> Result<(), Self::Error> { println!("Capture Session Closed"); @@ -52,22 +89,23 @@ impl WindowsCaptureHandler for Capture { fn main() { // Gets The Foreground Window, Checkout The Docs For Other Capture Items - let foreground_window = Window::foreground().expect("No Active Window Found"); + let primary_monitor = Monitor::primary().expect("There is no primary monitor"); let settings = Settings::new( // Item To Captue - foreground_window, + primary_monitor, // Capture Cursor - Some(true), + CursorCaptuerSettings::Default, // Draw Borders (None Means Default Api Configuration) - None, - // Kind Of Pixel Format For Frame To Have + DrawBorderSettings::Default, + // The desired color format for the captured frame. ColorFormat::Rgba8, - // Any Value To Pass To The New Function - "It Works".to_string(), + // Additional flags for the capture settings that will be passed to user defined `new` function. + "Yea This Works".to_string(), ) .unwrap(); - // Every Error From `new`, `on_frame_arrived` and `on_closed` Will End Up Here + // Starts the capture and takes control of the current thread. + // The errors from handler trait will end up here Capture::start(settings).expect("Screen Capture Failed"); } diff --git a/src/capture.rs b/src/capture.rs index 5817171..1516d11 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -33,35 +33,42 @@ use crate::{ settings::Settings, }; -/// Used To Handle Capture Control Errors #[derive(thiserror::Error, Debug)] -#[allow(clippy::module_name_repetitions)] pub enum CaptureControlError { - #[error("Failed To Join Thread")] + #[error("Failed to join thread")] FailedToJoinThread, - #[error("Thread Handle Is Taken Out Of Struct")] + #[error("Thread handle is taken out of struct")] ThreadHandleIsTaken, - #[error("Failed To Post Thread Message")] + #[error("Failed to post thread message")] FailedToPostThreadMessage, - #[error(transparent)] + #[error("Stopped handler error: {0}")] StoppedHandlerError(E), - #[error(transparent)] - WindowsCaptureError(#[from] WindowsCaptureError), + #[error("Windows capture error: {0}")] + GraphicsCaptureApiError(#[from] GraphicsCaptureApiError), } -/// Struct Used To Control Capture Thread -#[allow(clippy::module_name_repetitions)] -pub struct CaptureControl { - thread_handle: Option>>>, +/// Used to control the capture session +pub struct CaptureControl { + thread_handle: Option>>>, halt_handle: Arc, callback: Arc>, } -impl CaptureControl { - /// Create A New Capture Control Struct +impl CaptureControl { + /// Creates a new Capture Control struct. + /// + /// # Arguments + /// + /// * `thread_handle` - The join handle for the capture thread. + /// * `halt_handle` - The atomic boolean used to pause the capture thread. + /// * `callback` - The mutex-protected callback struct used to call struct methods directly. + /// + /// # Returns + /// + /// The newly created CaptureControl struct. #[must_use] pub fn new( - thread_handle: JoinHandle>>, + thread_handle: JoinHandle>>, halt_handle: Arc, callback: Arc>, ) -> Self { @@ -72,7 +79,11 @@ impl CaptureControl { } } - /// Check To See If Capture Thread Is Finished + /// Checks to see if the capture thread is finished. + /// + /// # Returns + /// + /// `true` if the capture thread is finished, `false` otherwise. #[must_use] pub fn is_finished(&self) -> bool { self.thread_handle @@ -80,25 +91,41 @@ impl CaptureControl { .map_or(true, std::thread::JoinHandle::is_finished) } - /// Get The Halt Handle Used To Pause The Capture Thread + /// Gets the join handle for the capture thread. + /// + /// # Returns + /// + /// The join handle for the capture thread. #[must_use] - pub fn into_thread_handle(self) -> JoinHandle>> { + pub fn into_thread_handle(self) -> JoinHandle>> { self.thread_handle.unwrap() } - /// Get The Halt Handle Used To Pause The Capture Thread + /// Gets the halt handle used to pause the capture thread. + /// + /// # Returns + /// + /// The halt handle used to pause the capture thread. #[must_use] pub fn halt_handle(&self) -> Arc { self.halt_handle.clone() } - /// Get The Callback Struct Used To Call Struct Methods Directly + /// Gets the callback struct used to call struct methods directly. + /// + /// # Returns + /// + /// The callback struct used to call struct methods directly. #[must_use] pub fn callback(&self) -> Arc> { self.callback.clone() } - /// Wait Until The Capturing Thread Stops + /// Waits until the capturing thread stops. + /// + /// # Returns + /// + /// `Ok(())` if the capturing thread stops successfully, an error otherwise. pub fn wait(mut self) -> Result<(), CaptureControlError> { if let Some(thread_handle) = self.thread_handle.take() { match thread_handle.join() { @@ -114,7 +141,11 @@ impl CaptureControl { Ok(()) } - /// Gracefully Stop The Capture Thread + /// Gracefully stops the capture thread. + /// + /// # Returns + /// + /// `Ok(())` if the capture thread stops successfully, an error otherwise. pub fn stop(mut self) -> Result<(), CaptureControlError> { self.halt_handle.store(true, atomic::Ordering::Relaxed); @@ -154,9 +185,8 @@ impl CaptureControl { } } -/// Used To Handle Capture Control Errors -#[derive(thiserror::Error, Debug)] -pub enum WindowsCaptureError { +#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)] +pub enum GraphicsCaptureApiError { #[error("Failed To Join Thread")] FailedToJoinThread, #[error("Failed To Initialize WinRT")] @@ -168,34 +198,43 @@ pub enum WindowsCaptureError { #[error("Failed To Set Dispatcher Queue Completed Handler")] FailedToSetDispatcherQueueCompletedHandler, #[error("Graphics Capture Error")] - GraphicsCaptureError(graphics_capture_api::Error), + GraphicsCaptureApiError(graphics_capture_api::Error), #[error("New Handler Error")] NewHandlerError(E), #[error("Frame Handler Error")] FrameHandlerError(E), } -/// Event Handler Trait -pub trait WindowsCaptureHandler: Sized { - /// To Get The Message From The Settings +/// A trait representing a graphics capture handler. + +pub trait GraphicsCaptureApiHandler: Sized { + /// The type of flags used to get the values from the settings. type Flags; - /// To Redirect To `CaptureControl` Or `Start` Method + /// The type of error that can occur during capture, the error will be returned from `CaptureControl` and `start` functions. type Error: Send + Sync; - /// Starts The Capture And Takes Control Of The Current Thread - fn start(settings: Settings) -> Result<(), WindowsCaptureError> + /// Starts the capture and takes control of the current thread. + /// + /// # Arguments + /// + /// * `settings` - The capture settings. + /// + /// # Returns + /// + /// Returns `Ok(())` if the capture was successful, otherwise returns an error of type `GraphicsCaptureApiError`. + fn start(settings: Settings) -> Result<(), GraphicsCaptureApiError> where Self: Send + 'static, - ::Flags: Send, + ::Flags: Send, { // Initialize WinRT unsafe { RoInitialize(RO_INIT_MULTITHREADED) - .map_err(|_| WindowsCaptureError::FailedToInitWinRT)?; + .map_err(|_| GraphicsCaptureApiError::FailedToInitWinRT)?; }; - // Create A Dispatcher Queue For Current Thread + // Create a dispatcher queue for the current thread let options = DispatcherQueueOptions { dwSize: u32::try_from(mem::size_of::()).unwrap(), threadType: DQTYPE_THREAD_CURRENT, @@ -203,32 +242,32 @@ pub trait WindowsCaptureHandler: Sized { }; let controller = unsafe { CreateDispatcherQueueController(options) - .map_err(|_| WindowsCaptureError::FailedToCreateDispatcherQueueController)? + .map_err(|_| GraphicsCaptureApiError::FailedToCreateDispatcherQueueController)? }; - // Get Thread Id + // Get current thread ID let thread_id = unsafe { GetCurrentThreadId() }; - // Start Capture + // Start capture let result = Arc::new(Mutex::new(None)); let callback = Arc::new(Mutex::new( - Self::new(settings.flags).map_err(WindowsCaptureError::NewHandlerError)?, + Self::new(settings.flags).map_err(GraphicsCaptureApiError::NewHandlerError)?, )); let mut capture = GraphicsCaptureApi::new( settings.item, callback, - settings.capture_cursor, + settings.cursor_capture, settings.draw_border, settings.color_format, thread_id, result.clone(), ) - .map_err(WindowsCaptureError::GraphicsCaptureError)?; + .map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?; capture .start_capture() - .map_err(WindowsCaptureError::GraphicsCaptureError)?; + .map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?; - // Message Loop + // Message loop let mut message = MSG::default(); unsafe { while GetMessageW(&mut message, None, 0, 0).as_bool() { @@ -237,10 +276,10 @@ pub trait WindowsCaptureHandler: Sized { } } - // Shutdown Dispatcher Queue + // Shutdown dispatcher queue let async_action = controller .ShutdownQueueAsync() - .map_err(|_| WindowsCaptureError::FailedToShutdownDispatcherQueue)?; + .map_err(|_| GraphicsCaptureApiError::FailedToShutdownDispatcherQueue)?; async_action .SetCompleted(&AsyncActionCompletedHandler::new( move |_, _| -> Result<(), windows::core::Error> { @@ -248,9 +287,9 @@ pub trait WindowsCaptureHandler: Sized { Ok(()) }, )) - .map_err(|_| WindowsCaptureError::FailedToSetDispatcherQueueCompletedHandler)?; + .map_err(|_| GraphicsCaptureApiError::FailedToSetDispatcherQueueCompletedHandler)?; - // Final Message Loop + // Final message loop let mut message = MSG::default(); unsafe { while GetMessageW(&mut message, None, 0, 0).as_bool() { @@ -259,81 +298,89 @@ pub trait WindowsCaptureHandler: Sized { } } - // Stop Capturing + // Stop capture capture.stop_capture(); // Uninitialize WinRT unsafe { RoUninitialize() }; - // Check Handler Result + // Check handler result if let Some(e) = result.lock().take() { - return Err(WindowsCaptureError::FrameHandlerError(e)); + return Err(GraphicsCaptureApiError::FrameHandlerError(e)); } Ok(()) } - /// Starts The Capture Without Taking Control Of The Current Thread - #[allow(clippy::too_many_lines)] + /// Starts the capture without taking control of the current thread. + /// + /// # Arguments + /// + /// * `settings` - The capture settings. + /// + /// # Returns + /// + /// Returns `Ok(CaptureControl)` if the capture was successful, otherwise returns an error of type `GraphicsCaptureApiError`. fn start_free_threaded( settings: Settings, - ) -> Result, WindowsCaptureError> + ) -> Result, GraphicsCaptureApiError> where Self: Send + 'static, - ::Flags: Send, + ::Flags: Send, { let (halt_sender, halt_receiver) = mpsc::channel::>(); let (callback_sender, callback_receiver) = mpsc::channel::>>(); - let thread_handle = - thread::spawn(move || -> Result<(), WindowsCaptureError> { + let thread_handle = thread::spawn( + move || -> Result<(), GraphicsCaptureApiError> { // Initialize WinRT unsafe { RoInitialize(RO_INIT_MULTITHREADED) - .map_err(|_| WindowsCaptureError::FailedToInitWinRT)?; + .map_err(|_| GraphicsCaptureApiError::FailedToInitWinRT)?; }; - // Create A Dispatcher Queue For Current Thread + // Create a dispatcher queue for the current thread let options = DispatcherQueueOptions { dwSize: u32::try_from(mem::size_of::()).unwrap(), threadType: DQTYPE_THREAD_CURRENT, apartmentType: DQTAT_COM_NONE, }; let controller = unsafe { - CreateDispatcherQueueController(options) - .map_err(|_| WindowsCaptureError::FailedToCreateDispatcherQueueController)? + CreateDispatcherQueueController(options).map_err(|_| { + GraphicsCaptureApiError::FailedToCreateDispatcherQueueController + })? }; - // Debug Thread ID + // Get current thread ID let thread_id = unsafe { GetCurrentThreadId() }; - // Start Capture + // Start capture let result = Arc::new(Mutex::new(None)); let callback = Arc::new(Mutex::new( - Self::new(settings.flags).map_err(WindowsCaptureError::NewHandlerError)?, + Self::new(settings.flags).map_err(GraphicsCaptureApiError::NewHandlerError)?, )); let mut capture = GraphicsCaptureApi::new( settings.item, callback.clone(), - settings.capture_cursor, + settings.cursor_capture, settings.draw_border, settings.color_format, thread_id, result.clone(), ) - .map_err(WindowsCaptureError::GraphicsCaptureError)?; + .map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?; capture .start_capture() - .map_err(WindowsCaptureError::GraphicsCaptureError)?; + .map_err(GraphicsCaptureApiError::GraphicsCaptureApiError)?; - // Send Halt Handle + // Send halt handle let halt_handle = capture.halt_handle(); halt_sender.send(halt_handle).unwrap(); - // Send Callback + // Send callback callback_sender.send(callback).unwrap(); - // Message Loop + // Message loop let mut message = MSG::default(); unsafe { while GetMessageW(&mut message, None, 0, 0).as_bool() { @@ -342,10 +389,10 @@ pub trait WindowsCaptureHandler: Sized { } } - // Shutdown Dispatcher Queue + // Shutdown dispatcher queue let async_action = controller .ShutdownQueueAsync() - .map_err(|_| WindowsCaptureError::FailedToShutdownDispatcherQueue)?; + .map_err(|_| GraphicsCaptureApiError::FailedToShutdownDispatcherQueue)?; async_action .SetCompleted(&AsyncActionCompletedHandler::new( @@ -354,9 +401,11 @@ pub trait WindowsCaptureHandler: Sized { Ok(()) }, )) - .map_err(|_| WindowsCaptureError::FailedToSetDispatcherQueueCompletedHandler)?; + .map_err(|_| { + GraphicsCaptureApiError::FailedToSetDispatcherQueueCompletedHandler + })?; - // Final Message Loop + // Final message loop let mut message = MSG::default(); unsafe { while GetMessageW(&mut message, None, 0, 0).as_bool() { @@ -365,25 +414,26 @@ pub trait WindowsCaptureHandler: Sized { } } - // Stop Capturing + // Stop capture capture.stop_capture(); // Uninitialize WinRT unsafe { RoUninitialize() }; - // Check Handler Result + // Check handler result if let Some(e) = result.lock().take() { - return Err(WindowsCaptureError::FrameHandlerError(e)); + return Err(GraphicsCaptureApiError::FrameHandlerError(e)); } Ok(()) - }); + }, + ); let Ok(halt_handle) = halt_receiver.recv() else { match thread_handle.join() { Ok(result) => return Err(result.err().unwrap()), Err(_) => { - return Err(WindowsCaptureError::FailedToJoinThread); + return Err(GraphicsCaptureApiError::FailedToJoinThread); } } }; @@ -392,7 +442,7 @@ pub trait WindowsCaptureHandler: Sized { match thread_handle.join() { Ok(result) => return Err(result.err().unwrap()), Err(_) => { - return Err(WindowsCaptureError::FailedToJoinThread); + return Err(GraphicsCaptureApiError::FailedToJoinThread); } } }; @@ -400,19 +450,38 @@ pub trait WindowsCaptureHandler: Sized { Ok(CaptureControl::new(thread_handle, halt_handle, callback)) } - /// Function That Will Be Called To Create The Struct The Flags Can Be - /// Passed From Settings + /// Function that will be called to create the struct. The flags can be passed from settings. + /// + /// # Arguments + /// + /// * `flags` - The flags used to create the struct. + /// + /// # Returns + /// + /// Returns `Ok(Self)` if the struct creation was successful, otherwise returns an error of type `Self::Error`. fn new(flags: Self::Flags) -> Result; - /// Called Every Time A New Frame Is Available + /// Called every time a new frame is available. + /// + /// # Arguments + /// + /// * `frame` - A mutable reference to the captured frame. + /// * `capture_control` - The internal capture control. + /// + /// # Returns + /// + /// Returns `Ok(())` if the frame processing was successful, otherwise returns an error of type `Self::Error`. fn on_frame_arrived( &mut self, frame: &mut Frame, capture_control: InternalCaptureControl, ) -> Result<(), Self::Error>; - /// Optional Handler Called When The Capture Item Closes Usually When The Window Closes, - /// Capture Session Will End After This Function Ends + /// Optional handler called when the capture item (usually a window) closes. + /// + /// # Returns + /// + /// Returns `Ok(())` if the handler execution was successful, otherwise returns an error of type `Self::Error`. fn on_closed(&mut self) -> Result<(), Self::Error> { Ok(()) } diff --git a/src/d3d11.rs b/src/d3d11.rs index bfaf987..8670ac5 100644 --- a/src/d3d11.rs +++ b/src/d3d11.rs @@ -18,10 +18,27 @@ use windows::{ }, }; -/// To Share DirectX Structs Between Threads +#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)] +pub enum Error { + #[error("Failed to create DirectX device with the recommended feature levels")] + FeatureLevelNotSatisfied, + #[error("Windows API Error: {0}")] + WindowsError(#[from] windows::core::Error), +} + +/// Used To Send DirectX Device Across Threads pub struct SendDirectX(pub T); impl SendDirectX { + /// Create A New `SendDirectX` Instance + /// + /// # Arguments + /// + /// * `device` - The DirectX Device + /// + /// # Returns + /// + /// Returns A New `SendDirectX` Instance pub const fn new(device: T) -> Self { Self(device) } @@ -30,18 +47,12 @@ impl SendDirectX { #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for SendDirectX {} -/// Used To Handle DirectX Errors -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Failed To Create DirectX Device With The Recommended Feature Level")] - FeatureLevelNotSatisfied, - #[error(transparent)] - WindowsError(#[from] windows::core::Error), -} - -/// Create `ID3D11Device` An`ID3D11DeviceContext`xt +/// Create `ID3D11Device` and `ID3D11DeviceContext` pub fn create_d3d_device() -> Result<(ID3D11Device, ID3D11DeviceContext), Error> { - // Set Feature Flags + // Array of Direct3D feature levels. + // The feature levels are listed in descending order of capability. + // The highest feature level supported by the system is at index 0. + // The lowest feature level supported by the system is at the last index. let feature_flags = [ D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, @@ -52,7 +63,6 @@ pub fn create_d3d_device() -> Result<(ID3D11Device, ID3D11DeviceContext), Error> D3D_FEATURE_LEVEL_9_1, ]; - // Try To Build A Hardware Device let mut d3d_device = None; let mut feature_level = D3D_FEATURE_LEVEL::default(); let mut d3d_device_context = None; @@ -77,7 +87,7 @@ pub fn create_d3d_device() -> Result<(ID3D11Device, ID3D11DeviceContext), Error> Ok((d3d_device.unwrap(), d3d_device_context.unwrap())) } -/// Create A `IDirect3DDevice` From `ID3D11Device` +/// Create `IDirect3DDevice` From `ID3D11Device` pub fn create_direct3d_device(d3d_device: &ID3D11Device) -> Result { let dxgi_device: IDXGIDevice = d3d_device.cast()?; let inspectable = unsafe { CreateDirect3D11DeviceFromDXGIDevice(&dxgi_device)? }; diff --git a/src/encoder.rs b/src/encoder.rs new file mode 100644 index 0000000..8dd9e89 --- /dev/null +++ b/src/encoder.rs @@ -0,0 +1,422 @@ +use std::{ + fs::{self, File}, + path::Path, + sync::{ + atomic::{self, AtomicBool}, + mpsc, Arc, + }, + thread::{self, JoinHandle}, +}; + +use parking_lot::{Condvar, Mutex}; +use windows::{ + core::HSTRING, + Foundation::{EventRegistrationToken, TimeSpan, TypedEventHandler}, + Graphics::{ + DirectX::Direct3D11::IDirect3DSurface, + Imaging::{BitmapAlphaMode, BitmapEncoder, BitmapPixelFormat}, + }, + Media::{ + Core::{ + MediaStreamSample, MediaStreamSource, MediaStreamSourceSampleRequestedEventArgs, + MediaStreamSourceStartingEventArgs, VideoStreamDescriptor, + }, + MediaProperties::{ + MediaEncodingProfile, MediaEncodingSubtypes, VideoEncodingProperties, + VideoEncodingQuality, + }, + Transcoding::MediaTranscoder, + }, + Storage::{ + FileAccessMode, StorageFile, + Streams::{Buffer, DataReader, InMemoryRandomAccessStream, InputStreamOptions}, + }, +}; + +use crate::{ + d3d11::SendDirectX, + frame::{Frame, ImageFormat}, + settings::ColorFormat, +}; + +#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)] +pub enum ImageEncoderError { + #[error("This color format is not supported for saving as image")] + UnsupportedFormat, + #[error("Windows API Error: {0}")] + WindowsError(#[from] windows::core::Error), +} + +/// The `ImageEncoder` struct represents an image encoder that can be used to encode image buffers to image bytes with a specified format and color format. +pub struct ImageEncoder { + format: ImageFormat, + color_format: ColorFormat, +} + +impl ImageEncoder { + /// Create a new ImageEncoder with the specified format and color format. + /// + /// # Arguments + /// + /// * `format` - The desired image format. + /// * `color_format` - The desired color format. + /// + /// # Returns + /// + /// A new `ImageEncoder` instance. + pub const fn new(format: ImageFormat, color_format: ColorFormat) -> Self { + Self { + format, + color_format, + } + } + + /// Encode the image buffer to image bytes with the specified format. + /// + /// # Arguments + /// + /// * `image_buffer` - The image buffer to encode. + /// * `width` - The width of the image. + /// * `height` - The height of the image. + /// + /// # Returns + /// + /// The encoded image bytes as a `Vec`. + /// + /// # Errors + /// + /// Returns an `Error` if the encoding fails or if the color format is unsupported. + pub fn encode( + &self, + image_buffer: &[u8], + width: u32, + height: u32, + ) -> Result, ImageEncoderError> { + let encoder = match self.format { + ImageFormat::Jpeg => BitmapEncoder::JpegEncoderId()?, + ImageFormat::Png => BitmapEncoder::PngEncoderId()?, + ImageFormat::Gif => BitmapEncoder::GifEncoderId()?, + ImageFormat::Tiff => BitmapEncoder::TiffEncoderId()?, + ImageFormat::Bmp => BitmapEncoder::BmpEncoderId()?, + ImageFormat::JpegXr => BitmapEncoder::JpegXREncoderId()?, + }; + + let stream = InMemoryRandomAccessStream::new()?; + let encoder = BitmapEncoder::CreateAsync(encoder, &stream)?.get()?; + + let pixelformat = match self.color_format { + ColorFormat::Bgra8 => BitmapPixelFormat::Bgra8, + ColorFormat::Rgba8 => BitmapPixelFormat::Rgba8, + ColorFormat::Rgba16F => return Err(ImageEncoderError::UnsupportedFormat), + }; + + encoder.SetPixelData( + pixelformat, + BitmapAlphaMode::Premultiplied, + width, + height, + 1.0, + 1.0, + image_buffer, + )?; + + encoder.FlushAsync()?.get()?; + + let buffer = Buffer::Create(u32::try_from(stream.Size()?).unwrap())?; + stream + .ReadAsync(&buffer, buffer.Capacity()?, InputStreamOptions::None)? + .get()?; + + let data_reader = DataReader::FromBuffer(&buffer)?; + let length = data_reader.UnconsumedBufferLength()?; + let mut bytes = vec![0u8; length as usize]; + data_reader.ReadBytes(&mut bytes)?; + + Ok(bytes) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum VideoEncoderError { + #[error("Windows API Error: {0}")] + WindowsError(#[from] windows::core::Error), + #[error("Frame send error")] + FrameSendError(#[from] mpsc::SendError, TimeSpan)>>), + #[error("IO Error: {0}")] + IoError(#[from] std::io::Error), +} + +unsafe impl Send for VideoEncoderError {} +unsafe impl Sync for VideoEncoderError {} + +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub enum VideoEncoderType { + Avi, + Hevc, + Mp4, + Wmv, +} + +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub enum VideoEncoderQuality { + Auto = 0, + HD1080p = 1, + HD720p = 2, + Wvga = 3, + Ntsc = 4, + Pal = 5, + Vga = 6, + Qvga = 7, + Uhd2160p = 8, + Uhd4320p = 9, +} + +/// The `VideoEncoder` struct represents a video encoder that can be used to encode video frames and save them to a specified file path. +pub struct VideoEncoder { + first_timespan: Option, + frame_sender: mpsc::Sender, TimeSpan)>>, + sample_requested: EventRegistrationToken, + media_stream_source: MediaStreamSource, + starting: EventRegistrationToken, + transcode_thread: Option>>, + frame_notify: Arc<(Mutex, Condvar)>, + error_notify: Arc, +} + +impl VideoEncoder { + /// Creates a new `VideoEncoder` instance with the specified parameters. + /// + /// # Arguments + /// + /// * `encoder_type` - The type of video encoder to use. + /// * `encoder_quality` - The quality of the video encoder. + /// * `width` - The width of the video frames. + /// * `height` - The height of the video frames. + /// * `path` - The file path where the encoded video will be saved. + /// + /// # Returns + /// + /// Returns a `Result` containing the `VideoEncoder` instance if successful, or a + /// `VideoEncoderError` if an error occurs. + pub fn new>( + encoder_type: VideoEncoderType, + encoder_quality: VideoEncoderQuality, + width: u32, + height: u32, + path: P, + ) -> Result { + let path = path.as_ref(); + + let media_encoding_profile = match encoder_type { + VideoEncoderType::Avi => { + MediaEncodingProfile::CreateAvi(VideoEncodingQuality(encoder_quality as i32))? + } + VideoEncoderType::Hevc => { + MediaEncodingProfile::CreateHevc(VideoEncodingQuality(encoder_quality as i32))? + } + VideoEncoderType::Mp4 => { + MediaEncodingProfile::CreateMp4(VideoEncodingQuality(encoder_quality as i32))? + } + VideoEncoderType::Wmv => { + MediaEncodingProfile::CreateWmv(VideoEncodingQuality(encoder_quality as i32))? + } + }; + + let video_encoding_properties = VideoEncodingProperties::CreateUncompressed( + &MediaEncodingSubtypes::Bgra8()?, + width, + height, + )?; + + let video_stream_descriptor = VideoStreamDescriptor::Create(&video_encoding_properties)?; + + let media_stream_source = + MediaStreamSource::CreateFromDescriptor(&video_stream_descriptor)?; + media_stream_source.SetBufferTime(TimeSpan::default())?; + + let (frame_sender, frame_receiver) = + mpsc::channel::, TimeSpan)>>(); + + let starting = media_stream_source.Starting(&TypedEventHandler::< + MediaStreamSource, + MediaStreamSourceStartingEventArgs, + >::new(move |_, stream_start| { + let stream_start = stream_start + .as_ref() + .expect("MediaStreamSource Starting parameter was None This Should Not Happen."); + + stream_start + .Request()? + .SetActualStartPosition(TimeSpan { Duration: 0 })?; + Ok(()) + }))?; + + let frame_notify = Arc::new((Mutex::new(false), Condvar::new())); + + let sample_requested = media_stream_source.SampleRequested(&TypedEventHandler::< + MediaStreamSource, + MediaStreamSourceSampleRequestedEventArgs, + >::new({ + let frame_receiver = frame_receiver; + let frame_notify = frame_notify.clone(); + + move |_, sample_requested| { + let sample_requested = sample_requested.as_ref().expect( + "MediaStreamSource SampleRequested parameter was None This Should Not Happen.", + ); + + let frame = match frame_receiver.recv() { + Ok(frame) => frame, + Err(e) => panic!("Failed to receive frame from frame sender: {e}"), + }; + + match frame { + Some((surface, timespan)) => { + let sample = + MediaStreamSample::CreateFromDirect3D11Surface(&surface.0, timespan)?; + sample_requested.Request()?.SetSample(&sample)?; + } + None => { + sample_requested.Request()?.SetSample(None)?; + } + } + + let (lock, cvar) = &*frame_notify; + *lock.lock() = true; + cvar.notify_one(); + + Ok(()) + } + }))?; + + let media_transcoder = MediaTranscoder::new()?; + media_transcoder.SetHardwareAccelerationEnabled(true)?; + + File::create(path)?; + let path = fs::canonicalize(path).unwrap().to_string_lossy()[4..].to_string(); + let path = Path::new(&path); + + let path = &HSTRING::from(path.as_os_str().to_os_string()); + + let file = StorageFile::GetFileFromPathAsync(path)?.get()?; + let media_stream_output = file.OpenAsync(FileAccessMode::ReadWrite)?.get()?; + + let transcode = media_transcoder + .PrepareMediaStreamSourceTranscodeAsync( + &media_stream_source, + &media_stream_output, + &media_encoding_profile, + )? + .get()?; + + let error_notify = Arc::new(AtomicBool::new(false)); + let transcode_thread = thread::spawn({ + let error_notify = error_notify.clone(); + + move || -> Result<(), VideoEncoderError> { + let result = transcode.TranscodeAsync(); + + if result.is_err() { + error_notify.store(true, atomic::Ordering::Relaxed); + } + + result?.get()?; + + drop(media_transcoder); + + Ok(()) + } + }); + + Ok(Self { + first_timespan: None, + frame_sender, + sample_requested, + media_stream_source, + starting, + transcode_thread: Some(transcode_thread), + frame_notify, + error_notify, + }) + } + + /// Sends a video frame to the video encoder for encoding. + /// + /// # Arguments + /// + /// * `frame` - A mutable reference to the `Frame` to be encoded. + /// + /// # Returns + /// + /// Returns `Ok(())` if the frame is successfully sent for encoding, or a `VideoEncoderError` + /// if an error occurs. + pub fn send_frame(&mut self, frame: &mut Frame) -> Result<(), VideoEncoderError> { + let timespan = match self.first_timespan { + Some(timespan) => TimeSpan { + Duration: frame.timespan().Duration - timespan.Duration, + }, + None => { + let timespan = frame.timespan(); + self.first_timespan = Some(timespan); + TimeSpan { Duration: 0 } + } + }; + + let surface = SendDirectX(unsafe { frame.as_raw_surface() }); + + self.frame_sender.send(Some((surface, timespan)))?; + + let (lock, cvar) = &*self.frame_notify; + let mut processed = lock.lock(); + if !*processed { + cvar.wait(&mut processed); + } + *processed = false; + drop(processed); + + if self.error_notify.load(atomic::Ordering::Relaxed) { + if let Some(transcode_thread) = self.transcode_thread.take() { + transcode_thread + .join() + .expect("Failed to join transcode thread")?; + } + } + + Ok(()) + } + + /// Finishes encoding the video and performs any necessary cleanup. + /// + /// # Returns + /// + /// Returns `Ok(())` if the encoding is successfully finished, or a `VideoEncoderError` if an + /// error occurs. + pub fn finish(mut self) -> Result<(), VideoEncoderError> { + self.frame_sender.send(None)?; + + if let Some(transcode_thread) = self.transcode_thread.take() { + transcode_thread + .join() + .expect("Failed to join transcode thread")?; + } + + self.media_stream_source.RemoveStarting(self.starting)?; + self.media_stream_source + .RemoveSampleRequested(self.sample_requested)?; + + Ok(()) + } +} + +impl Drop for VideoEncoder { + fn drop(&mut self) { + let _ = self.frame_sender.send(None); + + if let Some(transcode_thread) = self.transcode_thread.take() { + let _ = transcode_thread.join(); + } + } +} + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for VideoEncoder {} diff --git a/src/frame.rs b/src/frame.rs index c9e423a..692b308 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -7,8 +7,8 @@ use std::{ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use windows::{ - Graphics::Imaging::{BitmapAlphaMode, BitmapEncoder, BitmapPixelFormat}, - Storage::Streams::{Buffer, DataReader, InMemoryRandomAccessStream, InputStreamOptions}, + Foundation::TimeSpan, + Graphics::DirectX::Direct3D11::IDirect3DSurface, Win32::Graphics::{ Direct3D11::{ ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_BOX, D3D11_CPU_ACCESS_READ, @@ -19,21 +19,23 @@ use windows::{ }, }; -use crate::settings::ColorFormat; +use crate::{ + encoder::{self, ImageEncoder}, + settings::ColorFormat, +}; -/// Used To Handle Frame Errors #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("Invalid Box Size")] + #[error("Invalid box size")] InvalidSize, - #[error("Invalid Path")] - InvalidPath, - #[error("This Color Format Is Not Supported For Saving As Image")] + #[error("This color format is not supported for saving as image")] UnsupportedFormat, - #[error(transparent)] - WindowsError(#[from] windows::core::Error), - #[error(transparent)] + #[error("Failed to encode image buffer to image bytes with specified format: {0}")] + ImageEncoderError(#[from] encoder::ImageEncoderError), + #[error("IO error: {0}")] IoError(#[from] io::Error), + #[error("Windows API error: {0}")] + WindowsError(#[from] windows::core::Error), } #[derive(Eq, PartialEq, Clone, Copy, Debug)] @@ -46,10 +48,19 @@ pub enum ImageFormat { JpegXr, } -/// Frame Struct Used To Get The Frame Buffer +/// Represents a frame captured from a graphics capture item. +/// +/// # Example +/// ```no_run +/// // Get frame from capture the session +/// let mut buffer = frame.buffer()?; +/// buffer.save_as_image("screenshot.png", ImageFormat::Png)?; +/// ``` pub struct Frame<'a> { d3d_device: &'a ID3D11Device, - frame_surface: ID3D11Texture2D, + frame_surface: IDirect3DSurface, + frame_texture: ID3D11Texture2D, + time: TimeSpan, context: &'a ID3D11DeviceContext, buffer: &'a mut Vec, width: u32, @@ -58,11 +69,30 @@ pub struct Frame<'a> { } impl<'a> Frame<'a> { - /// Craete A New Frame + /// Create a new Frame. + /// + /// # Arguments + /// + /// * `d3d_device` - The ID3D11Device used for creating the frame. + /// * `frame_surface` - The IDirect3DSurface representing the frame surface. + /// * `frame_texture` - The ID3D11Texture2D representing the frame texture. + /// * `time` - The TimeSpan representing the frame time. + /// * `context` - The ID3D11DeviceContext used for copying the texture. + /// * `buffer` - The mutable Vec representing the frame buffer. + /// * `width` - The width of the frame. + /// * `height` - The height of the frame. + /// * `color_format` - The ColorFormat of the frame. + /// + /// # Returns + /// + /// A new Frame instance. + #[allow(clippy::too_many_arguments)] #[must_use] pub fn new( d3d_device: &'a ID3D11Device, - frame_surface: ID3D11Texture2D, + frame_surface: IDirect3DSurface, + frame_texture: ID3D11Texture2D, + time: TimeSpan, context: &'a ID3D11DeviceContext, buffer: &'a mut Vec, width: u32, @@ -72,6 +102,8 @@ impl<'a> Frame<'a> { Self { d3d_device, frame_surface, + frame_texture, + time, context, buffer, width, @@ -80,19 +112,55 @@ impl<'a> Frame<'a> { } } - /// Get The Frame Width + /// Get the width of the frame. + /// + /// # Returns + /// + /// The width of the frame. #[must_use] pub const fn width(&self) -> u32 { self.width } - /// Get The Frame Height + /// Get the height of the frame. + /// + /// # Returns + /// + /// The height of the frame. #[must_use] pub const fn height(&self) -> u32 { self.height } - /// Get The Frame Buffer + /// Get the time of the frame. + /// + /// # Returns + /// + /// The time of the frame. + #[must_use] + pub const fn timespan(&self) -> TimeSpan { + self.time + } + + /// Get the raw surface of the frame. + /// + /// # Returns + /// + /// The IDirect3DSurface representing the raw surface of the frame. + /// + /// # Safety + /// + /// This method is unsafe because it returns a raw pointer to the IDirect3DSurface. + #[must_use] + pub unsafe fn as_raw_surface(&self) -> IDirect3DSurface { + self.frame_surface.clone() + } + + /// Get the frame buffer. + /// + /// # Returns + /// + /// The FrameBuffer containing the frame data. pub fn buffer(&mut self) -> Result { // Texture Settings let texture_desc = D3D11_TEXTURE2D_DESC { @@ -111,7 +179,7 @@ impl<'a> Frame<'a> { MiscFlags: 0, }; - // Create A Texture That CPU Can Read + // Create a texture that CPU can read let mut texture = None; unsafe { self.d3d_device @@ -119,12 +187,12 @@ impl<'a> Frame<'a> { }; let texture = texture.unwrap(); - // Copy The Real Texture To Copy Texture + // Copy the real texture to copy texture unsafe { - self.context.CopyResource(&texture, &self.frame_surface); + self.context.CopyResource(&texture, &self.frame_texture); }; - // Map The Texture To Enable CPU Access + // Map the texture to enable CPU access let mut mapped_resource = D3D11_MAPPED_SUBRESOURCE::default(); unsafe { self.context.Map( @@ -136,7 +204,7 @@ impl<'a> Frame<'a> { )?; }; - // Get The Mapped Resource Data Slice + // Get the mapped resource data slice let mapped_frame_data = unsafe { slice::from_raw_parts_mut( mapped_resource.pData.cast(), @@ -144,7 +212,7 @@ impl<'a> Frame<'a> { ) }; - // Create Frame Buffer From Slice + // Create frame buffer from slice let frame_buffer = FrameBuffer::new( mapped_frame_data, self.buffer, @@ -158,7 +226,18 @@ impl<'a> Frame<'a> { Ok(frame_buffer) } - /// Get A Cropped Frame Buffer + /// Get a cropped frame buffer. + /// + /// # Arguments + /// + /// * `start_width` - The starting width of the cropped frame. + /// * `start_height` - The starting height of the cropped frame. + /// * `end_width` - The ending width of the cropped frame. + /// * `end_height` - The ending height of the cropped frame. + /// + /// # Returns + /// + /// The FrameBuffer containing the cropped frame data. pub fn buffer_crop( &mut self, start_width: u32, @@ -190,7 +269,7 @@ impl<'a> Frame<'a> { MiscFlags: 0, }; - // Create A Texture That CPU Can Read + // Create a texture that CPU can read let mut texture = None; unsafe { self.d3d_device @@ -208,7 +287,7 @@ impl<'a> Frame<'a> { back: 1, }; - // Copy The Real Texture To Copy Texture + // Copy the real texture to copy texture unsafe { self.context.CopySubresourceRegion( &texture, @@ -216,13 +295,13 @@ impl<'a> Frame<'a> { 0, 0, 0, - &self.frame_surface, + &self.frame_texture, 0, Some(&resource_box), ); }; - // Map The Texture To Enable CPU Access + // Map the texture to enable CPU access let mut mapped_resource = D3D11_MAPPED_SUBRESOURCE::default(); unsafe { self.context.Map( @@ -234,7 +313,7 @@ impl<'a> Frame<'a> { )?; }; - // Get The Mapped Resource Data Slice + // Get the mapped resource data slice let mapped_frame_data = unsafe { slice::from_raw_parts_mut( mapped_resource.pData.cast(), @@ -242,7 +321,7 @@ impl<'a> Frame<'a> { ) }; - // Create Frame Buffer From Slice + // Create frame buffer from slice let frame_buffer = FrameBuffer::new( mapped_frame_data, self.buffer, @@ -256,7 +335,16 @@ impl<'a> Frame<'a> { Ok(frame_buffer) } - /// Save The Frame Buffer As An Image To The Specified Path + /// Save the frame buffer as an image to the specified path. + /// + /// # Arguments + /// + /// * `path` - The path where the image will be saved. + /// * `format` - The ImageFormat of the saved image. + /// + /// # Returns + /// + /// An empty Result if successful, or an Error if there was an issue saving the image. pub fn save_as_image>( &mut self, path: T, @@ -270,8 +358,14 @@ impl<'a> Frame<'a> { } } -/// Frame Buffer Struct Used To Get Raw Pixel Data -#[allow(clippy::module_name_repetitions)] +/// Represents a frame buffer containing pixel data. +/// +/// # Example +/// ```no_run +/// // Get frame from the capture session +/// let mut buffer = frame.buffer()?; +/// buffer.save_as_image("screenshot.png", ImageFormat::Png)?; +/// ``` pub struct FrameBuffer<'a> { raw_buffer: &'a mut [u8], buffer: &'a mut Vec, @@ -283,7 +377,21 @@ pub struct FrameBuffer<'a> { } impl<'a> FrameBuffer<'a> { - /// Create A New Frame Buffer + /// Create a new Frame Buffer. + /// + /// # Arguments + /// + /// * `raw_buffer` - A mutable reference to the raw pixel data buffer. + /// * `buffer` - A mutable reference to the buffer used for copying pixel data without padding. + /// * `width` - The width of the frame buffer. + /// * `height` - The height of the frame buffer. + /// * `row_pitch` - The row pitch of the frame buffer. + /// * `depth_pitch` - The depth pitch of the frame buffer. + /// * `color_format` - The color format of the frame buffer. + /// + /// # Returns + /// + /// A new `FrameBuffer` instance. #[must_use] pub fn new( raw_buffer: &'a mut [u8], @@ -305,44 +413,47 @@ impl<'a> FrameBuffer<'a> { } } - /// Get The Frame Buffer Width + /// Get the width of the frame buffer. #[must_use] pub const fn width(&self) -> u32 { self.width } - /// Get The Frame Buffer Height + /// Get the height of the frame buffer. #[must_use] pub const fn height(&self) -> u32 { self.height } - /// Get The Frame Buffer Row Pitch + /// Get the row pitch of the frame buffer. #[must_use] pub const fn row_pitch(&self) -> u32 { self.row_pitch } - /// Get The Frame Buffer Depth Pitch + /// Get the depth pitch of the frame buffer. #[must_use] pub const fn depth_pitch(&self) -> u32 { self.depth_pitch } - /// Check If The Buffer Has Padding + /// Check if the buffer has padding. #[must_use] pub const fn has_padding(&self) -> bool { self.width * 4 != self.row_pitch } - /// Get The Raw Pixel Data Might Have Padding + /// Get the raw pixel data with possible padding. #[must_use] pub fn as_raw_buffer(&'a mut self) -> &'a mut [u8] { self.raw_buffer } - /// Get The Raw Pixel Data Without Padding - #[allow(clippy::type_complexity)] + /// Get the raw pixel data without padding. + /// + /// # Returns + /// + /// A mutable reference to the buffer containing pixel data without padding. pub fn as_raw_nopadding_buffer(&'a mut self) -> Result<&'a mut [u8], Error> { if !self.has_padding() { return Ok(self.raw_buffer); @@ -371,52 +482,30 @@ impl<'a> FrameBuffer<'a> { Ok(&mut self.buffer[0..frame_size]) } - /// Save The Frame Buffer As An Image To The Specified Path (Only `ColorFormat::Rgba8` And `ColorFormat::Bgra8`) + /// Save the frame buffer as an image to the specified path. + /// + /// # Arguments + /// + /// * `path` - The path where the image will be saved. + /// * `format` - The image format to use for saving. + /// + /// # Returns + /// + /// An `Ok` result if the image is successfully saved, or an `Err` result if there was an error. pub fn save_as_image>( &'a mut self, path: T, format: ImageFormat, ) -> Result<(), Error> { - let encoder = match format { - ImageFormat::Jpeg => BitmapEncoder::JpegEncoderId()?, - ImageFormat::Png => BitmapEncoder::PngEncoderId()?, - ImageFormat::Gif => BitmapEncoder::GifEncoderId()?, - ImageFormat::Tiff => BitmapEncoder::TiffEncoderId()?, - ImageFormat::Bmp => BitmapEncoder::BmpEncoderId()?, - ImageFormat::JpegXr => BitmapEncoder::JpegXREncoderId()?, - }; + let width = self.width; + let height = self.height; - let stream = InMemoryRandomAccessStream::new()?; - let encoder = BitmapEncoder::CreateAsync(encoder, &stream)?.get()?; - - let pixelformat = match self.color_format { - ColorFormat::Bgra8 => BitmapPixelFormat::Bgra8, - ColorFormat::Rgba8 => BitmapPixelFormat::Rgba8, - ColorFormat::Rgba16F => return Err(Error::UnsupportedFormat), - }; - - encoder.SetPixelData( - pixelformat, - BitmapAlphaMode::Premultiplied, - self.width, - self.height, - 1.0, - 1.0, + let bytes = ImageEncoder::new(format, self.color_format).encode( self.as_raw_nopadding_buffer()?, + width, + height, )?; - encoder.FlushAsync()?.get()?; - - let buffer = Buffer::Create(u32::try_from(stream.Size()?).unwrap())?; - stream - .ReadAsync(&buffer, buffer.Capacity()?, InputStreamOptions::None)? - .get()?; - - let data_reader = DataReader::FromBuffer(&buffer)?; - let length = data_reader.UnconsumedBufferLength()?; - let mut bytes = vec![0u8; length as usize]; - data_reader.ReadBytes(&mut bytes).unwrap(); - fs::write(path, bytes)?; Ok(()) diff --git a/src/graphics_capture_api.rs b/src/graphics_capture_api.rs index b27e81f..d5cad6d 100644 --- a/src/graphics_capture_api.rs +++ b/src/graphics_capture_api.rs @@ -22,109 +22,142 @@ use windows::{ }; use crate::{ - capture::WindowsCaptureHandler, + capture::GraphicsCaptureApiHandler, d3d11::{self, create_d3d_device, create_direct3d_device, SendDirectX}, frame::Frame, - settings::ColorFormat, + settings::{ColorFormat, CursorCaptuerSettings, DrawBorderSettings}, }; -/// Used To Handle Graphics Capture Errors -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)] pub enum Error { - #[error("Graphics Capture API Is Not Supported")] + #[error("Graphics capture API is not supported")] Unsupported, - #[error("Graphics Capture API Toggling Cursor Capture Is Not Supported")] + #[error("Graphics capture API toggling cursor capture is not supported")] CursorConfigUnsupported, - #[error("Graphics Capture API Toggling Border Capture Is Not Supported")] + #[error("Graphics capture API toggling border capture is not supported")] BorderConfigUnsupported, - #[error("Already Started")] + #[error("Already started")] AlreadyStarted, - #[error(transparent)] + #[error("DirectX error: {0}")] DirectXError(#[from] d3d11::Error), - #[error(transparent)] + #[error("Windows API error: {0}")] WindowsError(#[from] windows::core::Error), } -/// Struct Used To Control Capture Thread +/// Used to control the capture session pub struct InternalCaptureControl { stop: Arc, } impl InternalCaptureControl { - /// Create A New Capture Control Struct + /// Create a new `InternalCaptureControl` struct. + /// + /// # Arguments + /// + /// * `stop` - An `Arc` indicating whether the capture should stop. + /// + /// # Returns + /// + /// A new instance of `InternalCaptureControl`. #[must_use] pub fn new(stop: Arc) -> Self { Self { stop } } - /// Gracefully Stop The Capture Thread + /// Gracefully stop the capture thread. pub fn stop(self) { self.stop.store(true, atomic::Ordering::Relaxed); } } -/// Struct Used For Graphics Capture Api +/// Represents the GraphicsCaptureApi struct. pub struct GraphicsCaptureApi { + /// The GraphicsCaptureItem associated with the GraphicsCaptureApi. item: GraphicsCaptureItem, + /// The ID3D11Device associated with the GraphicsCaptureApi. _d3d_device: ID3D11Device, + /// The IDirect3DDevice associated with the GraphicsCaptureApi. _direct3d_device: IDirect3DDevice, + /// The ID3D11DeviceContext associated with the GraphicsCaptureApi. _d3d_device_context: ID3D11DeviceContext, + /// The optional Arc associated with the GraphicsCaptureApi. frame_pool: Option>, + /// The optional GraphicsCaptureSession associated with the GraphicsCaptureApi. session: Option, + /// The Arc used to halt the GraphicsCaptureApi. halt: Arc, + /// Indicates whether the GraphicsCaptureApi is active or not. active: bool, - capture_cursor: Option, - draw_border: Option, + /// The EventRegistrationToken associated with the capture closed event. capture_closed_event_token: EventRegistrationToken, + /// The EventRegistrationToken associated with the frame arrived event. frame_arrived_event_token: EventRegistrationToken, } impl GraphicsCaptureApi { - /// Create A New Graphics Capture Api Struct - #[allow(clippy::too_many_lines)] - pub fn new + Send + 'static, E: Send + Sync + 'static>( + /// Create a new Graphics Capture API struct. + /// + /// # Arguments + /// + /// * `item` - The graphics capture item to capture. + /// * `callback` - The callback handler for capturing frames. + /// * `capture_cursor` - Optional flag to capture the cursor. + /// * `draw_border` - Optional flag to draw a border around the captured region. + /// * `color_format` - The color format for the captured frames. + /// * `thread_id` - The ID of the thread where the capture is running. + /// * `result` - The result of the capture operation. + /// + /// # Returns + /// + /// Returns a `Result` containing the new `GraphicsCaptureApi` struct if successful, or an `Error` if an error occurred. + pub fn new< + T: GraphicsCaptureApiHandler + Send + 'static, + E: Send + Sync + 'static, + >( item: GraphicsCaptureItem, callback: Arc>, - capture_cursor: Option, - draw_border: Option, + cursor_capture: CursorCaptuerSettings, + draw_border: DrawBorderSettings, color_format: ColorFormat, thread_id: u32, result: Arc>>, ) -> Result { - // Check Support + // Check support if !Self::is_supported()? { return Err(Error::Unsupported); } - if draw_border.is_some() && !Self::is_border_toggle_supported()? { - return Err(Error::BorderConfigUnsupported); + if cursor_capture != CursorCaptuerSettings::Default + && !Self::is_cursor_settings_supported()? + { + return Err(Error::CursorConfigUnsupported); } - if capture_cursor.is_some() && !Self::is_cursor_toggle_supported()? { - return Err(Error::CursorConfigUnsupported); + if draw_border != DrawBorderSettings::Default && !Self::is_border_settings_supported()? { + return Err(Error::BorderConfigUnsupported); } - // Create DirectX Devices + // Create DirectX devices let (d3d_device, d3d_device_context) = create_d3d_device()?; let direct3d_device = create_direct3d_device(&d3d_device)?; let pixel_format = DirectXPixelFormat(color_format as i32); - // Create Frame Pool + // Create frame pool let frame_pool = Direct3D11CaptureFramePool::Create(&direct3d_device, pixel_format, 1, item.Size()?)?; let frame_pool = Arc::new(frame_pool); - // Create Capture Session + // Create capture session let session = frame_pool.CreateCaptureSession(&item)?; - // Preallocate Memory + // Preallocate memory let mut buffer = vec![0u8; 3840 * 2160 * 4]; - // Indicates If The Capture Is Closed + // Indicates if the capture is closed let halt = Arc::new(AtomicBool::new(false)); - // Set Capture Session Closed Event + // Set capture session closed event let capture_closed_event_token = item.Closed(&TypedEventHandler::< GraphicsCaptureItem, IInspectable, @@ -137,12 +170,12 @@ impl GraphicsCaptureApi { move |_, _| { halt_closed.store(true, atomic::Ordering::Relaxed); - // Notify The Struct That The Capture Session Is Closed + // Notify the struct that the capture session is closed if let Err(e) = callback_closed.lock().on_closed() { *result_closed.lock() = Some(e); } - // To Stop Messge Loop + // To stop message loop unsafe { PostThreadMessageW(thread_id, WM_QUIT, WPARAM::default(), LPARAM::default())?; }; @@ -151,7 +184,7 @@ impl GraphicsCaptureApi { } }))?; - // Set Frame Pool Frame Arrived Event + // Set frame pool frame arrived event let frame_arrived_event_token = frame_pool.FrameArrived(&TypedEventHandler::< Direct3D11CaptureFramePool, IInspectable, @@ -168,55 +201,60 @@ impl GraphicsCaptureApi { let direct3d_device_recreate = SendDirectX::new(direct3d_device.clone()); move |frame, _| { - // Return Early If The Capture Is Closed + // Return early if the capture is closed if halt_frame_pool.load(atomic::Ordering::Relaxed) { return Ok(()); } - // Get Frame - let frame = frame.as_ref().unwrap().TryGetNextFrame()?; + // Get frame + let frame = frame + .as_ref() + .expect("FrameArrived parameter was None this should never happen.") + .TryGetNextFrame()?; + let timespan = frame.SystemRelativeTime()?; - // Get Frame Content Size + // Get frame content size let frame_content_size = frame.ContentSize()?; - // Get Frame Surface + // Get frame surface let frame_surface = frame.Surface()?; - // Convert Surface To Texture - let frame_surface = frame_surface.cast::()?; - let frame_surface = unsafe { frame_surface.GetInterface::()? }; + // Convert surface to texture + let frame_dxgi_interface = frame_surface.cast::()?; + let frame_texture = + unsafe { frame_dxgi_interface.GetInterface::()? }; - // Get Texture Settings + // Get texture settings let mut desc = D3D11_TEXTURE2D_DESC::default(); - unsafe { frame_surface.GetDesc(&mut desc) } + unsafe { frame_texture.GetDesc(&mut desc) } - // Check If The Size Has Been Changed + // Check if the size has been changed if frame_content_size.Width != last_size.Width || frame_content_size.Height != last_size.Height { let direct3d_device_recreate = &direct3d_device_recreate; - frame_pool_recreate - .Recreate( - &direct3d_device_recreate.0, - pixel_format, - 1, - frame_content_size, - ) - .unwrap(); + frame_pool_recreate.Recreate( + &direct3d_device_recreate.0, + pixel_format, + 1, + frame_content_size, + )?; last_size = frame_content_size; return Ok(()); } - // Set Width & Height + // Set width & height let texture_width = desc.Width; let texture_height = desc.Height; - // Create A Frame + // Create a frame let mut frame = Frame::new( &d3d_device_frame_pool, frame_surface, + frame_texture, + timespan, &context, &mut buffer, texture_width, @@ -224,11 +262,11 @@ impl GraphicsCaptureApi { color_format, ); - // Init Internal Capture Control + // Init internal capture control let stop = Arc::new(AtomicBool::new(false)); let internal_capture_control = InternalCaptureControl::new(stop.clone()); - // Send The Frame To Trigger Struct + // Send the frame to the callback struct let result = callback_frame_pool .lock() .on_frame_arrived(&mut frame, internal_capture_control); @@ -240,7 +278,7 @@ impl GraphicsCaptureApi { halt_frame_pool.store(true, atomic::Ordering::Relaxed); - // To Stop Messge Loop + // To stop the message loop unsafe { PostThreadMessageW( thread_id, @@ -255,6 +293,34 @@ impl GraphicsCaptureApi { } }))?; + if cursor_capture != CursorCaptuerSettings::Default { + if Self::is_cursor_settings_supported()? { + match cursor_capture { + CursorCaptuerSettings::Default => (), + CursorCaptuerSettings::WithCursor => session.SetIsCursorCaptureEnabled(true)?, + CursorCaptuerSettings::WithoutCursor => { + session.SetIsCursorCaptureEnabled(false)? + } + }; + } else { + return Err(Error::CursorConfigUnsupported); + } + } + + if draw_border != DrawBorderSettings::Default { + if Self::is_border_settings_supported()? { + match draw_border { + DrawBorderSettings::Default => (), + DrawBorderSettings::WithBorder => { + session.SetIsBorderRequired(true)?; + } + DrawBorderSettings::WithoutBorder => session.SetIsBorderRequired(false)?, + } + } else { + return Err(Error::BorderConfigUnsupported); + } + } + Ok(Self { item, _d3d_device: d3d_device, @@ -264,58 +330,28 @@ impl GraphicsCaptureApi { session: Some(session), halt, active: false, - capture_cursor, - draw_border, frame_arrived_event_token, capture_closed_event_token, }) } - /// Start Capture + /// Start the capture. + /// + /// # Returns + /// + /// Returns `Ok(())` if the capture started successfully, or an `Error` if an error occurred. pub fn start_capture(&mut self) -> Result<(), Error> { - // Check If The Capture Is Already Installed if self.active { return Err(Error::AlreadyStarted); } + self.active = true; - // Config - if self.capture_cursor.is_some() { - if ApiInformation::IsPropertyPresent( - &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"), - &HSTRING::from("IsCursorCaptureEnabled"), - )? { - self.session - .as_ref() - .unwrap() - .SetIsCursorCaptureEnabled(self.capture_cursor.unwrap())?; - } else { - return Err(Error::CursorConfigUnsupported); - } - } - - if self.draw_border.is_some() { - if ApiInformation::IsPropertyPresent( - &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"), - &HSTRING::from("IsBorderRequired"), - )? { - self.session - .as_ref() - .unwrap() - .SetIsBorderRequired(self.draw_border.unwrap())?; - } else { - return Err(Error::BorderConfigUnsupported); - } - } - - // Start Capture self.session.as_ref().unwrap().StartCapture()?; - self.active = true; - Ok(()) } - /// Stop Capture + /// Stop the capture. pub fn stop_capture(mut self) { if let Some(frame_pool) = self.frame_pool.take() { frame_pool @@ -334,13 +370,21 @@ impl GraphicsCaptureApi { .expect("Failed to remove Capture Session Closed event handler"); } - /// Get Halt Handle + /// Get the halt handle. + /// + /// # Returns + /// + /// Returns an `Arc` representing the halt handle. #[must_use] pub fn halt_handle(&self) -> Arc { self.halt.clone() } - /// Check If Windows Graphics Capture Api Is Supported + /// Check if the Windows Graphics Capture API is supported. + /// + /// # Returns + /// + /// Returns `Ok(true)` if the API is supported, `Ok(false)` if the API is not supported, or an `Error` if an error occurred. pub fn is_supported() -> Result { Ok(ApiInformation::IsApiContractPresentByMajor( &HSTRING::from("Windows.Foundation.UniversalApiContract"), @@ -348,16 +392,24 @@ impl GraphicsCaptureApi { )? && GraphicsCaptureSession::IsSupported()?) } - /// Check If You Can Toggle The Cursor On Or Off - pub fn is_cursor_toggle_supported() -> Result { + /// Check if you can change the cursor capture setting. + /// + /// # Returns + /// + /// Returns `true` if toggling the cursor capture is supported, `false` otherwise. + pub fn is_cursor_settings_supported() -> Result { Ok(ApiInformation::IsPropertyPresent( &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"), &HSTRING::from("IsCursorCaptureEnabled"), )? && Self::is_supported()?) } - /// Check If You Can Toggle The Border On Or Off - pub fn is_border_toggle_supported() -> Result { + /// Check if you can change the border capture setting. + /// + /// # Returns + /// + /// Returns `true` if toggling the border capture is supported, `false` otherwise. + pub fn is_border_settings_supported() -> Result { Ok(ApiInformation::IsPropertyPresent( &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"), &HSTRING::from("IsBorderRequired"), @@ -365,7 +417,6 @@ impl GraphicsCaptureApi { } } -// Close Capture Session impl Drop for GraphicsCaptureApi { fn drop(&mut self) { if let Some(frame_pool) = self.frame_pool.take() { diff --git a/src/lib.rs b/src/lib.rs index 25a6ab4..433877a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ //! //! ```toml //! [dependencies] -//! windows-capture = "1.0.61" +//! windows-capture = "1.0.62" //! ``` //! or run this command //! @@ -33,51 +33,88 @@ //! ## Usage //! //! ```no_run +//! use std::{ +//! io::{self, Write}, +//! time::Instant, +//! }; +//! //! use windows_capture::{ -//! capture::WindowsCaptureHandler, -//! frame::{Frame, ImageFormat}, +//! capture::GraphicsCaptureApiHandler, +//! encoder::{VideoEncoder, VideoEncoderQuality, VideoEncoderType}, +//! frame::Frame, //! graphics_capture_api::InternalCaptureControl, -//! settings::{ColorFormat, Settings}, -//! window::Window, +//! monitor::Monitor, +//! settings::{ColorFormat, CursorCaptuerSettings, DrawBorderSettings, Settings}, //! }; //! -//! // Struct To Implement The Trait For -//! struct Capture; +//! // This struct will be used to handle the capture events. +//! struct Capture { +//! // The video encoder that will be used to encode the frames. +//! encoder: Option, +//! // To measure the time the capture has been running +//! start: Instant, +//! } //! -//! impl WindowsCaptureHandler for Capture { -//! // Any Value To Get From The Settings +//! impl GraphicsCaptureApiHandler for Capture { +//! // The type of flags used to get the values from the settings. //! type Flags = String; //! -//! // To Redirect To `CaptureControl` Or Start Method +//! // The type of error that can occur during capture, the error will be returned from `CaptureControl` and `start` functions. //! type Error = Box; //! -//! // Function That Will Be Called To Create The Struct The Flags Can Be Passed -//! // From `WindowsCaptureSettings` +//! // Function that will be called to create the struct. The flags can be passed from settings. //! fn new(message: Self::Flags) -> Result { //! println!("Got The Flag: {message}"); //! -//! Ok(Self {}) +//! let encoder = VideoEncoder::new( +//! VideoEncoderType::Mp4, +//! VideoEncoderQuality::HD1080p, +//! 1920, +//! 1080, +//! "video.mp4", +//! )?; +//! +//! Ok(Self { +//! encoder: Some(encoder), +//! start: Instant::now(), +//! }) //! } //! -//! // Called Every Time A New Frame Is Available +//! // Called every time a new frame is available. //! fn on_frame_arrived( //! &mut self, //! frame: &mut Frame, //! capture_control: InternalCaptureControl, //! ) -> Result<(), Self::Error> { -//! println!("New Frame Arrived"); +//! print!( +//! "\rRecording for: {} seconds", +//! self.start.elapsed().as_secs() +//! ); +//! io::stdout().flush()?; +//! +//! // Send the frame to the video encoder +//! self.encoder.as_mut().unwrap().send_frame(frame)?; +//! +//! // Note: The frame has other uses too for example you can save a single for to a file like this: +//! // frame.save_as_image("frame.png", ImageFormat::Png)?; +//! // Or get the raw data like this so you have full control: +//! // let data = frame.buffer()?; +//! +//! // Stop the capture after 6 seconds +//! if self.start.elapsed().as_secs() >= 6 { +//! // Finish the encoder and save the video. +//! self.encoder.take().unwrap().finish()?; //! -//! // Save The Frame As An Image To The Specified Path -//! frame.save_as_image("image.png", ImageFormat::Png)?; +//! capture_control.stop(); //! -//! // Gracefully Stop The Capture Thread -//! capture_control.stop(); +//! // Because there wasn't any new lines in previous prints +//! println!(); +//! } //! //! Ok(()) //! } //! -//! // Called When The Capture Item Closes Usually When The Window Closes, Capture -//! // Session Will End After This Function Ends +//! // Optional handler called when the capture item (usually a window) closes. //! fn on_closed(&mut self) -> Result<(), Self::Error> { //! println!("Capture Session Closed"); //! @@ -86,35 +123,42 @@ //! } //! //! // Gets The Foreground Window, Checkout The Docs For Other Capture Items -//! let foreground_window = Window::foreground().expect("No Active Window Found"); +//! let primary_monitor = Monitor::primary().expect("There is no primary monitor"); //! //! let settings = Settings::new( //! // Item To Captue -//! foreground_window, +//! primary_monitor, //! // Capture Cursor -//! Some(true), +//! CursorCaptuerSettings::Default, //! // Draw Borders (None Means Default Api Configuration) -//! None, -//! // Kind Of Pixel Format For Frame To Have +//! DrawBorderSettings::Default, +//! // The desired color format for the captured frame. //! ColorFormat::Rgba8, -//! // Any Value To Pass To The New Function -//! "It Works".to_string(), +//! // Additional flags for the capture settings that will be passed to user defined `new` function. +//! "Yea This Works".to_string(), //! ) //! .unwrap(); //! -//! // Every Error From `new`, `on_frame_arrived` and `on_closed` Will End Up Here +//! // Starts the capture and takes control of the current thread. +//! // The errors from handler trait will end up here //! Capture::start(settings).expect("Screen Capture Failed"); //! ``` -#![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![warn(clippy::cargo)] -#![allow(clippy::missing_errors_doc)] -#![allow(clippy::missing_panics_doc)] +/// Contains the main capture functionality, including the `WindowsCaptureHandler` trait and related types. pub mod capture; +/// Internal module for Direct3D 11 related functionality. mod d3d11; +/// Contains the encoder functionality for encoding captured frames. +pub mod encoder; +/// Contains the `Frame` struct and related types for representing captured frames. pub mod frame; +/// Contains the types and functions related to the Graphics Capture API. pub mod graphics_capture_api; +/// Contains the functionality for working with monitors and screen information. pub mod monitor; +/// Contains the `Settings` struct and related types for configuring the capture settings. pub mod settings; +/// Contains the functionality for working with windows and capturing specific windows. pub mod window; diff --git a/src/monitor.rs b/src/monitor.rs index 0a7f9e4..1df90d7 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -22,37 +22,51 @@ use windows::{ }, }; -/// Used To Handle Monitor Errors #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("Failed To Find Monitor")] + #[error("Failed to find monitor")] NotFound, - #[error("Failed To Find Monitor Name")] + #[error("Failed to find monitor name")] NameNotFound, - #[error("Monitor Index Is Lower Than One")] + #[error("Monitor index is lower than one")] IndexIsLowerThanOne, - #[error("Failed To Get Monitor Info")] + #[error("Failed to get monitor info")] FailedToGetMonitorInfo, - #[error("Failed To Get Monitor ettings")] + #[error("Failed to get monitor settings")] FailedToGetMonitorSettings, - #[error("Failed To Get Monitor Name")] + #[error("Failed to get monitor name")] FailedToGetMonitorName, - #[error(transparent)] + #[error("Failed to parse monitor index: {0}")] FailedToParseMonitorIndex(#[from] ParseIntError), - #[error(transparent)] + #[error("Failed to convert windows string: {0}")] FailedToConvertWindowsString(#[from] FromUtf16Error), - #[error(transparent)] + #[error("Windows API error: {0}")] WindowsError(#[from] windows::core::Error), } /// Represents A Monitor Device +/// +/// # Example +/// ```no_run +/// use windows_capture::monitor::Monitor; +/// +/// fn main() -> Result<(), Box> { +/// let monitor = Monitor::primary()?; +/// println!("Primary Monitor: {}", monitor.name()?); +/// +/// Ok(()) +/// } #[derive(Eq, PartialEq, Clone, Copy, Debug)] pub struct Monitor { monitor: HMONITOR, } impl Monitor { - /// Get The Primary Monitor + /// Returns the primary monitor. + /// + /// # Errors + /// + /// Returns an `Error::NotFound` if there is no primary monitor. pub fn primary() -> Result { let point = POINT { x: 0, y: 0 }; let monitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONULL) }; @@ -64,7 +78,16 @@ impl Monitor { Ok(Self { monitor }) } - /// Get The Monitor From It's Index + /// Returns the monitor at the specified index. + /// + /// # Arguments + /// + /// * `index` - The index of the monitor to retrieve. The index starts from 1. + /// + /// # Errors + /// + /// Returns an `Error::IndexIsLowerThanOne` if the index is less than 1. + /// Returns an `Error::NotFound` if the monitor at the specified index is not found. pub fn from_index(index: usize) -> Result { if index < 1 { return Err(Error::IndexIsLowerThanOne); @@ -79,12 +102,21 @@ impl Monitor { Ok(monitor) } + /// Returns the index of the monitor. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error retrieving the monitor index. pub fn index(&self) -> Result { let device_name = self.device_name()?; Ok(device_name.replace("\\\\.\\DISPLAY", "").parse()?) } - /// Get Monitor Name + /// Returns the name of the monitor. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error retrieving the monitor name. pub fn name(&self) -> Result { let mut monitor_info = MONITORINFOEXW { monitorInfo: MONITORINFO { @@ -112,7 +144,8 @@ impl Monitor { QDC_ONLY_ACTIVE_PATHS, &mut number_of_paths, &mut number_of_modes, - )?; + ) + .ok()?; }; let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); number_of_paths as usize]; @@ -126,7 +159,8 @@ impl Monitor { modes.as_mut_ptr(), None, ) - }?; + } + .ok()?; for path in paths { let mut source = DISPLAYCONFIG_SOURCE_DEVICE_NAME { @@ -191,7 +225,11 @@ impl Monitor { Err(Error::NameNotFound) } - /// Get Monitor Device Name + /// Returns the device name of the monitor. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error retrieving the monitor device name. pub fn device_name(&self) -> Result { let mut monitor_info = MONITORINFOEXW { monitorInfo: MONITORINFO { @@ -225,7 +263,11 @@ impl Monitor { Ok(device_name) } - /// Get Monitor Device String + /// Returns the device string of the monitor. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error retrieving the monitor device string. pub fn device_string(&self) -> Result { let mut monitor_info = MONITORINFOEXW { monitorInfo: MONITORINFO { @@ -280,8 +322,12 @@ impl Monitor { Ok(device_string) } - /// Get Monitor Width - pub fn width(&self) -> Result { + /// Returns the refresh rate of the monitor in hertz. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error retrieving the monitor refresh rate. + pub fn refresh_rate(&self) -> Result { let mut device_mode = DEVMODEW { dmSize: u16::try_from(mem::size_of::()).unwrap(), ..DEVMODEW::default() @@ -298,11 +344,15 @@ impl Monitor { return Err(Error::FailedToGetMonitorSettings); } - Ok(device_mode.dmPelsWidth) + Ok(device_mode.dmDisplayFrequency) } - /// Get Monitor Height - pub fn height(&self) -> Result { + /// Returns the width of the monitor in pixels. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error retrieving the monitor width. + pub fn width(&self) -> Result { let mut device_mode = DEVMODEW { dmSize: u16::try_from(mem::size_of::()).unwrap(), ..DEVMODEW::default() @@ -319,11 +369,15 @@ impl Monitor { return Err(Error::FailedToGetMonitorSettings); } - Ok(device_mode.dmPelsHeight) + Ok(device_mode.dmPelsWidth) } - /// Get Monitor Refresh Rate - pub fn refresh_rate(&self) -> Result { + /// Returns the height of the monitor in pixels. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error retrieving the monitor height. + pub fn height(&self) -> Result { let mut device_mode = DEVMODEW { dmSize: u16::try_from(mem::size_of::()).unwrap(), ..DEVMODEW::default() @@ -340,10 +394,14 @@ impl Monitor { return Err(Error::FailedToGetMonitorSettings); } - Ok(device_mode.dmDisplayFrequency) + Ok(device_mode.dmPelsHeight) } - /// Get A List Of All Monitors + /// Returns a list of all monitors. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error enumerating the monitors. pub fn enumerate() -> Result, Error> { let mut monitors: Vec = Vec::new(); @@ -360,13 +418,17 @@ impl Monitor { Ok(monitors) } - /// Create From A Raw HMONITOR + /// Creates a `Monitor` instance from a raw HMONITOR. + /// + /// # Arguments + /// + /// * `monitor` - The raw HMONITOR. #[must_use] pub const fn from_raw_hmonitor(monitor: HMONITOR) -> Self { Self { monitor } } - /// Get The Raw HMONITOR + /// Returns the raw HMONITOR of the monitor. #[must_use] pub const fn as_raw_hmonitor(&self) -> HMONITOR { self.monitor @@ -387,12 +449,11 @@ impl Monitor { } } -// Automatically Convert Monitor To GraphicsCaptureItem +// Implements TryFrom For Monitor To Convert It To GraphicsCaptureItem impl TryFrom for GraphicsCaptureItem { type Error = Error; fn try_from(value: Monitor) -> Result { - // Get Capture Item From HMONITOR let monitor = value.as_raw_hmonitor(); let interop = windows::core::factory::()?; diff --git a/src/settings.rs b/src/settings.rs index dd3c6e1..d50b365 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,13 +1,11 @@ use windows::Graphics::Capture::GraphicsCaptureItem; -/// Used To Handle Settings Errors -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)] pub enum Error { - #[error("Failed To Convert To GraphicsCaptureItem")] + #[error("Failed to convert item to GraphicsCaptureItem")] ItemConvertFailed, } -/// Kind Of Pixel Format For Frame To Have #[derive(Eq, PartialEq, Clone, Copy, Debug)] pub enum ColorFormat { Rgba16F = 10, @@ -15,22 +13,59 @@ pub enum ColorFormat { Bgra8 = 87, } -/// Capture Settings, None Means Default +impl Default for ColorFormat { + fn default() -> Self { + Self::Rgba8 + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum CursorCaptuerSettings { + Default, + WithCursor, + WithoutCursor, +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum DrawBorderSettings { + Default, + WithBorder, + WithoutBorder, +} + #[derive(Eq, PartialEq, Clone, Debug)] +/// Represents the settings for screen capturing. pub struct Settings { + /// The graphics capture item to capture. pub item: GraphicsCaptureItem, - pub capture_cursor: Option, - pub draw_border: Option, + /// Specifies whether to capture the cursor. + pub cursor_capture: CursorCaptuerSettings, + /// Specifies whether to draw a border around the captured region. + pub draw_border: DrawBorderSettings, + /// The color format for the captured graphics. pub color_format: ColorFormat, + /// Additional flags for capturing graphics. pub flags: Flags, } impl Settings { /// Create Capture Settings + /// + /// # Arguments + /// + /// * `item` - The graphics capture item. + /// * `capture_cursor` - Whether to capture the cursor or not. + /// * `draw_border` - Whether to draw a border around the captured region or not. + /// * `color_format` - The desired color format for the captured frame. + /// * `flags` - Additional flags for the capture settings that will be passed to user defined `new` function. + /// + /// # Returns + /// + /// Returns a `Result` containing the `Settings` if successful, or an `Error` if conversion to `GraphicsCaptureItem` fails. pub fn new>( item: T, - capture_cursor: Option, - draw_border: Option, + cursor_capture: CursorCaptuerSettings, + draw_border: DrawBorderSettings, color_format: ColorFormat, flags: Flags, ) -> Result { @@ -39,7 +74,7 @@ impl Settings { Ok(item) => item, Err(_) => return Err(Error::ItemConvertFailed), }, - capture_cursor, + cursor_capture, draw_border, color_format, flags, diff --git a/src/window.rs b/src/window.rs index 8bf3922..f56b3ab 100644 --- a/src/window.rs +++ b/src/window.rs @@ -19,27 +19,42 @@ use windows::{ use crate::monitor::Monitor; -/// Used To Handle Window Errors #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("Failed To Get The Foreground Window")] + #[error("No active window found")] NoActiveWindow, - #[error("Failed To Find Window")] - NotFound, - #[error(transparent)] + #[error("Failed to find window with name: {0}")] + NotFound(String), + #[error("Failed to convert windows string from UTF-16: {0}")] FailedToConvertWindowsString(#[from] FromUtf16Error), - #[error(transparent)] + #[error("Windows API error: {0}")] WindowsError(#[from] windows::core::Error), } -/// Represents A Windows +/// Represents a window in the Windows operating system. +/// +/// # Example +/// ```no_run +/// use windows_capture::window::Window; +/// +/// fn main() -> Result<(), Box> { +/// let window = Window::foreground()?; +/// println!("Foreground window title: {}", window.title()?); +/// +/// Ok(()) +/// } +/// ``` #[derive(Eq, PartialEq, Clone, Copy, Debug)] pub struct Window { window: HWND, } impl Window { - /// Get The Currently Active Window + /// Returns the foreground window. + /// + /// # Errors + /// + /// Returns an `Error::NoActiveWindow` if there is no active window. pub fn foreground() -> Result { let window = unsafe { GetForegroundWindow() }; @@ -50,19 +65,35 @@ impl Window { Ok(Self { window }) } - /// Create From A Window Name + /// Creates a `Window` instance from a window name. + /// + /// # Arguments + /// + /// * `title` - The name of the window. + /// + /// # Errors + /// + /// Returns an `Error::NotFound` if the window is not found. pub fn from_name(title: &str) -> Result { - let title = HSTRING::from(title); - let window = unsafe { FindWindowW(None, &title) }; + let hstring_title = HSTRING::from(title); + let window = unsafe { FindWindowW(None, &hstring_title) }; if window.0 == 0 { - return Err(Error::NotFound); + return Err(Error::NotFound(String::from(title))); } Ok(Self { window }) } - /// Create From A Window Name Substring + /// Creates a `Window` instance from a window name substring. + /// + /// # Arguments + /// + /// * `title` - The substring to search for in window names. + /// + /// # Errors + /// + /// Returns an `Error::NotFound` if no window with a matching name substring is found. pub fn from_contains_name(title: &str) -> Result { let windows = Self::enumerate()?; @@ -74,10 +105,14 @@ impl Window { } } - target_window.map_or_else(|| Err(Error::NotFound), Ok) + target_window.map_or_else(|| Err(Error::NotFound(String::from(title))), Ok) } - /// Get Window Title + /// Returns the title of the window. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error retrieving the window title. pub fn title(&self) -> Result { let len = unsafe { GetWindowTextLengthW(self.window) }; @@ -101,7 +136,9 @@ impl Window { Ok(name) } - /// Get The Monitor That Has The Largest Area Of Intersection With The Window, None Means Window Doesn't Intersect With Any Monitor + /// Returns the monitor that has the largest area of intersection with the window. + /// + /// Returns `None` if the window doesn't intersect with any monitor. #[must_use] pub fn monitor(&self) -> Option { let window = self.window; @@ -115,7 +152,15 @@ impl Window { } } - /// Check If The Window Is A Valid Window + /// Checks if the window is a valid window. + /// + /// # Arguments + /// + /// * `window` - The window handle to check. + /// + /// # Returns + /// + /// Returns `true` if the window is valid, `false` otherwise. #[must_use] pub fn is_window_valid(window: HWND) -> bool { if !unsafe { IsWindowVisible(window).as_bool() } { @@ -147,7 +192,11 @@ impl Window { true } - /// Get A List Of All Windows + /// Returns a list of all windows. + /// + /// # Errors + /// + /// Returns an `Error` if there is an error enumerating the windows. pub fn enumerate() -> Result, Error> { let mut windows: Vec = Vec::new(); @@ -163,19 +212,23 @@ impl Window { Ok(windows) } - /// Create From A Raw HWND + /// Creates a `Window` instance from a raw HWND. + /// + /// # Arguments + /// + /// * `window` - The raw HWND. #[must_use] pub const fn from_raw_hwnd(window: HWND) -> Self { Self { window } } - /// Get The Raw HWND + /// Returns the raw HWND of the window. #[must_use] pub const fn as_raw_hwnd(&self) -> HWND { self.window } - // Callback Used For Enumerating All Windows + // Callback used for enumerating all windows. unsafe extern "system" fn enum_windows_callback(window: HWND, vec: LPARAM) -> BOOL { let windows = &mut *(vec.0 as *mut Vec); @@ -187,12 +240,11 @@ impl Window { } } -// Automatically Convert Window To GraphicsCaptureItem +// Implements TryFrom For Window To Convert It To GraphicsCaptureItem impl TryFrom for GraphicsCaptureItem { type Error = Error; fn try_from(value: Window) -> Result { - // Get Capture Item From HWND let window = value.as_raw_hwnd(); let interop = windows::core::factory::()?; diff --git a/windows-capture-python/Cargo.toml b/windows-capture-python/Cargo.toml index 070b9ce..003b8ba 100644 --- a/windows-capture-python/Cargo.toml +++ b/windows-capture-python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windows-capture-python" -version = "1.0.61" +version = "1.0.62" authors = ["NiiightmareXD"] edition = "2021" description = "Fastest Windows Screen Capture Library For Python 🔥" diff --git a/windows-capture-python/README.md b/windows-capture-python/README.md index 6fbaf29..81167d3 100644 --- a/windows-capture-python/README.md +++ b/windows-capture-python/README.md @@ -35,7 +35,7 @@ from windows_capture import WindowsCapture, Frame, InternalCaptureControl # Every Error From on_closed and on_frame_arrived Will End Up Here capture = WindowsCapture( - capture_cursor=None, + cursor_capture=None, draw_border=None, monitor_index=None, window_name=None, diff --git a/windows-capture-python/pyproject.toml b/windows-capture-python/pyproject.toml index e9d5365..cdfe41f 100644 --- a/windows-capture-python/pyproject.toml +++ b/windows-capture-python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "windows-capture" -version = "1.0.61" +version = "1.0.62" description = "Fastest Windows Screen Capture Library For Python 🔥" readme = "README.md" requires-python = ">=3.9" diff --git a/windows-capture-python/src/lib.rs b/windows-capture-python/src/lib.rs index 1b69a46..630e67c 100644 --- a/windows-capture-python/src/lib.rs +++ b/windows-capture-python/src/lib.rs @@ -1,18 +1,17 @@ -#![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![warn(clippy::cargo)] -#![allow(clippy::missing_errors_doc)] -#![allow(clippy::missing_panics_doc)] #![allow(clippy::redundant_pub_crate)] use std::sync::Arc; use ::windows_capture::{ - capture::{CaptureControl, CaptureControlError, WindowsCaptureError, WindowsCaptureHandler}, + capture::{ + CaptureControl, CaptureControlError, GraphicsCaptureApiError, GraphicsCaptureApiHandler, + }, frame::{self, Frame}, graphics_capture_api::InternalCaptureControl, monitor::Monitor, - settings::{ColorFormat, Settings}, + settings::{ColorFormat, CursorCaptuerSettings, DrawBorderSettings, Settings}, window::Window, }; use pyo3::{exceptions::PyException, prelude::*, types::PyList}; @@ -59,8 +58,8 @@ impl NativeCaptureControl { match capture_control.wait() { Ok(()) => (), Err(e) => { - if let CaptureControlError::WindowsCaptureError( - WindowsCaptureError::FrameHandlerError( + if let CaptureControlError::GraphicsCaptureApiError( + GraphicsCaptureApiError::FrameHandlerError( InnerNativeWindowsCaptureError::PythonError(ref e), ), ) = e @@ -85,14 +84,14 @@ impl NativeCaptureControl { pub fn stop(&mut self, py: Python) -> PyResult<()> { // But Honestly WTF Is This? You Know How Much Time It Took Me To Debug This? - // Just Why? Who Decided This BS Threading Shit? + // Just Why? Who TF Decided This BS Threading Shit? py.allow_threads(|| { if let Some(capture_control) = self.capture_control.take() { match capture_control.stop() { Ok(()) => (), Err(e) => { - if let CaptureControlError::WindowsCaptureError( - WindowsCaptureError::FrameHandlerError( + if let CaptureControlError::GraphicsCaptureApiError( + GraphicsCaptureApiError::FrameHandlerError( InnerNativeWindowsCaptureError::PythonError(ref e), ), ) = e @@ -121,8 +120,8 @@ impl NativeCaptureControl { pub struct NativeWindowsCapture { on_frame_arrived_callback: Arc, on_closed: Arc, - capture_cursor: Option, - draw_border: Option, + cursor_capture: CursorCaptuerSettings, + draw_border: DrawBorderSettings, monitor_index: Option, window_name: Option, } @@ -133,7 +132,7 @@ impl NativeWindowsCapture { pub fn new( on_frame_arrived_callback: PyObject, on_closed: PyObject, - capture_cursor: Option, + cursor_capture: Option, draw_border: Option, mut monitor_index: Option, window_name: Option, @@ -148,10 +147,22 @@ impl NativeWindowsCapture { monitor_index = Some(1); } + let cursor_capture = match cursor_capture { + Some(true) => CursorCaptuerSettings::WithCursor, + Some(false) => CursorCaptuerSettings::WithoutCursor, + None => CursorCaptuerSettings::Default, + }; + + let draw_border = match draw_border { + Some(true) => DrawBorderSettings::WithBorder, + Some(false) => DrawBorderSettings::WithoutBorder, + None => DrawBorderSettings::Default, + }; + Ok(Self { on_frame_arrived_callback: Arc::new(on_frame_arrived_callback), on_closed: Arc::new(on_closed), - capture_cursor, + cursor_capture, draw_border, monitor_index, window_name, @@ -172,8 +183,8 @@ impl NativeWindowsCapture { match Settings::new( window, - self.capture_cursor, - self.draw_border, + self.cursor_capture.clone(), + self.draw_border.clone(), ColorFormat::Bgra8, ( self.on_frame_arrived_callback.clone(), @@ -199,8 +210,8 @@ impl NativeWindowsCapture { match Settings::new( monitor, - self.capture_cursor, - self.draw_border, + self.cursor_capture.clone(), + self.draw_border.clone(), ColorFormat::Bgra8, ( self.on_frame_arrived_callback.clone(), @@ -219,7 +230,7 @@ impl NativeWindowsCapture { match InnerNativeWindowsCapture::start(settings) { Ok(()) => (), Err(e) => { - if let WindowsCaptureError::FrameHandlerError( + if let GraphicsCaptureApiError::FrameHandlerError( InnerNativeWindowsCaptureError::PythonError(ref e), ) = e { @@ -251,8 +262,8 @@ impl NativeWindowsCapture { match Settings::new( window, - self.capture_cursor, - self.draw_border, + self.cursor_capture.clone(), + self.draw_border.clone(), ColorFormat::Bgra8, ( self.on_frame_arrived_callback.clone(), @@ -278,8 +289,8 @@ impl NativeWindowsCapture { match Settings::new( monitor, - self.capture_cursor, - self.draw_border, + self.cursor_capture.clone(), + self.draw_border.clone(), ColorFormat::Bgra8, ( self.on_frame_arrived_callback.clone(), @@ -298,7 +309,7 @@ impl NativeWindowsCapture { let capture_control = match InnerNativeWindowsCapture::start_free_threaded(settings) { Ok(capture_control) => capture_control, Err(e) => { - if let WindowsCaptureError::FrameHandlerError( + if let GraphicsCaptureApiError::FrameHandlerError( InnerNativeWindowsCaptureError::PythonError(ref e), ) = e { @@ -332,7 +343,7 @@ pub enum InnerNativeWindowsCaptureError { FrameProcessError(frame::Error), } -impl WindowsCaptureHandler for InnerNativeWindowsCapture { +impl GraphicsCaptureApiHandler for InnerNativeWindowsCapture { type Flags = (Arc, Arc); type Error = InnerNativeWindowsCaptureError; diff --git a/windows-capture-python/windows_capture/__init__.py b/windows-capture-python/windows_capture/__init__.py index b40611e..d602306 100644 --- a/windows-capture-python/windows_capture/__init__.py +++ b/windows-capture-python/windows_capture/__init__.py @@ -159,7 +159,7 @@ class WindowsCapture: def __init__( self, - capture_cursor: Optional[bool] = True, + cursor_capture: Optional[bool] = True, draw_border: Optional[bool] = None, monitor_index: Optional[int] = None, window_name: Optional[str] = None, @@ -171,7 +171,7 @@ def __init__( Parameters ---------- - capture_cursor : bool + cursor_capture : bool Whether To Capture The Cursor draw_border : bool Whether To draw The border @@ -188,7 +188,7 @@ def __init__( self.capture = NativeWindowsCapture( self.on_frame_arrived, self.on_closed, - capture_cursor, + cursor_capture, draw_border, monitor_index, window_name,