@@ -16,7 +16,9 @@ use windows::{
1616 MONITORINFOEXW , MonitorFromPoint , MonitorFromWindow , ReleaseDC , SelectObject ,
1717 } ,
1818 } ,
19- Storage :: FileSystem :: { GetFileVersionInfoSizeW , GetFileVersionInfoW , VerQueryValueW } ,
19+ Storage :: FileSystem :: {
20+ FILE_FLAGS_AND_ATTRIBUTES , GetFileVersionInfoSizeW , GetFileVersionInfoW , VerQueryValueW ,
21+ } ,
2022 System :: {
2123 Threading :: {
2224 GetCurrentProcessId , OpenProcess , PROCESS_NAME_FORMAT ,
@@ -29,15 +31,19 @@ use windows::{
2931 GetDpiForMonitor , GetDpiForWindow , GetProcessDpiAwareness , MDT_EFFECTIVE_DPI ,
3032 PROCESS_PER_MONITOR_DPI_AWARE ,
3133 } ,
32- Shell :: ExtractIconExW ,
34+ Shell :: {
35+ ExtractIconExW , SHFILEINFOW , SHGFI_ICON , SHGFI_LARGEICON , SHGFI_SMALLICON ,
36+ SHGetFileInfoW ,
37+ } ,
3338 WindowsAndMessaging :: {
3439 DI_FLAGS , DestroyIcon , DrawIconEx , EnumChildWindows , EnumWindows , GCLP_HICON ,
3540 GW_HWNDNEXT , GWL_EXSTYLE , GWL_STYLE , GetClassLongPtrW , GetClassNameW ,
3641 GetClientRect , GetCursorPos , GetDesktopWindow , GetIconInfo ,
3742 GetLayeredWindowAttributes , GetWindow , GetWindowLongPtrW , GetWindowLongW ,
3843 GetWindowRect , GetWindowTextLengthW , GetWindowTextW , GetWindowThreadProcessId ,
39- HICON , ICONINFO , IsIconic , IsWindowVisible , SendMessageW , WM_GETICON , WS_CHILD ,
40- WS_EX_LAYERED , WS_EX_TOOLWINDOW , WS_EX_TOPMOST , WS_EX_TRANSPARENT , WindowFromPoint ,
44+ HICON , ICONINFO , IsIconic , IsWindowVisible , PrivateExtractIconsW , SendMessageW ,
45+ WM_GETICON , WS_CHILD , WS_EX_LAYERED , WS_EX_TOOLWINDOW , WS_EX_TOPMOST ,
46+ WS_EX_TRANSPARENT , WindowFromPoint ,
4147 } ,
4248 } ,
4349 } ,
@@ -525,9 +531,23 @@ impl WindowImpl {
525531 pub fn app_icon ( & self ) -> Option < Vec < u8 > > {
526532 unsafe {
527533 // Target size for acceptable icon quality - early termination threshold
528- const GOOD_SIZE_THRESHOLD : i32 = 64 ;
534+ const GOOD_SIZE_THRESHOLD : i32 = 256 ;
535+
536+ // Method 1: Try shell icon extraction for highest quality
537+ if let Some ( exe_path) = self . get_executable_path ( ) {
538+ if let Some ( icon_data) = self . extract_shell_icon_high_res ( & exe_path, 512 ) {
539+ return Some ( icon_data) ;
540+ }
541+ }
529542
530- // Method 1: Try to get the window's large icon first
543+ // Method 2: Try executable file extraction with multiple icon sizes
544+ if let Some ( exe_path) = self . get_executable_path ( ) {
545+ if let Some ( icon_data) = self . extract_executable_icons_high_res ( & exe_path) {
546+ return Some ( icon_data) ;
547+ }
548+ }
549+
550+ // Method 3: Try to get the window's large icon
531551 let large_icon = SendMessageW (
532552 self . 0 ,
533553 WM_GETICON ,
@@ -544,7 +564,7 @@ impl WindowImpl {
544564 }
545565 }
546566
547- // Method 2 : Try executable file extraction (only first icon, most likely to be main app icon )
567+ // Method 4 : Try executable file extraction (fallback to original method )
548568 if let Some ( exe_path) = self . get_executable_path ( ) {
549569 let wide_path: Vec < u16 > =
550570 exe_path. encode_utf16 ( ) . chain ( std:: iter:: once ( 0 ) ) . collect ( ) ;
@@ -587,7 +607,7 @@ impl WindowImpl {
587607 }
588608 }
589609
590- // Method 3 : Try small window icon as fallback
610+ // Method 5 : Try small window icon as fallback
591611 let small_icon = SendMessageW (
592612 self . 0 ,
593613 WM_GETICON ,
@@ -601,7 +621,7 @@ impl WindowImpl {
601621 }
602622 }
603623
604- // Method 4 : Try class icon as last resort
624+ // Method 6 : Try class icon as last resort
605625 let class_icon = GetClassLongPtrW ( self . 0 , GCLP_HICON ) as isize ;
606626 if class_icon != 0 {
607627 if let Some ( result) = self . hicon_to_png_bytes_optimized ( HICON ( class_icon as _ ) ) {
@@ -613,6 +633,107 @@ impl WindowImpl {
613633 }
614634 }
615635
636+ fn extract_shell_icon_high_res ( & self , exe_path : & str , target_size : i32 ) -> Option < Vec < u8 > > {
637+ unsafe {
638+ let wide_path: Vec < u16 > = exe_path. encode_utf16 ( ) . chain ( std:: iter:: once ( 0 ) ) . collect ( ) ;
639+
640+ // Try different shell icon sizes
641+ let icon_flags = [
642+ SHGFI_ICON | SHGFI_LARGEICON , // Large system icon
643+ SHGFI_ICON | SHGFI_SMALLICON , // Small system icon as fallback
644+ ] ;
645+
646+ for flags in icon_flags {
647+ let mut file_info = SHFILEINFOW :: default ( ) ;
648+ let result = SHGetFileInfoW (
649+ windows:: core:: PCWSTR ( wide_path. as_ptr ( ) ) ,
650+ FILE_FLAGS_AND_ATTRIBUTES ( 0 ) ,
651+ Some ( & mut file_info) ,
652+ std:: mem:: size_of :: < SHFILEINFOW > ( ) as u32 ,
653+ flags,
654+ ) ;
655+
656+ if result != 0 && !file_info. hIcon . is_invalid ( ) {
657+ if let Some ( result) = self . hicon_to_png_bytes_optimized ( file_info. hIcon ) {
658+ let _ = DestroyIcon ( file_info. hIcon ) ;
659+ if result. 1 >= target_size / 2 {
660+ // Accept if at least half target size
661+ return Some ( result. 0 ) ;
662+ }
663+ }
664+ let _ = DestroyIcon ( file_info. hIcon ) ;
665+ }
666+ }
667+
668+ None
669+ }
670+ }
671+
672+ fn extract_executable_icons_high_res ( & self , exe_path : & str ) -> Option < Vec < u8 > > {
673+ unsafe {
674+ let wide_path: Vec < u16 > = exe_path. encode_utf16 ( ) . chain ( std:: iter:: once ( 0 ) ) . collect ( ) ;
675+
676+ let mut path_buffer = [ 0u16 ; 260 ] ;
677+ let copy_len = wide_path. len ( ) . min ( path_buffer. len ( ) ) ;
678+ path_buffer[ ..copy_len] . copy_from_slice ( & wide_path[ ..copy_len] ) ;
679+
680+ let icon_count = ExtractIconExW ( PCWSTR ( wide_path. as_ptr ( ) ) , -1 , None , None , 0 ) ;
681+
682+ let total_icons = if icon_count > 0 {
683+ icon_count as usize
684+ } else {
685+ 1
686+ } ;
687+
688+ let max_icons_to_try = total_icons. min ( 8 ) ;
689+ let size_candidates: [ i32 ; 12 ] = [ 512 , 400 , 256 , 192 , 128 , 96 , 72 , 64 , 48 , 32 , 24 , 16 ] ;
690+
691+ let mut best_icon: Option < Vec < u8 > > = None ;
692+ let mut best_size: i32 = 0 ;
693+
694+ for & size in & size_candidates {
695+ for index in 0 ..max_icons_to_try {
696+ let mut icon_slot = [ HICON :: default ( ) ; 1 ] ;
697+
698+ let extracted = PrivateExtractIconsW (
699+ & path_buffer,
700+ index as i32 ,
701+ size,
702+ size,
703+ Some ( & mut icon_slot) ,
704+ None ,
705+ 0 ,
706+ ) ;
707+
708+ if extracted == 0 {
709+ continue ;
710+ }
711+
712+ let icon_handle = icon_slot[ 0 ] ;
713+ if icon_handle. is_invalid ( ) {
714+ continue ;
715+ }
716+
717+ let icon_result = self . hicon_to_png_bytes_optimized ( icon_handle) ;
718+ let _ = DestroyIcon ( icon_handle) ;
719+
720+ if let Some ( ( png_data, realized_size) ) = icon_result {
721+ if realized_size > best_size {
722+ best_size = realized_size;
723+ best_icon = Some ( png_data) ;
724+
725+ if best_size >= 256 {
726+ return best_icon;
727+ }
728+ }
729+ }
730+ }
731+ }
732+
733+ best_icon
734+ }
735+ }
736+
616737 fn get_executable_path ( & self ) -> Option < String > {
617738 unsafe {
618739 let mut process_id = 0u32 ;
@@ -674,50 +795,57 @@ impl WindowImpl {
674795
675796 fn hicon_to_png_bytes_optimized ( & self , icon : HICON ) -> Option < ( Vec < u8 > , i32 ) > {
676797 unsafe {
677- // Get icon info to determine actual size
678798 let mut icon_info = ICONINFO :: default ( ) ;
679799 if !GetIconInfo ( icon, & mut icon_info) . is_ok ( ) {
680800 return None ;
681801 }
682802
683- // Get device context
684803 let screen_dc = GetDC ( Some ( HWND :: default ( ) ) ) ;
685804 let mem_dc = CreateCompatibleDC ( Some ( screen_dc) ) ;
686805
687- // Get the native icon size to prioritize it
688806 let native_size = self . get_icon_size ( icon) ;
689-
690- // Determine the best size to try based on native size
691- let target_sizes = if let Some ( ( width, height) ) = native_size {
807+ let target_sizes: Vec < i32 > = if let Some ( ( width, height) ) = native_size {
692808 let native_dim = width. max ( height) ;
693- if native_dim >= 256 {
694- vec ! [ native_dim, 256 , 128 ] // High-res icon
695- } else if native_dim >= 64 {
696- vec ! [ native_dim, 64 , 32 ] // Medium-res icon
697- } else if native_dim >= 32 {
698- vec ! [ native_dim, 32 , 16 ] // Standard icon
809+ if native_dim > 0 {
810+ let mut sizes = Vec :: with_capacity ( 10 ) ;
811+ sizes. push ( native_dim) ;
812+ for & candidate in & [ 256 , 192 , 128 , 96 , 64 , 48 , 32 , 24 , 16 ] {
813+ if candidate > 0 && candidate < native_dim {
814+ sizes. push ( candidate) ;
815+ }
816+ }
817+ if sizes. is_empty ( ) {
818+ vec ! [ native_dim]
819+ } else {
820+ sizes
821+ }
699822 } else {
700- vec ! [ 32 , 16 ] // Small icon, try standard sizes
823+ vec ! [ 256 , 192 , 128 , 96 , 64 , 48 , 32 , 24 , 16 ]
701824 }
702825 } else {
703- // No native size info, try reasonable defaults
704- vec ! [ 128 , 64 , 32 , 16 ]
826+ vec ! [ 512 , 256 , 192 , 128 , 96 , 64 , 48 , 32 , 24 , 16 ]
705827 } ;
706828
707- // Try each target size, return the first successful one
708- for & size in & target_sizes {
709- if let Some ( result) = self . try_convert_icon_to_png ( icon, size, screen_dc, mem_dc) {
710- // Cleanup
829+ let mut deduped = Vec :: new ( ) ;
830+ for size in target_sizes. into_iter ( ) {
831+ if !deduped. contains ( & size) {
832+ deduped. push ( size) ;
833+ }
834+ }
835+
836+ for size in deduped. into_iter ( ) . filter ( |size| * size > 0 ) {
837+ if let Some ( ( png_data, realized_size) ) =
838+ self . try_convert_icon_to_png ( icon, size, screen_dc, mem_dc)
839+ {
711840 let _ = DeleteDC ( mem_dc) ;
712841 let _ = ReleaseDC ( Some ( HWND :: default ( ) ) , screen_dc) ;
713842 let _ = DeleteObject ( icon_info. hbmColor . into ( ) ) ;
714843 let _ = DeleteObject ( icon_info. hbmMask . into ( ) ) ;
715844
716- return Some ( ( result , size ) ) ;
845+ return Some ( ( png_data , realized_size ) ) ;
717846 }
718847 }
719848
720- // Cleanup
721849 let _ = DeleteDC ( mem_dc) ;
722850 let _ = ReleaseDC ( Some ( HWND :: default ( ) ) , screen_dc) ;
723851 let _ = DeleteObject ( icon_info. hbmColor . into ( ) ) ;
@@ -733,19 +861,18 @@ impl WindowImpl {
733861 size : i32 ,
734862 screen_dc : HDC ,
735863 mem_dc : HDC ,
736- ) -> Option < Vec < u8 > > {
864+ ) -> Option < ( Vec < u8 > , i32 ) > {
737865 unsafe {
738866 let width = size;
739867 let height = size;
740868
741- // Create bitmap info for this size
742869 let mut bitmap_info = BITMAPINFO {
743870 bmiHeader : BITMAPINFOHEADER {
744871 biSize : mem:: size_of :: < BITMAPINFOHEADER > ( ) as u32 ,
745872 biWidth : width,
746- biHeight : -height, // Top-down DIB
873+ biHeight : -height,
747874 biPlanes : 1 ,
748- biBitCount : 32 , // 32 bits per pixel (BGRA)
875+ biBitCount : 32 ,
749876 biCompression : BI_RGB . 0 ,
750877 biSizeImage : 0 ,
751878 biXPelsPerMeter : 0 ,
@@ -756,15 +883,13 @@ impl WindowImpl {
756883 bmiColors : [ Default :: default ( ) ; 1 ] ,
757884 } ;
758885
759- // Create a bitmap
760886 let bitmap = CreateCompatibleBitmap ( screen_dc, width, height) ;
761887 if bitmap. is_invalid ( ) {
762888 return None ;
763889 }
764890
765891 let old_bitmap = SelectObject ( mem_dc, bitmap. into ( ) ) ;
766892
767- // Fill with transparent background
768893 let brush = CreateSolidBrush ( windows:: Win32 :: Foundation :: COLORREF ( 0 ) ) ;
769894 let rect = RECT {
770895 left : 0 ,
@@ -775,7 +900,6 @@ impl WindowImpl {
775900 let _ = FillRect ( mem_dc, & rect, brush) ;
776901 let _ = DeleteObject ( brush. into ( ) ) ;
777902
778- // Draw the icon onto the bitmap with proper scaling
779903 let draw_result = DrawIconEx (
780904 mem_dc,
781905 0 ,
@@ -785,13 +909,12 @@ impl WindowImpl {
785909 height,
786910 0 ,
787911 Some ( HBRUSH :: default ( ) ) ,
788- DI_FLAGS ( 0x0003 ) , // DI_NORMAL
912+ DI_FLAGS ( 0x0003 ) ,
789913 ) ;
790914
791- let mut result = None ;
915+ let mut result: Option < ( Vec < u8 > , i32 ) > = None ;
792916
793917 if draw_result. is_ok ( ) {
794- // Get bitmap bits
795918 let mut buffer = vec ! [ 0u8 ; ( width * height * 4 ) as usize ] ;
796919 let get_bits_result = GetDIBits (
797920 mem_dc,
@@ -804,16 +927,13 @@ impl WindowImpl {
804927 ) ;
805928
806929 if get_bits_result > 0 {
807- // Check if we have any non-transparent pixels
808930 let has_content = buffer. chunks_exact ( 4 ) . any ( |chunk| chunk[ 3 ] != 0 ) ;
809931
810932 if has_content {
811- // Convert BGRA to RGBA
812933 for chunk in buffer. chunks_exact_mut ( 4 ) {
813- chunk. swap ( 0 , 2 ) ; // Swap B and R
934+ chunk. swap ( 0 , 2 ) ;
814935 }
815936
816- // Create PNG using the image crate
817937 if let Some ( img) =
818938 image:: RgbaImage :: from_raw ( width as u32 , height as u32 , buffer)
819939 {
@@ -825,14 +945,13 @@ impl WindowImpl {
825945 )
826946 . is_ok ( )
827947 {
828- result = Some ( png_data) ;
948+ result = Some ( ( png_data, width ) ) ;
829949 }
830950 }
831951 }
832952 }
833953 }
834954
835- // Cleanup for this iteration
836955 let _ = SelectObject ( mem_dc, old_bitmap) ;
837956 let _ = DeleteObject ( bitmap. into ( ) ) ;
838957
0 commit comments