skip to Main Content

I am making a window-switcherin tauri using Rust and solidjs.

I am using winapi to interact with Windows.

For now I have:

  1. HashMap of <window_title (string), HWND >
  2. I send the window titles to the frontend
  3. When clicked, it correctly switches the windows.

I want to display the app icon beside its title, so it can be more recognizable.

This is my method:

unsafe extern "system" fn enum_windows_proc(hwnd: HWND, lparam: LPARAM) -> BOOL {
    let title_length = GetWindowTextLengthW(hwnd);
    if title_length > 0 {
        let mut buffer: Vec<u16> = vec![0; (title_length + 1) as usize]; // +1 for null terminator
        GetWindowTextW(hwnd, buffer.as_mut_ptr(), buffer.len() as i32);

        if IsWindowVisible(hwnd) != 0 {
            let window_title = OsString::from_wide(&buffer).to_string_lossy().to_string();
            let title_map = &mut *(lparam as *mut HashMap<String, (HWND, HICON)>);
            let app_icon = get_app_icon(hwnd);

            if app_icon.is_some() {
                title_map.insert(window_title, (hwnd, app_icon.unwrap()));
            }
        }
    }

    1
}

fn get_app_icon(hwnd: HWND) -> Option<HICON> {
    let icon_handle: usize = unsafe { GetClassLongPtrW(hwnd, GCLP_HICON as i32) };
    if icon_handle == 0 {
        None
    } else {
        Some(icon_handle as HICON)
    }
}

I modified it to have the icon beside the window handle

Is my method for retrieving the HICON correct? How can I log it? I tried to print the icon_handle but it shows nothing. But when I try to log any string in its place it logs correctly.

2

