From f200b66ad78304c5481041654c6e524b8caee89e Mon Sep 17 00:00:00 2001 From: NiiightmareXD Date: Fri, 20 Oct 2023 11:45:51 -0700 Subject: [PATCH] =?UTF-8?q?Stabilize=20=E2=AD=90=20=09modified:=20=20=20Ca?= =?UTF-8?q?rgo.lock=20=09modified:=20=20=20Cargo.toml=20=09modified:=20=20?= =?UTF-8?q?=20README.md=20=09new=20file:=20=20=20src/buffer.rs=20=09modifi?= =?UTF-8?q?ed:=20=20=20src/capture.rs=20=09modified:=20=20=20src/d3d11.rs?= =?UTF-8?q?=20=09modified:=20=20=20src/frame.rs=20=09new=20file:=20=20=20s?= =?UTF-8?q?rc/graphics=5Fcapture=5Fapi.rs=20=09modified:=20=20=20src/lib.r?= =?UTF-8?q?s=20=09modified:=20=20=20src/monitor.rs=20=09new=20file:=20=20?= =?UTF-8?q?=20src/settings.rs=20=09modified:=20=20=20src/window.rs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 316 +++++++++++++++++++++++++++++++- Cargo.toml | 9 +- README.md | 63 ++++--- src/buffer.rs | 24 +++ src/capture.rs | 302 +++++-------------------------- src/d3d11.rs | 83 +++++---- src/frame.rs | 282 ++++++++++------------------- src/graphics_capture_api.rs | 350 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 89 ++++----- src/monitor.rs | 46 +++-- src/settings.rs | 38 ++++ src/window.rs | 83 ++++++--- 12 files changed, 1090 insertions(+), 595 deletions(-) create mode 100644 src/buffer.rs create mode 100644 src/graphics_capture_api.rs create mode 100644 src/settings.rs diff --git a/Cargo.lock b/Cargo.lock index f4b28fe..90c0331 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,24 +2,205 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "qoi", + "tiff", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.148" @@ -42,6 +223,55 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -65,6 +295,19 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "proc-macro2" version = "1.0.68" @@ -74,6 +317,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quote" version = "1.0.33" @@ -83,6 +335,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -98,12 +370,27 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "smallvec" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "syn" version = "2.0.38" @@ -135,12 +422,29 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "windows" version = "0.51.1" @@ -153,8 +457,9 @@ dependencies = [ [[package]] name = "windows-capture" -version = "1.0.19" +version = "1.0.20" dependencies = [ + "image", "log", "parking_lot", "thiserror", @@ -226,3 +531,12 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 290d04d..a871182 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "windows-capture" -version = "1.0.19" +version = "1.0.20" authors = ["NiiightmareXD"] edition = "2021" -description = "Windows Capture Simple Screen Capture for Windows 🔥" +description = "Simple Windows Screen Capture Library For Rust And Python 🔥" documentation = "https://docs.rs/windows-capture" readme = "README.md" repository = "https://github.com/NiiightmareXD/windows-capture" @@ -18,6 +18,7 @@ categories = [ ] [dependencies] +image = "0.24.7" log = "0.4.20" parking_lot = "0.12.1" thiserror = "1.0.49" @@ -26,8 +27,6 @@ windows = { version = "0.51.1", features = [ "Win32_Graphics_Direct3D11", "Win32_Foundation", "Graphics_Capture", - "Graphics", - "Foundation", "Win32_System_WinRT_Direct3D11", "Win32_System_Threading", "Win32_UI_WindowsAndMessaging", @@ -36,4 +35,6 @@ windows = { version = "0.51.1", features = [ "Win32_Graphics_Gdi", "System", "Graphics_DirectX_Direct3D11", + "Foundation_Metadata", + "Win32_UI_HiDpi", ] } diff --git a/README.md b/README.md index ba26e5f..17ecd3c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Windows Capture ![Crates.io](https://img.shields.io/crates/l/windows-capture) ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/NiiightmareXD/windows-capture/rust.yml) ![Crates.io](https://img.shields.io/crates/v/windows-capture) -**Windows Capture** is a highly efficient Rust library that enables you to effortlessly capture the screen using the Graphics Capture API. This library allows you to easily capture the screen of your Windows-based computer and use it for various purposes, such as creating instructional videos, taking screenshots, or recording your gameplay. With its intuitive interface and robust functionality, Windows-Capture is an excellent choice for anyone looking for a reliable and easy-to-use screen capturing solution. +**Windows Capture** is a highly efficient Rust and Python library that enables you to effortlessly capture the screen using the Graphics Capture API. This library allows you to easily capture the screen of your Windows-based computer and use it for various purposes, such as creating instructional videos, taking screenshots, or recording your gameplay. With its intuitive interface and robust functionality, Windows-Capture is an excellent choice for anyone looking for a reliable and easy-to-use screen capturing solution. ## Features @@ -16,7 +16,7 @@ Add this library to your `Cargo.toml`: ```toml [dependencies] -windows-capture = "1.0.19" +windows-capture = "1.0.20" ``` or run this command @@ -27,46 +27,57 @@ cargo add windows-capture ## Usage ```rust -use std::time::Instant; - use windows_capture::{ - capture::{WindowsCaptureHandler, WindowsCaptureSettings}, - frame::Frame, - window::Window, + capture::WindowsCaptureHandler, frame::Frame, settings::WindowsCaptureSettings, window::Window, }; -struct Capture { - fps: usize, - last_output: Instant, -} +struct Capture; impl WindowsCaptureHandler for Capture { - type Flags = (); + type Flags = String; // To Get The Message (Or A Variable Or ...) From The Settings - fn new(_: Self::Flags) -> Self { - Self { - fps: 0, - last_output: Instant::now(), - } + fn new(message: Self::Flags) -> Self { + // Function That Will Be Called To Create The Struct The Flags Can Be Passed + // From Settings + println!("Got The Message: {message}"); + + Self {} } - fn on_frame_arrived(&mut self, _frame: &Frame) { - self.fps += 1; + fn on_frame_arrived(&mut self, frame: Frame) { + // Called Every Time A New Frame Is Available + println!("Got A New Frame"); + + // Save The Frame As An Image To Specified Path + frame.save_as_image("image.png").unwrap(); - if self.last_output.elapsed().as_secs() >= 1 { - println!("{}", self.fps); - self.fps = 0; - self.last_output = Instant::now(); - } + // Call To Stop The Capture Thread, You Might Receive A Few More Frames + // Before It Stops + self.stop(); } fn on_closed(&mut self) { - println!("Closed"); + // Called When The Capture Item Closes Usually When The Window Closes, + // Capture Will End After This Function Ends + println!("Capture Item Closed"); } } fn main() { - let settings = WindowsCaptureSettings::new(Monitor::get_primary(), true, false, ()); + // Checkout Docs For Other Capture Items + let foreground_window = Window::foreground().unwrap(); + + let settings = WindowsCaptureSettings::new( + // Item To Captue + foreground_window, + // Capture Cursor + Some(true), + // Draw Borders + Some(false), + // This Will Be Passed To The New Function + "It Works".to_string(), + ) + .unwrap(); Capture::start(settings).unwrap(); } diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..0a1b168 --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,24 @@ +use std::alloc::Layout; + +/// To Send Raw Pointers Between Threads +pub struct SendPtr(pub *mut T); + +impl SendPtr { + pub fn new(ptr: *mut T) -> Self { + Self(ptr) + } +} + +unsafe impl Send for SendPtr {} + +/// To Save Pointer And It's Layout Together +pub struct Buffer { + pub ptr: *mut u8, + pub layout: Layout, +} + +impl Buffer { + pub const fn new(ptr: *mut u8, layout: Layout) -> Self { + Self { ptr, layout } + } +} diff --git a/src/capture.rs b/src/capture.rs index 55ea7c4..54cc0c2 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -1,258 +1,24 @@ -use std::sync::Arc; - -use log::info; -use parking_lot::Mutex; -use thiserror::Error; +use log::{info, trace}; use windows::{ - core::IInspectable, - Foundation::{AsyncActionCompletedHandler, TypedEventHandler}, - Graphics::{ - Capture::{Direct3D11CaptureFramePool, GraphicsCaptureItem, GraphicsCaptureSession}, - DirectX::DirectXPixelFormat, - }, + Foundation::AsyncActionCompletedHandler, Win32::{ System::WinRT::{ CreateDispatcherQueueController, DispatcherQueueOptions, RoInitialize, RoUninitialize, - DQTAT_COM_NONE, DQTYPE_THREAD_CURRENT, RO_INIT_MULTITHREADED, + DQTAT_COM_NONE, DQTYPE_THREAD_CURRENT, RO_INIT_SINGLETHREADED, }, - UI::WindowsAndMessaging::{ - DispatchMessageW, GetMessageW, PostQuitMessage, TranslateMessage, MSG, + UI::{ + HiDpi::{SetProcessDpiAwareness, PROCESS_PER_MONITOR_DPI_AWARE}, + WindowsAndMessaging::{ + DispatchMessageW, GetMessageW, PostQuitMessage, TranslateMessage, MSG, + }, }, }, }; -use crate::{d3d11::SendDirectX, monitor::Monitor}; - -use super::{ - d3d11::{create_d3d_device, create_direct3d_device}, - frame::Frame, +use crate::{ + frame::Frame, graphics_capture_api::GraphicsCaptureApi, settings::WindowsCaptureSettings, }; -/// Used To Handle Internal Errors -#[derive(Error, Debug)] -pub enum WindowsCaptureError { - #[error("Graphics Capture API Is Not Supported")] - Unsupported, - #[error("Already Started")] - AlreadyStarted, - #[error("Unknown Error")] - Unknown, -} - -/// Capture Settings -#[derive(Eq, PartialEq, Clone, Debug)] -pub struct WindowsCaptureSettings { - /// Item That Can Be Created From Monitor Or Window - item: GraphicsCaptureItem, - /// Capture Mouse Cursor - capture_cursor: bool, - /// Draw Yellow Border Around Captured Window - draw_border: bool, - /// Flags To Pass To The New Function - flags: Flags, -} - -impl WindowsCaptureSettings { - /// Create Capture Settings - pub fn new>( - item: T, - capture_cursor: bool, - draw_border: bool, - flags: Flags, - ) -> Self { - Self { - item: item.into(), - capture_cursor, - draw_border, - flags, - } - } -} - -impl Default for WindowsCaptureSettings -where - Flags: Default, -{ - fn default() -> Self { - Self { - item: Monitor::get_primary().into(), - capture_cursor: false, - draw_border: false, - flags: Default::default(), - } - } -} - -/// Internal Capture Struct -#[derive(Eq, PartialEq, Clone, Debug)] -pub struct WindowsCapture { - frame_pool: Option>, - session: Option, - started: bool, -} - -impl WindowsCapture { - /// Create A New Internal Capture Item - pub fn new< - T: WindowsCaptureHandler + std::marker::Send + 'static, - U: Into, - >( - item: U, - trigger: T, - ) -> Result> { - // Check Support - if !GraphicsCaptureSession::IsSupported()? { - return Err(Box::new(WindowsCaptureError::Unsupported)); - } - - // Init Item - let item = item.into(); - - // Create Device - let d3d_device = create_d3d_device()?; - let device = create_direct3d_device(&d3d_device)?; - - // Create Frame Pool - let frame_pool = Direct3D11CaptureFramePool::Create( - &device, - DirectXPixelFormat::R8G8B8A8UIntNormalized, - 2, - item.Size()?, - )?; - - // Init - let session = frame_pool.CreateCaptureSession(&item)?; - let trigger = Arc::new(Mutex::new(trigger)); - let trigger_item = trigger.clone(); - let trigger_frame_pool = trigger; - let frame_pool = Arc::new(frame_pool); - let device = SendDirectX::new(device); - - // Set CaptureItem Closed Event - item.Closed( - &TypedEventHandler::::new({ - move |_, _| { - trigger_item.lock().on_closed(); - - unsafe { PostQuitMessage(0) }; - - Result::Ok(()) - } - }), - )?; - - // Set FramePool FrameArrived Event - frame_pool.FrameArrived( - &TypedEventHandler::::new({ - let context = unsafe { d3d_device.GetImmediateContext()? }; - let frame_pool = frame_pool.clone(); - let mut last_size = item.Size()?; - - move |frame, _| { - // Get Frame - let frame = frame.as_ref().unwrap(); - let frame = frame.TryGetNextFrame()?; - - // Get Frame Content Size - let frame_content_size = frame.ContentSize()?; - - // Get Frame Surface - let surface = frame.Surface()?; - - // Check If The Size Has Been Changed - if frame_content_size.Width != last_size.Width - || frame_content_size.Height != last_size.Height - { - info!("Size Changed Recreating Device"); - let device = &device; - frame_pool - .Recreate( - &device.inner, - DirectXPixelFormat::R8G8B8A8UIntNormalized, - 2, - frame_content_size, - ) - .unwrap(); - - last_size = frame_content_size; - } else { - let frame = Frame::new( - &surface, - &d3d_device, - &context, - frame_content_size.Width, - frame_content_size.Height, - ); - - // Send The Frame To Trigger Struct - trigger_frame_pool.lock().on_frame_arrived(&frame); - } - - Result::Ok(()) - } - }), - )?; - - Ok(Self { - frame_pool: Some(frame_pool), - session: Some(session), - started: false, - }) - } - - /// Start Internal Capture - pub fn start_capture( - &mut self, - capture_cursor: bool, - draw_border: bool, - ) -> Result<(), Box> { - if self.started { - return Err(Box::new(WindowsCaptureError::AlreadyStarted)); - } - - // Config - self.session - .as_ref() - .unwrap() - .SetIsCursorCaptureEnabled(capture_cursor)?; - self.session - .as_ref() - .unwrap() - .SetIsBorderRequired(draw_border)?; - self.started = true; - - // Start Capture - self.session.as_ref().unwrap().StartCapture()?; - - Ok(()) - } - - /// Stop Internal Capture - pub fn stop_capture(mut self) { - // Stop Capturing - if let Some(frame_pool) = self.frame_pool.take() { - frame_pool.Close().expect("Failed to Close Frame Pool"); - } - - if let Some(session) = self.session.take() { - session.Close().expect("Failed to Close Frame Pool"); - } - } -} - -impl Drop for WindowsCapture { - fn drop(&mut self) { - // Stop Capturing - if let Some(frame_pool) = self.frame_pool.take() { - frame_pool.Close().expect("Failed to Close Frame Pool"); - } - - if let Some(session) = self.session.take() { - session.Close().expect("Failed to Close Frame Pool"); - } - } -} - /// Event Handler Trait pub trait WindowsCaptureHandler: Sized { type Flags; @@ -264,10 +30,16 @@ pub trait WindowsCaptureHandler: Sized { where Self: std::marker::Send + 'static, { - // Init WinRT - unsafe { RoInitialize(RO_INIT_MULTITHREADED)? }; + // Initialize WinRT + trace!("Initializing WinRT"); + unsafe { RoInitialize(RO_INIT_SINGLETHREADED)? }; + + // Set DPI Awarness + trace!("Setting DPI Awarness"); + unsafe { SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)? }; // Create A Dispatcher Queue For Current Thread + trace!("Creating A Dispatcher Queue For Capture Thread"); let options = DispatcherQueueOptions { dwSize: std::mem::size_of::() as u32, threadType: DQTYPE_THREAD_CURRENT, @@ -275,53 +47,67 @@ pub trait WindowsCaptureHandler: Sized { }; let controller = unsafe { CreateDispatcherQueueController(options)? }; + // Start Capture + info!("Starting Capture Thread"); let trigger = Self::new(settings.flags); - let mut capture = WindowsCapture::new(settings.item, trigger)?; + let mut capture = GraphicsCaptureApi::new(settings.item, trigger)?; capture.start_capture(settings.capture_cursor, settings.draw_border)?; // Message Loop + trace!("Entering Message Loop"); let mut message = MSG::default(); unsafe { - while GetMessageW(&mut message, None, 0, 0).into() { + while GetMessageW(&mut message, None, 0, 0).as_bool() { TranslateMessage(&message); DispatchMessageW(&message); } } // Shutdown Dispatcher Queue + trace!("Shutting Down Dispatcher Queue"); let async_action = controller.ShutdownQueueAsync()?; async_action.SetCompleted(&AsyncActionCompletedHandler::new( - move |_, _| -> windows::core::Result<()> { + move |_, _| -> Result<(), windows::core::Error> { unsafe { PostQuitMessage(0) }; Ok(()) }, ))?; - // Message Loop - let mut msg = MSG::default(); + // Final Message Loop + trace!("Entering Final Message Loop"); + let mut message = MSG::default(); unsafe { - while GetMessageW(&mut msg, None, 0, 0).into() { - TranslateMessage(&msg); - DispatchMessageW(&msg); + while GetMessageW(&mut message, None, 0, 0).as_bool() { + TranslateMessage(&message); + DispatchMessageW(&message); } } // Stop Capturing + info!("Stopping Capture Thread"); capture.stop_capture(); - // Uninit WinRT + // Uninitialize WinRT + trace!("Uninitializing WinRT"); unsafe { RoUninitialize() }; Ok(()) } /// Function That Will Be Called To Create The Struct The Flags Can Be - /// Passed From Settigns + /// Passed From Settings fn new(flags: Self::Flags) -> Self; /// Called Every Time A New Frame Is Available - fn on_frame_arrived(&mut self, frame: &Frame); + fn on_frame_arrived(&mut self, frame: Frame); - /// Called If The Capture Item Closed Usually When The Window Closes + /// Called When The Capture Item Closes Usually When The Window Closes, + /// Capture Will End After This Function Ends fn on_closed(&mut self); + + /// Call To Stop The Capture Thread, You Might Receive A Few More Frames + /// Before It Stops + fn stop(&self) { + unsafe { PostQuitMessage(0) }; + } } diff --git a/src/d3d11.rs b/src/d3d11.rs index fa7e0d1..76edc3c 100644 --- a/src/d3d11.rs +++ b/src/d3d11.rs @@ -1,78 +1,85 @@ -use log::warn; +use thiserror::Error; use windows::{ core::ComInterface, Graphics::DirectX::Direct3D11::IDirect3DDevice, Win32::{ Graphics::{ - Direct3D::{D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP}, + Direct3D::{ + D3D_DRIVER_TYPE_HARDWARE, D3D_FEATURE_LEVEL, D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_9_1, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_3, + }, Direct3D11::{ - D3D11CreateDevice, ID3D11Device, D3D11_CREATE_DEVICE_BGRA_SUPPORT, - D3D11_SDK_VERSION, + D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, + D3D11_CREATE_DEVICE_DISABLE_GPU_TIMEOUT, D3D11_SDK_VERSION, }, - Dxgi::{IDXGIDevice, DXGI_ERROR_UNSUPPORTED}, + Dxgi::IDXGIDevice, }, System::WinRT::Direct3D11::CreateDirect3D11DeviceFromDXGIDevice, }, }; -pub struct SendDirectX { - pub inner: T, -} +/// To Share DirectX Structs Between Threads +pub struct SendDirectX(pub T); impl SendDirectX { pub fn new(device: T) -> Self { - Self { inner: device } + Self(device) } } unsafe impl Send for SendDirectX {} -pub fn create_d3d_device() -> Result> { +/// Used To Handle Internal DirectX Errors +#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +pub enum DirectXErrors { + #[error("Failed To Create DirectX Device With The Recommended Feature Level")] + FeatureLevelNotSatisfied, +} + +/// Create ID3D11Device And ID3D11DeviceContext +pub fn create_d3d_device() -> Result<(ID3D11Device, ID3D11DeviceContext), Box> +{ + // Set Feature Flags + let feature_flags = [ + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1, + ]; + // Try To Build A Hardware Device let mut d3d_device = None; - let result = unsafe { + let mut feature_level = D3D_FEATURE_LEVEL::default(); + let mut d3d_device_context = None; + unsafe { D3D11CreateDevice( None, D3D_DRIVER_TYPE_HARDWARE, None, - D3D11_CREATE_DEVICE_BGRA_SUPPORT, - None, + D3D11_CREATE_DEVICE_DISABLE_GPU_TIMEOUT, + Some(&feature_flags), D3D11_SDK_VERSION, Some(&mut d3d_device), - None, - None, - ) + Some(&mut feature_level), + Some(&mut d3d_device_context), + )? }; - // If Failed Switch To Warp - if result.as_ref().is_err() { - if result.as_ref().err().unwrap() == &DXGI_ERROR_UNSUPPORTED.into() { - warn!("Failed To Create D3D_DRIVER_TYPE_HARDWARE DirectX 11 Device"); - unsafe { - D3D11CreateDevice( - None, - D3D_DRIVER_TYPE_WARP, - None, - D3D11_CREATE_DEVICE_BGRA_SUPPORT, - None, - D3D11_SDK_VERSION, - Some(&mut d3d_device), - None, - None, - )?; - }; - } else { - result?; - } + if feature_level != D3D_FEATURE_LEVEL_11_1 { + return Err(Box::new(DirectXErrors::FeatureLevelNotSatisfied)); } - Ok(d3d_device.unwrap()) + Ok((d3d_device.unwrap(), d3d_device_context.unwrap())) } +/// Create A IDirect3DDevice From ID3D11Device pub fn create_direct3d_device( d3d_device: &ID3D11Device, ) -> Result> { - // Create A IDirect3DDevice From ID3D11Device let dxgi_device: IDXGIDevice = d3d_device.cast()?; let inspectable = unsafe { CreateDirect3D11DeviceFromDXGIDevice(&dxgi_device)? }; let device: IDirect3DDevice = inspectable.cast()?; diff --git a/src/frame.rs b/src/frame.rs index 1153bdc..6293d4a 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -1,19 +1,22 @@ -use windows::{ - core::ComInterface, - Graphics::DirectX::Direct3D11::IDirect3DSurface, - Win32::{ - Graphics::Direct3D11::{ - ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_BOX, D3D11_CPU_ACCESS_READ, - D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ, D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, - }, - System::WinRT::Direct3D11::IDirect3DDxgiInterfaceAccess, - }, +use std::{mem, ptr}; + +use image::ColorType; +use thiserror::Error; +use windows::Win32::Graphics::Direct3D11::{ + ID3D11DeviceContext, ID3D11Texture2D, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ, }; +/// Used To Handle Internal Frame Errors +#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +pub enum FrameError { + #[error("Graphics Capture API Is Not Supported")] + InvalidSize, +} + /// Pixels Color Representation #[derive(Eq, PartialEq, Clone, Copy, Debug)] #[repr(C)] -pub struct RGBA { +pub struct Rgba { pub r: u8, pub g: u8, pub b: u8, @@ -21,148 +24,58 @@ pub struct RGBA { } /// Frame Struct Used To Crop And Get The Frame Buffer -#[derive(Eq, PartialEq, Clone, Copy, Debug)] -pub struct Frame<'a> { - surface: &'a IDirect3DSurface, - d3d_device: &'a ID3D11Device, - context: &'a ID3D11DeviceContext, - width: i32, - height: i32, +pub struct Frame { + buffer: *mut u8, + texture: ID3D11Texture2D, + frame_surface: ID3D11Texture2D, + context: ID3D11DeviceContext, + width: u32, + height: u32, } -impl<'a> Frame<'a> { +impl Frame { /// Craete A New Frame pub const fn new( - surface: &'a IDirect3DSurface, - d3d_device: &'a ID3D11Device, - context: &'a ID3D11DeviceContext, - width: i32, - height: i32, + buffer: *mut u8, + texture: ID3D11Texture2D, + frame_surface: ID3D11Texture2D, + context: ID3D11DeviceContext, + width: u32, + height: u32, ) -> Self { Self { - surface, - d3d_device, + texture, + frame_surface, context, width, height, + buffer, } } - /// Get The Frame Buffer - pub fn buffer(&self) -> Result> { - // Convert Surface To Texture - let access = self.surface.cast::()?; - let texture = unsafe { access.GetInterface::()? }; - - // Texture Settings - let mut texture_desc = D3D11_TEXTURE2D_DESC::default(); - unsafe { texture.GetDesc(&mut texture_desc) } - texture_desc.Usage = D3D11_USAGE_STAGING; - texture_desc.BindFlags = 0; - texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0 as u32; - texture_desc.MiscFlags = 0; - - // Create A Copy Texture To Process On - let mut texture_copy = None; - unsafe { - self.d3d_device - .CreateTexture2D(&texture_desc, None, Some(&mut texture_copy))? - }; - let texture_copy = texture_copy.unwrap(); - - // Copy The Real Texture To Copy Texture - unsafe { self.context.CopyResource(&texture_copy, &texture) }; - - // Map The Texture To Enable CPU Access - let mut mapped_resource = D3D11_MAPPED_SUBRESOURCE::default(); - unsafe { - self.context.Map( - &texture_copy, - 0, - D3D11_MAP_READ, - 0, - Some(&mut mapped_resource), - )? - }; - - // Create A Slice From The Bits - let slice: &[u8] = unsafe { - std::slice::from_raw_parts( - mapped_resource.pData as *const u8, - (texture_desc.Height * mapped_resource.RowPitch) as usize, - ) - }; - - unsafe { self.context.Unmap(&texture_copy, 0) }; - - Ok(FrameBuffer { - slice, - width: texture_desc.Width, - height: texture_desc.Height, - }) + /// Get The Frame Width + pub fn width(&self) -> u32 { + self.width } - /// Get Part Of The Frame Buffer - pub fn sub_buffer( - &self, - start_width: u32, - start_height: u32, - width: u32, - height: u32, - ) -> Result> { - // Convert Surface To Texture - let access = self.surface.cast::()?; - let texture = unsafe { access.GetInterface::()? }; - - // Texture Settings - let mut texture_desc = D3D11_TEXTURE2D_DESC::default(); - unsafe { texture.GetDesc(&mut texture_desc) } - texture_desc.Usage = D3D11_USAGE_STAGING; - texture_desc.BindFlags = 0; - texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0 as u32; - texture_desc.MiscFlags = 0; - - // Create A Copy Texture To Process On - let mut texture_copy = None; - unsafe { - self.d3d_device - .CreateTexture2D(&texture_desc, None, Some(&mut texture_copy))? - }; - let texture_copy = texture_copy.unwrap(); - - // Create Box Settings - println!( - "start_width: {start_width}, start_height: {start_height}, width: {width}, height: \ - {height}" - ); - let src_box = D3D11_BOX { - left: start_width, - top: start_height, - front: 0, - right: start_width + width, - bottom: start_height + height, - back: 1, - }; + // Get The Frame Height + pub fn height(&self) -> u32 { + self.height + } + /// Get The Frame Buffer + pub fn buffer(&self) -> Result<&[Rgba], Box> { // Copy The Real Texture To Copy Texture unsafe { - self.context.CopySubresourceRegion( - &texture_copy, - 0, - 0, - 0, - 0, - &texture, - 0, - Some(&src_box), - ) + self.context + .CopyResource(&self.texture, &self.frame_surface) }; // Map The Texture To Enable CPU Access let mut mapped_resource = D3D11_MAPPED_SUBRESOURCE::default(); unsafe { self.context.Map( - &texture_copy, + &self.texture, 0, D3D11_MAP_READ, 0, @@ -171,70 +84,63 @@ impl<'a> Frame<'a> { }; // Create A Slice From The Bits - let slice: &[u8] = unsafe { - std::slice::from_raw_parts( - mapped_resource.pData as *const u8, - (texture_desc.Height * mapped_resource.RowPitch) as usize, - ) + let slice = if self.width * 4 == mapped_resource.RowPitch { + // Means There Is No Padding And We Can Do Our Work + unsafe { + std::slice::from_raw_parts( + mapped_resource.pData as *const Rgba, + (self.height * mapped_resource.RowPitch) as usize / std::mem::size_of::(), + ) + } + } else { + // There Is Padding So We Have To Work According To: + // https://learn.microsoft.com/en-us/windows/win32/medfound/image-stride + let row_size = self.width as usize * std::mem::size_of::(); + + for i in 0..self.height { + unsafe { + ptr::copy_nonoverlapping( + mapped_resource + .pData + .add((i * mapped_resource.RowPitch) as usize) + as *mut u8, + self.buffer.add(i as usize * row_size), + row_size, + ) + }; + } + + unsafe { + std::slice::from_raw_parts( + self.buffer as *mut Rgba, + (self.width * self.height) as usize, + ) + } }; - unsafe { self.context.Unmap(&texture_copy, 0) }; - - Ok(FrameBuffer { - slice, - width: texture_desc.Width, - height: texture_desc.Height, - }) - } - - /// Get Frame Width - pub const fn width(&self) -> i32 { - self.width - } - - /// Get Frame Height - pub const fn height(&self) -> i32 { - self.height - } -} - -/// FrameBuffer Struct Used To Crop And Get The Buffer -#[derive(Eq, PartialEq, Clone, Copy, Debug)] -pub struct FrameBuffer<'a> { - slice: &'a [u8], - width: u32, - height: u32, -} - -impl<'a> FrameBuffer<'a> { - /// Create A New FrameBuffer - pub const fn new(slice: &'a [u8], width: u32, height: u32) -> Self { - Self { - slice, - width, - height, - } + Ok(slice) } - /// Get The Frame Buffer - pub const fn pixels(&self) -> &'a [RGBA] { - let pixel_slice: &[RGBA] = unsafe { - std::slice::from_raw_parts( - self.slice.as_ptr() as *const RGBA, - self.slice.len() / std::mem::size_of::(), - ) + // /// Get Part Of The Frame Buffer + // pub fn sub_buffer( + // &self, + // start_width: u32, + // start_height: u32, + // width: u32, + // height: u32, + // ) -> Result> { + // } + + /// Save The Frame As An Image To Specified Path + pub fn save_as_image(&self, path: &str) -> Result<(), Box> { + let buffer = self.buffer()?; + + let buf = unsafe { + std::slice::from_raw_parts(buffer.as_ptr() as *mut u8, mem::size_of_val(buffer)) }; - pixel_slice - } - - /// Get Buffer Width - pub const fn width(&self) -> u32 { - self.width - } + image::save_buffer(path, buf, self.width, self.height, ColorType::Rgba8)?; - /// Get Buffer Height - pub const fn height(&self) -> u32 { - self.height + Ok(()) } } diff --git a/src/graphics_capture_api.rs b/src/graphics_capture_api.rs new file mode 100644 index 0000000..5fe6b5c --- /dev/null +++ b/src/graphics_capture_api.rs @@ -0,0 +1,350 @@ +use std::{ + alloc::{self, Layout}, + sync::{ + atomic::{self, AtomicBool}, + Arc, + }, +}; + +use log::{info, trace}; +use parking_lot::Mutex; +use thiserror::Error; +use windows::{ + core::{ComInterface, IInspectable, HSTRING}, + Foundation::{Metadata::ApiInformation, TypedEventHandler}, + Graphics::{ + Capture::{Direct3D11CaptureFramePool, GraphicsCaptureItem, GraphicsCaptureSession}, + DirectX::{Direct3D11::IDirect3DDevice, DirectXPixelFormat}, + }, + Win32::{ + Graphics::{ + Direct3D11::{ + ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_CPU_ACCESS_READ, + D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, + }, + Dxgi::Common::{DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SAMPLE_DESC}, + }, + System::WinRT::Direct3D11::IDirect3DDxgiInterfaceAccess, + UI::WindowsAndMessaging::PostQuitMessage, + }, +}; + +use crate::{ + buffer::{Buffer, SendPtr}, + capture::WindowsCaptureHandler, + d3d11::{create_d3d_device, create_direct3d_device, SendDirectX}, + frame::Frame, +}; + +/// Used To Handle Internal Capture Errors +#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +pub enum WindowsCaptureError { + #[error("Graphics Capture API Is Not Supported")] + Unsupported, + #[error("Graphics Capture API Changing Cursor Status Is Not Supported")] + CursorUnsupported, + #[error("Graphics Capture API Changing Border Status Is Not Supported")] + BorderUnsupported, + #[error("Already Started")] + AlreadyStarted, + #[error("Capture Session Is Closed")] + CaptureClosed, +} + +/// Internal Capture Struct +pub struct GraphicsCaptureApi { + _item: GraphicsCaptureItem, + _d3d_device: ID3D11Device, + _direct3d_device: IDirect3DDevice, + _d3d_device_context: ID3D11DeviceContext, + buffer: Buffer, + frame_pool: Option>, + session: Option, + active: bool, + closed: bool, +} + +impl GraphicsCaptureApi { + /// Create A New Internal Capture Item + pub fn new( + item: GraphicsCaptureItem, + callback: T, + ) -> Result> { + // Check Support + if !ApiInformation::IsApiContractPresentByMajor( + &HSTRING::from("Windows.Foundation.UniversalApiContract"), + 8, + )? { + return Err(Box::new(WindowsCaptureError::Unsupported)); + } + + // Allocate 128MB Of Memory + trace!("Allocating 128MB Of Memory"); + let layout = Layout::new::<[u8; 128 * 1024 * 1024]>(); + let ptr = unsafe { alloc::alloc(layout) }; + if ptr.is_null() { + alloc::handle_alloc_error(layout); + } + + let buffer = Buffer::new(ptr, layout); + + // Create DirectX Devices + trace!("Creating DirectX Devices"); + let (d3d_device, d3d_device_context) = create_d3d_device()?; + let direct3d_device = create_direct3d_device(&d3d_device)?; + + // Create Frame Pool + trace!("Creating Frame Pool"); + let frame_pool = Direct3D11CaptureFramePool::Create( + &direct3d_device, + DirectXPixelFormat::R8G8B8A8UIntNormalized, + 2, + item.Size()?, + )?; + let frame_pool = Arc::new(frame_pool); + + // Create Capture Session + trace!("Creating Capture Session"); + let session = frame_pool.CreateCaptureSession(&item)?; + + // Trigger Struct + let callback = Arc::new(Mutex::new(callback)); + + // Indicates If The Capture Is Closed + let closed = Arc::new(AtomicBool::new(false)); + + // Set Capture Session Closed Event + item.Closed( + &TypedEventHandler::::new({ + // Init + let callback_closed = callback.clone(); + let closed_item = closed.clone(); + + move |_, _| { + unsafe { PostQuitMessage(0) }; + + callback_closed.lock().on_closed(); + + closed_item.store(true, atomic::Ordering::Relaxed); + + Result::Ok(()) + } + }), + )?; + + // Set Frame Pool Frame Arrived Event + frame_pool.FrameArrived( + &TypedEventHandler::::new({ + // Init + let frame_pool_recreate = frame_pool.clone(); + let closed_frame_pool = closed.clone(); + let d3d_device_frame_pool = d3d_device.clone(); + let context = d3d_device_context.clone(); + + let mut last_size = item.Size()?; + let callback_frame_arrived = callback; + let direct3d_device_recreate = SendDirectX::new(direct3d_device.clone()); + + let buffer = SendPtr::new(buffer.ptr); + + move |frame, _| { + // Return Early If The Capture Is Closed + if closed_frame_pool.load(atomic::Ordering::Relaxed) { + return Ok(()); + } + + // Get Frame + let frame = frame.as_ref().unwrap().TryGetNextFrame()?; + + // Get Frame Content Size + let frame_content_size = frame.ContentSize()?; + + // 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::()? }; + + // Get Texture Settings + let mut desc = D3D11_TEXTURE2D_DESC::default(); + unsafe { frame_surface.GetDesc(&mut desc) } + + // Check Frame Format + if desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM { + // Check If The Size Has Been Changed + if frame_content_size.Width != last_size.Width + || frame_content_size.Height != last_size.Height + { + info!( + "Size Changed From {}x{} to {}x{} -> Recreating Device", + last_size.Width, + last_size.Height, + frame_content_size.Width, + frame_content_size.Height, + ); + let direct3d_device_recreate = &direct3d_device_recreate; + frame_pool_recreate + .Recreate( + &direct3d_device_recreate.0, + DirectXPixelFormat::R8G8B8A8UIntNormalized, + 2, + frame_content_size, + ) + .unwrap(); + + last_size = frame_content_size; + + return Ok(()); + } + + // Set Width & Height + let texture_width = desc.Width; + let texture_height = desc.Height; + + // Texture Settings + let texture_desc = D3D11_TEXTURE2D_DESC { + Width: texture_width, + Height: texture_height, + MipLevels: 1, + ArraySize: 1, + Format: DXGI_FORMAT_R8G8B8A8_UNORM, + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Usage: D3D11_USAGE_STAGING, + BindFlags: 0, + CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32, + MiscFlags: 0, + }; + + // Create A Texture That CPU Can Read + let mut texture = None; + unsafe { + d3d_device_frame_pool.CreateTexture2D( + &texture_desc, + None, + Some(&mut texture), + )? + }; + let texture = texture.unwrap(); + + let buffer = &buffer; + let frame = Frame::new( + buffer.0, + texture, + frame_surface, + context.clone(), + texture_width, + texture_height, + ); + + // Send The Frame To Trigger Struct + callback_frame_arrived.lock().on_frame_arrived(frame); + } else { + callback_frame_arrived.lock().on_closed(); + + unsafe { PostQuitMessage(0) }; + closed_frame_pool.store(true, atomic::Ordering::Relaxed); + } + + Result::Ok(()) + } + }), + )?; + + Ok(Self { + _item: item, + _d3d_device: d3d_device, + _direct3d_device: direct3d_device, + _d3d_device_context: d3d_device_context, + buffer, + frame_pool: Some(frame_pool), + session: Some(session), + active: false, + closed: false, + }) + } + + /// Start Internal Capture + pub fn start_capture( + &mut self, + capture_cursor: Option, + draw_border: Option, + ) -> Result<(), Box> { + // Check If The Capture Is Already Installed + if self.active { + return Err(Box::new(WindowsCaptureError::AlreadyStarted)); + } + + if self.closed { + return Err(Box::new(WindowsCaptureError::CaptureClosed)); + } + + // Config + if capture_cursor.is_some() { + if ApiInformation::IsPropertyPresent( + &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"), + &HSTRING::from("IsCursorCaptureEnabled"), + )? { + self.session + .as_ref() + .unwrap() + .SetIsCursorCaptureEnabled(capture_cursor.unwrap())?; + } else { + return Err(Box::new(WindowsCaptureError::CursorUnsupported)); + } + } + + if draw_border.is_some() { + if ApiInformation::IsPropertyPresent( + &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"), + &HSTRING::from("IsBorderRequired"), + )? { + self.session + .as_ref() + .unwrap() + .SetIsBorderRequired(draw_border.unwrap())?; + } else { + return Err(Box::new(WindowsCaptureError::BorderUnsupported)); + } + } + + // Start Capture + self.session.as_ref().unwrap().StartCapture()?; + + self.active = true; + + Ok(()) + } + + /// Stop Internal Capture + pub fn stop_capture(&mut self) { + self.closed = true; + + if let Some(frame_pool) = self.frame_pool.take() { + frame_pool.Close().expect("Failed to Close Frame Pool"); + } + + if let Some(session) = self.session.take() { + session.Close().expect("Failed to Close Capture Session"); + } + } +} + +impl Drop for GraphicsCaptureApi { + fn drop(&mut self) { + if !self.closed { + if let Some(frame_pool) = self.frame_pool.take() { + frame_pool.Close().expect("Failed to Close Frame Pool"); + } + + if let Some(session) = self.session.take() { + session.Close().expect("Failed to Close Capture Session"); + } + } + + unsafe { alloc::dealloc(self.buffer.ptr, self.buffer.layout) }; + } +} diff --git a/src/lib.rs b/src/lib.rs index 23577e5..03f6efd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,13 @@ //! # Windows Capture Rust Library //! -//! **Windows Capture** is a highly efficient Rust library that enables you to -//! effortlessly capture the screen using the Graphics Capture API. This library -//! allows you to easily capture the screen of your Windows-based computer and -//! use it for various purposes, such as creating instructional videos, taking -//! screenshots, or recording your gameplay. With its intuitive interface and -//! robust functionality, Windows-Capture is an excellent choice for anyone -//! looking for a reliable and easy-to-use screen capturing solution. +//! **Windows Capture** is a highly efficient Rust and Python library that +//! enables you to effortlessly capture the screen using the Graphics Capture +//! API. This library allows you to easily capture the screen of your +//! Windows-based computer and use it for various purposes, such as creating +//! instructional videos, taking screenshots, or recording your gameplay. With +//! its intuitive interface and robust functionality, Windows-Capture is an +//! excellent choice for anyone looking for a reliable and easy-to-use screen +//! capturing solution. //! //! ## Features //! @@ -21,7 +22,7 @@ //! //! ```toml //! [dependencies] -//! windows-capture = "1.0.19" +//! windows-capture = "1.0.20" //! ``` //! or run this command //! @@ -32,58 +33,66 @@ //! ## Usage //! //! ```no_run -//! use std::time::Instant; //! use windows_capture::{ -//! capture::{WindowsCaptureHandler, WindowsCaptureSettings}, -//! frame::Frame, -//! monitor::Monitor, +//! capture::WindowsCaptureHandler, frame::Frame, settings::WindowsCaptureSettings, +//! window::Window, //! }; //! -//! struct Capture { -//! fps: usize, -//! last_output: Instant, -//! } +//! struct Capture; //! //! impl WindowsCaptureHandler for Capture { -//! type Flags = (); +//! type Flags = String; // To Get The Message (Or A Variable Or ...) From The Settings +//! +//! fn new(message: Self::Flags) -> Self { +//! // Function That Will Be Called To Create The Struct The Flags Can Be Passed +//! // From Settings +//! println!("Got The Message: {message}"); //! -//! fn new(_: Self::Flags) -> Self { -//! Self { -//! fps: 0, -//! last_output: Instant::now(), -//! } +//! Self {} //! } //! -//! fn on_frame_arrived(&mut self, _frame: &Frame) { -//! self.fps += 1; +//! fn on_frame_arrived(&mut self, frame: Frame) { +//! // Called Every Time A New Frame Is Available +//! println!("Got A New Frame"); //! -//! if self.last_output.elapsed().as_secs() >= 1 { -//! println!("{}", self.fps); -//! self.fps = 0; -//! self.last_output = Instant::now(); -//! } +//! // Save The Frame As An Image To Specified Path +//! frame.save_as_image("image.png").unwrap(); +//! +//! // Call To Stop The Capture Thread, You Might Receive A Few More Frames +//! // Before It Stops +//! self.stop(); //! } //! //! fn on_closed(&mut self) { -//! println!("Closed"); +//! // Called When The Capture Item Closes Usually When The Window Closes, +//! // Capture Will End After This Function Ends +//! println!("Capture Item Closed"); //! } //! } //! -//! let settings = WindowsCaptureSettings::new(Monitor::get_primary(), true, false, ()); +//! // Checkout Docs For Other Capture Items +//! let foreground_window = Window::foreground().unwrap(); +//! +//! let settings = WindowsCaptureSettings::new( +//! // Item To Captue +//! foreground_window, +//! // Capture Cursor +//! Some(true), +//! // Draw Borders +//! Some(false), +//! // This Will Be Passed To The New Function +//! "It Works".to_string(), +//! ) +//! .unwrap(); +//! //! Capture::start(settings).unwrap(); //! ``` -//! -//! ## Documentation -//! -//! Detailed documentation for each API and type can be found [here](https://docs.rs/windows-capture). -//! -//! ## Contributing -//! -//! Contributions are welcome! If you find a bug or want to add new features to -//! the library, please open an issue or submit a pull request. +mod buffer; pub mod capture; mod d3d11; pub mod frame; +pub mod graphics_capture_api; pub mod monitor; +pub mod settings; pub mod window; diff --git a/src/monitor.rs b/src/monitor.rs index 1896023..6f445c8 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,3 +1,4 @@ +use thiserror::Error; use windows::{ Graphics::Capture::GraphicsCaptureItem, Win32::{ @@ -9,27 +10,41 @@ use windows::{ }, }; +/// Used To Handle Internal Monitor Errors +#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +pub enum MonitorErrors { + #[error("Failed To Find Monitor")] + NotFound, +} + /// Represents A Monitor Device +#[derive(Eq, PartialEq, Clone, Copy, Debug)] pub struct Monitor { monitor: HMONITOR, } impl Monitor { /// Get The Primary Monitor - pub fn get_primary() -> Self { + pub fn primary() -> Self { let point = POINT { x: 0, y: 0 }; let monitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTOPRIMARY) }; Self { monitor } } - /// Create From A HMONITOR - pub const fn from_hmonitor(monitor: HMONITOR) -> Self { - Self { monitor } + /// Get The Monitor From It's Index + pub fn from_index(index: usize) -> Result> { + let monitor = Self::enumerate()?; + let monitor = match monitor.get(index) { + Some(monitor) => *monitor, + None => return Err(Box::new(MonitorErrors::NotFound)), + }; + + Ok(Self { monitor }) } /// Get A List Of All Monitors - pub fn list_monitors() -> Result, Box> { + pub fn enumerate() -> Result, Box> { let mut monitors: Vec = Vec::new(); unsafe { @@ -45,11 +60,17 @@ impl Monitor { Ok(monitors) } + /// Create From A Raw HMONITOR + pub const fn from_raw_hmonitor(monitor: HMONITOR) -> Self { + Self { monitor } + } + /// Get The Raw HMONITOR - pub const fn get_raw_hmonitor(&self) -> HMONITOR { + pub const fn as_raw_hmonitor(&self) -> HMONITOR { self.monitor } + // Callback Used For Enumerating All Monitors unsafe extern "system" fn enum_monitors_callback( monitor: HMONITOR, _: HDC, @@ -65,13 +86,14 @@ impl Monitor { } // Automatically Convert Monitor To GraphicsCaptureItem -impl From for GraphicsCaptureItem { - fn from(value: Monitor) -> Self { +impl TryFrom for GraphicsCaptureItem { + type Error = Box; + + fn try_from(value: Monitor) -> Result { // Get Capture Item From HMONITOR - let monitor = value.get_raw_hmonitor(); + let monitor = value.as_raw_hmonitor(); - let interop = - windows::core::factory::().unwrap(); - unsafe { interop.CreateForMonitor(monitor).unwrap() } + let interop = windows::core::factory::()?; + Ok(unsafe { interop.CreateForMonitor(monitor)? }) } } diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..a6ad6c6 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,38 @@ +use thiserror::Error; +use windows::Graphics::Capture::GraphicsCaptureItem; + +/// Used To Handle Internal Settings Errors +#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +pub enum SettingsErrors { + #[error("Failed To Convert To GraphicsCaptureItem")] + ConvertFailed, +} + +/// Capture Settings, None Means Default +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct WindowsCaptureSettings { + pub item: GraphicsCaptureItem, + pub capture_cursor: Option, + pub draw_border: Option, + pub flags: Flags, +} + +impl WindowsCaptureSettings { + /// Create Capture Settings + pub fn new>( + item: T, + capture_cursor: Option, + draw_border: Option, + flags: Flags, + ) -> Result> { + Ok(Self { + item: match item.try_into() { + Ok(item) => item, + Err(_) => return Err(Box::new(SettingsErrors::ConvertFailed)), + }, + capture_cursor, + draw_border, + flags, + }) + } +} diff --git a/src/window.rs b/src/window.rs index 53724d5..a747f68 100644 --- a/src/window.rs +++ b/src/window.rs @@ -16,34 +16,35 @@ use windows::{ }, }; -/// Used To Handle Internal Errors -#[derive(Error, Debug)] +/// Used To Handle Internal Window Errors +#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] pub enum WindowErrors { + #[error("Failed To Get The Foreground Window")] + NoActiveWindow, #[error("Failed To Find Window")] NotFound, - #[error("Unknown Error")] - Unknown, } /// Represents A Windows +#[derive(Eq, PartialEq, Clone, Copy, Debug)] pub struct Window { window: HWND, } impl Window { /// Get The Currently Active Foreground Window - pub fn get_foreground() -> Self { + pub fn foreground() -> Result> { let window = unsafe { GetForegroundWindow() }; - Self { window } - } - /// Crate From A HWND - pub const fn from_hwnd(window: HWND) -> Self { - Self { window } + if window.0 == 0 { + return Err(Box::new(WindowErrors::NoActiveWindow)); + } + + Ok(Self { window }) } /// Create From A Window Name - pub fn from_window_name(title: &str) -> Result> { + pub fn from_name(title: &str) -> Result> { let title = HSTRING::from(title); let window = unsafe { FindWindowW(None, &title) }; @@ -54,13 +55,31 @@ impl Window { Ok(Self { window }) } + /// Create From A Window Name Substring + pub fn from_contains_name(title: &str) -> Result> { + let windows = Self::enumerate()?; + + let mut target_window = None; + for window in windows { + if window.title()?.contains(title) { + target_window = Some(window); + break; + } + } + + match target_window { + Some(window) => Ok(window), + None => Err(Box::new(WindowErrors::NotFound)), + } + } + /// Get Window Title - pub fn get_window_title(window: HWND) -> Result> { - let len = unsafe { GetWindowTextLengthW(window) } + 1; + pub fn title(&self) -> Result> { + let len = unsafe { GetWindowTextLengthW(self.window) } + 1; let mut name = vec![0u16; len as usize]; if len > 1 { - let copied = unsafe { GetWindowTextW(window, &mut name) }; + let copied = unsafe { GetWindowTextW(self.window, &mut name) }; if copied == 0 { return Ok(String::new()); } @@ -95,20 +114,21 @@ impl Window { } } else { warn!("GetClientRect Failed"); + return false; } true } /// Get A List Of All Windows - pub fn get_windows() -> Result, Box> { - let mut windows: Vec = Vec::new(); + pub fn enumerate() -> Result, Box> { + let mut windows: Vec = Vec::new(); unsafe { EnumChildWindows( GetDesktopWindow(), Some(Self::enum_windows_callback), - LPARAM(&mut windows as *mut Vec as isize), + LPARAM(&mut windows as *mut Vec as isize), ) .ok()? }; @@ -125,16 +145,22 @@ impl Window { } } + /// Create From A Raw HWND + pub const fn from_raw_hwnd(window: HWND) -> Self { + Self { window } + } + /// Get The Raw HWND - pub const fn get_raw_hwnd(&self) -> HWND { + pub const fn as_raw_hwnd(&self) -> HWND { self.window } + // 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); + let windows = &mut *(vec.0 as *mut Vec); if Self::is_window_valid(window) { - windows.push(window); + windows.push(Self { window }); } TRUE @@ -142,13 +168,14 @@ impl Window { } // Automatically Convert Window To GraphicsCaptureItem -impl From for GraphicsCaptureItem { - fn from(value: Window) -> Self { - // Get Capture Item From HMONITOR - let window = value.get_raw_hwnd(); - - let interop = - windows::core::factory::().unwrap(); - unsafe { interop.CreateForWindow(window).unwrap() } +impl TryFrom for GraphicsCaptureItem { + type Error = Box; + + fn try_from(value: Window) -> Result { + // Get Capture Item From HWND + let window = value.as_raw_hwnd(); + + let interop = windows::core::factory::()?; + Ok(unsafe { interop.CreateForWindow(window)? }) } }