diff --git a/Cargo.lock b/Cargo.lock index 40f9a94..5f70c0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + [[package]] name = "autocfg" version = "1.1.0" @@ -404,6 +410,17 @@ dependencies = [ "pyo3-build-config", ] +[[package]] +name = "pyo3-log" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c10808ee7250403bedb24bc30c32493e93875fef7ba3e4292226fe924f398bd" +dependencies = [ + "arc-swap", + "log", + "pyo3", +] + [[package]] name = "pyo3-macros" version = "0.20.0" @@ -586,7 +603,7 @@ dependencies = [ [[package]] name = "windows-capture" -version = "1.0.23" +version = "1.0.24" dependencies = [ "image", "log", @@ -597,10 +614,12 @@ dependencies = [ ] [[package]] -name = "windows-capture-native" -version = "1.0.22" +name = "windows-capture-python" +version = "1.0.24" dependencies = [ + "log", "pyo3", + "pyo3-log", "windows", "windows-capture", ] diff --git a/Cargo.toml b/Cargo.toml index 196f607..3bea882 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windows-capture" -version = "1.0.23" +version = "1.0.24" authors = ["NiiightmareXD"] edition = "2021" description = "Fastest Windows Screen Capture Library For Rust 🔥" @@ -42,9 +42,13 @@ windows = { version = "0.51.1", features = [ "Win32_System_Com", ] } -[workspace] -members = ["Python/windows-capture-native"] +[package.metadata.docs.rs] +default-target = "x86_64-pc-windows-msvc" +targets = ["x86_64-pc-windows-msvc"] [[example]] name = "basic" doc-scrape-examples = false + +[workspace] +members = ["windows-capture-python"] diff --git a/Python/WindowsCapture/main.py b/Python/WindowsCapture/main.py deleted file mode 100644 index 4cc9910..0000000 --- a/Python/WindowsCapture/main.py +++ /dev/null @@ -1,67 +0,0 @@ -from windows_capture_native import NativeWindowsCapture -import ctypes -import numpy - - -class Capture: - def __init__(self, capture_cursor: bool = True, draw_border: bool = False): - self.frame_handler = None - self.closed_handler = None - self.capture = NativeWindowsCapture( - True, False, self.on_frame_arrived, self.on_closed - ) - self.capture_cursor = capture_cursor - self.draw_border = draw_border - - def start(self): - self.capture.start() - - def stop(self): - self.capture.stop() - - def on_frame_arrived(self, buffer_ptr, width, height, row_pitch): - if self.frame_handler: - num_array = numpy.ctypeslib.as_array( - ctypes.cast(buffer_ptr, ctypes.POINTER(ctypes.c_uint8)), - shape=(height, row_pitch), - ) - - if row_pitch == width * 4: - self.frame_handler(num_array.reshape(height, width, 4)) - else: - self.frame_handler(num_array[:, : width * 4].reshape(height, width, 4)) - - else: - raise Exception("on_frame_arrived Event Handler Is Not Set") - - def on_closed(self): - if self.closed_handler: - self.closed_handler() - else: - raise Exception("on_closed Event Handler Is Not Set") - - def event(self, handler): - if handler.__name__ == "on_frame_arrived": - self.frame_handler = handler - elif handler.__name__ == "on_closed": - self.closed_handler = handler - else: - raise Exception("Invalid Event Handler Use on_frame_arrived Or on_closed") - return handler - - -capture = Capture(False, False) - - -@capture.event -def on_frame_arrived(frame_bytes): - print("lol") - capture.stop() - - -@capture.event -def on_closed(): - print("on_closed") - - -capture.start() diff --git a/Python/windows-capture-native/.gitignore b/Python/windows-capture-native/.gitignore deleted file mode 100644 index 2f7896d..0000000 --- a/Python/windows-capture-native/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target/ diff --git a/Python/windows-capture-native/src/lib.rs b/Python/windows-capture-native/src/lib.rs deleted file mode 100644 index b0ac75e..0000000 --- a/Python/windows-capture-native/src/lib.rs +++ /dev/null @@ -1,131 +0,0 @@ -#![warn(clippy::semicolon_if_nothing_returned)] -#![warn(clippy::inconsistent_struct_constructor)] -#![warn(clippy::must_use_candidate)] -#![warn(clippy::ptr_as_ptr)] -#![warn(clippy::borrow_as_ptr)] -#![warn(clippy::nursery)] -#![warn(clippy::cargo)] -#![allow(clippy::redundant_pub_crate)] - -use std::{ - sync::{ - atomic::{self, AtomicBool}, - Arc, - }, - time::Instant, -}; - -use pyo3::prelude::*; -use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage; -use windows_capture::{ - capture::WindowsCaptureHandler, frame::Frame, monitor::Monitor, - settings::WindowsCaptureSettings, -}; - -/// Fastest Windows Screen Capture Library For Python 🔥. -#[pymodule] -fn windows_capture_native(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_class::()?; - Ok(()) -} - -/// Internal Struct Used For Windows Capture -#[pyclass] -pub struct NativeWindowsCapture { - capture_cursor: bool, - draw_border: bool, - on_frame_arrived_callback: Arc, - on_closed: Arc, - active: Arc, -} - -#[pymethods] -impl NativeWindowsCapture { - /// Create A New Windows Capture Struct - #[new] - #[must_use] - pub fn new( - capture_cursor: bool, - draw_border: bool, - on_frame_arrived_callback: PyObject, - on_closed: PyObject, - ) -> Self { - Self { - capture_cursor, - draw_border, - on_frame_arrived_callback: Arc::new(on_frame_arrived_callback), - on_closed: Arc::new(on_closed), - active: Arc::new(AtomicBool::new(true)), - } - } - - /// Start Capture - pub fn start(&mut self) { - let settings = WindowsCaptureSettings::new( - Monitor::primary(), - Some(self.capture_cursor), - Some(self.draw_border), - ( - self.on_frame_arrived_callback.clone(), - self.on_closed.clone(), - self.active.clone(), - ), - ) - .unwrap(); - - InnerNativeWindowsCapture::start(settings).unwrap(); - } - - /// Stop Capture - pub fn stop(&mut self) { - println!("STOP"); - self.active.store(false, atomic::Ordering::Relaxed); - } -} - -/// Internal Capture Struct Used From NativeWindowsCapture -struct InnerNativeWindowsCapture { - on_frame_arrived_callback: Arc, - on_closed: Arc, - active: Arc, -} - -impl WindowsCaptureHandler for InnerNativeWindowsCapture { - type Flags = (Arc, Arc, Arc); - - fn new((on_frame_arrived_callback, on_closed, active): Self::Flags) -> Self { - Self { - on_frame_arrived_callback, - on_closed, - active, - } - } - - fn on_frame_arrived(&mut self, mut frame: Frame) { - if !self.active.load(atomic::Ordering::Relaxed) { - unsafe { PostQuitMessage(0) }; - return; - } - - let instant = Instant::now(); - let width = frame.width(); - let height = frame.height(); - let buf = frame.buffer().unwrap(); - let buf = buf.as_raw_buffer(); - let row_pitch = buf.len() / height as usize; - - Python::with_gil(|py| { - py.check_signals().unwrap(); - - self.on_frame_arrived_callback - .call1(py, (buf.as_ptr() as isize, width, height, row_pitch)) - .unwrap(); - }); - - println!("Took: {}", instant.elapsed().as_nanos() as f32 / 1000000.0); - } - - fn on_closed(&mut self) { - Python::with_gil(|py| self.on_closed.call0(py)).unwrap(); - } -} diff --git a/README.md b/README.md index badee95..98f1e2f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Add this library to your `Cargo.toml`: ```toml [dependencies] -windows-capture = "1.0.22" +windows-capture = "1.0.24" ``` or run this command @@ -27,39 +27,53 @@ cargo add windows-capture ## Usage ```rust +use std::error::Error; + use windows_capture::{ - capture::WindowsCaptureHandler, frame::Frame, settings::WindowsCaptureSettings, window::Window, + capture::WindowsCaptureHandler, + frame::Frame, + graphics_capture_api::InternalCaptureControl, + settings::{ColorFormat, WindowsCaptureSettings}, + window::Window, }; +// Struct To Implement Methods For struct Capture; impl WindowsCaptureHandler for Capture { - type Flags = String; // To Get The Message (Or A Variable Or ...) From The Settings + type Flags = String; // To Get The Message 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 + // 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 Message: {message}"); - Self {} + Ok(Self {}) } - fn on_frame_arrived(&mut self, mut frame: Frame) { - // Called Every Time A New Frame Is Available - println!("Got A New Frame"); + // Called Every Time A New Frame Is Available + fn on_frame_arrived( + &mut self, + mut frame: Frame, + capture_control: InternalCaptureControl, + ) -> Result<(), Box> { + println!("New Frame Arrived"); // Save The Frame As An Image To Specified Path - frame.save_as_image("image.png").unwrap(); + frame.save_as_image("image.png")?; + + // Gracefully Stop The Capture Thread + capture_control.stop(); - // Call To Stop The Capture Thread, You Might Receive A Few More Frames - // Before It Stops - self.stop(); + Ok(()) } - fn on_closed(&mut self) { - // Called When The Capture Item Closes Usually When The Window Closes, - // Capture Will End After This Function Ends - println!("Capture Item Closed"); + // Called When The Capture Item Closes Usually When The Window Closes, Capture + // Will End After This Function Ends + fn on_closed(&mut self) -> Result<(), Box> { + println!("Capture Session Closed"); + + Ok(()) } } @@ -72,13 +86,16 @@ fn main() { foreground_window, // Capture Cursor Some(true), - // Draw Border + // Draw Borders Some(false), - // This Will Be Passed To The New Function + // Kind Of Pixel Format For Frame To Have + ColorFormat::Rgba8, + // Will Be Passed To The New Function "It Works".to_string(), ) .unwrap(); + // Every Error From on_closed and on_frame_arrived Will End Up Here Capture::start(settings).unwrap(); } ``` diff --git a/examples/basic.rs b/examples/basic.rs index 051636b..b36b494 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,36 +1,50 @@ +use std::error::Error; + use windows_capture::{ - capture::WindowsCaptureHandler, frame::Frame, settings::WindowsCaptureSettings, window::Window, + capture::WindowsCaptureHandler, + frame::Frame, + graphics_capture_api::InternalCaptureControl, + settings::{ColorFormat, WindowsCaptureSettings}, + window::Window, }; +// Struct To Implement Methods For struct Capture; impl WindowsCaptureHandler for Capture { - type Flags = String; // To Get The Message (Or A Variable Or ...) From The Settings + type Flags = String; // To Get The Message 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 + // 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 Message: {message}"); - Self {} + Ok(Self {}) } - fn on_frame_arrived(&mut self, mut frame: Frame) { - // Called Every Time A New Frame Is Available - println!("Got A New Frame"); + // Called Every Time A New Frame Is Available + fn on_frame_arrived( + &mut self, + mut frame: Frame, + capture_control: InternalCaptureControl, + ) -> Result<(), Box> { + println!("New Frame Arrived"); // Save The Frame As An Image To Specified Path - frame.save_as_image("image.png").unwrap(); + frame.save_as_image("image.png")?; + + // Gracefully Stop The Capture Thread + capture_control.stop(); - // Call To Stop The Capture Thread, You Might Receive A Few More Frames - // Before It Stops - self.stop(); + Ok(()) } - fn on_closed(&mut self) { - // Called When The Capture Item Closes Usually When The Window Closes, - // Capture Will End After This Function Ends - println!("Capture Item Closed"); + // Called When The Capture Item Closes Usually When The Window Closes, Capture + // Will End After This Function Ends + fn on_closed(&mut self) -> Result<(), Box> { + println!("Capture Session Closed"); + + Ok(()) } } @@ -43,12 +57,15 @@ fn main() { foreground_window, // Capture Cursor Some(true), - // Draw Border + // Draw Borders Some(false), - // This Will Be Passed To The New Function + // Kind Of Pixel Format For Frame To Have + ColorFormat::Rgba8, + // Will Be Passed To The New Function "It Works".to_string(), ) .unwrap(); + // Every Error From on_closed and on_frame_arrived Will End Up Here Capture::start(settings).unwrap(); } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 292fe49..fa60a1c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,3 @@ [toolchain] channel = "stable" +targets = ["x86_64-pc-windows-msvc"] diff --git a/src/capture.rs b/src/capture.rs index e470bdb..12d8b60 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -1,10 +1,10 @@ use std::{ + error::Error, os::windows::prelude::AsRawHandle, thread::{self, JoinHandle}, }; use log::{info, trace, warn}; -use thiserror::Error; use windows::{ Foundation::AsyncActionCompletedHandler, Win32::{ @@ -28,11 +28,13 @@ use windows::{ }; use crate::{ - frame::Frame, graphics_capture_api::GraphicsCaptureApi, settings::WindowsCaptureSettings, + frame::Frame, + graphics_capture_api::{GraphicsCaptureApi, InternalCaptureControl, RESULT}, + settings::WindowsCaptureSettings, }; /// Used To Handle Capture Control Errors -#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +#[derive(thiserror::Error, Eq, PartialEq, Clone, Copy, Debug)] pub enum CaptureControlError { #[error("Failed To Join Thread")] FailedToJoin, @@ -40,22 +42,20 @@ pub enum CaptureControlError { /// Struct Used To Control Capture Thread pub struct CaptureControl { - thread_handle: Option>>>, + thread_handle: Option>>>, } impl CaptureControl { /// Create A New Capture Control Struct #[must_use] - pub fn new( - thread_handle: JoinHandle>>, - ) -> Self { + pub fn new(thread_handle: JoinHandle>>) -> Self { Self { thread_handle: Some(thread_handle), } } /// Wait Until The Thread Stops - pub fn wait(mut self) -> Result<(), Box> { + pub fn wait(mut self) -> Result<(), Box> { if let Some(thread_handle) = self.thread_handle.take() { match thread_handle.join() { Ok(result) => result?, @@ -69,7 +69,7 @@ impl CaptureControl { } /// Gracefully Stop The Capture Thread - pub fn stop(mut self) -> Result<(), Box> { + pub fn stop(mut self) -> Result<(), Box> { if let Some(thread_handle) = self.thread_handle.take() { let handle = thread_handle.as_raw_handle(); let handle = HANDLE(handle as isize); @@ -108,12 +108,13 @@ impl CaptureControl { /// Event Handler Trait pub trait WindowsCaptureHandler: Sized { + /// To Get The Message From The Settings type Flags; /// Starts The Capture And Takes Control Of The Current Thread fn start( settings: WindowsCaptureSettings, - ) -> Result<(), Box> + ) -> Result<(), Box> where Self: Send + 'static, ::Flags: Send, @@ -137,12 +138,13 @@ pub trait WindowsCaptureHandler: Sized { // Start Capture info!("Starting Capture Thread"); - let trigger = Self::new(settings.flags); + let trigger = Self::new(settings.flags)?; let mut capture = GraphicsCaptureApi::new( settings.item, trigger, settings.capture_cursor, settings.draw_border, + settings.color_format, )?; capture.start_capture()?; @@ -184,6 +186,12 @@ pub trait WindowsCaptureHandler: Sized { trace!("Uninitializing COM"); unsafe { CoUninitialize() }; + // Check RESULT + trace!("Checking RESULT"); + let result = RESULT.take().expect("Failed To Take RESULT"); + + result?; + Ok(()) } @@ -200,14 +208,18 @@ pub trait WindowsCaptureHandler: Sized { /// Function That Will Be Called To Create The Struct The Flags Can Be /// Passed From Settings - fn new(flags: Self::Flags) -> Self; + fn new(flags: Self::Flags) -> Result>; /// Called Every Time A New Frame Is Available - fn on_frame_arrived(&mut self, frame: Frame); + fn on_frame_arrived( + &mut self, + frame: Frame, + capture_control: InternalCaptureControl, + ) -> Result<(), Box>; /// Called When The Capture Item Closes Usually When The Window Closes, /// Capture Will End After This Function Ends - fn on_closed(&mut self); + fn on_closed(&mut self) -> Result<(), Box>; /// Call To Stop The Capture Thread, You Might Receive A Few More Frames /// Before It Stops diff --git a/src/d3d11.rs b/src/d3d11.rs index 98d87b1..cd9f733 100644 --- a/src/d3d11.rs +++ b/src/d3d11.rs @@ -1,4 +1,5 @@ -use thiserror::Error; +use std::error::Error; + use windows::{ core::ComInterface, Graphics::DirectX::Direct3D11::IDirect3DDevice, @@ -32,15 +33,15 @@ impl SendDirectX { unsafe impl Send for SendDirectX {} /// Used To Handle DirectX Errors -#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +#[derive(thiserror::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> { +pub fn create_d3d_device( +) -> Result<(ID3D11Device, ID3D11DeviceContext), Box> { // Set Feature Flags let feature_flags = [ D3D_FEATURE_LEVEL_11_1, @@ -80,7 +81,7 @@ pub fn create_d3d_device() /// Create A IDirect3DDevice From ID3D11Device pub fn create_direct3d_device( d3d_device: &ID3D11Device, -) -> Result> { +) -> Result> { 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 3038c73..d3bfc96 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -1,14 +1,15 @@ -use std::path::Path; +use std::{error::Error, path::Path}; use image::{Rgba, RgbaImage}; use ndarray::{s, ArrayBase, ArrayView, Dim, OwnedRepr}; -use thiserror::Error; use windows::Win32::Graphics::Direct3D11::{ ID3D11DeviceContext, ID3D11Texture2D, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ, }; +use crate::settings::ColorFormat; + /// Used To Handle Frame Errors -#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +#[derive(thiserror::Error, Eq, PartialEq, Clone, Copy, Debug)] pub enum FrameError { #[error("Graphics Capture API Is Not Supported")] InvalidSize, @@ -21,6 +22,7 @@ pub struct Frame<'a> { context: &'a ID3D11DeviceContext, width: u32, height: u32, + color_format: ColorFormat, } impl<'a> Frame<'a> { @@ -32,6 +34,7 @@ impl<'a> Frame<'a> { context: &'a ID3D11DeviceContext, width: u32, height: u32, + color_format: ColorFormat, ) -> Self { Self { texture, @@ -39,6 +42,7 @@ impl<'a> Frame<'a> { context, width, height, + color_format, } } @@ -55,7 +59,7 @@ impl<'a> Frame<'a> { } /// Get The Frame Buffer - pub fn buffer(&mut self) -> Result> { + pub fn buffer(&mut self) -> Result> { // Copy The Real Texture To Copy Texture unsafe { self.context @@ -92,23 +96,35 @@ impl<'a> Frame<'a> { pub fn save_as_image>( &mut self, path: T, - ) -> Result<(), Box> { + ) -> Result<(), Box> { let buffer = self.buffer()?; - let nopadding_buffer = buffer.as_raw_nopadding_buffer()?; // ArrayView<'a, u8, Dim<[usize; 3]> + let nopadding_buffer = buffer.as_raw_nopadding_buffer()?; let (height, width, _) = nopadding_buffer.dim(); - let mut rgba_image: RgbaImage = RgbaImage::new(width as u32, height as u32); - for y in 0..height { - for x in 0..width { - let r = nopadding_buffer[(y, x, 0)]; - let g = nopadding_buffer[(y, x, 1)]; - let b = nopadding_buffer[(y, x, 2)]; - let a = nopadding_buffer[(y, x, 3)]; + if self.color_format == ColorFormat::Rgba8 { + for y in 0..height { + for x in 0..width { + let r = nopadding_buffer[(y, x, 0)]; + let g = nopadding_buffer[(y, x, 1)]; + let b = nopadding_buffer[(y, x, 2)]; + let a = nopadding_buffer[(y, x, 3)]; - rgba_image.put_pixel(x as u32, y as u32, Rgba([r, g, b, a])); + rgba_image.put_pixel(x as u32, y as u32, Rgba([r, g, b, a])); + } + } + } else { + for y in 0..height { + for x in 0..width { + let b = nopadding_buffer[(y, x, 0)]; + let g = nopadding_buffer[(y, x, 1)]; + let r = nopadding_buffer[(y, x, 2)]; + let a = nopadding_buffer[(y, x, 3)]; + + rgba_image.put_pixel(x as u32, y as u32, Rgba([r, g, b, a])); + } } } @@ -166,7 +182,7 @@ impl<'a> FrameBuffer<'a> { #[allow(clippy::type_complexity)] pub fn as_raw_nopadding_buffer( &self, - ) -> Result, Dim<[usize; 3]>>, Box> { + ) -> Result, Dim<[usize; 3]>>, Box> { let row_pitch = self.raw_buffer.len() / self.height as usize; let array = diff --git a/src/graphics_capture_api.rs b/src/graphics_capture_api.rs index a0b2398..e35d189 100644 --- a/src/graphics_capture_api.rs +++ b/src/graphics_capture_api.rs @@ -1,11 +1,14 @@ -use std::sync::{ - atomic::{self, AtomicBool}, - Arc, +use std::{ + cell::RefCell, + error::Error, + 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}, @@ -19,7 +22,7 @@ use windows::{ ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_CPU_ACCESS_READ, D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, }, - Dxgi::Common::{DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SAMPLE_DESC}, + Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC}, }, System::WinRT::Direct3D11::IDirect3DDxgiInterfaceAccess, UI::WindowsAndMessaging::PostQuitMessage, @@ -30,10 +33,15 @@ use crate::{ capture::WindowsCaptureHandler, d3d11::{create_d3d_device, create_direct3d_device, SendDirectX}, frame::Frame, + settings::ColorFormat, }; +thread_local! { + pub static RESULT: RefCell>>> = RefCell::new(Some(Ok(()))); +} + /// Used To Handle Capture Errors -#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +#[derive(thiserror::Error, Eq, PartialEq, Clone, Copy, Debug)] pub enum WindowsCaptureError { #[error("Graphics Capture API Is Not Supported")] Unsupported, @@ -45,6 +53,24 @@ pub enum WindowsCaptureError { AlreadyStarted, } +/// Struct Used To Control Capture Thread +pub struct InternalCaptureControl { + stop: Arc, +} + +impl InternalCaptureControl { + /// Create A New Capture Control Struct + #[must_use] + pub fn new(stop: Arc) -> Self { + Self { stop } + } + + /// Gracefully Stop The Capture Thread + pub fn stop(self) { + self.stop.store(true, atomic::Ordering::Relaxed); + } +} + /// Struct Used For Graphics Capture Api pub struct GraphicsCaptureApi { _item: GraphicsCaptureItem, @@ -65,7 +91,8 @@ impl GraphicsCaptureApi { callback: T, capture_cursor: Option, draw_border: Option, - ) -> Result> { + color_format: ColorFormat, + ) -> Result> { // Check Support if !ApiInformation::IsApiContractPresentByMajor( &HSTRING::from("Windows.Foundation.UniversalApiContract"), @@ -79,14 +106,16 @@ impl GraphicsCaptureApi { let (d3d_device, d3d_device_context) = create_d3d_device()?; let direct3d_device = create_direct3d_device(&d3d_device)?; + let pixel_format = if color_format == ColorFormat::Rgba8 { + DirectXPixelFormat::R8G8B8A8UIntNormalized + } else { + DirectXPixelFormat::B8G8R8A8UIntNormalized + }; + // Create Frame Pool trace!("Creating Frame Pool"); - let frame_pool = Direct3D11CaptureFramePool::Create( - &direct3d_device, - DirectXPixelFormat::R8G8B8A8UIntNormalized, - 1, - item.Size()?, - )?; + let frame_pool = + Direct3D11CaptureFramePool::Create(&direct3d_device, pixel_format, 2, item.Size()?)?; let frame_pool = Arc::new(frame_pool); // Create Capture Session @@ -109,9 +138,15 @@ impl GraphicsCaptureApi { move |_, _| { closed_item.store(true, atomic::Ordering::Relaxed); + // To Stop Messge Loop unsafe { PostQuitMessage(0) }; - callback_closed.lock().on_closed(); + // Notify The Struct That The Capture Session Is Closed + let result = callback_closed.lock().on_closed(); + + let _ = RESULT + .replace(Some(result)) + .expect("Failed To Replace RESULT"); Result::Ok(()) } @@ -128,7 +163,7 @@ impl GraphicsCaptureApi { let context = d3d_device_context.clone(); let mut last_size = item.Size()?; - let callback_frame_arrived = callback; + let callback_frame_pool = callback; let direct3d_device_recreate = SendDirectX::new(direct3d_device.clone()); move |frame, _| { @@ -155,7 +190,7 @@ impl GraphicsCaptureApi { unsafe { frame_surface.GetDesc(&mut desc) } // Check Frame Format - if desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM { + if desc.Format.0 as i32 == pixel_format.0 { // Check If The Size Has Been Changed if frame_content_size.Width != last_size.Width || frame_content_size.Height != last_size.Height @@ -171,8 +206,8 @@ impl GraphicsCaptureApi { frame_pool_recreate .Recreate( &direct3d_device_recreate.0, - DirectXPixelFormat::R8G8B8A8UIntNormalized, - 1, + pixel_format, + 2, frame_content_size, ) .unwrap(); @@ -192,7 +227,7 @@ impl GraphicsCaptureApi { Height: texture_height, MipLevels: 1, ArraySize: 1, - Format: DXGI_FORMAT_R8G8B8A8_UNORM, + Format: DXGI_FORMAT(pixel_format.0 as u32), SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0, @@ -214,21 +249,47 @@ impl GraphicsCaptureApi { }; let texture = texture.unwrap(); + // Create A Frame let frame = Frame::new( texture, frame_surface, &context, texture_width, texture_height, + color_format, ); + // 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 - callback_frame_arrived.lock().on_frame_arrived(frame); + let result = callback_frame_pool + .lock() + .on_frame_arrived(frame, internal_capture_control); + + if stop.load(atomic::Ordering::Relaxed) || result.is_err() { + let _ = RESULT + .replace(Some(result)) + .expect("Failed To Replace RESULT"); + + closed_frame_pool.store(true, atomic::Ordering::Relaxed); + + // To Stop Messge Loop + unsafe { PostQuitMessage(0) }; + } } else { - callback_frame_arrived.lock().on_closed(); + closed_frame_pool.store(true, atomic::Ordering::Relaxed); + // To Stop Messge Loop unsafe { PostQuitMessage(0) }; - closed_frame_pool.store(true, atomic::Ordering::Relaxed); + + // Notify The Struct That The Capture Session Is Closed + let result = callback_frame_pool.lock().on_closed(); + + let _ = RESULT + .replace(Some(result)) + .expect("Failed To Replace RESULT"); } Result::Ok(()) @@ -250,7 +311,7 @@ impl GraphicsCaptureApi { } /// Start Capture - pub fn start_capture(&mut self) -> Result<(), Box> { + pub fn start_capture(&mut self) -> Result<(), Box> { // Check If The Capture Is Already Installed if self.active { return Err(Box::new(WindowsCaptureError::AlreadyStarted)); @@ -305,7 +366,7 @@ impl GraphicsCaptureApi { } /// Check If Windows Graphics Capture Api Is Supported - pub fn is_supported() -> Result> { + pub fn is_supported() -> Result> { Ok(ApiInformation::IsApiContractPresentByMajor( &HSTRING::from("Windows.Foundation.UniversalApiContract"), 8, @@ -313,7 +374,7 @@ impl GraphicsCaptureApi { } /// Check If You Can Toggle The Cursor On Or Off - pub fn is_cursor_toggle_supported() -> Result> { + pub fn is_cursor_toggle_supported() -> Result> { Ok(ApiInformation::IsPropertyPresent( &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"), &HSTRING::from("IsCursorCaptureEnabled"), @@ -321,7 +382,7 @@ impl GraphicsCaptureApi { } /// Check If You Can Toggle The Border On Or Off - pub fn is_border_toggle_supported() -> Result> { + pub fn is_border_toggle_supported() -> Result> { Ok(ApiInformation::IsPropertyPresent( &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"), &HSTRING::from("IsBorderRequired"), diff --git a/src/lib.rs b/src/lib.rs index f480f33..5034cea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ //! //! ```toml //! [dependencies] -//! windows-capture = "1.0.22" +//! windows-capture = "1.0.24" //! ``` //! or run this command //! @@ -33,40 +33,53 @@ //! ## Usage //! //! ```no_run +//! use std::error::Error; +//! //! use windows_capture::{ -//! capture::WindowsCaptureHandler, frame::Frame, settings::WindowsCaptureSettings, +//! capture::WindowsCaptureHandler, +//! frame::Frame, +//! graphics_capture_api::InternalCaptureControl, +//! settings::{ColorFormat, WindowsCaptureSettings}, //! window::Window, //! }; //! +//! // Struct To Implement Methods For //! struct Capture; //! //! impl WindowsCaptureHandler for Capture { -//! type Flags = String; // To Get The Message (Or A Variable Or ...) From The Settings +//! type Flags = String; // To Get The Message 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 +//! // 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 Message: {message}"); //! -//! Self {} +//! Ok(Self {}) //! } //! -//! fn on_frame_arrived(&mut self, mut frame: Frame) { -//! // Called Every Time A New Frame Is Available -//! println!("Got A New Frame"); +//! // Called Every Time A New Frame Is Available +//! fn on_frame_arrived( +//! &mut self, +//! mut frame: Frame, +//! capture_control: InternalCaptureControl, +//! ) -> Result<(), Box> { +//! println!("New Frame Arrived"); //! //! // Save The Frame As An Image To Specified Path -//! frame.save_as_image("image.png").unwrap(); +//! frame.save_as_image("image.png")?; +//! +//! // Gracefully Stop The Capture Thread +//! capture_control.stop(); //! -//! // Call To Stop The Capture Thread, You Might Receive A Few More Frames -//! // Before It Stops -//! self.stop(); +//! Ok(()) //! } //! -//! fn on_closed(&mut self) { -//! // Called When The Capture Item Closes Usually When The Window Closes, -//! // Capture Will End After This Function Ends -//! println!("Capture Item Closed"); +//! // Called When The Capture Item Closes Usually When The Window Closes, Capture +//! // Will End After This Function Ends +//! fn on_closed(&mut self) -> Result<(), Box> { +//! println!("Capture Session Closed"); +//! +//! Ok(()) //! } //! } //! @@ -78,13 +91,16 @@ //! foreground_window, //! // Capture Cursor //! Some(true), -//! // Draw Border +//! // Draw Borders //! Some(false), -//! // This Will Be Passed To The New Function +//! // Kind Of Pixel Format For Frame To Have +//! ColorFormat::Rgba8, +//! // Will Be Passed To The New Function //! "It Works".to_string(), //! ) //! .unwrap(); //! +//! // Every Error From on_closed and on_frame_arrived Will End Up Here //! Capture::start(settings).unwrap(); //! ``` #![warn(clippy::semicolon_if_nothing_returned)] diff --git a/src/monitor.rs b/src/monitor.rs index 8e8e6aa..43e7159 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,4 +1,5 @@ -use thiserror::Error; +use std::error::Error; + use windows::{ Graphics::Capture::GraphicsCaptureItem, Win32::{ @@ -11,7 +12,7 @@ use windows::{ }; /// Used To Handle Monitor Errors -#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +#[derive(thiserror::Error, Eq, PartialEq, Clone, Copy, Debug)] pub enum MonitorErrors { #[error("Failed To Find Monitor")] NotFound, @@ -34,7 +35,7 @@ impl Monitor { } /// Get The Monitor From It's Index - pub fn from_index(index: usize) -> Result> { + pub fn from_index(index: usize) -> Result> { let monitor = Self::enumerate()?; let monitor = match monitor.get(index) { Some(monitor) => *monitor, @@ -45,7 +46,7 @@ impl Monitor { } /// Get A List Of All Monitors - pub fn enumerate() -> Result, Box> { + pub fn enumerate() -> Result, Box> { let mut monitors: Vec = Vec::new(); unsafe { @@ -90,7 +91,7 @@ impl Monitor { // Automatically Convert Monitor To GraphicsCaptureItem impl TryFrom for GraphicsCaptureItem { - type Error = Box; + type Error = Box; fn try_from(value: Monitor) -> Result { // Get Capture Item From HMONITOR diff --git a/src/settings.rs b/src/settings.rs index 168f07c..37b3c24 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,19 +1,28 @@ -use thiserror::Error; +use std::error::Error; + use windows::Graphics::Capture::GraphicsCaptureItem; /// Used To Handle Settings Errors -#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +#[derive(thiserror::Error, Eq, PartialEq, Clone, Copy, Debug)] pub enum SettingsErrors { #[error("Failed To Convert To GraphicsCaptureItem")] ConvertFailed, } +/// Kind Of Pixel Format For Frame To Have +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub enum ColorFormat { + Rgba8, + Bgra8, +} + /// 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 color_format: ColorFormat, pub flags: Flags, } @@ -23,8 +32,9 @@ impl WindowsCaptureSettings { item: T, capture_cursor: Option, draw_border: Option, + color_format: ColorFormat, flags: Flags, - ) -> Result> { + ) -> Result> { Ok(Self { item: match item.try_into() { Ok(item) => item, @@ -32,6 +42,7 @@ impl WindowsCaptureSettings { }, capture_cursor, draw_border, + color_format, flags, }) } diff --git a/src/window.rs b/src/window.rs index bb1e7a4..cbe62f7 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,5 +1,6 @@ +use std::error::Error; + use log::warn; -use thiserror::Error; use windows::{ core::HSTRING, Graphics::Capture::GraphicsCaptureItem, @@ -17,7 +18,7 @@ use windows::{ }; /// Used To Handle Window Errors -#[derive(Error, Eq, PartialEq, Clone, Copy, Debug)] +#[derive(thiserror::Error, Eq, PartialEq, Clone, Copy, Debug)] pub enum WindowErrors { #[error("Failed To Get The Foreground Window")] NoActiveWindow, @@ -33,7 +34,7 @@ pub struct Window { impl Window { /// Get The Currently Active Foreground Window - pub fn foreground() -> Result> { + pub fn foreground() -> Result> { let window = unsafe { GetForegroundWindow() }; if window.0 == 0 { @@ -44,7 +45,7 @@ impl Window { } /// Create From A Window Name - pub fn from_name(title: &str) -> Result> { + pub fn from_name(title: &str) -> Result> { let title = HSTRING::from(title); let window = unsafe { FindWindowW(None, &title) }; @@ -56,7 +57,7 @@ impl Window { } /// Create From A Window Name Substring - pub fn from_contains_name(title: &str) -> Result> { + pub fn from_contains_name(title: &str) -> Result> { let windows = Self::enumerate()?; let mut target_window = None; @@ -71,7 +72,7 @@ impl Window { } /// Get Window Title - pub fn title(&self) -> Result> { + pub fn title(&self) -> Result> { let len = unsafe { GetWindowTextLengthW(self.window) } + 1; let mut name = vec![0u16; len as usize]; @@ -119,7 +120,7 @@ impl Window { } /// Get A List Of All Windows - pub fn enumerate() -> Result, Box> { + pub fn enumerate() -> Result, Box> { let mut windows: Vec = Vec::new(); unsafe { @@ -169,7 +170,7 @@ impl Window { // Automatically Convert Window To GraphicsCaptureItem impl TryFrom for GraphicsCaptureItem { - type Error = Box; + type Error = Box; fn try_from(value: Window) -> Result { // Get Capture Item From HWND diff --git a/windows-capture-python/.gitignore b/windows-capture-python/.gitignore new file mode 100644 index 0000000..843aa7e --- /dev/null +++ b/windows-capture-python/.gitignore @@ -0,0 +1,2 @@ +target/ +develop/ diff --git a/Python/windows-capture-native/Cargo.lock b/windows-capture-python/Cargo.lock similarity index 99% rename from Python/windows-capture-native/Cargo.lock rename to windows-capture-python/Cargo.lock index 9b900b6..61b41f0 100644 --- a/Python/windows-capture-native/Cargo.lock +++ b/windows-capture-python/Cargo.lock @@ -559,7 +559,7 @@ dependencies = [ ] [[package]] -name = "windows-capture-native" +name = "windows-capture" version = "1.0.22" dependencies = [ "pyo3", diff --git a/Python/windows-capture-native/Cargo.toml b/windows-capture-python/Cargo.toml similarity index 69% rename from Python/windows-capture-native/Cargo.toml rename to windows-capture-python/Cargo.toml index d6fd5a8..d96ed77 100644 --- a/Python/windows-capture-native/Cargo.toml +++ b/windows-capture-python/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "windows-capture-native" -version = "1.0.22" +name = "windows-capture-python" +version = "1.0.24" authors = ["NiiightmareXD"] edition = "2021" description = "Fastest Windows Screen Capture Library For Python 🔥" documentation = "https://docs.rs/windows-capture" -readme = "../../README.md" +readme = "../README.md" repository = "https://github.com/NiiightmareXD/windows-capture" license = "MIT" keywords = ["screen", "capture", "screenshot", "graphics", "windows"] @@ -17,15 +17,16 @@ categories = [ "multimedia", ] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -name = "windows_capture_native" +name = "windows_capture" crate-type = ["cdylib"] [dependencies] -pyo3 = "0.20.0" +log = "0.4.20" +pyo3 = { version = "0.20.0", features = ["extension-module"] } +pyo3-log = "0.9.0" windows = { version = "0.51.1", features = [ "Win32_UI_WindowsAndMessaging", "Win32_Foundation", ] } -windows-capture = { path = "../../" } +windows-capture = { path = "../" } diff --git a/Python/windows-capture-native/LICENCE b/windows-capture-python/LICENCE similarity index 100% rename from Python/windows-capture-native/LICENCE rename to windows-capture-python/LICENCE diff --git a/Python/windows-capture-native/pyproject.toml b/windows-capture-python/pyproject.toml similarity index 63% rename from Python/windows-capture-native/pyproject.toml rename to windows-capture-python/pyproject.toml index 35713e6..f77a126 100644 --- a/Python/windows-capture-native/pyproject.toml +++ b/windows-capture-python/pyproject.toml @@ -3,8 +3,15 @@ requires = ["maturin>=1.3,<2.0"] build-backend = "maturin" [project] -name = "windows-capture-native" -requires-python = ">=3.7" +name = "windows-capture" +version = "1.0.24" +description = "Fastest Windows Screen Capture Library For Python 🔥" +readme = "../README.md" +requires-python = ">=3.9" +license = "MIT" +authors = [{ name = "NiiightmareXD" }] +keywords = ["screen", "capture", "screenshot", "graphics", "windows"] +dependencies = ["numpy", "opencv-python"] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: GPU", @@ -17,7 +24,6 @@ classifiers = [ "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", "Topic :: Multimedia :: Video :: Capture", ] -dynamic = ["version"] [tool.maturin] features = ["pyo3/extension-module"] diff --git a/windows-capture-python/src/lib.rs b/windows-capture-python/src/lib.rs new file mode 100644 index 0000000..13fc1a5 --- /dev/null +++ b/windows-capture-python/src/lib.rs @@ -0,0 +1,157 @@ +#![warn(clippy::semicolon_if_nothing_returned)] +#![warn(clippy::inconsistent_struct_constructor)] +#![warn(clippy::must_use_candidate)] +#![warn(clippy::ptr_as_ptr)] +#![warn(clippy::borrow_as_ptr)] +#![warn(clippy::nursery)] +#![warn(clippy::cargo)] +#![allow(clippy::redundant_pub_crate)] + +use std::sync::Arc; + +use ::windows_capture::{ + capture::WindowsCaptureHandler, + frame::Frame, + graphics_capture_api::InternalCaptureControl, + monitor::Monitor, + settings::{ColorFormat, WindowsCaptureSettings}, +}; +use log::{error, info}; +use pyo3::{exceptions::PyException, prelude::*, types::PyList}; + +/// Fastest Windows Screen Capture Library For Python 🔥. +#[pymodule] +fn windows_capture(_py: Python, m: &PyModule) -> PyResult<()> { + pyo3_log::init(); + + m.add_class::()?; + Ok(()) +} + +/// Internal Struct Used For Windows Capture +#[pyclass] +pub struct NativeWindowsCapture { + capture_cursor: bool, + draw_border: bool, + on_frame_arrived_callback: Arc, + on_closed: Arc, +} + +#[pymethods] +impl NativeWindowsCapture { + /// Create A New Windows Capture Struct + #[new] + #[must_use] + pub fn new( + capture_cursor: bool, + draw_border: bool, + on_frame_arrived_callback: PyObject, + on_closed: PyObject, + ) -> Self { + Self { + capture_cursor, + draw_border, + on_frame_arrived_callback: Arc::new(on_frame_arrived_callback), + on_closed: Arc::new(on_closed), + } + } + + /// Start Capture + pub fn start(&mut self) -> PyResult<()> { + let settings = match WindowsCaptureSettings::new( + Monitor::primary(), + Some(self.capture_cursor), + Some(self.draw_border), + ColorFormat::Bgra8, + ( + self.on_frame_arrived_callback.clone(), + self.on_closed.clone(), + ), + ) { + Ok(settings) => settings, + Err(e) => Err(PyException::new_err(format!( + "Failed To Create Windows Capture Settings -> {e}" + )))?, + }; + + match InnerNativeWindowsCapture::start(settings) { + Ok(_) => (), + Err(e) => Err(PyException::new_err(format!( + "Capture Session Threw An Exception -> {e}" + )))?, + } + + Ok(()) + } +} + +struct InnerNativeWindowsCapture { + on_frame_arrived_callback: Arc, + on_closed: Arc, +} + +impl WindowsCaptureHandler for InnerNativeWindowsCapture { + type Flags = (Arc, Arc); + + fn new((on_frame_arrived_callback, on_closed): Self::Flags) -> Self { + Self { + on_frame_arrived_callback, + on_closed, + } + } + + fn on_frame_arrived(&mut self, mut frame: Frame, capture_control: InternalCaptureControl) { + let width = frame.width(); + let height = frame.height(); + let buf = match frame.buffer() { + Ok(buf) => buf, + Err(e) => { + error!( + "Failed To Get Frame Buffer -> {e} -> Gracefully Stopping The Capture Thread" + ); + capture_control.stop(); + return; + } + }; + + let buf = buf.as_raw_buffer(); + + Python::with_gil(|py| { + match py.check_signals() { + Ok(_) => (), + Err(_) => { + info!("KeyboardInterrupt Detected -> Gracefully Stopping The Capture Thread"); + capture_control.stop(); + return; + } + } + + let stop_list = PyList::new(py, [false]); + match self.on_frame_arrived_callback.call1( + py, + (buf.as_ptr() as isize, buf.len(), width, height, stop_list), + ) { + Ok(_) => (), + Err(e) => { + error!( + "on_frame_arrived Threw An Exception -> {e} -> Gracefully Stopping The \ + Capture Thread" + ); + capture_control.stop(); + return; + } + }; + + if stop_list[0].is_true().unwrap_or(false) { + capture_control.stop(); + } + }); + } + + fn on_closed(&mut self) { + Python::with_gil(|py| match self.on_closed.call0(py) { + Ok(_) => (), + Err(e) => error!("on_closed Threw An Exception -> {e}"), + }); + } +} diff --git a/windows-capture-python/windows_capture/__init__.py b/windows-capture-python/windows_capture/__init__.py new file mode 100644 index 0000000..6559d0e --- /dev/null +++ b/windows-capture-python/windows_capture/__init__.py @@ -0,0 +1,134 @@ +"""Fastest Windows Screen Capture Library For Python 🔥.""" + +from .windows_capture import NativeWindowsCapture +import ctypes +import numpy +import cv2 +import types + + +class Frame: + """ + Class To Store A Frame + + ... + + Attributes + ---------- + frame_buffer : numpy.ndarray + Raw Buffer Of The Frame + width : str + Width Of The Frame + age : int + Height Of The Frame + + Methods + ------- + save_as_image(path: str): + Saves The Frame As An Image To Specified Path + """ + + def __init__(self, frame_buffer: numpy.ndarray, width: int, height: int) -> None: + """Constructs All The Necessary Attributes For The Frame Object""" + self.frame_buffer = frame_buffer + self.width = width + self.height = height + + def save_as_image(self, path: str): + """Save The Frame As An Image To Specified Path""" + cv2.imwrite(path, self.frame_buffer) + + +class CaptureControl: + """ + Class To Control The Capturing Session + + ... + + Attributes + ---------- + _list : list + The First Index Is Used To Stop The Capture Thread + + Methods + ------- + stop(): + Stops The Capture Thread + """ + + def __init__(self, list: list) -> None: + """Constructs All The Necessary Attributes For The CaptureControl Object""" + self._list = list + + def stop(self) -> None: + """Stops The Capturing Thread""" + self._list[0] = True + + +class WindowsCapture: + def __init__(self, capture_cursor: bool = True, draw_border: bool = False) -> None: + self.frame_handler = None + self.closed_handler = None + self.capture = NativeWindowsCapture( + capture_cursor, draw_border, self.on_frame_arrived, self.on_closed + ) + + def start(self) -> None: + if self.frame_handler is None: + raise Exception("on_frame_arrived Event Handler Is Not Set") + elif self.closed_handler is None: + raise Exception("on_closed Event Handler Is Not Set") + + self.capture.start() + + def on_frame_arrived( + self, + buf: ctypes.POINTER, + buf_len: int, + width: int, + height: int, + stop_list: list, + ) -> None: + if self.frame_handler: + internal_capture_control = CaptureControl(stop_list) + + row_pitch = buf_len / height + if row_pitch == width * 4: + num_array = numpy.ctypeslib.as_array( + ctypes.cast(buf, ctypes.POINTER(ctypes.c_uint8)), + shape=(height, width, 4), + ) + + frame = Frame(num_array, width, height) + self.frame_handler(frame, internal_capture_control) + else: + num_array = numpy.ctypeslib.as_array( + ctypes.cast(buf, ctypes.POINTER(ctypes.c_uint8)), + shape=(height, row_pitch), + )[:, : width * 4].reshape(height, width, 4) + + frame = Frame(num_array, width, height) + self.frame_handler(frame, internal_capture_control) + + self.frame_handler( + frame, + internal_capture_control, + ) + + else: + raise Exception("on_frame_arrived Event Handler Is Not Set") + + def on_closed(self) -> None: + if self.closed_handler: + self.closed_handler() + else: + raise Exception("on_closed Event Handler Is Not Set") + + def event(self, handler: types.FunctionType) -> None: + if handler.__name__ == "on_frame_arrived": + self.frame_handler = handler + elif handler.__name__ == "on_closed": + self.closed_handler = handler + else: + raise Exception("Invalid Event Handler Use on_frame_arrived Or on_closed") + return handler diff --git a/windows-capture-python/windows_capture/windows_capture.cp311-win_amd64.pyd b/windows-capture-python/windows_capture/windows_capture.cp311-win_amd64.pyd new file mode 100644 index 0000000..a78e0c0 Binary files /dev/null and b/windows-capture-python/windows_capture/windows_capture.cp311-win_amd64.pyd differ