11use std:: cell:: RefCell ;
2+ use std:: collections:: HashMap ;
23use std:: ffi:: c_void;
34use std:: mem:: MaybeUninit ;
45use std:: rc:: Rc ;
56use std:: sync:: Arc ;
67
8+ use accessibility:: AXAttribute ;
79use accessibility:: AXUIElement ;
810use accessibility:: AXUIElementAttributes ;
911use accessibility:: Error ;
@@ -15,12 +17,17 @@ use accessibility_sys::AXUIElementRef;
1517use accessibility_sys:: kAXDialogSubrole;
1618use accessibility_sys:: kAXErrorSuccess;
1719use accessibility_sys:: kAXStandardWindowSubrole;
20+ use accessibility_sys:: kAXTabGroupRole;
21+ use accessibility_sys:: kAXTabsAttribute;
1822use accessibility_sys:: kAXTitleChangedNotification;
1923use accessibility_sys:: kAXUIElementDestroyedNotification;
2024use accessibility_sys:: kAXWindowCreatedNotification;
2125use accessibility_sys:: kAXWindowRole;
2226use accessibility_sys:: pid_t;
2327use anyhow:: Context ;
28+ use core_foundation:: array:: CFArray ;
29+ use core_foundation:: base:: CFType ;
30+ use core_foundation:: base:: FromVoid ;
2431use core_foundation:: base:: TCFType ;
2532use core_foundation:: runloop:: CFRunLoop ;
2633use core_foundation:: runloop:: kCFRunLoopDefaultMode;
@@ -32,17 +39,28 @@ use objc2_app_kit::NSRunningApplication;
3239use uuid:: Uuid ;
3340
3441use super :: sys:: AXObserver ;
35- use super :: sys:: bruteforce_windows_for_app ;
42+ use super :: sys:: ax_window_id ;
3643
3744pub struct WindowNotificationDelegate {
3845 app_element : AXUIElement ,
3946 observer : AXObserver ,
4047 inner : Rc < WindowNotificationDelegateInner > ,
4148}
4249
50+ pub enum WindowType {
51+ Window ,
52+ Tab ,
53+ }
54+
55+ struct WindowData {
56+ app_pid : pid_t ,
57+ window_type : WindowType ,
58+ element : AXUIElement ,
59+ }
60+
4361struct WindowNotificationDelegateInner {
4462 app_pid : pid_t ,
45- windows : Rc < RefCell < Vec < ( String , pid_t , AXUIElement ) > > > ,
63+ windows : Rc < RefCell < HashMap < String , WindowData > > > ,
4664 application_manager : Arc < ApplicationManager > ,
4765}
4866
@@ -52,14 +70,8 @@ const WINDOW_EVENTS: [&str; 3] = [
5270 kAXTitleChangedNotification,
5371] ;
5472
55- const MESSAGING_TIMEOUT_SEC : f32 = 1.0 ;
56-
5773impl WindowNotificationDelegate {
58- pub fn new (
59- pid : pid_t ,
60- application_manager : Arc < ApplicationManager > ,
61- windows : Rc < RefCell < Vec < ( String , pid_t , AXUIElement ) > > > ,
62- ) -> anyhow:: Result < Self > {
74+ pub fn new ( pid : pid_t , application_manager : Arc < ApplicationManager > ) -> anyhow:: Result < Self > {
6375 let observer = unsafe {
6476 let mut result = MaybeUninit :: uninit ( ) ;
6577
@@ -76,7 +88,7 @@ impl WindowNotificationDelegate {
7688 let element = AXUIElement :: application ( pid) ;
7789
7890 element
79- . set_messaging_timeout ( MESSAGING_TIMEOUT_SEC )
91+ . set_messaging_timeout ( 1.0 )
8092 . context ( "Failed to set messaging timeout" ) ?;
8193
8294 element
@@ -87,7 +99,7 @@ impl WindowNotificationDelegate {
8799 observer,
88100 inner : Rc :: new ( WindowNotificationDelegateInner {
89101 app_pid : pid,
90- windows,
102+ windows : Rc :: new ( RefCell :: new ( HashMap :: new ( ) ) ) ,
91103 application_manager,
92104 } ) ,
93105 } )
@@ -142,7 +154,7 @@ impl WindowNotificationDelegate {
142154
143155 let windows = self . inner . windows . borrow ( ) ;
144156
145- for ( window_id, _, _ ) in windows. iter ( ) {
157+ for ( window_id, _) in windows. iter ( ) {
146158 let event = MacosWindowTrackingEvent :: WindowClosed {
147159 window_id : window_id. clone ( ) ,
148160 } ;
@@ -205,40 +217,51 @@ fn get_bundle_path(pid: pid_t) -> Option<String> {
205217impl WindowNotificationDelegateInner {
206218 fn refresh_windows ( & self ) -> anyhow:: Result < ( ) > {
207219 tracing:: debug!( "Refreshing windows for app: {}" , self . app_pid) ;
220+
208221 let mut retrieved_windows: Vec < _ > = AXUIElement :: application ( self . app_pid )
209222 . windows ( ) ?
210223 . into_iter ( )
211- . map ( |item| item. clone ( ) )
212- . collect ( ) ;
224+ . map ( |window| window. clone ( ) )
225+ . flat_map ( |window| {
226+ let tabs = list_tabs ( window. clone ( ) ) ;
213227
214- for window in bruteforce_windows_for_app ( self . app_pid ) {
215- if !retrieved_windows. contains ( & window) {
216- retrieved_windows. push ( window) ;
217- } ;
218- }
228+ if !tabs. is_empty ( ) {
229+ return tabs. into_iter ( ) . map ( |tab| ( WindowType :: Tab , tab) ) . collect :: < Vec < _ > > ( ) ;
230+ }
231+
232+ return vec ! [ ( WindowType :: Window , window) ] ;
233+ } )
234+ . collect ( ) ;
219235
220- tracing:: debug!( "Retrieved {} windows " , retrieved_windows. len( ) ) ;
236+ tracing:: debug!( "Retrieved windows: {} " , retrieved_windows. len( ) ) ;
221237
222238 let stored_windows = self
223239 . windows
224240 . borrow ( )
225241 . iter ( )
226- . map ( |( _, _ , window) | window. clone ( ) )
242+ . map ( |( _, window) | ( window. app_pid . clone ( ) , window . clone ( ) ) )
227243 . collect :: < Vec < _ > > ( ) ;
228244
229- tracing:: debug!( "Stored {} windows" , stored_windows. len( ) ) ;
245+ tracing:: debug!( "Stored windows: {}" , stored_windows. len( ) ) ;
246+
247+ let mut destroyed_windows = 0 ;
248+ for ( pid, window) in stored_windows. iter ( ) {
249+ if pid != & self . app_pid {
250+ continue ;
251+ }
230252
231- for window in stored_windows. into_iter ( ) {
232- let Some ( index) = retrieved_windows. iter ( ) . position ( |el| el == & window) else {
253+ let Some ( index) = retrieved_windows. iter ( ) . position ( |el| el == window) else {
233254 // doesn't exist anymore, destroy it
234- self . window_destroyed ( window) ;
255+ self . window_destroyed ( window. clone ( ) ) ;
235256 continue ;
236257 } ;
237258
259+ destroyed_windows += 1 ;
238260 retrieved_windows. swap_remove ( index) ;
239261 }
240262
241- tracing:: debug!( "left retrieved {} windows" , retrieved_windows. len( ) ) ;
263+ tracing:: debug!( "Destroyed windows: {}" , retrieved_windows. len( ) ) ;
264+ tracing:: debug!( "New windows: {}" , retrieved_windows. len( ) ) ;
242265
243266 for window in retrieved_windows. into_iter ( ) {
244267 self . window_opened ( window) ;
@@ -264,7 +287,7 @@ impl WindowNotificationDelegateInner {
264287 }
265288
266289 let window_id = Uuid :: new_v4 ( ) . to_string ( ) ;
267- windows. push ( ( window_id. clone ( ) , self . app_pid , window. clone ( ) ) ) ;
290+ windows. insert ( window_id. clone ( ) , ( self . app_pid , window. clone ( ) ) ) ;
268291
269292 let title = window. title ( ) . map ( |title| title. to_string ( ) ) . ok ( ) ;
270293 let bundle_path = get_bundle_path ( self . app_pid ) ;
@@ -285,7 +308,7 @@ impl WindowNotificationDelegateInner {
285308 return ;
286309 } ;
287310
288- let ( window_id, _, _) = windows. swap_remove ( index) ;
311+ let ( window_id, _, _) = windows. remove ( index) ;
289312
290313 let event = MacosWindowTrackingEvent :: WindowClosed { window_id } ;
291314
@@ -309,3 +332,40 @@ impl WindowNotificationDelegateInner {
309332 self . application_manager . send_macos_window_tracking_event ( event) ;
310333 }
311334}
335+
336+ fn list_tabs ( window : AXUIElement ) -> Vec < AXUIElement > {
337+ let Ok ( children) = window. children ( ) else {
338+ return vec ! [ ] ;
339+ } ;
340+
341+ let tab_group = children. into_iter ( ) . find ( |child| {
342+ let role = child. role ( ) . map ( |val| val. to_string ( ) ) . ok ( ) ;
343+ if role. as_deref ( ) != Some ( kAXTabGroupRole) {
344+ return false ;
345+ }
346+
347+ let title = child. title ( ) . map ( |val| val. to_string ( ) ) . ok ( ) ;
348+ if title. as_deref ( ) != Some ( "tab bar" ) {
349+ return false ;
350+ }
351+
352+ return true ;
353+ } ) ;
354+
355+ let Some ( tab_group) = tab_group else {
356+ return vec ! [ ] ;
357+ } ;
358+
359+ let tabs_attribute = AXAttribute :: < CFType > :: new ( & CFString :: from_static_string ( kAXTabsAttribute) ) ;
360+ let Some ( tabs) = tab_group. attribute ( & tabs_attribute) . ok ( ) else {
361+ return vec ! [ ] ;
362+ } ;
363+
364+ let Some ( tabs) = tabs. downcast :: < CFArray > ( ) else {
365+ return vec ! [ ] ;
366+ } ;
367+
368+ tabs. into_iter ( )
369+ . map ( |item| unsafe { AXUIElement :: from_void ( item. clone ( ) ) } . clone ( ) )
370+ . collect ( )
371+ }
0 commit comments