@@ -132,12 +132,13 @@ pub struct NativeWindowsCapture {
132132 dirty_region_settings : DirtyRegionSettings ,
133133 monitor_index : Option < usize > ,
134134 window_name : Option < String > ,
135+ window_hwnd : Option < isize > ,
135136}
136137
137138#[ pymethods]
138139impl NativeWindowsCapture {
139140 #[ new]
140- #[ pyo3( signature = ( on_frame_arrived_callback, on_closed, cursor_capture=None , draw_border=None , secondary_window=None , minimum_update_interval=None , dirty_region=None , monitor_index=None , window_name=None ) ) ]
141+ #[ pyo3( signature = ( on_frame_arrived_callback, on_closed, cursor_capture=None , draw_border=None , secondary_window=None , minimum_update_interval=None , dirty_region=None , monitor_index=None , window_name=None , window_hwnd= None ) ) ]
141142 #[ inline]
142143 #[ allow( clippy:: too_many_arguments) ]
143144 pub fn new (
@@ -150,12 +151,22 @@ impl NativeWindowsCapture {
150151 dirty_region : Option < bool > ,
151152 mut monitor_index : Option < usize > ,
152153 window_name : Option < String > ,
154+ window_hwnd : Option < isize > ,
153155 ) -> PyResult < Self > {
154- if window_name. is_some ( ) && monitor_index. is_some ( ) {
155- return Err ( PyException :: new_err ( "You can't specify both the monitor index and the window name" ) ) ;
156+ // Count how many capture targets are specified
157+ let targets_specified = [ monitor_index. is_some ( ) , window_name. is_some ( ) , window_hwnd. is_some ( ) ]
158+ . iter ( )
159+ . filter ( |& & x| x)
160+ . count ( ) ;
161+
162+ if targets_specified > 1 {
163+ return Err ( PyException :: new_err (
164+ "You can only specify one of: monitor_index, window_name, or window_hwnd"
165+ ) ) ;
156166 }
157167
158- if window_name. is_none ( ) && monitor_index. is_none ( ) {
168+ // Default to primary monitor if no target specified
169+ if targets_specified == 0 {
159170 monitor_index = Some ( 1 ) ;
160171 }
161172
@@ -198,14 +209,39 @@ impl NativeWindowsCapture {
198209 dirty_region_settings,
199210 monitor_index,
200211 window_name,
212+ window_hwnd,
201213 } )
202214 }
203215
204216 /// Start capture.
205217 #[ inline]
206218 pub fn start ( & mut self ) -> PyResult < ( ) > {
207- if self . window_name . is_some ( ) {
208- let window = match Window :: from_contains_name ( self . window_name . as_ref ( ) . unwrap ( ) ) {
219+ if let Some ( hwnd) = self . window_hwnd {
220+ // Capture by window handle (HWND)
221+ let window = Window :: from_raw_hwnd ( hwnd as * mut std:: ffi:: c_void ) ;
222+
223+ let settings = Settings :: new (
224+ window,
225+ self . cursor_capture ,
226+ self . draw_border ,
227+ SecondaryWindowSettings :: Default ,
228+ MinimumUpdateIntervalSettings :: Default ,
229+ DirtyRegionSettings :: Default ,
230+ ColorFormat :: Bgra8 ,
231+ ( self . on_frame_arrived_callback . clone ( ) , self . on_closed . clone ( ) ) ,
232+ ) ;
233+
234+ match InnerNativeWindowsCapture :: start ( settings) {
235+ Ok ( ( ) ) => ( ) ,
236+ Err ( e) => {
237+ return Err ( PyException :: new_err ( format ! (
238+ "InnerNativeWindowsCapture::start threw an exception: {e}" ,
239+ ) ) ) ;
240+ }
241+ }
242+ } else if let Some ( ref name) = self . window_name {
243+ // Capture by window name (substring match)
244+ let window = match Window :: from_contains_name ( name) {
209245 Ok ( window) => window,
210246 Err ( e) => {
211247 return Err ( PyException :: new_err ( format ! ( "Failed to find window: {e}" ) ) ) ;
@@ -232,6 +268,7 @@ impl NativeWindowsCapture {
232268 }
233269 }
234270 } else {
271+ // Capture by monitor index
235272 let monitor = match Monitor :: from_index ( self . monitor_index . unwrap ( ) ) {
236273 Ok ( monitor) => monitor,
237274 Err ( e) => {
@@ -266,8 +303,39 @@ impl NativeWindowsCapture {
266303 /// Start capture on a dedicated thread.
267304 #[ inline]
268305 pub fn start_free_threaded ( & mut self ) -> PyResult < NativeCaptureControl > {
269- let capture_control = if self . window_name . is_some ( ) {
270- let window = match Window :: from_contains_name ( self . window_name . as_ref ( ) . unwrap ( ) ) {
306+ let capture_control = if let Some ( hwnd) = self . window_hwnd {
307+ // Capture by window handle (HWND)
308+ let window = Window :: from_raw_hwnd ( hwnd as * mut std:: ffi:: c_void ) ;
309+
310+ let settings = Settings :: new (
311+ window,
312+ self . cursor_capture ,
313+ self . draw_border ,
314+ SecondaryWindowSettings :: Default ,
315+ MinimumUpdateIntervalSettings :: Default ,
316+ DirtyRegionSettings :: Default ,
317+ ColorFormat :: Bgra8 ,
318+ ( self . on_frame_arrived_callback . clone ( ) , self . on_closed . clone ( ) ) ,
319+ ) ;
320+
321+ let capture_control = match InnerNativeWindowsCapture :: start_free_threaded ( settings) {
322+ Ok ( capture_control) => capture_control,
323+ Err ( e) => {
324+ if let GraphicsCaptureApiError :: FrameHandlerError ( InnerNativeWindowsCaptureError :: PythonError (
325+ ref e,
326+ ) ) = e
327+ {
328+ return Err ( PyException :: new_err ( format ! ( "Capture session threw an exception: {e}" , ) ) ) ;
329+ }
330+
331+ return Err ( PyException :: new_err ( format ! ( "Capture session threw an exception: {e}" , ) ) ) ;
332+ }
333+ } ;
334+
335+ NativeCaptureControl :: new ( capture_control)
336+ } else if let Some ( ref name) = self . window_name {
337+ // Capture by window name (substring match)
338+ let window = match Window :: from_contains_name ( name) {
271339 Ok ( window) => window,
272340 Err ( e) => {
273341 return Err ( PyException :: new_err ( format ! ( "Failed to find window: {e}" ) ) ) ;
@@ -301,6 +369,7 @@ impl NativeWindowsCapture {
301369
302370 NativeCaptureControl :: new ( capture_control)
303371 } else {
372+ // Capture by monitor index
304373 let monitor = match Monitor :: from_index ( self . monitor_index . unwrap ( ) ) {
305374 Ok ( monitor) => monitor,
306375 Err ( e) => {
0 commit comments