@@ -35,34 +35,219 @@ int setApplicationAttribute(int pid, const char* attribute, int value) {
3535 return (error == kAXErrorSuccess ) ? 1 : 0 ;
3636}
3737
38- void ** getVisibleChildren (void * element, int * count) {
38+ // Helper function to check if a point is actually visible (not occluded by other windows)
39+ static bool isPointVisible (CGPoint point, pid_t elementPid) {
40+ // Use AXUIElementCopyElementAtPosition to check what's at this point
41+ AXUIElementRef systemWide = AXUIElementCreateSystemWide ();
42+ if (!systemWide) return true ;
43+
44+ AXUIElementRef elementAtPoint = NULL ;
45+ AXError error = AXUIElementCopyElementAtPosition (systemWide, point.x , point.y , &elementAtPoint);
46+ CFRelease (systemWide);
47+
48+ if (error != kAXErrorSuccess || !elementAtPoint) {
49+ return true ; // Assume visible if we can't check
50+ }
51+
52+ // Get the PID of the element at this point
53+ pid_t pidAtPoint;
54+ bool isVisible = false ;
55+
56+ if (AXUIElementGetPid (elementAtPoint, &pidAtPoint) == kAXErrorSuccess ) {
57+ // The point is visible if it belongs to the same application
58+ isVisible = (pidAtPoint == elementPid);
59+ }
60+
61+ CFRelease (elementAtPoint);
62+ return isVisible;
63+ }
64+
65+ // Helper function to check if an element is occluded by checking multiple sample points
66+ static bool isElementOccluded (CGRect elementRect, pid_t elementPid) {
67+ // Sample 5 points: center and 4 corners (slightly inset)
68+ CGFloat inset = 2.0 ; // Inset from edges to avoid border issues
69+
70+ CGPoint samplePoints[5 ] = {
71+ // Center
72+ CGPointMake (elementRect.origin .x + elementRect.size .width / 2 ,
73+ elementRect.origin .y + elementRect.size .height / 2 ),
74+ // Top-left
75+ CGPointMake (elementRect.origin .x + inset,
76+ elementRect.origin .y + inset),
77+ // Top-right
78+ CGPointMake (elementRect.origin .x + elementRect.size .width - inset,
79+ elementRect.origin .y + inset),
80+ // Bottom-left
81+ CGPointMake (elementRect.origin .x + inset,
82+ elementRect.origin .y + elementRect.size .height - inset),
83+ // Bottom-right
84+ CGPointMake (elementRect.origin .x + elementRect.size .width - inset,
85+ elementRect.origin .y + elementRect.size .height - inset)
86+ };
87+
88+ // Element is considered visible if at least 2 sample points are visible
89+ int visiblePoints = 0 ;
90+ for (int i = 0 ; i < 5 ; i++) {
91+ if (isPointVisible (samplePoints[i], elementPid)) {
92+ visiblePoints++;
93+ if (visiblePoints >= 2 ) {
94+ return false ; // Not occluded
95+ }
96+ }
97+ }
98+
99+ return true ; // Occluded (less than 2 points visible)
100+ }
101+
102+ // Get children that are actually visible on screen
103+ // checkOcclusion: if true, filters out elements covered by other windows
104+ void ** getVisibleChildren (void * element, int * count, int checkOcclusion) {
39105 if (!element || !count) return NULL ;
40106
41107 AXUIElementRef axElement = (AXUIElementRef)element;
42- CFTypeRef visibleChildrenValue = NULL ;
43108
44- if (AXUIElementCopyAttributeValue (axElement, kAXVisibleChildrenAttribute , &visibleChildrenValue) == kAXErrorSuccess && visibleChildrenValue) {
45- if (CFGetTypeID (visibleChildrenValue) == CFArrayGetTypeID ()) {
46- CFArrayRef children = (CFArrayRef)visibleChildrenValue;
47- CFIndex childCount = CFArrayGetCount (children);
48- *count = (int )childCount;
109+ // Get the PID of the element's application
110+ pid_t elementPid;
111+ if (AXUIElementGetPid (axElement, &elementPid) != kAXErrorSuccess ) {
112+ *count = 0 ;
113+ return NULL ;
114+ }
115+
116+ // Check if this is the Dock
117+ NSRunningApplication * app = [NSRunningApplication runningApplicationWithProcessIdentifier: elementPid];
118+ BOOL isDock = app && [[app bundleIdentifier ] isEqualToString: @" com.apple.dock" ];
49119
50- void ** result = (void **)malloc (childCount * sizeof (void *));
120+ // First, get all children
121+ int totalCount = 0 ;
122+ void ** allChildren = getChildren (element, &totalCount);
51123
52- for (CFIndex i = 0 ; i < childCount; i++) {
53- AXUIElementRef child = (AXUIElementRef)CFArrayGetValueAtIndex (children, i);
54- CFRetain (child);
55- result[i] = (void *)child;
124+ if (!allChildren || totalCount == 0 ) {
125+ *count = 0 ;
126+ return NULL ;
127+ }
128+
129+ // For the Dock, skip all filtering and return all children
130+ if (isDock) {
131+ *count = totalCount;
132+ return allChildren;
133+ }
134+
135+ // For non-Dock apps, continue with normal filtering...
136+
137+ // Try to get the parent element's bounds (if available)
138+ CGRect parentBounds = CGRectZero;
139+ bool hasParentBounds = false ;
140+
141+ CFTypeRef posValue = NULL , sizeValue = NULL ;
142+
143+ if (AXUIElementCopyAttributeValue (axElement, kAXPositionAttribute , &posValue) == kAXErrorSuccess && posValue) {
144+ if (AXValueGetValue (posValue, kAXValueCGPointType , &parentBounds.origin )) {
145+ if (AXUIElementCopyAttributeValue (axElement, kAXSizeAttribute , &sizeValue) == kAXErrorSuccess && sizeValue) {
146+ if (AXValueGetValue (sizeValue, kAXValueCGSizeType , &parentBounds.size )) {
147+ hasParentBounds = true ;
148+ }
149+ CFRelease (sizeValue);
56150 }
151+ }
152+ CFRelease (posValue);
153+ }
154+
155+ // Get screen bounds for all displays
156+ uint32_t displayCount;
157+ CGDirectDisplayID displays[32 ];
158+ CGGetActiveDisplayList (32 , displays, &displayCount);
159+
160+ // Create a union of all display bounds
161+ CGRect allScreensBounds = CGRectZero;
162+ for (uint32_t i = 0 ; i < displayCount; i++) {
163+ CGRect displayBounds = CGDisplayBounds (displays[i]);
164+ if (i == 0 ) {
165+ allScreensBounds = displayBounds;
166+ } else {
167+ allScreensBounds = CGRectUnion (allScreensBounds, displayBounds);
168+ }
169+ }
170+
171+ // Temporary array to hold visible children
172+ void ** visibleChildren = (void **)malloc (totalCount * sizeof (void *));
173+ int visibleCount = 0 ;
174+
175+ for (int i = 0 ; i < totalCount; i++) {
176+ AXUIElementRef child = (AXUIElementRef)allChildren[i];
177+
178+ // Get child's position and size
179+ CGPoint childPos = CGPointZero;
180+ CGSize childSize = CGSizeZero;
181+ bool hasPosition = false , hasSize = false ;
182+
183+ CFTypeRef childPosValue = NULL ;
184+ if (AXUIElementCopyAttributeValue (child, kAXPositionAttribute , &childPosValue) == kAXErrorSuccess && childPosValue) {
185+ if (AXValueGetValue (childPosValue, kAXValueCGPointType , &childPos)) {
186+ hasPosition = true ;
187+ }
188+ CFRelease (childPosValue);
189+ }
190+
191+ CFTypeRef childSizeValue = NULL ;
192+ if (AXUIElementCopyAttributeValue (child, kAXSizeAttribute , &childSizeValue) == kAXErrorSuccess && childSizeValue) {
193+ if (AXValueGetValue (childSizeValue, kAXValueCGSizeType , &childSize)) {
194+ hasSize = true ;
195+ }
196+ CFRelease (childSizeValue);
197+ }
198+
199+ // If we can't get position/size, include it anyway (might be a container)
200+ if (!hasPosition && !hasSize) {
201+ visibleChildren[visibleCount++] = (void *)child;
202+ continue ;
203+ }
57204
58- CFRelease (visibleChildrenValue);
59- return result;
205+ // Check if child has non-zero size (if size is available)
206+ if (hasSize && (childSize.width <= 0 || childSize.height <= 0 )) {
207+ CFRelease (child);
208+ continue ;
60209 }
61210
62- CFRelease (visibleChildrenValue);
211+ // If we have position, do spatial checks
212+ if (hasPosition && hasSize) {
213+ CGRect childRect = CGRectMake (childPos.x , childPos.y , childSize.width , childSize.height );
214+
215+ // Check if child intersects with any screen bounds
216+ if (!CGRectIntersectsRect (childRect, allScreensBounds)) {
217+ CFRelease (child);
218+ continue ;
219+ }
220+
221+ // If parent has bounds, check if child intersects with parent
222+ if (hasParentBounds && !CGRectIntersectsRect (childRect, parentBounds)) {
223+ CFRelease (child);
224+ continue ;
225+ }
226+
227+ // Only check occlusion if requested
228+ if (checkOcclusion && isElementOccluded (childRect, elementPid)) {
229+ CFRelease (child);
230+ continue ;
231+ }
232+ }
233+
234+ // This child is visible, add it to our result
235+ visibleChildren[visibleCount++] = (void *)child;
63236 }
64237
65- return getChildren (element, count);
238+ free (allChildren);
239+
240+ if (visibleCount == 0 ) {
241+ free (visibleChildren);
242+ *count = 0 ;
243+ return NULL ;
244+ }
245+
246+ // Resize array to actual visible count
247+ void ** result = (void **)realloc (visibleChildren, visibleCount * sizeof (void *));
248+ *count = visibleCount;
249+
250+ return result ? result : visibleChildren;
66251}
67252
68253// Get system-wide accessibility element
0 commit comments