use std::{fmt, io};
use bitflags::bitflags;
use encoding_rs::Encoding;
use zerocopy::{Immutable, KnownLayout, LE, TryFromBytes, ValidityError, try_transmute};
use super::Condition;
use crate::{
InnoVersion, ReadBytesExt, WindowsVersionRange, header::flag_reader::read_flags::read_flags,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Icon {
name: Option<String>,
filename: Option<String>,
parameters: Option<String>,
working_directory: Option<String>,
file: Option<String>,
comment: Option<String>,
app_user_model_id: Option<String>,
app_user_model_toast_activator_clsid: String,
index: i32,
show_command: i32,
close_on_exit: CloseSetting,
hotkey: u16,
flags: IconFlags,
}
impl Icon {
pub fn read<R>(
mut reader: R,
codepage: &'static Encoding,
version: InnoVersion,
) -> io::Result<Self>
where
R: io::Read,
{
if version < 1.3 {
let _uncompressed_size = reader.read_u32::<LE>()?;
}
let mut icon = Self {
name: reader.read_decoded_pascal_string(codepage)?,
filename: reader.read_decoded_pascal_string(codepage)?,
parameters: reader.read_decoded_pascal_string(codepage)?,
working_directory: reader.read_decoded_pascal_string(codepage)?,
file: reader.read_decoded_pascal_string(codepage)?,
comment: reader.read_decoded_pascal_string(codepage)?,
..Self::default()
};
Condition::read(&mut reader, codepage, version)?;
if version >= (5, 3, 5) {
icon.app_user_model_id = reader.read_decoded_pascal_string(codepage)?;
}
if version >= 6.1 {
let mut buf = [0; 16];
reader.read_exact(&mut buf)?;
icon.app_user_model_toast_activator_clsid = codepage.decode(&buf).0.into_owned();
}
WindowsVersionRange::read_from(&mut reader, version)?;
icon.index = reader.read_i32::<LE>()?;
if version >= (1, 3, 24) {
icon.show_command = reader.read_i32::<LE>()?;
}
if version >= (1, 3, 15) {
icon.close_on_exit = CloseSetting::try_read_from_io(&mut reader)?;
}
if version >= (2, 0, 7) {
icon.hotkey = reader.read_u16::<LE>()?;
}
icon.flags = read_flags!(&mut reader,
IconFlags::NEVER_UNINSTALL,
if version < (1, 3, 26) => IconFlags::RUN_MINIMIZED,
[IconFlags::CREATE_ONLY_IF_FILE_EXISTS, IconFlags::USE_APP_PATHS],
if ((5, 0, 3)..(6, 3, 0)).contains(&version) => IconFlags::FOLDER_SHORTCUT,
if version >= (5, 4, 2) => IconFlags::EXCLUDE_FROM_SHOW_IN_NEW_INSTALL,
if version >= 5.5 => IconFlags::PREVENT_PINNING,
if version >= 6.1 => IconFlags::HAS_APP_USER_MODEL_TOAST_ACTIVATOR_CLSID
)?;
Ok(icon)
}
#[must_use]
#[inline]
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
#[must_use]
#[inline]
pub fn filename(&self) -> Option<&str> {
self.filename.as_deref()
}
#[must_use]
#[inline]
pub fn parameters(&self) -> Option<&str> {
self.parameters.as_deref()
}
#[must_use]
#[inline]
pub fn working_directory(&self) -> Option<&str> {
self.working_directory.as_deref()
}
#[must_use]
#[inline]
pub fn file(&self) -> Option<&str> {
self.file.as_deref()
}
#[must_use]
#[inline]
pub fn comment(&self) -> Option<&str> {
self.comment.as_deref()
}
#[must_use]
#[inline]
pub fn app_user_model_id(&self) -> Option<&str> {
self.app_user_model_id.as_deref()
}
#[must_use]
#[inline]
pub const fn app_user_model_toast_activator_clsid(&self) -> &str {
self.app_user_model_toast_activator_clsid.as_str()
}
#[must_use]
#[inline]
pub const fn index(&self) -> i32 {
self.index
}
#[must_use]
#[inline]
pub const fn show_command(&self) -> i32 {
self.show_command
}
#[must_use]
#[inline]
pub const fn close_on_exit(&self) -> CloseSetting {
self.close_on_exit
}
#[must_use]
#[inline]
pub const fn hotkey(&self) -> u16 {
self.hotkey
}
#[must_use]
#[inline]
pub const fn flags(&self) -> IconFlags {
self.flags
}
}
impl Default for Icon {
fn default() -> Self {
Self {
name: None,
filename: None,
parameters: None,
working_directory: None,
file: None,
comment: None,
app_user_model_id: None,
app_user_model_toast_activator_clsid: String::new(),
index: 0,
show_command: 1,
close_on_exit: CloseSetting::default(),
hotkey: 0,
flags: IconFlags::default(),
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Immutable, KnownLayout, TryFromBytes)]
#[repr(u8)]
pub enum CloseSetting {
#[default]
NoSetting = 0,
CloseOnExit = 1,
DontCloseOnExit = 2,
}
impl CloseSetting {
pub fn try_read_from_io<R>(mut src: R) -> io::Result<Self>
where
Self: Sized,
R: io::Read,
{
src.read_u8().and_then(|value| {
Self::try_from(value).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
})
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::NoSetting => "No setting",
Self::CloseOnExit => "Close on exit",
Self::DontCloseOnExit => "Dont close on exit",
}
}
}
impl fmt::Display for CloseSetting {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl TryFrom<u8> for CloseSetting {
type Error = ValidityError<u8, Self>;
fn try_from(value: u8) -> Result<Self, Self::Error> {
try_transmute!(value)
}
}
bitflags! {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct IconFlags: u8 {
const NEVER_UNINSTALL = 1;
const CREATE_ONLY_IF_FILE_EXISTS = 1 << 1;
const USE_APP_PATHS = 1 << 2;
const FOLDER_SHORTCUT = 1 << 3;
const EXCLUDE_FROM_SHOW_IN_NEW_INSTALL = 1 << 4;
const PREVENT_PINNING = 1 << 5;
const HAS_APP_USER_MODEL_TOAST_ACTIVATOR_CLSID = 1 << 6;
const RUN_MINIMIZED = 1 << 7;
}
}
impl fmt::Display for IconFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
bitflags::parser::to_writer(self, f)
}
}