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_UI_WindowsAndMessaging",
|
||||||
"Win32_Security",
|
"Win32_Security",
|
||||||
"Win32_System_Environment",
|
"Win32_System_Environment",
|
||||||
|
"Win32_System_LibraryLoader",
|
||||||
"Win32_System_Registry",
|
"Win32_System_Registry",
|
||||||
"Win32_System_SystemServices",
|
"Win32_System_SystemServices",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@ -187,6 +187,172 @@ impl FromStr for StrRefStr {
|
|||||||
|
|
||||||
// endregion
|
// 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
|
// region: Expand String
|
||||||
|
|
||||||
/// Error occurs when creating Expand String.
|
/// Error occurs when creating Expand String.
|
||||||
@ -297,87 +463,6 @@ impl FromStr for ExpandString {
|
|||||||
|
|
||||||
// endregion
|
// 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: Windows Commandline
|
||||||
|
|
||||||
// region: Cmd Lexer
|
// region: Cmd Lexer
|
||||||
|
|||||||
@ -1,25 +1,48 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use wfassoc::extra::windows::*;
|
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]
|
#[test]
|
||||||
fn test_icon_rc() {
|
fn test_icon_rc() {
|
||||||
|
fn tester(file: &str, index: u32) {
|
||||||
}
|
let icon = IconRc::new(Path::new(file), index, IconSizeKind::Small);
|
||||||
|
|
||||||
#[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);
|
|
||||||
assert!(icon.is_ok())
|
assert!(icon.is_ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +50,16 @@ fn test_icon() {
|
|||||||
tester("imageres.dll", 72);
|
tester("imageres.dll", 72);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_str_rc() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_expand_string() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cmd_args() {
|
fn test_cmd_args() {
|
||||||
// Declare tester
|
// Declare tester
|
||||||
|
|||||||
Reference in New Issue
Block a user