@@ -12,7 +12,6 @@ impl LauncherWindow {
1212 // but GPUI uses its own async executor
1313 let ( tx, rx) = std:: sync:: mpsc:: channel ( ) ;
1414
15- let photoncast_app_for_icons = Arc :: clone ( & photoncast_app) ;
1615 std:: thread:: spawn ( move || {
1716 tracing:: info!( "Starting application indexing..." ) ;
1817
@@ -23,42 +22,7 @@ impl LauncherWindow {
2322 } ) ;
2423
2524 // Send scan results immediately so UI becomes responsive
26- let apps_for_icons = result. as_ref ( ) . ok ( ) . cloned ( ) ;
2725 let _ = tx. send ( result) ;
28-
29- // Now extract icons in the same background thread (doesn't block UI)
30- if let Some ( apps) = apps_for_icons {
31- tracing:: info!(
32- "Starting background icon extraction for {} apps" ,
33- apps. len( )
34- ) ;
35- let start = std:: time:: Instant :: now ( ) ;
36- let mut extracted = 0 ;
37- let mut cached = 0 ;
38-
39- for app in & apps {
40- // Extract or get cached icon
41- if let Some ( icon_path) = Self :: get_app_icon_path ( & app. path ) {
42- // Update the app's icon in shared state
43- photoncast_app_for_icons
44- . write ( )
45- . update_app_icon ( & app. bundle_id . to_string ( ) , icon_path) ;
46-
47- if Self :: get_cached_icon_path ( & app. path ) . is_some ( ) {
48- cached += 1 ;
49- } else {
50- extracted += 1 ;
51- }
52- }
53- }
54-
55- tracing:: info!(
56- "Icon extraction complete: {} cached, {} extracted in {:?}" ,
57- cached,
58- extracted,
59- start. elapsed( )
60- ) ;
61- }
6226 } ) ;
6327
6428 // Poll for results in GPUI's async context
@@ -73,6 +37,7 @@ impl LauncherWindow {
7337 match rx. try_recv ( ) {
7438 Ok ( Ok ( apps) ) => {
7539 let app_count = apps. len ( ) ;
40+ let apps_for_icons = apps. clone ( ) ;
7641 tracing:: info!( "Indexed {} applications" , app_count) ;
7742
7843 // Update the PhotonCast app with indexed apps
@@ -87,6 +52,43 @@ impl LauncherWindow {
8752 this. start_app_watching ( cx) ;
8853 cx. notify ( ) ;
8954 } ) ;
55+
56+ let photoncast_app_for_icons = Arc :: clone ( & photoncast_app) ;
57+ let this_for_icons = this. clone ( ) ;
58+ cx. spawn ( |mut cx| async move {
59+ tracing:: info!(
60+ "Starting background icon extraction for {} apps" ,
61+ apps_for_icons. len( )
62+ ) ;
63+
64+ let mut updated_icons = 0usize ;
65+ for app in apps_for_icons {
66+ let app_path = app. path . clone ( ) ;
67+ let bundle_id = app. bundle_id . to_string ( ) ;
68+
69+ let icon_result = cx
70+ . background_executor ( )
71+ . spawn ( async move { Self :: get_app_icon_path_static ( & app_path) } )
72+ . await ;
73+
74+ if let Some ( icon_path) = icon_result {
75+ photoncast_app_for_icons
76+ . write ( )
77+ . update_app_icon ( & bundle_id, icon_path) ;
78+ updated_icons += 1 ;
79+
80+ let _ = this_for_icons. update ( & mut cx, |this, cx| {
81+ this. refresh_visible_app_icons ( cx) ;
82+ } ) ;
83+ }
84+ }
85+
86+ tracing:: info!(
87+ "Background icon extraction complete: {} icons updated" ,
88+ updated_icons
89+ ) ;
90+ } )
91+ . detach ( ) ;
9092 break ;
9193 } ,
9294 Ok ( Err ( e) ) => {
@@ -156,30 +158,44 @@ impl LauncherWindow {
156158 }
157159 } ) ;
158160 } ) ;
161+ let event_rx = Arc :: new ( std:: sync:: Mutex :: new ( event_rx) ) ;
159162
160163 // Process watch events in GPUI's async context
161164 cx. spawn ( |this, mut cx| async move {
165+ const WATCH_EVENT_TIMEOUT_MS : u64 = 500 ;
166+
162167 loop {
163- // Poll for events periodically
164- cx. background_executor ( )
165- . timer ( Duration :: from_millis ( 100 ) )
166- . await ;
168+ let next_events = {
169+ let event_rx = Arc :: clone ( & event_rx) ;
170+ cx. background_executor ( ) . spawn ( async move {
171+ let receiver = event_rx. lock ( ) . expect ( "watch event receiver poisoned" ) ;
172+ match receiver. recv_timeout ( Duration :: from_millis ( WATCH_EVENT_TIMEOUT_MS ) ) {
173+ Ok ( first_event) => {
174+ let mut events = vec ! [ first_event] ;
175+ while let Ok ( event) = receiver. try_recv ( ) {
176+ events. push ( event) ;
177+ }
178+ Ok ( events)
179+ } ,
180+ Err ( err) => Err ( err) ,
181+ }
182+ } )
183+ }
184+ . await ;
167185
168- // Process all pending events
169- loop {
170- match event_rx. try_recv ( ) {
171- Ok ( event) => {
186+ match next_events {
187+ Ok ( events) => {
188+ for event in events {
172189 Self :: handle_watch_event ( & this, & mut cx, & photoncast_app, event) . await ;
173- } ,
174- Err ( std:: sync:: mpsc:: TryRecvError :: Empty ) => {
175- // No more events, wait for next poll
176- break ;
177- } ,
178- Err ( std:: sync:: mpsc:: TryRecvError :: Disconnected ) => {
179- tracing:: info!( "Watcher thread disconnected" ) ;
180- return ;
181- } ,
182- }
190+ }
191+ } ,
192+ Err ( std:: sync:: mpsc:: RecvTimeoutError :: Timeout ) => {
193+ // No watch events before timeout, continue waiting.
194+ } ,
195+ Err ( std:: sync:: mpsc:: RecvTimeoutError :: Disconnected ) => {
196+ tracing:: info!( "Watcher thread disconnected" ) ;
197+ return ;
198+ } ,
183199 }
184200 }
185201 } )
@@ -271,9 +287,8 @@ impl LauncherWindow {
271287 . write ( )
272288 . update_app_icon ( & bundle_id, icon_path) ;
273289
274- // Notify UI that icon is now available
275- let _ = this_for_icon. update ( & mut cx, |_this, cx| {
276- cx. notify ( ) ;
290+ let _ = this_for_icon. update ( & mut cx, |this, cx| {
291+ this. refresh_visible_app_icons ( cx) ;
277292 } ) ;
278293 }
279294 } )
@@ -327,9 +342,8 @@ impl LauncherWindow {
327342 . write ( )
328343 . update_app_icon ( & bundle_id, icon_path) ;
329344
330- // Notify UI that icon has been updated
331- let _ = this_for_icon. update ( & mut cx, |_this, cx| {
332- cx. notify ( ) ;
345+ let _ = this_for_icon. update ( & mut cx, |this, cx| {
346+ this. refresh_visible_app_icons ( cx) ;
333347 } ) ;
334348 }
335349 } )
@@ -415,6 +429,51 @@ impl LauncherWindow {
415429 crate :: icon_cache:: get_cached_icon_path ( app_path)
416430 }
417431
432+ fn backfill_cached_icon_paths ( results : & mut [ SearchResult ] ) -> bool {
433+ let mut changed = false ;
434+
435+ for result in results {
436+ let SearchAction :: LaunchApp { path, .. } = & result. action else {
437+ continue ;
438+ } ;
439+
440+ let IconSource :: AppIcon { icon_path, .. } = & mut result. icon else {
441+ continue ;
442+ } ;
443+
444+ if icon_path. is_some ( ) {
445+ continue ;
446+ }
447+
448+ if let Some ( cached_path) = Self :: get_cached_icon_path ( path) {
449+ * icon_path = Some ( cached_path) ;
450+ changed = true ;
451+ }
452+ }
453+
454+ changed
455+ }
456+
457+ fn refresh_visible_app_icons ( & mut self , cx : & mut ViewContext < Self > ) {
458+ let suggestions_changed = Self :: backfill_cached_icon_paths ( & mut self . search . suggestions ) ;
459+ let core_changed = Self :: backfill_cached_icon_paths ( & mut self . search . core_results ) ;
460+
461+ if !( suggestions_changed || core_changed) {
462+ return ;
463+ }
464+
465+ if !matches ! ( self . search. mode, SearchMode :: Calendar { .. } ) {
466+ self . search . results = self
467+ . search
468+ . core_results
469+ . iter ( )
470+ . map ( Self :: search_result_to_result_item)
471+ . collect ( ) ;
472+ }
473+
474+ cx. notify ( ) ;
475+ }
476+
418477 /// Converts an icon source to a display emoji (fallback)
419478 pub ( super ) fn icon_to_emoji ( icon : & IconSource ) -> SharedString {
420479 match icon {
0 commit comments