doc: add docstring for lowlevel

This commit is contained in:
2026-06-26 13:44:56 +08:00
parent 6a354bd7a7
commit a350a591ad
12 changed files with 307 additions and 55 deletions

View File

@@ -22,7 +22,7 @@ pub const INVALID_INDEX: usize = usize::MAX;
/// However, I don't want to add it as this crate's dependency, /// However, I don't want to add it as this crate's dependency,
/// because I don't use anything within it except this type. /// because I don't use anything within it except this type.
/// So I check Microsoft document, re-define it in there for this crate. /// So I check Microsoft document, re-define it in there for this crate.
/// Reference: https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types /// Reference: <https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types>
pub type HICON = *mut c_void; pub type HICON = *mut c_void;
/// The invalid value of Win32 HICON handle. /// The invalid value of Win32 HICON handle.

View File

@@ -1,3 +1,5 @@
//! The module gives a convenient highlevel wrapper for lowlevel file association operations.
use crate::lowlevel; use crate::lowlevel;
// region: Utilities // region: Utilities

View File

@@ -1,5 +1,5 @@
//! This crate provide utilities fetching and manilupating Windows file association. //! This crate provide utilities fetching and manilupating Windows file association.
//! All code under crate are following Microsoft document: https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper //! All code under crate are following Microsoft document: <https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper>
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
compile_error!("Crate wfassoc is only supported on Windows."); compile_error!("Crate wfassoc is only supported on Windows.");

View File

