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,
/// 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: <https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types>
pub type HICON = *mut c_void;
/// 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;
// region: Utilities

View File

@@ -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: <https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper>
#[cfg(not(target_os = "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 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<Scope> 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<concept::ProgId> 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<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 {
IconResVariant::Plain(v) => concept::IconRc::with_ico_file(v.as_str(), kind)?,
IconResVariant::RefStr(v) => {
@@ -214,6 +231,9 @@ impl From<concept::IconRefStr> 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<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 {
// For plain string, we just simply clone it.
StrResVariant::Plain(v) => v.clone(),
@@ -276,6 +301,12 @@ impl From<concept::StrRefStr> 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<RegKey>) -> 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<View> 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;

View File

@@ -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\<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)]
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<OpenedKey> {
// 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<OpenedKey> {
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> {
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> {
let key = self.open_scope_for_read(scope)?.this_key;
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> {
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<RegKey> {
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<String> {
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<String> {
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)?;

View File

@@ -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\<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)]
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<OpenedKey> {
// 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<OpenedKey> {
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> {
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> {
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: <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> {
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<RegKey> {
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<Option<ShellVerb>> {
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<Option<IconResVariant>> {
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<Option<StrResVariant>> {
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<Option<Vec<concept::Ext>>> {
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<bool> {
let key = self.open_view_for_getter(view)?;
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<()> {
let key = self.open_scope_for_setter(scope)?;
if flag {

View File

@@ -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\<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)]
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<OpenedKey> {
// 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<OpenedKey> {
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> {
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> {
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: <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:
// 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<RegKey> {
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<RegKey> {
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<Option<LosseProgId>> {
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<Option<Vec<LosseProgId>>> {
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<bool> {
@@ -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

View File

@@ -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\<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)]
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<OpenedKey> {
// 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<OpenedKey> {
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> {
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> {
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: <https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations>
// 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<RegKey> {
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<RegKey> {
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<Option<StrResVariant>> {
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<Option<ShellVerb>> {
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<Option<StrResVariant>> {
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<Option<IconResVariant>> {
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)?;

View File

@@ -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<T>
where
T: Iterator<Item = char>,
@@ -72,6 +74,7 @@ where
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 {
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 utilities;
pub mod regext;

View File

@@ -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
/// - <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 {

View File

@@ -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: <https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership>
///
/// # Panics
///