Answers


  1. Chosen as BEST ANSWER

    Thanks @DeveloperMindset.com for their crucial help.

    I modified it a bit to make it work with winapi since i wasnt able to make it work with window.rs, and change it in a way that extracts the icon from the exe path.

    unsafe fn icon_to_rgba_image(icon: HICON, hwnd: HWND) -> String {
        let bitmap_size = i32::try_from(mem::size_of::<BITMAP>()).unwrap();
        let biheader_size = u32::try_from(mem::size_of::<BITMAPINFOHEADER>()).unwrap();
    
        let mut info = MaybeUninit::uninit();
        GetIconInfo(icon, info.as_mut_ptr());
        let info = info.assume_init_ref();
        DeleteObject(info.hbmMask as *mut c_void);
    
        let mut bitmap: MaybeUninit<BITMAP> = MaybeUninit::uninit();
        let result = GetObjectW(
            info.hbmColor as *mut c_void,
            bitmap_size,
            bitmap.as_mut_ptr().cast(),
        );
    
        if result != bitmap_size {
            return "".to_string();
        }
    
        let bitmap = bitmap.assume_init_ref();
    
        let width = u32::try_from(bitmap.bmWidth).unwrap();
        let height = u32::try_from(bitmap.bmHeight).unwrap();
        let w = usize::try_from(bitmap.bmWidth).unwrap();
        let h = usize::try_from(bitmap.bmHeight).unwrap();
    
        let buf_size = w
            .checked_mul(h)
            .and_then(|size| size.checked_mul(4))
            .unwrap();
        let mut buf: Vec<u8> = Vec::with_capacity(buf_size);
    
        let dc = GetDC(hwnd);
    
        let mut bitmap_info = BITMAPINFOHEADER {
            biSize: biheader_size,
            biWidth: bitmap.bmWidth,
            biHeight: -bitmap.bmHeight,
            biPlanes: 1,
            biBitCount: 32,
            biCompression: 0,
            biSizeImage: 0,
            biXPelsPerMeter: 0,
            biYPelsPerMeter: 0,
            biClrUsed: 0,
            biClrImportant: 0,
        };
        GetDIBits(
            dc,
            info.hbmColor,
            0,
            height,
            buf.as_mut_ptr().cast(),
            addr_of_mut!(bitmap_info).cast(),
            DIB_RGB_COLORS,
        );
    
        buf.set_len(buf.capacity());
    
        let result = ReleaseDC(hwnd, dc);
        assert!(result == 1);
        DeleteObject(info.hbmColor as *mut c_void);
    
        for chunk in buf.chunks_exact_mut(4) {
            let [b, _, r, _] = chunk else { unreachable!() };
            mem::swap(b, r);
        }
    
        let img = RgbaImage::from_vec(width, height, buf).unwrap();
    
        let mut data: Vec<u8> = Vec::new();
        img.write_to(&mut Cursor::new(&mut data), image::ImageOutputFormat::Png)
            .unwrap();
    
        general_purpose::STANDARD.encode(&data)
    }
    

  2. Here’s how you can convert HICON to a base64 representation of PNG icon:

    use base64::{engine::general_purpose, Engine as _};
    use image::RgbaImage;
    use std::{mem::{self, MaybeUninit},ptr::addr_of_mut};
    use windows::Win32::{Foundation::HWND, Graphics::Gdi::{DeleteObject, GetDC, GetDIBits, GetObjectW, ReleaseDC, BITMAP, BITMAPINFOHEADER, BI_RGB,DIB_RGB_COLORS, HDC}, UI::WindowsAndMessaging::{GetIconInfo, HICON}};
    
    unsafe fn icon_to_rgba_image(icon: HICON) -> String {
        let bitmap_size = i32::try_from(mem::size_of::<BITMAP>()).unwrap();
        let biheader_size = u32::try_from(mem::size_of::<BITMAPINFOHEADER>()).unwrap();
    
        let mut info = MaybeUninit::uninit();
        GetIconInfo(icon, info.as_mut_ptr()).unwrap();
        let info = info.assume_init_ref();
        DeleteObject(info.hbmMask).unwrap();
    
        let mut bitmap: MaybeUninit<BITMAP> = MaybeUninit::uninit();
        let result = GetObjectW(
            info.hbmColor,
            bitmap_size,
            Some(bitmap.as_mut_ptr().cast()),
        );
        assert!(result == bitmap_size);
        let bitmap = bitmap.assume_init_ref();
    
        let width = u32::try_from(bitmap.bmWidth).unwrap();
        let height = u32::try_from(bitmap.bmHeight).unwrap();
        let w = usize::try_from(bitmap.bmWidth).unwrap();
        let h = usize::try_from(bitmap.bmHeight).unwrap();
    
        let buf_size = w
            .checked_mul(h)
            .and_then(|size| size.checked_mul(4))
            .unwrap();
        let mut buf: Vec<u8> = Vec::with_capacity(buf_size);
    
        let dc = GetDC(HWND(0));
        assert!(dc != HDC(0));
    
        let mut bitmap_info = BITMAPINFOHEADER {
            biSize: biheader_size,
            biWidth: bitmap.bmWidth,
            biHeight: -bitmap.bmHeight,
            biPlanes: 1,
            biBitCount: 32,
            biCompression: BI_RGB,
            biSizeImage: 0,
            biXPelsPerMeter: 0,
            biYPelsPerMeter: 0,
            biClrUsed: 0,
            biClrImportant: 0,
        };
        let result = GetDIBits(
            dc,
            info.hbmColor,
            0,
            height,
            Some(buf.as_mut_ptr().cast()),
            addr_of_mut!(bitmap_info).cast(),
            DIB_RGB_COLORS,
        );
        buf.set_len(buf.capacity());
    
        let result = ReleaseDC(HWND(0), dc);
        assert!(result == 1);
        DeleteObject(info.hbmColor).unwrap();
    
        for chunk in buf.chunks_exact_mut(4) {
            let [b, _, r, _] = chunk else { unreachable!() };
            mem::swap(b, r);
        }
    
        let img = RgbaImage::from_vec(width, height, buf).unwrap();
    
        let mut data: Vec<u8> = Vec::new();
        img.write_to(&mut Cursor::new(&mut data), image::ImageOutputFormat::Png)?;
    
        general_purpose::STANDARD.encode(&data);
    }
    

    =============

    So Tauri is built on top of TAO, which is a fork of winit and they implement window management for each platform there.

    Specifically windows/window.rs, so in your case I would clone the TAO repository and add required changes there, and then patch your dependencies to make it point to your version of TAO.

    Now, here’s how TAO implements hicon related class:

    let class = WNDCLASSEXW {
        cbSize: mem::size_of::<WNDCLASSEXW>() as u32,
        style: CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
        lpfnWndProc: Some(window_proc),
        cbClsExtra: 0,
        cbWndExtra: 0,
        hInstance: HINSTANCE(GetModuleHandleW(PCWSTR::null()).unwrap_or_default().0),
        hIcon: HICON::default(),
        hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly
        hbrBackground: HBRUSH::default(),
        lpszMenuName: PCWSTR::null(),
        lpszClassName: PCWSTR::from_raw(class_name.as_ptr()),
        hIconSm: HICON::default(),
      };
    

    Oh, it’s worth mentioning, Tauri uses windows crate, instead of winapi. Which has less downloads, but IMO better support from Microsoft. And essentially very similar to what apple-sys done for Macs.

    As Jonathan Potter pointed out, you need to use GetIconInfo to get hicon info:

    pub struct ICONINFO {
        pub fIcon: BOOL,
        pub xHotspot: u32,
        pub yHotspot: u32,
        pub hbmMask: HBITMAP,
        pub hbmColor: HBITMAP,
    }
    

    So to answer your original question, no you can’t print hicon because it’s just a handle:

    pub type HICON = isize;
    

    P.S. This is how TAO check visibility of a window on Windows.

    P.P.S. Same place has ~7 ways to get your windows hardware id in HWND and other formats.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search