@@ -1,3 +1,5 @@
//! The module containing lowlevel operation involving file association.
use crate::win32::{concept, regext, utilities}; use crate::win32::{concept, regext, utilities};
use std::fmt::Display; use std::fmt::Display;
use std::str::FromStr; use std::str::FromStr;
@@ -58,6 +60,7 @@ pub enum Scope {
pub struct TryFromViewError {} pub struct TryFromViewError {}
impl TryFromViewError { impl TryFromViewError {
/// Creates a new `TryFromViewError`.
fn new() -> Self { fn new() -> Self {
Self {} Self {}
} }
@@ -106,9 +109,13 @@ impl From<Scope> for View {
/// The enum representing a losse Programmatic Identifiers (ProgId). /// The enum representing a losse Programmatic Identifiers (ProgId).
/// ///
/// In real world, not all software developers are willing to following Microsoft suggestions to use ProgId. /// In real world, not all software developers are willing
/// They use string which do not have any regulation as ProgId. /// to following Microsoft suggestions to use ProgId.
/// They frequently use string which do not have any regulation as ProgId.
/// This enum is designed for handling this scenario. /// This enum is designed for handling this scenario.
///
/// You can utilize [FromStr] trait to build this struct.
/// Standard ProgId and any casual string are both acceptable.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LosseProgId { pub enum LosseProgId {
Plain(String), Plain(String),
@@ -151,6 +158,11 @@ impl From<concept::ProgId> for LosseProgId {
/// In real usage, programmer can use Icon Reference String, /// In real usage, programmer can use Icon Reference String,
/// or a plain string pointing to a icon file as the icon setting value. /// or a plain string pointing to a icon file as the icon setting value.
/// This enum is designed for handling this scenario. /// This enum is designed for handling this scenario.
///
/// You can utilize [FromStr] trait to build this struct.
/// You can pass a standard Icon Reference String or a path to specific icon file.
/// However, if you choose the last one, you must make sure
/// that your path is must be unquoted, but expand string is allowed.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum IconResVariant { pub enum IconResVariant {
Plain(String), Plain(String),
@@ -158,7 +170,12 @@ pub enum IconResVariant {
} }
impl IconResVariant { impl IconResVariant {
/// Extracts the icon resource with the given size kind.
pub fn extract(&self, kind: concept::IconSizeKind) -> Result<concept::IconRc> { pub fn extract(&self, kind: concept::IconSizeKind) -> Result<concept::IconRc> {
// TODO:
// Once we implement quote mechanism inside IconRefStr,
// there is no need to preserve this unquote code in there.
// However the expand string mechanism should be kept.
let rc = match self { let rc = match self {
IconResVariant::Plain(v) => concept::IconRc::with_ico_file(v.as_str(), kind)?, IconResVariant::Plain(v) => concept::IconRc::with_ico_file(v.as_str(), kind)?,
IconResVariant::RefStr(v) => { IconResVariant::RefStr(v) => {
@@ -214,6 +231,9 @@ impl From<concept::IconRefStr> for IconResVariant {
/// In real usage, programmer can use String Reference String, /// In real usage, programmer can use String Reference String,
/// or a plain string as the string setting value. /// or a plain string as the string setting value.
/// This enum is designed for handling this scenario. /// This enum is designed for handling this scenario.
///
/// You can utilize [FromStr] trait to build this struct.
/// You can pass a standard String Reference String or a plain string.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum StrResVariant { pub enum StrResVariant {
Plain(String), Plain(String),
@@ -221,7 +241,12 @@ pub enum StrResVariant {
} }
impl StrResVariant { impl StrResVariant {
/// Resolves this string resource variant into a plain string.
pub fn extract(&self) -> Result<String> { pub fn extract(&self) -> Result<String> {
// TODO:
// Once we implement quote mechanism inside StrRefStr,
// there is no need to preserve this unquote code in there.
// However the expand string mechanism should be kept.
let rv = match self { let rv = match self {
// For plain string, we just simply clone it. // For plain string, we just simply clone it.
StrResVariant::Plain(v) => v.clone(), StrResVariant::Plain(v) => v.clone(),
@@ -276,6 +301,12 @@ impl From<concept::StrRefStr> for StrResVariant {
// region: Shell Verb // region: Shell Verb
/// The struct representing a shell verb pair. /// The struct representing a shell verb pair.
///
/// Shell verb is frequently used in Windows to describe how to use an application.
/// It can be used to define the default behavior of application,
/// or the way to use this application for opening a specific file.
///
/// You can use [ShellVerb::new] with given verb and command line infos to build this struct.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShellVerb { pub struct ShellVerb {
verb: concept::Verb, verb: concept::Verb,
@@ -283,14 +314,17 @@ pub struct ShellVerb {
} }
impl ShellVerb { impl ShellVerb {
/// Creates a new `ShellVerb` from a verb and a command.
pub fn new(verb: concept::Verb, command: concept::CmdLine) -> Self { pub fn new(verb: concept::Verb, command: concept::CmdLine) -> Self {
Self { verb, command } Self { verb, command }
} }
/// Returns the verb of this shell verb pair.
pub fn get_verb(&self) -> &concept::Verb { pub fn get_verb(&self) -> &concept::Verb {
&self.verb &self.verb
} }
/// Returns the command of this shell verb pair.
pub fn get_command(&self) -> &concept::CmdLine { pub fn get_command(&self) -> &concept::CmdLine {
&self.command &self.command
} }
@@ -304,7 +338,7 @@ impl ShellVerb {
// region: Opened Key // region: Opened Key
/// Internal used struct representing the result of opening scope or view. /// Internal used struct representing the result about opening registry key in given scope or view.
#[derive(Debug)] #[derive(Debug)]
struct OpenedKey { struct OpenedKey {
/// The parent key of opened key which must be presented. /// The parent key of opened key which must be presented.
@@ -314,6 +348,7 @@ struct OpenedKey {
} }
impl OpenedKey { impl OpenedKey {
/// Creates a new `OpenedKey` with the given parent key and optional opened key.
fn new(parent_key: RegKey, this_key: Option<RegKey>) -> Self { fn new(parent_key: RegKey, this_key: Option<RegKey>) -> Self {
Self { Self {
parent_key, parent_key,
@@ -326,10 +361,10 @@ impl OpenedKey {
// region: Open Key Territory // region: Open Key Territory
/// The territory of opening key. /// Internal used enum representing the territory of opening registry key.
/// ///
/// Scope and View will finally be converted into this type, /// [Scope] and [View] will finally be converted into this type,
/// and delivered to an uniform function to open key. /// and delivered to an uniform function to open registry key.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
enum OpenKeyTerritory { enum OpenKeyTerritory {
System, System,
@@ -360,7 +395,7 @@ impl From<View> for OpenKeyTerritory {
// region: Open Key Purpose // region: Open Key Purpose
/// The purpose of opening this key. /// The purpose of opening given registry key.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
enum OpenKeyPurpose { enum OpenKeyPurpose {
/// Only read something. /// Only read something.
@@ -370,6 +405,7 @@ enum OpenKeyPurpose {
} }
impl OpenKeyPurpose { impl OpenKeyPurpose {
/// Converts this purpose to the corresponding registry permission flags.
fn to_permission(&self) -> u32 { fn to_permission(&self) -> u32 {
match self { match self {
OpenKeyPurpose::Read => PERM_R, OpenKeyPurpose::Read => PERM_R,
@@ -411,7 +447,16 @@ fn check_privilege(territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Resu
} }
} }
/// Remove quote pair if possible. // TODO:
// This function should be eliminated or moved to another place,
// once we finish the quote mechanism in StrRefStr and IconRefStr.
/// Internal function removing quote pair if possible.
///
/// # Todo
///
/// This function actually is the compromise about the current design of this crate.
/// This function will be optimized in future.
/// ///
/// In some cases, the path part of [concept::IconRefStr] or [concept::StrRefStr] is quoted by quote. /// In some cases, the path part of [concept::IconRefStr] or [concept::StrRefStr] is quoted by quote.
/// This can no be recognized by Win32 functions. /// This can no be recognized by Win32 functions.
@@ -431,6 +476,9 @@ fn strip_quote<'a>(s: &'a str) -> &'a str {
// region: Registry Keys // region: Registry Keys
// YYC MARK:
// Load submodules and re-export them.
mod app_path_key; mod app_path_key;
mod applications_key; mod applications_key;
mod ext_key; mod ext_key;

View File

@@ -5,16 +5,30 @@ use crate::win32::{concept, regext};
use winreg::RegKey; use winreg::RegKey;
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
/// A registry key wrapper for `Software\Microsoft\Windows\CurrentVersion\App Paths\<APP>`.
///
/// This key can only be opened under a Scope (not a View), since
/// "App Paths" only exists under HKCU and HKLM, not under a merged HKCR view.
///
/// This struct provides [Self::is_exist], [Self::ensure] and [Self::delete] to
/// check whether this key is in Registry, make sure this key is presneted in Registry,
/// and delete self from Registry respectively.
///
/// And there are some getter and setter in this struct,
/// before calling them, you must use [Self::ensure] make sure that this key is presented in Registry,
/// otherwise these functions will return errors.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AppPathsKey { pub struct AppPathsKey {
key_name: concept::FileName, key_name: concept::FileName,
} }
impl AppPathsKey { impl AppPathsKey {
/// Creates a new `AppPathsKey` with the given file name.
pub fn new(inner: concept::FileName) -> Self { pub fn new(inner: concept::FileName) -> Self {
Self { key_name: inner } Self { key_name: inner }
} }
/// Returns the inner file name of this application path key.
pub fn inner(&self) -> &concept::FileName { pub fn inner(&self) -> &concept::FileName {
&self.key_name &self.key_name
} }
@@ -23,6 +37,7 @@ impl AppPathsKey {
impl AppPathsKey { impl AppPathsKey {
const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths"; const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
/// Attempts to open the registry key for this application path with the given territory and purpose.
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> { fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
// check privilege // check privilege
check_privilege(territory, purpose)?; check_privilege(territory, purpose)?;
@@ -49,14 +64,18 @@ impl AppPathsKey {
Ok(OpenedKey::new(app_paths, this_app)) Ok(OpenedKey::new(app_paths, this_app))
} }
/// Opens the registry key for read-only access under the given scope.
fn open_scope_for_read(&self, scope: Scope) -> Result<OpenedKey> { fn open_scope_for_read(&self, scope: Scope) -> Result<OpenedKey> {
self.open_key(scope.into(), OpenKeyPurpose::Read) self.open_key(scope.into(), OpenKeyPurpose::Read)
} }
/// Opens the registry key for read-write access under the given scope.
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> { fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
} }
/// Checks whether this application path key exists in the registry under the given scope.
/// Return true if it is presented in registry, otherwise false.
pub fn is_exist(&self, scope: Scope) -> Result<bool> { pub fn is_exist(&self, scope: Scope) -> Result<bool> {
let key = self.open_scope_for_read(scope)?.this_key; let key = self.open_scope_for_read(scope)?.this_key;
Ok(key.is_some()) Ok(key.is_some())
@@ -91,55 +110,63 @@ impl AppPathsKey {
)?) )?)
} }
// YYC MARK:
// Reference: <https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-app-paths-subkey>
// TODO:
// We only support these keys in there because current interface are enough to use.
// We may expand these in future.
/// Opens the registry key for getter reading, returning an error if the key does not exist.
fn open_scope_for_getter(&self, scope: Scope) -> Result<RegKey> { fn open_scope_for_getter(&self, scope: Scope) -> Result<RegKey> {
self.open_scope_for_read(scope)? self.open_scope_for_read(scope)?
.this_key .this_key
.ok_or(Error::InexistantKey) .ok_or(Error::InexistantKey)
} }
/// Opens the registry key for setter writing, returning an error if the key does not exist.
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> { fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
self.open_scope_for_write(scope)? self.open_scope_for_write(scope)?
.this_key .this_key
.ok_or(Error::InexistantKey) .ok_or(Error::InexistantKey)
} }
// YYC MARK:
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-app-paths-subkey
const NAMEOF_DEFAULT: &str = ""; const NAMEOF_DEFAULT: &str = "";
/// Gets the default value of this application path key.
/// ///
/// /// The content of this key should point to the fully qualified path of the application.
/// This field point to the fully qualified path to the application.
pub fn get_default(&self, scope: Scope) -> Result<String> { pub fn get_default(&self, scope: Scope) -> Result<String> {
let key = self.open_scope_for_getter(scope)?; let key = self.open_scope_for_getter(scope)?;
Ok(key.get_value(Self::NAMEOF_DEFAULT)?) Ok(key.get_value(Self::NAMEOF_DEFAULT)?)
} }
/// /// Sets the default value of this application path key.
///
/// This field should be filled with fully qualified path to the application.
pub fn set_default(&mut self, scope: Scope, value: &str) -> Result<()> { pub fn set_default(&mut self, scope: Scope, value: &str) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;
key.set_value(Self::NAMEOF_DEFAULT, &value)?; key.set_value(Self::NAMEOF_DEFAULT, &value)?;
Ok(()) Ok(())
} }
// TODO:
// This key may be REG_SZ or REG_EXPAND_SZ.
// Currently we see them as one type.
// This should be improved in future.
const NAMEOF_PATH: &str = "Path"; const NAMEOF_PATH: &str = "Path";
/// Gets the `Path` value of this application path key.
/// ///
/// /// The content of this key will be added to PATH environment variable,
/// This field point to the added path for PATH environment variable. /// to enable that application can find its dependencies conveniently.
/// Usually it is the path to application directory. /// If it contains multiple pathes, semicolon-separated form is required.
/// Usually this is the path to application directory.
pub fn get_path(&self, scope: Scope) -> Result<String> { pub fn get_path(&self, scope: Scope) -> Result<String> {
let key = self.open_scope_for_getter(scope)?; let key = self.open_scope_for_getter(scope)?;
Ok(key.get_value(Self::NAMEOF_PATH)?) Ok(key.get_value(Self::NAMEOF_PATH)?)
} }
/// /// Sets the `Path` value of this application path key.
///
/// This field should be the added path for PATH environment variable.
/// Usually it is the path to application directory.
pub fn set_path(&mut self, scope: Scope, value: &str) -> Result<()> { pub fn set_path(&mut self, scope: Scope, value: &str) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;
key.set_value(Self::NAMEOF_PATH, &value)?; key.set_value(Self::NAMEOF_PATH, &value)?;

View File

@@ -6,16 +6,27 @@ use crate::win32::{concept, regext};
use winreg::RegKey; use winreg::RegKey;
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
/// A registry key wrapper for `Software\Classes\Applications\<APP>`.
///
/// This struct provides [Self::is_exist], [Self::ensure] and [Self::delete] to
/// check whether this key is in Registry, make sure this key is presneted in Registry,
/// and delete self from Registry respectively.
///
/// And there are some getter and setter in this struct,
/// before calling them, you must use [Self::ensure] make sure that this key is presented in Registry,
/// otherwise these functions will return errors.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ApplicationsKey { pub struct ApplicationsKey {
key_name: concept::FileName, key_name: concept::FileName,
} }
impl ApplicationsKey { impl ApplicationsKey {
/// Creates a new `ApplicationsKey` with the given file name.
pub fn new(inner: concept::FileName) -> Self { pub fn new(inner: concept::FileName) -> Self {
Self { key_name: inner } Self { key_name: inner }
} }
/// Returns the inner file name of this applications key.
pub fn inner(&self) -> &concept::FileName { pub fn inner(&self) -> &concept::FileName {
&self.key_name &self.key_name
} }
@@ -25,6 +36,7 @@ impl ApplicationsKey {
const FULL_APPLICATIONS: &str = "Software\\Classes\\Applications"; const FULL_APPLICATIONS: &str = "Software\\Classes\\Applications";
const PARTIAL_APPLICATIONS: &str = "Applications"; const PARTIAL_APPLICATIONS: &str = "Applications";
/// Attempts to open the registry key for this application with the given territory and purpose.
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> { fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
// check privilege // check privilege
check_privilege(territory, purpose)?; check_privilege(territory, purpose)?;
@@ -55,14 +67,18 @@ impl ApplicationsKey {
Ok(OpenedKey::new(applications, this_app)) Ok(OpenedKey::new(applications, this_app))
} }
/// Opens the registry key for read-only access under the given view.
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> { fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
self.open_key(view.into(), OpenKeyPurpose::Read) self.open_key(view.into(), OpenKeyPurpose::Read)
} }
/// Opens the registry key for read-write access under the given scope.
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> { fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
} }
/// Checks whether this applications key exists in the registry under the given view.
/// Return true if it is presented in registry, otherwise false.
pub fn is_exist(&self, view: View) -> Result<bool> { pub fn is_exist(&self, view: View) -> Result<bool> {
let key = self.open_view_for_read(view)?.this_key; let key = self.open_view_for_read(view)?.this_key;
Ok(key.is_some()) Ok(key.is_some())
@@ -98,24 +114,41 @@ impl ApplicationsKey {
} }
// YYC MARK: // YYC MARK:
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-applications-subkey // Reference: <https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-applications-subkey>
// TODO:
// We only support these keys in there because current interface are enough to use.
// We may expand these in future.
/// Opens the registry key for getter reading, returning an error if the key does not exist.
fn open_view_for_getter(&self, view: View) -> Result<RegKey> { fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
self.open_view_for_read(view)? self.open_view_for_read(view)?
.this_key .this_key
.ok_or(Error::InexistantKey) .ok_or(Error::InexistantKey)
} }
/// Opens the registry key for setter writing, returning an error if the key does not exist.
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> { fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
self.open_scope_for_write(scope)? self.open_scope_for_write(scope)?
.this_key .this_key
.ok_or(Error::InexistantKey) .ok_or(Error::InexistantKey)
} }
// TODO:
// Support mutiple ShellVerb in future.
const NAMEOF_SHELL_VERB_PART1: &str = "shell"; const NAMEOF_SHELL_VERB_PART1: &str = "shell";
const NAMEOF_SHELL_VERB_PART3: &str = "command"; const NAMEOF_SHELL_VERB_PART3: &str = "command";
const NAMEOF_SHELL_VERB_PART4: &str = ""; const NAMEOF_SHELL_VERB_PART4: &str = "";
/// Gets the shell verb registered for this application under the given view.
/// Return `None` if there is no any shell verb.
///
/// # Todo
///
/// Currently we only support single shell verb.
/// So if there is multiple shell verb, this function still return `None`.
/// This issue will be resolved in future.
pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> { pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
@@ -166,6 +199,15 @@ impl ApplicationsKey {
))) )))
} }
/// Sets the shell verb for this application under the given scope.
/// Pass `None` as shell verb to remove the shell verb.
///
/// # Todo
///
/// Currently we only support single shell verb.
/// So if there is multiple shell verb, all of them will be deleted at first,
/// and then write your given shell verb inside it.
/// This issue will be resolved in future.
pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> { pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;
@@ -195,6 +237,10 @@ impl ApplicationsKey {
const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon"; const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon";
const NAMEOF_DEFAULT_ICON_PART2: &str = ""; const NAMEOF_DEFAULT_ICON_PART2: &str = "";
/// Gets the default icon registered for this application under the given view.
/// Return `None` if there is no such set default icon.
///
/// This icon will be used to represent the application instead of the first icon stored in the .exe file.
pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> { pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
// Get default icon subkey // Get default icon subkey
@@ -213,6 +259,8 @@ impl ApplicationsKey {
Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str()))) Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str())))
} }
/// Sets the default icon for this application under the given scope.
/// Pass `None` as icon to remove the default icon.
pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> { pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;
@@ -235,6 +283,10 @@ impl ApplicationsKey {
const NAMEOF_FRIENDLY_APP_NAME: &str = "FriendlyAppName"; const NAMEOF_FRIENDLY_APP_NAME: &str = "FriendlyAppName";
/// Gets the friendly app name registered for this application under the given view.
/// Return `None` if there is no such set friendly app name.
///
/// This name will be used to display for an application instead of just the version information appearing.
pub fn get_friendly_app_name(&self, view: View) -> Result<Option<StrResVariant>> { pub fn get_friendly_app_name(&self, view: View) -> Result<Option<StrResVariant>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
// Get value of it // Get value of it
@@ -243,6 +295,8 @@ impl ApplicationsKey {
Ok(value.map(|v| StrResVariant::from(v.as_str()))) Ok(value.map(|v| StrResVariant::from(v.as_str())))
} }
/// Sets the friendly app name for this application under the given scope.
/// Pass `None` to remove the friendly app name value.
pub fn set_friendly_app_name( pub fn set_friendly_app_name(
&mut self, &mut self,
scope: Scope, scope: Scope,
@@ -266,6 +320,10 @@ impl ApplicationsKey {
const NAMEOF_SUPPORTED_TYPES: &str = "SupportedTypes"; const NAMEOF_SUPPORTED_TYPES: &str = "SupportedTypes";
/// Gets the supported file types registered for this application under the given view.
/// Return `None` if application doesn't set this (because this is suggested but not forced).
///
/// This is a list defining the file types that the application supports.
pub fn get_supported_types(&self, view: View) -> Result<Option<Vec<concept::Ext>>> { pub fn get_supported_types(&self, view: View) -> Result<Option<Vec<concept::Ext>>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
// Get supported types subkey // Get supported types subkey
@@ -285,6 +343,8 @@ impl ApplicationsKey {
Ok(Some(exts)) Ok(Some(exts))
} }
/// Sets the supported file types for this application under the given scope.
/// Pass `None` to remove this list.
pub fn set_supported_types( pub fn set_supported_types(
&mut self, &mut self,
scope: Scope, scope: Scope,
@@ -316,6 +376,7 @@ impl ApplicationsKey {
const NAMEOF_NO_OPEN_WITH: &str = "NoOpenWith"; const NAMEOF_NO_OPEN_WITH: &str = "NoOpenWith";
/// Checks whether the "NoOpenWith" flag is set for this application under the given view.
pub fn get_no_open_with(&self, view: View) -> Result<bool> { pub fn get_no_open_with(&self, view: View) -> Result<bool> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
match regext::try_get_value::<String, _>(&key, Self::NAMEOF_NO_OPEN_WITH)? { match regext::try_get_value::<String, _>(&key, Self::NAMEOF_NO_OPEN_WITH)? {
@@ -324,6 +385,11 @@ impl ApplicationsKey {
} }
} }
/// Sets or clears the "NoOpenWith" flag for this application under the given scope.
///
/// Microsoft document said that this key indicates that no application is specified for opening this file type.
/// But I totally doesn't understand this. However, most of application set this.
/// So let we set it together.
pub fn set_no_open_with(&mut self, scope: Scope, flag: bool) -> Result<()> { pub fn set_no_open_with(&mut self, scope: Scope, flag: bool) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;
if flag { if flag {

View File

@@ -6,16 +6,27 @@ use crate::win32::{concept, regext};
use winreg::RegKey; use winreg::RegKey;
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
/// A registry key wrapper for `Software\Classes\<EXTENSION>`.
///
/// This struct provides [Self::is_exist], [Self::ensure] and [Self::delete] to
/// check whether this key is in Registry, make sure this key is presneted in Registry,
/// and delete self from Registry respectively.
///
/// And there are some getter and setter in this struct,
/// before calling them, you must use [Self::ensure] make sure that this key is presented in Registry,
/// otherwise these functions will return errors.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExtKey { pub struct ExtKey {
ext: concept::Ext, ext: concept::Ext,
} }
impl ExtKey { impl ExtKey {
/// Creates a new `ExtKey` with the given file extension.
pub fn new(inner: concept::Ext) -> Self { pub fn new(inner: concept::Ext) -> Self {
Self { ext: inner } Self { ext: inner }
} }
/// Returns the inner file extension of this extension key.
pub fn inner(&self) -> &concept::Ext { pub fn inner(&self) -> &concept::Ext {
&self.ext &self.ext
} }
@@ -25,6 +36,7 @@ impl ExtKey {
const FULL_CLASSES: &str = "Software\\Classes"; const FULL_CLASSES: &str = "Software\\Classes";
const PARTIAL_CLASSES: &str = ""; const PARTIAL_CLASSES: &str = "";
/// Attempts to open the registry key for this file extension with the given territory and purpose.
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> { fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
// check privilege // check privilege
check_privilege(territory, purpose)?; check_privilege(territory, purpose)?;
@@ -53,14 +65,18 @@ impl ExtKey {
Ok(OpenedKey::new(classes, this_ext)) Ok(OpenedKey::new(classes, this_ext))
} }
/// Opens the registry key for read-only access under the given view.
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> { fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
self.open_key(view.into(), OpenKeyPurpose::Read) self.open_key(view.into(), OpenKeyPurpose::Read)
} }
/// Opens the registry key for read-write access under the given scope.
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> { fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
} }
/// Checks whether this file extension key exists in the registry under the given view.
/// Return true if it is presented in registry, otherwise false.
pub fn is_exist(&self, view: View) -> Result<bool> { pub fn is_exist(&self, view: View) -> Result<bool> {
let key = self.open_view_for_read(view)?.this_key; let key = self.open_view_for_read(view)?.this_key;
Ok(key.is_some()) Ok(key.is_some())
@@ -96,20 +112,21 @@ impl ExtKey {
} }
// YYC MARK: // YYC MARK:
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes // Reference: <https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes>
// And we explicitly do not support "OpenWithList", because it is obsolete in modern Windows.
// TODO: // TODO:
// We do not support "Content Type" and "PerceivedType" // We only support these keys in there because current interface are enough to use.
// because current interface are enough to use,
// and these types has not been made as concept struct in Rust.
// We may expand these in future. // We may expand these in future.
/// Opens the registry key for getter reading, returning an error if the key does not exist.
fn open_view_for_getter(&self, view: View) -> Result<RegKey> { fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
self.open_view_for_read(view)? self.open_view_for_read(view)?
.this_key .this_key
.ok_or(Error::InexistantKey) .ok_or(Error::InexistantKey)
} }
/// Opens the registry key for setter writing, returning an error if the key does not exist.
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> { fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
self.open_scope_for_write(scope)? self.open_scope_for_write(scope)?
.this_key .this_key
@@ -118,6 +135,10 @@ impl ExtKey {
const NAMEOF_DEFAULT: &str = ""; const NAMEOF_DEFAULT: &str = "";
/// Gets the default value of this file extension under the given view.
/// Return `None` if there is no association for this file extension.
///
/// The content of this key is this file extension associated ProgId.
pub fn get_default(&self, view: View) -> Result<Option<LosseProgId>> { pub fn get_default(&self, view: View) -> Result<Option<LosseProgId>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
// Get value of it // Get value of it
@@ -126,6 +147,8 @@ impl ExtKey {
Ok(value.map(|v| LosseProgId::from(v.as_str()))) Ok(value.map(|v| LosseProgId::from(v.as_str())))
} }
/// Sets the default value (ProgId association) for this file extension under the given scope.
/// Pass `None` to remove the association.
pub fn set_default(&mut self, scope: Scope, pid: Option<&LosseProgId>) -> Result<()> { pub fn set_default(&mut self, scope: Scope, pid: Option<&LosseProgId>) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;
@@ -145,6 +168,11 @@ impl ExtKey {
const NAMEOF_OPEN_WITH_PROGIDS: &str = "OpenWithProgIds"; const NAMEOF_OPEN_WITH_PROGIDS: &str = "OpenWithProgIds";
/// Gets the list of ProgIds registered in the "OpenWithProgIds" subkey under the given view.
/// Return `None` is there is no this subkey.
///
/// This subkey contains a list of alternate ProgId for this file type.
/// In Windows Explorer, these ProgId should appear in the Open with menu in right click menu of this file extension.
pub fn get_open_with_progids(&self, view: View) -> Result<Option<Vec<LosseProgId>>> { pub fn get_open_with_progids(&self, view: View) -> Result<Option<Vec<LosseProgId>>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
// Get OpenWithProgIds subkey // Get OpenWithProgIds subkey
@@ -165,7 +193,7 @@ impl ExtKey {
Ok(Some(progids)) Ok(Some(progids))
} }
/// /// Checks whether the given ProgId is in the "OpenWithProgIds" subkey under the given view.
/// ///
/// If there is no "OpenWithProgIds" subkey, this function return false. /// If there is no "OpenWithProgIds" subkey, this function return false.
pub fn is_in_open_with_progids(&self, view: View, pid: &LosseProgId) -> Result<bool> { pub fn is_in_open_with_progids(&self, view: View, pid: &LosseProgId) -> Result<bool> {
@@ -185,7 +213,7 @@ impl ExtKey {
.is_some()) .is_some())
} }
/// /// Adds the given ProgId into the "OpenWithProgIds" subkey under the given scope.
/// ///
/// If there is no "OpenWithProgIds" subkey, this function will create it first, /// If there is no "OpenWithProgIds" subkey, this function will create it first,
/// then add your given ProgId into it. /// then add your given ProgId into it.
@@ -199,9 +227,9 @@ impl ExtKey {
Ok(()) Ok(())
} }
/// Removes the given ProgId from the "OpenWithProgIds" subkey under the given scope.
/// ///
/// /// If there is no "OpenWithProgIds" subkey, this function does nothing.
/// If there is no "OpenWithProgIds" subkey, this function do nothing.
pub fn remove_from_open_with_progids(&mut self, scope: Scope, pid: &LosseProgId) -> Result<()> { pub fn remove_from_open_with_progids(&mut self, scope: Scope, pid: &LosseProgId) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;
// Try get subkey // Try get subkey

View File

@@ -6,16 +6,27 @@ use crate::win32::regext;
use winreg::RegKey; use winreg::RegKey;
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
/// A registry key wrapper for `Software\Classes\<PROGID>`.
///
/// This struct provides [Self::is_exist], [Self::ensure] and [Self::delete] to
/// check whether this key is in Registry, make sure this key is presneted in Registry,
/// and delete self from Registry respectively.
///
/// And there are some getter and setter in this struct,
/// before calling them, you must use [Self::ensure] make sure that this key is presented in Registry,
/// otherwise these functions will return errors.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProgIdKey { pub struct ProgIdKey {
progid: LosseProgId, progid: LosseProgId,
} }
impl ProgIdKey { impl ProgIdKey {
/// Creates a new `ProgIdKey` with the given ProgId.
pub fn new(inner: LosseProgId) -> Self { pub fn new(inner: LosseProgId) -> Self {
Self { progid: inner } Self { progid: inner }
} }
/// Returns the inner ProgId of this ProgId key.
pub fn inner(&self) -> &LosseProgId { pub fn inner(&self) -> &LosseProgId {
&self.progid &self.progid
} }
@@ -25,6 +36,7 @@ impl ProgIdKey {
const FULL_CLASSES: &str = "Software\\Classes"; const FULL_CLASSES: &str = "Software\\Classes";
const PARTIAL_CLASSES: &str = ""; const PARTIAL_CLASSES: &str = "";
/// Attempts to open the registry key for this ProgId with the given territory and purpose.
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> { fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
// check privilege // check privilege
check_privilege(territory, purpose)?; check_privilege(territory, purpose)?;
@@ -53,14 +65,18 @@ impl ProgIdKey {
Ok(OpenedKey::new(classes, this_progid)) Ok(OpenedKey::new(classes, this_progid))
} }
/// Opens the registry key for read-only access under the given view.
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> { fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
self.open_key(view.into(), OpenKeyPurpose::Read) self.open_key(view.into(), OpenKeyPurpose::Read)
} }
/// Opens the registry key for read-write access under the given scope.
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> { fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
} }
/// Checks whether this ProgId key exists in the registry under the given view.
/// Return true if it is presented in registry, otherwise false.
pub fn is_exist(&self, view: View) -> Result<bool> { pub fn is_exist(&self, view: View) -> Result<bool> {
let key = self.open_view_for_read(view)?.this_key; let key = self.open_view_for_read(view)?.this_key;
Ok(key.is_some()) Ok(key.is_some())
@@ -96,19 +112,20 @@ impl ProgIdKey {
} }
// YYC MARK: // YYC MARK:
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations // Reference: <https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations>
// TODO: // TODO:
// Currently we only support (Default), FriendlyTypeName and DefaultIcon // We only support these keys in there because current interface are enough to use.
// to just cover the basic usage.
// We may expand these in future. // We may expand these in future.
/// Opens the registry key for getter reading, returning an error if the key does not exist.
fn open_view_for_getter(&self, view: View) -> Result<RegKey> { fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
self.open_view_for_read(view)? self.open_view_for_read(view)?
.this_key .this_key
.ok_or(Error::InexistantKey) .ok_or(Error::InexistantKey)
} }
/// Opens the registry key for setter writing, returning an error if the key does not exist.
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> { fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
self.open_scope_for_write(scope)? self.open_scope_for_write(scope)?
.this_key .this_key
@@ -117,6 +134,11 @@ impl ProgIdKey {
const NAMEOF_DEFAULT: &str = ""; const NAMEOF_DEFAULT: &str = "";
/// Gets the default value of this ProgId under the given view.
///
/// The content of this key is the legacy way to introduce the friendly name of application.
/// If you are fetching friendly name, please choose "FriendlyTypeName" at first, then fallback to this.
/// If you are setting friendly name, please set this together.
pub fn get_default(&self, view: View) -> Result<Option<StrResVariant>> { pub fn get_default(&self, view: View) -> Result<Option<StrResVariant>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
// Get value of it // Get value of it
@@ -125,9 +147,7 @@ impl ProgIdKey {
Ok(value.map(|v| StrResVariant::from(v.as_str()))) Ok(value.map(|v| StrResVariant::from(v.as_str())))
} }
/// /// Sets the default value (friendly name) for this ProgId under the given scope.
///
/// The legacy way to set friendly name for this ProgId.
pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<()> { pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;
@@ -145,10 +165,21 @@ impl ProgIdKey {
Ok(()) Ok(())
} }
// TODO:
// Support mutiple ShellVerb in future.
const NAMEOF_SHELL_VERB_PART1: &str = "shell"; const NAMEOF_SHELL_VERB_PART1: &str = "shell";
const NAMEOF_SHELL_VERB_PART3: &str = "command"; const NAMEOF_SHELL_VERB_PART3: &str = "command";
const NAMEOF_SHELL_VERB_PART4: &str = ""; const NAMEOF_SHELL_VERB_PART4: &str = "";
/// Gets the shell verb registered for this ProgId under the given view.
/// Return `None` if there is no any shell verb.
///
/// # Todo
///
/// Currently we only support single shell verb.
/// So if there is multiple shell verb, this function still return `None`.
/// This issue will be resolved in future.
pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> { pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
@@ -199,6 +230,15 @@ impl ProgIdKey {
))) )))
} }
/// Sets the shell verb for this ProgId under the given scope.
/// Pass `None` as shell verb to remove the shell verb.
///
/// # Todo
///
/// Currently we only support single shell verb.
/// So if there is multiple shell verb, all of them will be deleted at first,
/// and then write your given shell verb inside it.
/// This issue will be resolved in future.
pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> { pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;
@@ -225,8 +265,17 @@ impl ProgIdKey {
Ok(()) Ok(())
} }
// TODO:
// This key may be REG_SZ or REG_EXPAND_SZ.
// Currently we see them as one type.
// This should be improved in future.
const NAMEOF_FRIENDLY_TYPE_NAME: &str = "FriendlyTypeName"; const NAMEOF_FRIENDLY_TYPE_NAME: &str = "FriendlyTypeName";
/// Gets the friendly type name registered for this ProgId under the given view.
/// Return `None` if there is no such key.
///
/// The content of this key is the file type name which is suitable to display to the user.
pub fn get_friendly_type_name(&self, view: View) -> Result<Option<StrResVariant>> { pub fn get_friendly_type_name(&self, view: View) -> Result<Option<StrResVariant>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
// Get value of it // Get value of it
@@ -235,9 +284,8 @@ impl ProgIdKey {
Ok(value.map(|v| StrResVariant::from(v.as_str()))) Ok(value.map(|v| StrResVariant::from(v.as_str())))
} }
/// /// Sets the friendly type name for this ProgId under the given scope.
/// /// Pass `None` to remove friendly type name.
/// Set this entry to a friendly name for the ProgID.
pub fn set_friendly_type_name( pub fn set_friendly_type_name(
&mut self, &mut self,
scope: Scope, scope: Scope,
@@ -259,9 +307,18 @@ impl ProgIdKey {
Ok(()) Ok(())
} }
// TODO:
// This key may be REG_SZ or REG_EXPAND_SZ.
// Currently we see them as one type.
// This should be improved in future.
const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon"; const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon";
const NAMEOF_DEFAULT_ICON_PART2: &str = ""; const NAMEOF_DEFAULT_ICON_PART2: &str = "";
/// Gets the default icon registered for this ProgId under the given view.
/// Return `None` if there is no default icon.
///
/// The content of this key will be displayed for file types associated with this ProgId.
pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> { pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> {
let key = self.open_view_for_getter(view)?; let key = self.open_view_for_getter(view)?;
// Get default icon subkey // Get default icon subkey
@@ -280,6 +337,11 @@ impl ProgIdKey {
Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str()))) Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str())))
} }
/// Sets the default icon for this ProgId under the given scope.
/// Pass `None` to remove the default icon subkey.
///
/// Although this function give the ability that do not set this key,
/// however, I strongly suggest that set this to let file associated with this ProgId have an icon.
pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> { pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> {
let key = self.open_scope_for_setter(scope)?; let key = self.open_scope_for_setter(scope)?;

View File

@@ -5,9 +5,9 @@ use std::iter::FusedIterator;
use std::path::Path; use std::path::Path;
use thiserror::Error as TeError; use thiserror::Error as TeError;
// region: OS String Related // region: OS String Casting
/// The error occurs when casting `OsStr` into `str`. /// The error occurs when casting [OsStr] into [str].
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
#[error("fail to cast OS string into Rust string")] #[error("fail to cast OS string into Rust string")]
pub struct CastOsStrError {} pub struct CastOsStrError {}
@@ -32,6 +32,8 @@ pub fn osstr_to_str(osstr: &OsStr) -> Result<&str, CastOsStrError> {
// region: Capitalize First ASCII Letter // region: Capitalize First ASCII Letter
/// The struct transforming accept an iterator and perform as an iterator
/// whose transform the first character of accepted iterator into capital form (ASCII char only).
struct CapitalizeFirstAscii<T> struct CapitalizeFirstAscii<T>
where where
T: Iterator<Item = char>, T: Iterator<Item = char>,
@@ -72,6 +74,7 @@ where
impl<T> FusedIterator for CapitalizeFirstAscii<T> where T: Iterator<Item = char> + FusedIterator {} impl<T> FusedIterator for CapitalizeFirstAscii<T> where T: Iterator<Item = char> + FusedIterator {}
/// Accept an string slice, transform its first char (ASCII only) as capital form and output.
pub fn capitalize_first_ascii(s: &str) -> String { pub fn capitalize_first_ascii(s: &str) -> String {
CapitalizeFirstAscii::new(s.chars()).collect() CapitalizeFirstAscii::new(s.chars()).collect()
} }

View File

@@ -1,3 +1,6 @@
//! The module containing Win32 stuff related to file association operation,
//! including Win32 concept, Registry operation helper and etc.
pub mod concept; pub mod concept;
pub mod utilities; pub mod utilities;
pub mod regext; pub mod regext;

View File

@@ -154,8 +154,8 @@ impl BadProgIdPartError {
/// outputs legal ProgId in string form. /// outputs legal ProgId in string form.
/// ///
/// Reference: /// Reference:
/// - https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids /// - <https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids>
/// - https://learn.microsoft.com/en-us/windows/win32/com/-progid--key /// - <https://learn.microsoft.com/en-us/windows/win32/com/-progid--key>
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProgId { pub struct ProgId {
/// The vendor part of ProgId. /// The vendor part of ProgId.
@@ -168,6 +168,9 @@ pub struct ProgId {
impl ProgId { impl ProgId {
/// Create a new ProgId with given parts. /// Create a new ProgId with given parts.
///
/// Empty string or string with embedded dot is not allowed
/// for vendor and component parts.
pub fn new( pub fn new(
vendor: &str, vendor: &str,
component: &str, component: &str,
@@ -279,7 +282,7 @@ pub struct Clsid {
impl Clsid { impl Clsid {
/// Create new CLSID from underlying UUID. /// Create new CLSID from underlying UUID.
fn new(uuid: &Uuid) -> Self { pub fn new(uuid: &Uuid) -> Self {
Self { inner: *uuid } Self { inner: *uuid }
} }
@@ -897,6 +900,7 @@ impl FromStr for ExpandString {
// region: File Name // region: File Name
/// The error occurs when constructing FileName with bad file name.
pub type BadFileNameError = ParseFileNameError; pub type BadFileNameError = ParseFileNameError;
/// The struct representing a legal Windows file name. /// The struct representing a legal Windows file name.
@@ -928,7 +932,7 @@ impl FileName {
} }
} }
/// The error occurs when constructing FileName with bad file name. /// The error occurs when parsing FileName with bad file name.
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
#[error("given file name is illegal in Windows")] #[error("given file name is illegal in Windows")]
pub enum ParseFileNameError { pub enum ParseFileNameError {
@@ -975,6 +979,7 @@ impl FromStr for FileName {
// region: Verb // region: Verb
/// The error occurs when constructing Verb with bad verb name.
pub type BadVerbError = ParseVerbError; pub type BadVerbError = ParseVerbError;
/// The struct representing a verb when manipulating file /// The struct representing a verb when manipulating file
@@ -1024,7 +1029,7 @@ impl Verb {
} }
} }
/// The error occurs when constructing Verb with bad verb name. /// The error occurs when parsing Verb with bad verb name.
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
#[error("given verb \"{inner}\" is illegal")] #[error("given verb \"{inner}\" is illegal")]
pub struct ParseVerbError { pub struct ParseVerbError {
@@ -1071,11 +1076,21 @@ impl FromStr for Verb {
/// The error occurs when constructing CmdLine with bad arguments. /// The error occurs when constructing CmdLine with bad arguments.
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
#[error("given file extension body \"{inner}\" is invalid")] #[error("given command line argument \"{inner}\" is invalid")]
pub struct BadCmdLineError { pub struct BadCmdLineError {
inner: String, inner: String,
} }
impl BadCmdLineError {
// TODO: Remove this dead_code attribute once we need use this error type.
#[allow(dead_code)]
fn new(inner: &str) -> Self {
Self {
inner: inner.to_string(),
}
}
}
/// The struct representing a Windows command line. /// The struct representing a Windows command line.
/// ///
/// If you have a complete Windows command line expressed in string form, /// If you have a complete Windows command line expressed in string form,
@@ -1145,7 +1160,7 @@ impl CmdLine {
} }
} }
/// The error occurs when constructing CmdLine with bad syntax. /// The error occurs when parsing CmdLine with bad syntax.
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
#[error("given command line \"{inner}\" is invalid")] #[error("given command line \"{inner}\" is invalid")]
pub struct ParseCmdLineError { pub struct ParseCmdLineError {
@@ -1153,9 +1168,7 @@ pub struct ParseCmdLineError {
} }
impl ParseCmdLineError { impl ParseCmdLineError {
// TODO: Remove this dead_code attribute once we need use this error type. // TODO: Remove this dead_code attribute once we need use this error type.
#[allow(dead_code)] #[allow(dead_code)]
fn new(inner: &str) -> Self { fn new(inner: &str) -> Self {
Self { Self {

View File

@@ -7,7 +7,7 @@
/// It usually means that checking whether current process is running as Administrator. /// It usually means that checking whether current process is running as Administrator.
/// Return true if it is, otherwise false. /// Return true if it is, otherwise false.
/// ///
/// Reference: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership /// Reference: <https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership>
/// ///
/// # Panics /// # Panics
/// ///