@@ -16,19 +16,24 @@ use core::{
1616} ;
1717use std:: {
1818 env,
19+ io:: Cursor ,
1920 path:: { Path , PathBuf } ,
2021 sync:: LazyLock ,
2122} ;
2223
24+ use iced:: widget:: image:: Handle ;
2325use log:: error;
26+ use objc2:: { Message , rc:: Retained } ;
27+ use objc2_app_kit:: { NSBitmapImageFileType , NSBitmapImageRep , NSImage , NSImageRep , NSWorkspace } ;
2428use objc2_core_foundation:: { CFArray , CFRetained , CFURL } ;
25- use objc2_foundation:: { NSBundle , NSNumber , NSString , NSURL , ns_string} ;
29+ use objc2_foundation:: {
30+ NSBundle , NSData , NSDictionary , NSNumber , NSSize , NSString , NSURL , ns_string,
31+ } ;
2632use rayon:: iter:: { IntoParallelIterator , ParallelIterator as _} ;
2733
2834use crate :: {
2935 app:: apps:: { App , AppCommand } ,
3036 commands:: Function ,
31- utils:: handle_from_icns,
3237} ;
3338
3439use super :: super :: cross;
@@ -238,18 +243,17 @@ fn query_app(url: impl AsRef<NSURL>, store_icons: bool) -> Option<App> {
238243 . map ( |stem| stem. to_string_lossy ( ) . into_owned ( ) )
239244 } ) ?;
240245
241- let icons = store_icons
242- . then ( || {
243- get_string ( ns_string ! ( "CFBundleIconFile" ) ) . and_then ( |icon| {
244- let mut path = path. join ( "Contents/Resources" ) . join ( & icon) ;
245- if path. extension ( ) . is_none ( ) {
246- path. set_extension ( "icns" ) ;
247- }
248-
249- handle_from_icns ( & path)
250- } )
251- } )
252- . flatten ( ) ;
246+ let icon = icon_of_path_ns ( path. to_str ( ) . unwrap_or ( & name) ) . unwrap_or ( vec ! [ ] ) ;
247+ let icons = if store_icons {
248+ image:: ImageReader :: new ( Cursor :: new ( icon) )
249+ . with_guessed_format ( )
250+ . unwrap ( )
251+ . decode ( )
252+ . ok ( )
253+ . map ( |img| Handle :: from_rgba ( img. width ( ) , img. height ( ) , img. into_bytes ( ) ) )
254+ } else {
255+ None
256+ } ;
253257
254258 Some ( App {
255259 ranking : 0 ,
@@ -307,3 +311,82 @@ fn is_helper_location(path: &Path) -> bool {
307311 || s. contains ( "/Contents/Frameworks/" )
308312 || s. contains ( "/Library/PrivilegedHelperTools/" )
309313}
314+
315+ /// https://github.com/cardisoft/cardinal/blob/339b27c3c6abaf94405a9ab09ec39296baba4f91/fs-icon/src/lib.rs#L37
316+ pub fn icon_of_path_ns ( path : & str ) -> Option < Vec < u8 > > {
317+ objc2:: rc:: autoreleasepool ( |_| -> Option < Vec < u8 > > {
318+ let path_ns = NSString :: from_str ( path) ;
319+ let image = NSWorkspace :: sharedWorkspace ( ) . iconForFile ( & path_ns) ;
320+
321+ // Choose what you consider "high quality" output.
322+ // 256 is a good default; you can bump to 512 if you want.
323+ let target: f64 = 256.0 ;
324+
325+ let png_data: Retained < NSData > = ( || -> Option < _ > {
326+ unsafe {
327+ // Pick the best representation:
328+ // - Prefer the smallest rep that is >= target (avoids upscaling)
329+ // - Otherwise pick the largest available rep
330+ let mut best_rep = None :: < Retained < NSImageRep > > ;
331+ let mut best_w = 0.0 ;
332+ let mut best_h = 0.0 ;
333+
334+ let mut largest_rep = None :: < Retained < NSImageRep > > ;
335+ let mut largest_area = 0.0 ;
336+ let mut largest_w = 0.0 ;
337+ let mut largest_h = 0.0 ;
338+
339+ for rep in image. representations ( ) . iter ( ) {
340+ let s = rep. size ( ) ;
341+ let w = s. width ;
342+ let h = s. height ;
343+
344+ // Track largest (fallback)
345+ let area = w * h;
346+ if area > largest_area {
347+ largest_area = area;
348+ largest_rep = Some ( rep. retain ( ) ) ;
349+ largest_w = w;
350+ largest_h = h;
351+ }
352+
353+ // Track best rep for target (no upscale if possible)
354+ if w >= target && h >= target {
355+ let best_area = best_w * best_h;
356+ if best_rep. is_none ( ) || area < best_area {
357+ best_rep = Some ( rep. retain ( ) ) ;
358+ best_w = w;
359+ best_h = h;
360+ }
361+ }
362+ }
363+
364+ let ( rep, out_w, out_h) = if let Some ( rep) = best_rep {
365+ ( rep, target, target)
366+ } else if let Some ( rep) = largest_rep {
367+ // If nothing reaches target, use largest and render at its native size
368+ ( rep, largest_w, largest_h)
369+ } else {
370+ return None ;
371+ } ;
372+
373+ let new_image = NSImage :: imageWithSize_flipped_drawingHandler (
374+ NSSize :: new ( out_w, out_h) ,
375+ false ,
376+ & block2:: RcBlock :: new ( move |rect| {
377+ rep. drawInRect ( rect) ;
378+ true . into ( )
379+ } ) ,
380+ ) ;
381+
382+ NSBitmapImageRep :: imageRepWithData ( & * new_image. TIFFRepresentation ( ) ?) ?
383+ . representationUsingType_properties (
384+ NSBitmapImageFileType :: PNG ,
385+ & NSDictionary :: new ( ) ,
386+ )
387+ }
388+ } ) ( ) ?;
389+
390+ Some ( png_data. to_vec ( ) )
391+ } )
392+ }
0 commit comments