From 81fd2242369abaa1e930f93aa69626f259fe4a60 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 27 Oct 2025 14:09:51 +0800 Subject: [PATCH] feat(windows): implement icon and string resource loading - Add IconRc struct for loading icons from executable or .ico files - Implement LoadIconRcError for icon loading error handling - Add StrRc struct for loading string resources from files - Implement LoadStrRcError for string loading error handling - Remove old Icon struct and related error types - Update tests to use new resource loading implementations - Add Windows Resource section with proper documentation - Include Win32_System_LibraryLoader feature in Cargo.toml --- wfassoc/Cargo.toml | 1 + wfassoc/src/extra/windows.rs | 247 ++++++++++++++++++++++----------- wfassoc/tests/extra/windows.rs | 0 wfassoc/tests/extra_windows.rs | 67 ++++++--- 4 files changed, 217 insertions(+), 98 deletions(-) delete mode 100644 wfassoc/tests/extra/windows.rs diff --git a/wfassoc/Cargo.toml b/wfassoc/Cargo.toml index 97bd06a..b07584d 100644 --- a/wfassoc/Cargo.toml +++ b/wfassoc/Cargo.toml @@ -13,6 +13,7 @@ windows-sys = { version = "0.60.2", features = [ "Win32_UI_WindowsAndMessaging", "Win32_Security", "Win32_System_Environment", + "Win32_System_LibraryLoader", "Win32_System_Registry", "Win32_System_SystemServices", ] } diff --git a/wfassoc/src/extra/windows.rs b/wfassoc/src/extra/windows.rs index a0a4106..8ab5e9b 100644 --- a/wfassoc/src/extra/windows.rs +++ b/wfassoc/src/extra/windows.rs @@ -187,6 +187,172 @@ impl FromStr for StrRefStr { // endregion +// region: Windows Resource + +// region: Icon Resource + +/// Error occurs when loading icon. +#[derive(Debug, TeError)] +#[error("error occurs when loading icon resource")] +pub enum LoadIconRcError { + /// Given path has embedded NUL. + EmbeddedNul(#[from] widestring::error::ContainsNul), + /// Error occurs when executing Win32 extract function. + ExtractIcon, +} + +/// The size kind of loaded icon +#[derive(Debug, Clone, Copy)] +pub enum IconSizeKind { + /// Small Icon + Small, + /// Large Icon + Large, +} + +/// The struct representing a loaded icon resource. +pub struct IconRc { + icon: HICON, +} + +impl IconRc { + /// Load icon from executable or `.ico` file. + /// + /// If you want to extract icon from `.ico` file, please pass `0` to `index` parameter. + /// Otherwise `index` is the icon resource index located in executable. + pub fn new(file: &Path, index: u32, kind: IconSizeKind) -> Result { + use windows_sys::Win32::UI::Shell::ExtractIconExW; + + let mut icon = HICON::default(); + let icon_ptr = &mut icon as *mut HICON; + let file = WideCString::from_os_str(file.as_os_str())?; + let index = index as i32; + + let rv = unsafe { + match kind { + IconSizeKind::Small => { + ExtractIconExW(file.as_ptr(), index, Default::default(), icon_ptr, 1) + } + IconSizeKind::Large => { + ExtractIconExW(file.as_ptr(), index, icon_ptr, Default::default(), 1) + } + } + }; + + if rv != 1 || icon.is_null() { + Err(LoadIconRcError::ExtractIcon) + } else { + Ok(Self { icon }) + } + } + + /// An alias to default constructor. + /// It automatically handle the index parameter for you + /// when loading `.ico` file, rather than executable file. + pub fn with_ico_file(file: &Path, kind: IconSizeKind) -> Result { + Self::new(file, 0, kind) + } + + pub unsafe fn from_raw(hicon: HICON) -> Self { + Self { icon: hicon } + } + + pub fn into_raw(self) -> HICON { + self.icon + } +} + +impl IconRc { + pub fn get_icon(&self) -> HICON { + self.icon + } +} + +impl Drop for IconRc { + fn drop(&mut self) { + use windows_sys::Win32::UI::WindowsAndMessaging::DestroyIcon; + + if !self.icon.is_null() { + unsafe { + DestroyIcon(self.icon); + } + } + } +} + +// endregion + +// region: String Resource + +/// Error occurs when loading string. +#[derive(Debug, TeError)] +#[error("error occurs when loading string resource")] +pub enum LoadStrRcError { + /// Given path has embedded NUL. + EmbeddedNul(#[from] widestring::error::ContainsNul), + /// The C-format string is invalid. + BadString(#[from] widestring::error::NulError), + /// The encoding of string is invalid. + BadEncoding(#[from] widestring::error::Utf16Error), + /// Error when casting integer + CastInteger(#[from] std::num::TryFromIntError), + /// Can no load library including string resource. + LoadLibrary, + /// Fail to load string resource from file. + LoadString, +} + +pub struct StrRc { + inner: String +} + +impl StrRc { + pub fn new(file: &Path, index: u32) -> Result { + use windows_sys::core::PWSTR; + use windows_sys::Win32::UI::WindowsAndMessaging::LoadStringW; + use windows_sys::Win32::Foundation::FreeLibrary; + use windows_sys::Win32::System::LibraryLoader::{LoadLibraryExW, LOAD_LIBRARY_AS_DATAFILE, LOAD_LIBRARY_AS_IMAGE_RESOURCE}; + + // Load library first + let file = WideCString::from_os_str(file.as_os_str())?; + let hmodule = unsafe { LoadLibraryExW(file.as_ptr(), Default::default(), LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE) }; + if hmodule.is_null() { + return Err(LoadStrRcError::LoadLibrary); + } + + // Load string + let mut buffer: *const u16 = Default::default(); + let buffer_ptr= &mut buffer as *mut *const u16 as PWSTR; + let char_count = unsafe { LoadStringW(hmodule, index, buffer_ptr, 0) }; + // We write this function to make sure following "FreeLibrary" must be executed. + fn load_string(buffer: *const u16, char_count: i32) -> Result { + if char_count == 0 { + Err(LoadStrRcError::LoadString) + } else { + let buffer = unsafe { WideCStr::from_ptr(buffer, char_count.try_into()?)? }; + Ok(buffer.to_string()?) + } + } + let res_str = load_string(buffer, char_count); + + // Unload library + unsafe { FreeLibrary(hmodule) }; + + // Return value + res_str.map(|s| Self { inner: s }) + } +} + +impl StrRc { + pub fn get_string(&self) -> &str { + &self.inner + } +} + +// endregion + +// endregion + // region: Expand String /// Error occurs when creating Expand String. @@ -297,87 +463,6 @@ impl FromStr for ExpandString { // endregion -// region: Icon - -/// Error occurs when loading icon. -#[derive(Debug, TeError)] -#[error("error occurs when loading icon")] -pub enum LoadIconError { - /// Given path has embedded NUL. - EmbeddedNul(#[from] widestring::error::ContainsNul), - /// Error occurs when executing Win32 extract function. - ExtractIcon, -} - -/// The size kind of loaded icon -#[derive(Debug, Clone, Copy)] -pub enum IconSizeKind { - /// Small Icon - Small, - /// Large Icon - Large, -} - -/// The struct representing a loaded icon resource. -pub struct Icon { - icon: HICON, -} - -impl Icon { - pub fn new(file: &Path, index: i32, kind: IconSizeKind) -> Result { - use windows_sys::Win32::UI::Shell::ExtractIconExW; - - let mut icon = HICON::default(); - let icon_ptr = &mut icon as *mut HICON; - let file = WideCString::from_os_str(file.as_os_str())?; - - let rv = unsafe { - match kind { - IconSizeKind::Small => { - ExtractIconExW(file.as_ptr(), index, Default::default(), icon_ptr, 1) - } - IconSizeKind::Large => { - ExtractIconExW(file.as_ptr(), index, icon_ptr, Default::default(), 1) - } - } - }; - - if rv != 1 || icon.is_null() { - Err(LoadIconError::ExtractIcon) - } else { - Ok(Self { icon }) - } - } - - pub unsafe fn from_raw(hicon: HICON) -> Self { - Self { icon: hicon } - } - - pub fn into_raw(self) -> HICON { - self.icon - } -} - -impl Icon { - pub fn get_icon(&self) -> HICON { - self.icon - } -} - -impl Drop for Icon { - fn drop(&mut self) { - use windows_sys::Win32::UI::WindowsAndMessaging::DestroyIcon; - - if !self.icon.is_null() { - unsafe { - DestroyIcon(self.icon); - } - } - } -} - -// endregion - // region: Windows Commandline // region: Cmd Lexer diff --git a/wfassoc/tests/extra/windows.rs b/wfassoc/tests/extra/windows.rs deleted file mode 100644 index e69de29..0000000 diff --git a/wfassoc/tests/extra_windows.rs b/wfassoc/tests/extra_windows.rs index f3d7559..3b542f0 100644 --- a/wfassoc/tests/extra_windows.rs +++ b/wfassoc/tests/extra_windows.rs @@ -1,25 +1,48 @@ use std::path::Path; use wfassoc::extra::windows::*; +#[test] +fn test_icon_ref_str() { + fn ok_tester(s: &str, probe: (&str, u32)) { + let rv = s.parse::(); + assert!(rv.is_ok()); + let rv = rv.unwrap(); + assert_eq!(rv.get_path(), probe.0); + assert_eq!(rv.get_index(), probe.1); + } + fn err_tester(s: &str) { + let rv = s.parse::(); + assert!(rv.is_err()); + } + + ok_tester(r#"%SystemRoot%\System32\imageres.dll,-72"#, (r#"%SystemRoot%\System32\imageres.dll"#, 72)); + err_tester(r#"C:\Windows\Cursors\aero_arrow.cur"#); + err_tester(r#"@%SystemRoot%\System32\shell32.dll,-30596"#); +} + +#[test] +fn test_str_ref_str() { + fn ok_tester(s: &str, probe: (&str, u32)) { + let rv = s.parse::(); + assert!(rv.is_ok()); + let rv = rv.unwrap(); + assert_eq!(rv.get_path(), probe.0); + assert_eq!(rv.get_index(), probe.1); + } + fn err_tester(s: &str) { + let rv = s.parse::(); + assert!(rv.is_err()); + } + + ok_tester(r#"@%SystemRoot%\System32\shell32.dll,-30596"#, (r#"%SystemRoot%\System32\shell32.dll"#, 30596)); + err_tester(r#"This is my application, OK?"#); + err_tester(r#"%SystemRoot%\System32\imageres.dll,-72"#); +} + #[test] fn test_icon_rc() { - -} - -#[test] -fn test_str_rc() { - -} - -#[test] -fn test_expand_string() { - -} - -#[test] -fn test_icon() { - fn tester(file: &str, index: i32) { - let icon = Icon::new(Path::new(file), index, IconSizeKind::Small); + fn tester(file: &str, index: u32) { + let icon = IconRc::new(Path::new(file), index, IconSizeKind::Small); assert!(icon.is_ok()) } @@ -27,6 +50,16 @@ fn test_icon() { tester("imageres.dll", 72); } +#[test] +fn test_str_rc() { + +} + +#[test] +fn test_expand_string() { + +} + #[test] fn test_cmd_args() { // Declare tester