From a350a591ad8da01e9fb9a6ef574df605493dd313 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Fri, 26 Jun 2026 13:44:56 +0800 Subject: [PATCH] doc: add docstring for lowlevel --- wfassoc-cdylib/src/ffi_types.rs | 2 +- wfassoc/src/highlevel.rs | 2 + wfassoc/src/lib.rs | 2 +- wfassoc/src/lowlevel.rs | 64 ++++++++++++++++--- wfassoc/src/lowlevel/app_path_key.rs | 57 ++++++++++++----- wfassoc/src/lowlevel/applications_key.rs | 68 +++++++++++++++++++- wfassoc/src/lowlevel/ext_key.rs | 44 ++++++++++--- wfassoc/src/lowlevel/progid_key.rs | 80 +++++++++++++++++++++--- wfassoc/src/utilities.rs | 7 ++- wfassoc/src/win32.rs | 3 + wfassoc/src/win32/concept.rs | 31 ++++++--- wfassoc/src/win32/utilities.rs | 2 +- 12 files changed, 307 insertions(+), 55 deletions(-) diff --git a/wfassoc-cdylib/src/ffi_types.rs b/wfassoc-cdylib/src/ffi_types.rs index a4b34f5..aa5e61e 100644 --- a/wfassoc-cdylib/src/ffi_types.rs +++ b/wfassoc-cdylib/src/ffi_types.rs @@ -22,7 +22,7 @@ pub const INVALID_INDEX: usize = usize::MAX; /// However, I don't want to add it as this crate's dependency, /// because I don't use anything within it except this type. /// 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: pub type HICON = *mut c_void; /// The invalid value of Win32 HICON handle. diff --git a/wfassoc/src/highlevel.rs b/wfassoc/src/highlevel.rs index f96ea4d..4d13001 100644 --- a/wfassoc/src/highlevel.rs +++ b/wfassoc/src/highlevel.rs @@ -1,3 +1,5 @@ +//! The module gives a convenient highlevel wrapper for lowlevel file association operations. + use crate::lowlevel; // region: Utilities diff --git a/wfassoc/src/lib.rs b/wfassoc/src/lib.rs index 5a7ef46..d60372b 100644 --- a/wfassoc/src/lib.rs +++ b/wfassoc/src/lib.rs @@ -1,5 +1,5 @@ //! 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: #[cfg(not(target_os = "windows"))] compile_error!("Crate wfassoc is only supported on Windows."); diff --git a/wfassoc/src/lowlevel.rs b/wfassoc/src/lowlevel.rs index 02db357..60b9359 100644 --- a/wfassoc/src/lowlevel.rs +++ b/wfassoc/src/lowlevel.rs @@ -1,3 +1,5 @@ +//! The module containing lowlevel operation involving file association. + use crate::win32::{concept, regext, utilities}; use std::fmt::Display; use std::str::FromStr; @@ -58,6 +60,7 @@ pub enum Scope { pub struct TryFromViewError {} impl TryFromViewError { + /// Creates a new `TryFromViewError`. fn new() -> Self { Self {} } @@ -106,9 +109,13 @@ impl From for View { /// The enum representing a losse Programmatic Identifiers (ProgId). /// -/// In real world, not all software developers are willing to following Microsoft suggestions to use ProgId. -/// They use string which do not have any regulation as ProgId. +/// In real world, not all software developers are willing +/// 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. +/// +/// 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)] pub enum LosseProgId { Plain(String), @@ -151,6 +158,11 @@ impl From for LosseProgId { /// In real usage, programmer can use Icon Reference String, /// or a plain string pointing to a icon file as the icon setting value. /// 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)] pub enum IconResVariant { Plain(String), @@ -158,7 +170,12 @@ pub enum IconResVariant { } impl IconResVariant { + /// Extracts the icon resource with the given size kind. pub fn extract(&self, kind: concept::IconSizeKind) -> Result { + // 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 { IconResVariant::Plain(v) => concept::IconRc::with_ico_file(v.as_str(), kind)?, IconResVariant::RefStr(v) => { @@ -214,6 +231,9 @@ impl From for IconResVariant { /// In real usage, programmer can use String Reference String, /// or a plain string as the string setting value. /// 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)] pub enum StrResVariant { Plain(String), @@ -221,7 +241,12 @@ pub enum StrResVariant { } impl StrResVariant { + /// Resolves this string resource variant into a plain string. pub fn extract(&self) -> Result { + // 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 { // For plain string, we just simply clone it. StrResVariant::Plain(v) => v.clone(), @@ -276,6 +301,12 @@ impl From for StrResVariant { // region: Shell Verb /// 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)] pub struct ShellVerb { verb: concept::Verb, @@ -283,14 +314,17 @@ pub struct ShellVerb { } impl ShellVerb { + /// Creates a new `ShellVerb` from a verb and a command. pub fn new(verb: concept::Verb, command: concept::CmdLine) -> Self { Self { verb, command } } + /// Returns the verb of this shell verb pair. pub fn get_verb(&self) -> &concept::Verb { &self.verb } + /// Returns the command of this shell verb pair. pub fn get_command(&self) -> &concept::CmdLine { &self.command } @@ -304,7 +338,7 @@ impl ShellVerb { // 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)] struct OpenedKey { /// The parent key of opened key which must be presented. @@ -314,6 +348,7 @@ struct OpenedKey { } impl OpenedKey { + /// Creates a new `OpenedKey` with the given parent key and optional opened key. fn new(parent_key: RegKey, this_key: Option) -> Self { Self { parent_key, @@ -326,10 +361,10 @@ impl OpenedKey { // 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, -/// and delivered to an uniform function to open key. +/// [Scope] and [View] will finally be converted into this type, +/// and delivered to an uniform function to open registry key. #[derive(Debug, Copy, Clone)] enum OpenKeyTerritory { System, @@ -360,7 +395,7 @@ impl From for OpenKeyTerritory { // region: Open Key Purpose -/// The purpose of opening this key. +/// The purpose of opening given registry key. #[derive(Debug, Copy, Clone)] enum OpenKeyPurpose { /// Only read something. @@ -370,6 +405,7 @@ enum OpenKeyPurpose { } impl OpenKeyPurpose { + /// Converts this purpose to the corresponding registry permission flags. fn to_permission(&self) -> u32 { match self { OpenKeyPurpose::Read => PERM_R, @@ -411,8 +447,17 @@ 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. /// This can no be recognized by Win32 functions. /// So in this case, we should remove this quote pair. @@ -431,6 +476,9 @@ fn strip_quote<'a>(s: &'a str) -> &'a str { // region: Registry Keys +// YYC MARK: +// Load submodules and re-export them. + mod app_path_key; mod applications_key; mod ext_key; diff --git a/wfassoc/src/lowlevel/app_path_key.rs b/wfassoc/src/lowlevel/app_path_key.rs index 76556d1..1c889c2 100644 --- a/wfassoc/src/lowlevel/app_path_key.rs +++ b/wfassoc/src/lowlevel/app_path_key.rs @@ -5,16 +5,30 @@ use crate::win32::{concept, regext}; use winreg::RegKey; use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; +/// A registry key wrapper for `Software\Microsoft\Windows\CurrentVersion\App Paths\`. +/// +/// 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)] pub struct AppPathsKey { key_name: concept::FileName, } impl AppPathsKey { + /// Creates a new `AppPathsKey` with the given file name. pub fn new(inner: concept::FileName) -> Self { Self { key_name: inner } } + /// Returns the inner file name of this application path key. pub fn inner(&self) -> &concept::FileName { &self.key_name } @@ -23,6 +37,7 @@ impl AppPathsKey { impl AppPathsKey { 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 { // check privilege check_privilege(territory, purpose)?; @@ -49,14 +64,18 @@ impl AppPathsKey { 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 { 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 { 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 { let key = self.open_scope_for_read(scope)?.this_key; Ok(key.is_some()) @@ -91,55 +110,63 @@ impl AppPathsKey { )?) } + // YYC MARK: + // Reference: + + // 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 { self.open_scope_for_read(scope)? .this_key .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 { self.open_scope_for_write(scope)? .this_key .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 = ""; + /// Gets the default value of this application path key. /// - /// - /// This field point to the fully qualified path to the application. + /// The content of this key should point to the fully qualified path of the application. pub fn get_default(&self, scope: Scope) -> Result { let key = self.open_scope_for_getter(scope)?; Ok(key.get_value(Self::NAMEOF_DEFAULT)?) } - /// - /// - /// This field should be filled with fully qualified path to the application. + /// Sets the default value of this application path key. pub fn set_default(&mut self, scope: Scope, value: &str) -> Result<()> { let key = self.open_scope_for_setter(scope)?; key.set_value(Self::NAMEOF_DEFAULT, &value)?; 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"; + /// Gets the `Path` value of this application path key. /// - /// - /// This field point to the added path for PATH environment variable. - /// Usually it is the path to application directory. + /// The content of this key will be added to PATH environment variable, + /// to enable that application can find its dependencies conveniently. + /// 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 { let key = self.open_scope_for_getter(scope)?; Ok(key.get_value(Self::NAMEOF_PATH)?) } - /// - /// - /// This field should be the added path for PATH environment variable. - /// Usually it is the path to application directory. + /// Sets the `Path` value of this application path key. pub fn set_path(&mut self, scope: Scope, value: &str) -> Result<()> { let key = self.open_scope_for_setter(scope)?; key.set_value(Self::NAMEOF_PATH, &value)?; diff --git a/wfassoc/src/lowlevel/applications_key.rs b/wfassoc/src/lowlevel/applications_key.rs index a5263c4..715070f 100644 --- a/wfassoc/src/lowlevel/applications_key.rs +++ b/wfassoc/src/lowlevel/applications_key.rs @@ -6,16 +6,27 @@ use crate::win32::{concept, regext}; use winreg::RegKey; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; +/// A registry key wrapper for `Software\Classes\Applications\`. +/// +/// 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)] pub struct ApplicationsKey { key_name: concept::FileName, } impl ApplicationsKey { + /// Creates a new `ApplicationsKey` with the given file name. pub fn new(inner: concept::FileName) -> Self { Self { key_name: inner } } + /// Returns the inner file name of this applications key. pub fn inner(&self) -> &concept::FileName { &self.key_name } @@ -25,6 +36,7 @@ impl ApplicationsKey { const FULL_APPLICATIONS: &str = "Software\\Classes\\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 { // check privilege check_privilege(territory, purpose)?; @@ -55,14 +67,18 @@ impl ApplicationsKey { 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 { 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 { 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 { let key = self.open_view_for_read(view)?.this_key; Ok(key.is_some()) @@ -98,24 +114,41 @@ impl ApplicationsKey { } // YYC MARK: - // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-applications-subkey + // Reference: + // 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 { self.open_view_for_read(view)? .this_key .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 { self.open_scope_for_write(scope)? .this_key .ok_or(Error::InexistantKey) } + // TODO: + // Support mutiple ShellVerb in future. + const NAMEOF_SHELL_VERB_PART1: &str = "shell"; const NAMEOF_SHELL_VERB_PART3: &str = "command"; 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> { 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<()> { 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_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> { let key = self.open_view_for_getter(view)?; // Get default icon subkey @@ -213,6 +259,8 @@ impl ApplicationsKey { 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<()> { let key = self.open_scope_for_setter(scope)?; @@ -235,6 +283,10 @@ impl ApplicationsKey { 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> { let key = self.open_view_for_getter(view)?; // Get value of it @@ -243,6 +295,8 @@ impl ApplicationsKey { 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( &mut self, scope: Scope, @@ -266,6 +320,10 @@ impl ApplicationsKey { 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>> { let key = self.open_view_for_getter(view)?; // Get supported types subkey @@ -285,6 +343,8 @@ impl ApplicationsKey { 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( &mut self, scope: Scope, @@ -316,6 +376,7 @@ impl ApplicationsKey { 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 { let key = self.open_view_for_getter(view)?; match regext::try_get_value::(&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<()> { let key = self.open_scope_for_setter(scope)?; if flag { diff --git a/wfassoc/src/lowlevel/ext_key.rs b/wfassoc/src/lowlevel/ext_key.rs index b2cda8a..c94f817 100644 --- a/wfassoc/src/lowlevel/ext_key.rs +++ b/wfassoc/src/lowlevel/ext_key.rs @@ -6,16 +6,27 @@ use crate::win32::{concept, regext}; use winreg::RegKey; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; +/// A registry key wrapper for `Software\Classes\`. +/// +/// 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)] pub struct ExtKey { ext: concept::Ext, } impl ExtKey { + /// Creates a new `ExtKey` with the given file extension. pub fn new(inner: concept::Ext) -> Self { Self { ext: inner } } + /// Returns the inner file extension of this extension key. pub fn inner(&self) -> &concept::Ext { &self.ext } @@ -25,6 +36,7 @@ impl ExtKey { const FULL_CLASSES: &str = "Software\\Classes"; 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 { // check privilege check_privilege(territory, purpose)?; @@ -53,14 +65,18 @@ impl ExtKey { 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 { 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 { 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 { let key = self.open_view_for_read(view)?.this_key; Ok(key.is_some()) @@ -96,20 +112,21 @@ impl ExtKey { } // YYC MARK: - // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes + // Reference: + // And we explicitly do not support "OpenWithList", because it is obsolete in modern Windows. // TODO: - // We do not support "Content Type" and "PerceivedType" - // because current interface are enough to use, - // and these types has not been made as concept struct in Rust. + // 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 { self.open_view_for_read(view)? .this_key .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 { self.open_scope_for_write(scope)? .this_key @@ -118,6 +135,10 @@ impl ExtKey { 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> { let key = self.open_view_for_getter(view)?; // Get value of it @@ -126,6 +147,8 @@ impl ExtKey { 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<()> { let key = self.open_scope_for_setter(scope)?; @@ -145,6 +168,11 @@ impl ExtKey { 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>> { let key = self.open_view_for_getter(view)?; // Get OpenWithProgIds subkey @@ -165,7 +193,7 @@ impl ExtKey { 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. pub fn is_in_open_with_progids(&self, view: View, pid: &LosseProgId) -> Result { @@ -185,7 +213,7 @@ impl ExtKey { .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, /// then add your given ProgId into it. @@ -199,9 +227,9 @@ impl ExtKey { Ok(()) } + /// Removes the given ProgId from the "OpenWithProgIds" subkey under the given scope. /// - /// - /// If there is no "OpenWithProgIds" subkey, this function do nothing. + /// If there is no "OpenWithProgIds" subkey, this function does nothing. pub fn remove_from_open_with_progids(&mut self, scope: Scope, pid: &LosseProgId) -> Result<()> { let key = self.open_scope_for_setter(scope)?; // Try get subkey diff --git a/wfassoc/src/lowlevel/progid_key.rs b/wfassoc/src/lowlevel/progid_key.rs index 1a7be7b..4ba700a 100644 --- a/wfassoc/src/lowlevel/progid_key.rs +++ b/wfassoc/src/lowlevel/progid_key.rs @@ -6,16 +6,27 @@ use crate::win32::regext; use winreg::RegKey; use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; +/// A registry key wrapper for `Software\Classes\`. +/// +/// 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)] pub struct ProgIdKey { progid: LosseProgId, } impl ProgIdKey { + /// Creates a new `ProgIdKey` with the given ProgId. pub fn new(inner: LosseProgId) -> Self { Self { progid: inner } } + /// Returns the inner ProgId of this ProgId key. pub fn inner(&self) -> &LosseProgId { &self.progid } @@ -25,6 +36,7 @@ impl ProgIdKey { const FULL_CLASSES: &str = "Software\\Classes"; 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 { // check privilege check_privilege(territory, purpose)?; @@ -53,14 +65,18 @@ impl ProgIdKey { 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 { 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 { 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 { let key = self.open_view_for_read(view)?.this_key; Ok(key.is_some()) @@ -96,19 +112,20 @@ impl ProgIdKey { } // YYC MARK: - // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations + // Reference: // TODO: - // Currently we only support (Default), FriendlyTypeName and DefaultIcon - // to just cover the basic usage. + // 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 { self.open_view_for_read(view)? .this_key .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 { self.open_scope_for_write(scope)? .this_key @@ -117,6 +134,11 @@ impl ProgIdKey { 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> { let key = self.open_view_for_getter(view)?; // Get value of it @@ -125,9 +147,7 @@ impl ProgIdKey { Ok(value.map(|v| StrResVariant::from(v.as_str()))) } - /// - /// - /// The legacy way to set friendly name for this ProgId. + /// Sets the default value (friendly name) for this ProgId under the given scope. pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<()> { let key = self.open_scope_for_setter(scope)?; @@ -145,10 +165,21 @@ impl ProgIdKey { Ok(()) } + // TODO: + // Support mutiple ShellVerb in future. + const NAMEOF_SHELL_VERB_PART1: &str = "shell"; const NAMEOF_SHELL_VERB_PART3: &str = "command"; 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> { 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<()> { let key = self.open_scope_for_setter(scope)?; @@ -225,8 +265,17 @@ impl ProgIdKey { 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"; + /// 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> { let key = self.open_view_for_getter(view)?; // Get value of it @@ -235,9 +284,8 @@ impl ProgIdKey { Ok(value.map(|v| StrResVariant::from(v.as_str()))) } - /// - /// - /// Set this entry to a friendly name for the ProgID. + /// Sets the friendly type name for this ProgId under the given scope. + /// Pass `None` to remove friendly type name. pub fn set_friendly_type_name( &mut self, scope: Scope, @@ -259,9 +307,18 @@ impl ProgIdKey { 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_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> { let key = self.open_view_for_getter(view)?; // Get default icon subkey @@ -280,6 +337,11 @@ impl ProgIdKey { 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<()> { let key = self.open_scope_for_setter(scope)?; diff --git a/wfassoc/src/utilities.rs b/wfassoc/src/utilities.rs index 356021c..9e408a6 100644 --- a/wfassoc/src/utilities.rs +++ b/wfassoc/src/utilities.rs @@ -5,9 +5,9 @@ use std::iter::FusedIterator; use std::path::Path; 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)] #[error("fail to cast OS string into Rust string")] pub struct CastOsStrError {} @@ -32,6 +32,8 @@ pub fn osstr_to_str(osstr: &OsStr) -> Result<&str, CastOsStrError> { // 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 where T: Iterator, @@ -72,6 +74,7 @@ where impl FusedIterator for CapitalizeFirstAscii where T: Iterator + FusedIterator {} +/// Accept an string slice, transform its first char (ASCII only) as capital form and output. pub fn capitalize_first_ascii(s: &str) -> String { CapitalizeFirstAscii::new(s.chars()).collect() } diff --git a/wfassoc/src/win32.rs b/wfassoc/src/win32.rs index 2788102..2c30ef9 100644 --- a/wfassoc/src/win32.rs +++ b/wfassoc/src/win32.rs @@ -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 utilities; pub mod regext; \ No newline at end of file diff --git a/wfassoc/src/win32/concept.rs b/wfassoc/src/win32/concept.rs index 5e7a047..6ddba0c 100644 --- a/wfassoc/src/win32/concept.rs +++ b/wfassoc/src/win32/concept.rs @@ -154,8 +154,8 @@ impl BadProgIdPartError { /// outputs legal ProgId in string form. /// /// Reference: -/// - https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids -/// - https://learn.microsoft.com/en-us/windows/win32/com/-progid--key +/// - +/// - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ProgId { /// The vendor part of ProgId. @@ -168,6 +168,9 @@ pub struct ProgId { impl ProgId { /// 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( vendor: &str, component: &str, @@ -279,7 +282,7 @@ pub struct Clsid { impl Clsid { /// Create new CLSID from underlying UUID. - fn new(uuid: &Uuid) -> Self { + pub fn new(uuid: &Uuid) -> Self { Self { inner: *uuid } } @@ -897,6 +900,7 @@ impl FromStr for ExpandString { // region: File Name +/// The error occurs when constructing FileName with bad file name. pub type BadFileNameError = ParseFileNameError; /// 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)] #[error("given file name is illegal in Windows")] pub enum ParseFileNameError { @@ -975,6 +979,7 @@ impl FromStr for FileName { // region: Verb +/// The error occurs when constructing Verb with bad verb name. pub type BadVerbError = ParseVerbError; /// 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)] #[error("given verb \"{inner}\" is illegal")] pub struct ParseVerbError { @@ -1071,11 +1076,21 @@ impl FromStr for Verb { /// The error occurs when constructing CmdLine with bad arguments. #[derive(Debug, TeError)] -#[error("given file extension body \"{inner}\" is invalid")] +#[error("given command line argument \"{inner}\" is invalid")] pub struct BadCmdLineError { 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. /// /// 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)] #[error("given command line \"{inner}\" is invalid")] pub struct ParseCmdLineError { @@ -1153,9 +1168,7 @@ pub struct ParseCmdLineError { } impl ParseCmdLineError { - // TODO: Remove this dead_code attribute once we need use this error type. - #[allow(dead_code)] fn new(inner: &str) -> Self { Self { diff --git a/wfassoc/src/win32/utilities.rs b/wfassoc/src/win32/utilities.rs index 9356c0b..31f665e 100644 --- a/wfassoc/src/win32/utilities.rs +++ b/wfassoc/src/win32/utilities.rs @@ -7,7 +7,7 @@ /// It usually means that checking whether current process is running as Administrator. /// Return true if it is, otherwise false. /// -/// Reference: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership +/// Reference: /// /// # Panics ///