@@ -277,9 +277,6 @@ public Task<Bitmap> CaptureAsync(CancellationToken cancellationToken)
277277 var screen = NSScreen . Screens . FirstOrDefault ( s => s . Frame . IntersectsWith ( rect ) ) ;
278278 var scale = screen ? . BackingScaleFactor ?? 1.0 ;
279279
280- var targetWidth = rect . Width * scale ;
281- var targetHeight = rect . Height * scale ;
282-
283280 // cgImage captures the window content starting at (0,0) in Window Local Coordinates.
284281 // rect contains Screen Coordinates (including Dock/Menu bar offsets).
285282 // To crop correctly, we must transform rect to Window-Relative coordinates.
@@ -303,7 +300,8 @@ public Task<Bitmap> CaptureAsync(CancellationToken cancellationToken)
303300
304301 // Check if captured image approximately matches target size (allowing for rounding/shadows).
305302 // If it matches, we assume full window capture and start at 0,0.
306- bool isFullWindow = cgImage . Width >= targetWidth - 2 && cgImage . Width <= targetWidth + 100 ;
303+ var targetWidth = rect . Width * scale ;
304+ var isFullWindow = cgImage . Width >= targetWidth - 2 && cgImage . Width <= targetWidth + 100 ;
307305
308306 // If full window, offset is 0.
309307 // If partial (element inside window), offset is (ElementScreenPos - WindowScreenPos).
@@ -406,6 +404,70 @@ private void PerformAction(NSString actionName)
406404 return handle != 0 ? new AXUIElement ( handle ) : null ;
407405 }
408406
407+ /// <summary>
408+ /// Gets the AXUIElement corresponding to the specified CGWindowID.
409+ /// This is a reverse lookup using _AXUIElementGetWindow under the hood.
410+ /// </summary>
411+ /// <param name="cgWindowId">The target CGWindowID.</param>
412+ /// <returns>The matching AXUIElement, or null if not found.</returns>
413+ public static AXUIElement ? ElementFromWindowId ( uint cgWindowId )
414+ {
415+ if ( cgWindowId == 0 ) return null ;
416+
417+ // 1. Get the owner PID from the CGWindowID using CoreGraphics
418+ var ownerPid = 0 ;
419+ var windowInfoArrayPtr = CGInterop . CGWindowListCopyWindowInfo ( CGWindowListOption . IncludingWindow , cgWindowId ) ;
420+
421+ if ( windowInfoArrayPtr != 0 )
422+ {
423+ // Take ownership of the CFArray returned by Create/Copy rule
424+ using var windowInfoArray = Runtime . GetNSObject < NSArray > ( windowInfoArrayPtr , owns : true ) ;
425+ if ( windowInfoArray is { Count : > 0 } )
426+ {
427+ using var windowInfo = windowInfoArray . GetItem < NSDictionary > ( 0 ) ;
428+ using var pidKey = new NSString ( "kCGWindowOwnerPID" ) ;
429+
430+ if ( windowInfo ? . ObjectForKey ( pidKey ) is NSNumber pidNumber )
431+ {
432+ ownerPid = pidNumber . Int32Value ;
433+ }
434+ }
435+ }
436+
437+ if ( ownerPid == 0 ) return null ;
438+
439+ // 2. Create the AXApplication element from the PID
440+ using var appElement = ElementFromPid ( ownerPid ) ;
441+ if ( appElement is null ) return null ;
442+
443+ // 3. Get all windows of the application
444+ // Note: Replace with AXAttributeConstants.Windows if you have it defined.
445+ using var windowsKey = new NSString ( "AXWindows" ) ;
446+ using var windows = appElement . GetAttribute < NSArray > ( windowsKey ) ;
447+
448+ if ( windows is null ) return null ;
449+
450+ // 4. Iterate through the windows and find the matching CGWindowID
451+ for ( nuint i = 0 ; i < windows . Count ; i ++ )
452+ {
453+ var windowElement = FromCopyArray ( windows , i ) ;
454+ if ( windowElement is null ) continue ;
455+
456+ // Use your existing property which correctly handles the _AXUIElementGetWindow P/Invoke
457+ if ( windowElement . NativeWindowHandle == ( nint ) cgWindowId )
458+ {
459+ // Found it! Return the retained element.
460+ return windowElement ;
461+ }
462+
463+ // Not a match: explicitly dispose to release the CFRetain applied in FromCopyArray,
464+ // avoiding memory leaks during traversal.
465+ windowElement . Dispose ( ) ;
466+ }
467+
468+ return null ;
469+ }
470+
409471 [ LibraryImport ( AppServices , EntryPoint = "AXUIElementCreateSystemWide" ) ]
410472 private static partial nint CreateSystemWide ( ) ;
411473
0 commit comments