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
This commit is contained in:
@ -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",
|
||||
] }
|
||||
|
||||
@ -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<WideChar>),
|
||||
/// 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<Self, LoadIconRcError> {
|
||||
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, LoadIconRcError> {
|
||||
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<WideChar>),
|
||||
/// The C-format string is invalid.
|
||||
BadString(#[from] widestring::error::NulError<WideChar>),
|
||||
/// 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<Self, LoadStrRcError> {
|
||||
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<String, LoadStrRcError> {
|
||||
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<WideChar>),
|
||||
/// 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<Self, LoadIconError> {
|
||||
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
|
||||
|
||||
@ -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::<IconRefStr>();
|
||||
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::<IconRefStr>();
|
||||
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::<StrRefStr>();
|
||||
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::<StrRefStr>();
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user