From ae0231fe69215754720d554466f330866c943dcc Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 18 May 2026 16:27:39 +0800 Subject: [PATCH] refactor: seperate lowlevel into 4 individual submodules --- wfassoc/src/lowlevel.rs | 992 ++--------------------- wfassoc/src/lowlevel/app_path_key.rs | 152 ++++ wfassoc/src/lowlevel/applications_key.rs | 343 ++++++++ wfassoc/src/lowlevel/ext_key.rs | 217 +++++ wfassoc/src/lowlevel/progid_key.rs | 171 ++++ 5 files changed, 967 insertions(+), 908 deletions(-) create mode 100644 wfassoc/src/lowlevel/app_path_key.rs create mode 100644 wfassoc/src/lowlevel/applications_key.rs create mode 100644 wfassoc/src/lowlevel/ext_key.rs create mode 100644 wfassoc/src/lowlevel/progid_key.rs diff --git a/wfassoc/src/lowlevel.rs b/wfassoc/src/lowlevel.rs index 2fdd485..4142bae 100644 --- a/wfassoc/src/lowlevel.rs +++ b/wfassoc/src/lowlevel.rs @@ -3,11 +3,9 @@ use std::fmt::Display; use std::str::FromStr; use thiserror::Error as TeError; use winreg::RegKey; -use winreg::enums::{ - HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE, -}; +use winreg::enums::{KEY_READ, KEY_WRITE}; -// region: Error Type +// region: Error and Result Types /// Error occurs in this module. #[derive(Debug, TeError)] @@ -34,9 +32,14 @@ pub enum Error { ParseExt(#[from] concept::ParseExtError), } +/// The result type used in this module and all sub-modules. +type Result = std::result::Result; + // endregion -// region: Scope and View +// region: Exposed Stuff + +// region: Scope /// The scope where wfassoc will register and unregister. #[derive(Debug, Copy, Clone)] @@ -47,18 +50,6 @@ pub enum Scope { System, } -/// The view when wfassoc querying infomations. -#[derive(Debug, Copy, Clone)] -pub enum View { - /// The view of current user. - User, - /// The view of system. - System, - /// Hybrid view of User and System. - /// It can be seen as that we use System first and then use User to override any existing items. - Hybrid, -} - /// The error occurs when cast View into Scope. #[derive(Debug, TeError)] #[error("hybrid view can not be cast into any scope")] @@ -70,19 +61,10 @@ impl TryFromViewError { } } -impl From for View { - fn from(value: Scope) -> Self { - match value { - Scope::User => Self::User, - Scope::System => Self::System, - } - } -} - impl TryFrom for Scope { type Error = TryFromViewError; - fn try_from(value: View) -> Result { + fn try_from(value: View) -> std::result::Result { match value { View::User => Ok(Self::User), View::System => Ok(Self::System), @@ -93,7 +75,30 @@ impl TryFrom for Scope { // endregion -// region: Exposed Structs (Losse, Variant and others) +// region: View + +/// The view when wfassoc querying infomations. +#[derive(Debug, Copy, Clone)] +pub enum View { + /// The view of current user. + User, + /// The view of system. + System, + /// Hybrid view of User and System. + /// It can be seen as that we use System first and then use User to override any existing items. + Hybrid, +} + +impl From for View { + fn from(value: Scope) -> Self { + match value { + Scope::User => Self::User, + Scope::System => Self::System, + } + } +} + +// endregion // region: Losse ProgId @@ -151,7 +156,7 @@ pub enum IconResVariant { } impl IconResVariant { - pub fn extract(&self, kind: concept::IconSizeKind) -> Result { + pub fn extract(&self, kind: concept::IconSizeKind) -> Result { let rc = match self { IconResVariant::Plain(v) => concept::IconRc::with_ico_file(v.as_str(), kind)?, IconResVariant::RefStr(v) => concept::IconRc::new(v.get_path(), v.get_index(), kind)?, @@ -203,7 +208,7 @@ pub enum StrResVariant { } impl StrResVariant { - pub fn extract(&self) -> Result { + pub fn extract(&self) -> Result { let rv = match self { // For plain string, we just simply clone it. StrResVariant::Plain(v) => v.clone(), @@ -273,7 +278,31 @@ impl ShellVerb { // endregion -// region: Utilities +// region: Internal Stuff + +// region: Opened Key + +/// Internal used struct representing the result of opening scope or view. +#[derive(Debug)] +struct OpenedKey { + /// The parent key of opened key which must be presented. + parent_key: RegKey, + /// The opened key which may not be presented in registry. + this_key: Option, +} + +impl OpenedKey { + fn new(parent_key: RegKey, this_key: Option) -> Self { + Self { + parent_key, + this_key, + } + } +} + +// endregion + +// region: Open Key Territory /// The territory of opening key. /// @@ -305,8 +334,9 @@ impl From for OpenKeyTerritory { } } -const PERM_R: u32 = KEY_READ; -const PERM_RW: u32 = KEY_READ | KEY_WRITE; +// endregion + +// region: Open Key Purpose /// The purpose of opening this key. #[derive(Debug, Copy, Clone)] @@ -326,9 +356,20 @@ impl OpenKeyPurpose { } } +// endregion + +// region: Permission Constants + +/// The read-only permission when opening Windows Registry. +const PERM_R: u32 = KEY_READ; +/// The read-write permission when opening Windows Registry. +const PERM_RW: u32 = KEY_READ | KEY_WRITE; + +// endregion + /// Check whether we have enough privilege when operating in given territory and purpose. /// If we have, simply return, otherwise return error. -fn check_privilege(territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<(), Error> { +fn check_privilege(territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<()> { match purpose { // Read is okey for every territory. OpenKeyPurpose::Read => Ok(()), @@ -348,883 +389,18 @@ fn check_privilege(territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Resu } } -/// Internal used struct representing the result of opening scope or view. -#[derive(Debug)] -struct OpenedKey { - /// The parent key of opened key which must be presented. - parent_key: RegKey, - /// The opened key which may not be presented in registry. - this_key: Option, -} - -impl OpenedKey { - fn new(parent_key: RegKey, this_key: Option) -> Self { - Self { - parent_key, - this_key, - } - } -} - // endregion -// region: App Paths Key +// region: Registry Keys -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct AppPathsKey { - key_name: concept::FileName, -} +mod app_path_key; +mod applications_key; +mod ext_key; +mod progid_key; -impl AppPathsKey { - pub fn new(inner: concept::FileName) -> Self { - Self { key_name: inner } - } - - pub fn inner(&self) -> &concept::FileName { - &self.key_name - } -} - -impl AppPathsKey { - const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths"; - - fn open_key( - &self, - territory: OpenKeyTerritory, - purpose: OpenKeyPurpose, - ) -> Result { - // check privilege - check_privilege(territory, purpose)?; - // Fetch the permission - let perms = purpose.to_permission(); - - // get the root key - let hk = match territory { - OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER), - OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE), - // There is no way that territory is hybrid. - OpenKeyTerritory::Hybrid => panic!("unexpected hybrid key territory"), - }; - // navigate to App Paths - let app_paths = - hk.open_subkey_with_flags(regext::blank_path_guard(Self::APP_PATHS)?, perms)?; - // open file name key if possible - let this_app = regext::try_open_subkey_with_flags( - &app_paths, - regext::blank_path_guard(self.key_name.inner())?, - perms, - )?; - - // okey - Ok(OpenedKey::new(app_paths, this_app)) - } - - fn open_scope_for_read(&self, scope: Scope) -> Result { - self.open_key(scope.into(), OpenKeyPurpose::Read) - } - - fn open_scope_for_write(&self, scope: Scope) -> Result { - self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) - } - - pub fn is_exist(&self, scope: Scope) -> Result { - let key = self.open_scope_for_read(scope)?.this_key; - Ok(key.is_some()) - } - - /// Ensure this application key is presented in App Paths. - /// - /// Return true if we newly create this key, - /// otherwise false indicating there already is an existing key. - pub fn ensure(&mut self, scope: Scope) -> Result { - let key = self.open_scope_for_write(scope)?; - if let None = key.this_key { - let _ = key.parent_key.create_subkey_with_flags( - regext::blank_path_guard(self.key_name.inner())?, - PERM_RW, - )?; - Ok(true) - } else { - Ok(false) - } - } - - /// Delete this application key from App Paths. - /// - /// If there is no such key in App Paths, - /// this function does nothing. - pub fn delete(&mut self, scope: Scope) -> Result<(), Error> { - let key = self.open_scope_for_write(scope)?; - key.parent_key - .delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?; - Ok(()) - } - - fn open_scope_for_getter(&self, scope: Scope) -> Result { - self.open_scope_for_read(scope)? - .this_key - .ok_or(Error::InexistantKey) - } - - 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 = ""; - - /// - /// - /// This field point to the fully qualified path to 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. - pub fn set_default(&mut self, scope: Scope, value: &str) -> Result<(), Error> { - let key = self.open_scope_for_setter(scope)?; - key.set_value(Self::NAMEOF_DEFAULT, &value)?; - Ok(()) - } - - const NAMEOF_PATH: &str = "Path"; - - /// - /// - /// This field point to the added path for PATH environment variable. - /// Usually it 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. - pub fn set_path(&mut self, scope: Scope, value: &str) -> Result<(), Error> { - let key = self.open_scope_for_setter(scope)?; - key.set_value(Self::NAMEOF_PATH, &value)?; - Ok(()) - } -} - -// endregion - -// region: Applications Key - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ApplicationsKey { - key_name: concept::FileName, -} - -impl ApplicationsKey { - pub fn new(inner: concept::FileName) -> Self { - Self { key_name: inner } - } - - pub fn inner(&self) -> &concept::FileName { - &self.key_name - } -} - -impl ApplicationsKey { - const FULL_APPLICATIONS: &str = "Software\\Classes\\Applications"; - const PARTIAL_APPLICATIONS: &str = "Applications"; - - fn open_key( - &self, - territory: OpenKeyTerritory, - purpose: OpenKeyPurpose, - ) -> Result { - // check privilege - check_privilege(territory, purpose)?; - // Fetch the permission - let perms = purpose.to_permission(); - - // get the root key - let hk = match territory { - OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER), - OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE), - OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT), - }; - let applications = match territory { - OpenKeyTerritory::User | OpenKeyTerritory::System => hk.open_subkey_with_flags( - regext::blank_path_guard(Self::FULL_APPLICATIONS)?, - perms, - )?, - OpenKeyTerritory::Hybrid => hk.open_subkey_with_flags( - regext::blank_path_guard(Self::PARTIAL_APPLICATIONS)?, - perms, - )?, - }; - // open app key if possible - let this_app = regext::try_open_subkey_with_flags( - &applications, - regext::blank_path_guard(self.key_name.inner())?, - perms, - )?; - // okey - Ok(OpenedKey::new(applications, this_app)) - } - - fn open_view_for_read(&self, view: View) -> Result { - self.open_key(view.into(), OpenKeyPurpose::Read) - } - - fn open_scope_for_write(&self, scope: Scope) -> Result { - self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) - } - - pub fn is_exist(&self, view: View) -> Result { - let key = self.open_view_for_read(view)?.this_key; - Ok(key.is_some()) - } - - pub fn ensure(&mut self, scope: Scope) -> Result { - let key = self.open_scope_for_write(scope)?; - if let None = key.this_key { - let _ = key.parent_key.create_subkey_with_flags( - regext::blank_path_guard(self.key_name.inner())?, - PERM_RW, - )?; - Ok(true) - } else { - Ok(false) - } - } - - pub fn delete(&mut self, scope: Scope) -> Result<(), Error> { - let key = self.open_scope_for_write(scope)?; - key.parent_key - .delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?; - Ok(()) - } - - // YYC MARK: - // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-applications-subkey - - fn open_view_for_getter(&self, view: View) -> Result { - self.open_view_for_read(view)? - .this_key - .ok_or(Error::InexistantKey) - } - - fn open_scope_for_setter(&self, scope: Scope) -> Result { - self.open_scope_for_write(scope)? - .this_key - .ok_or(Error::InexistantKey) - } - - const NAMEOF_SHELL_VERB_PART1: &str = "shell"; - const NAMEOF_SHELL_VERB_PART3: &str = "command"; - const NAMEOF_SHELL_VERB_PART4: &str = ""; - - pub fn get_shell_verb(&self, view: View) -> Result, Error> { - let key = self.open_view_for_getter(view)?; - - // Get shell subkey - let shell_key = match regext::try_open_subkey_with_flags( - &key, - regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART1)?, - PERM_R, - )? { - Some(key) => key, - None => return Ok(None), - }; - // Get verb subkey name, then get subkey itself. - let verb_key_name = match regext::get_sole_subkey_name(&shell_key)? { - Some(key) => key, - None => return Ok(None), - }; - let verb_key = match regext::try_open_subkey_with_flags( - &shell_key, - regext::blank_path_guard(&verb_key_name)?, - PERM_R, - )? { - Some(key) => key, - None => return Ok(None), - }; - // Get command subkey. - let command_key = match regext::try_open_subkey_with_flags( - &verb_key, - regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART3)?, - PERM_R, - )? { - Some(key) => key, - None => return Ok(None), - }; - // Get the default value of command subkey - let command_default_value = match regext::try_get_value::( - &command_key, - Self::NAMEOF_SHELL_VERB_PART4, - )? { - Some(value) => value, - None => return Ok(None), - }; - - // Okey, return value. - Ok(Some(ShellVerb::new( - verb_key_name.parse()?, - command_default_value.parse()?, - ))) - } - - pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<(), Error> { - let key = self.open_scope_for_setter(scope)?; - - match sv { - Some(sv) => { - // Create shell subkey - let (shell_key, _) = key.create_subkey_with_flags( - regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART1)?, - PERM_RW, - )?; - // Create verb key - let (verb_key, _) = - shell_key.create_subkey_with_flags(sv.get_verb().inner(), PERM_RW)?; - // Create command key - let (command_key, _) = verb_key.create_subkey_with_flags( - regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART3)?, - PERM_RW, - )?; - // Set command key default value - command_key.set_value(Self::NAMEOF_SHELL_VERB_PART4, &sv.get_command().full())?; - } - None => { - // Delete shell and its all subkey. - key.delete_subkey_all(regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART1)?)?; - } - } - - Ok(()) - } - - const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon"; - const NAMEOF_DEFAULT_ICON_PART2: &str = ""; - - pub fn get_default_icon(&self, view: View) -> Result, Error> { - let key = self.open_view_for_getter(view)?; - // Get default icon subkey - let default_icon_key = match regext::try_open_subkey_with_flags( - &key, - regext::blank_path_guard(Self::NAMEOF_DEFAULT_ICON_PART1)?, - PERM_R, - )? { - Some(key) => key, - None => return Ok(None), - }; - // Get the default value of default icon subkey - let default_icon_default_value = - regext::try_get_value::(&default_icon_key, Self::NAMEOF_DEFAULT_ICON_PART2)?; - // Transform it as result - Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str()))) - } - - pub fn set_default_icon( - &mut self, - scope: Scope, - icon: Option<&IconResVariant>, - ) -> Result<(), Error> { - let key = self.open_scope_for_setter(scope)?; - - match icon { - Some(icon) => { - // Create default icon subkey - let (default_icon_key, _) = key.create_subkey_with_flags( - regext::blank_path_guard(Self::NAMEOF_DEFAULT_ICON_PART1)?, - PERM_RW, - )?; - // Set default value of default icon subkey. - default_icon_key.set_value(Self::NAMEOF_DEFAULT_ICON_PART2, &icon.to_string())?; - } - None => { - // Delete shell and its all subkey. - key.delete_subkey_all(regext::blank_path_guard(Self::NAMEOF_DEFAULT_ICON_PART1)?)?; - } - } - - Ok(()) - } - - const NAMEOF_FRIENDLY_APP_NAME: &str = "FriendlyAppName"; - - pub fn get_friendly_app_name(&self, view: View) -> Result, Error> { - let key = self.open_view_for_getter(view)?; - // Get value of it - let value = regext::try_get_value::(&key, Self::NAMEOF_FRIENDLY_APP_NAME)?; - // Transform it as result - Ok(value.map(|v| StrResVariant::from(v.as_str()))) - } - - pub fn set_friendly_app_name( - &mut self, - scope: Scope, - name: Option<&StrResVariant>, - ) -> Result<(), Error> { - let key = self.open_scope_for_setter(scope)?; - - match name { - Some(name) => { - // Set value for this key - key.set_value(Self::NAMEOF_FRIENDLY_APP_NAME, &name.to_string())?; - } - None => { - // Delete this key - key.delete_value(Self::NAMEOF_FRIENDLY_APP_NAME)?; - } - } - - Ok(()) - } - - const NAMEOF_SUPPORTED_TYPES: &str = "SupportedTypes"; - - pub fn get_supported_types(&self, view: View) -> Result>, Error> { - let key = self.open_view_for_getter(view)?; - // Get supported types subkey - let supported_types_key = match regext::try_open_subkey_with_flags( - &key, - regext::blank_path_guard(Self::NAMEOF_SUPPORTED_TYPES)?, - PERM_R, - )? { - Some(key) => key, - None => return Ok(None), - }; - // Fetch all sub-values - let key_names = regext::get_all_string_subkey_names(&supported_types_key)?; - // Map the result - let exts = key_names - .into_iter() - .map(|name| name.parse::()) - .collect::, _>>()?; - // Return value - Ok(Some(exts)) - } - - pub fn set_supported_types( - &mut self, - scope: Scope, - tys: Option<&[&concept::Ext]>, - ) -> Result<(), Error> { - let key = self.open_scope_for_setter(scope)?; - - match tys { - Some(tys) => { - // Create supported types key - let (supported_types_key, _) = key.create_subkey_with_flags( - regext::blank_path_guard(Self::NAMEOF_SUPPORTED_TYPES)?, - PERM_RW, - )?; - // Clean all contents of this key - regext::clean_all_contents(&supported_types_key)?; - // Add file types one by one - for ty in tys { - supported_types_key.set_value(ty.dotted_inner(), &"")?; - } - } - None => { - // Delete this subkey. - key.delete_subkey_all(regext::blank_path_guard(Self::NAMEOF_SUPPORTED_TYPES)?)?; - } - } - - Ok(()) - } - - const NAMEOF_NO_OPEN_WITH: &str = "NoOpenWith"; - - 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)? { - Some(_) => Ok(true), - None => Ok(false), - } - } - - pub fn set_no_open_with(&mut self, scope: Scope, flag: bool) -> Result<(), Error> { - let key = self.open_scope_for_setter(scope)?; - if flag { - key.set_value(Self::NAMEOF_NO_OPEN_WITH, &"")?; - } else { - key.delete_value(Self::NAMEOF_NO_OPEN_WITH)?; - } - Ok(()) - } -} - -// endregion - -// region: File Extension Key - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ExtKey { - ext: concept::Ext, -} - -impl ExtKey { - pub fn new(inner: concept::Ext) -> Self { - Self { ext: inner } - } - - pub fn inner(&self) -> &concept::Ext { - &self.ext - } -} - -impl ExtKey { - const FULL_CLASSES: &str = "Software\\Classes"; - const PARTIAL_CLASSES: &str = ""; - - fn open_key( - &self, - territory: OpenKeyTerritory, - purpose: OpenKeyPurpose, - ) -> Result { - // check privilege - check_privilege(territory, purpose)?; - // Fetch the permission - let perms = purpose.to_permission(); - - // navigate to extension container - let hk = match territory { - OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER), - OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE), - OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT), - }; - let classes = match territory { - OpenKeyTerritory::User | OpenKeyTerritory::System => { - hk.open_subkey_with_flags(regext::blank_path_guard(Self::FULL_CLASSES)?, perms)? - } - OpenKeyTerritory::Hybrid => { - hk.open_subkey_with_flags(regext::blank_path_guard(Self::PARTIAL_CLASSES)?, perms)? - } - }; - // open extension key if possible - let this_ext = regext::try_open_subkey_with_flags( - &classes, - regext::blank_path_guard(self.ext.dotted_inner())?, - perms, - )?; - // okey - Ok(OpenedKey::new(classes, this_ext)) - } - - fn open_view_for_read(&self, view: View) -> Result { - self.open_key(view.into(), OpenKeyPurpose::Read) - } - - fn open_scope_for_write(&self, scope: Scope) -> Result { - self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) - } - - pub fn is_exist(&self, view: View) -> Result { - let key = self.open_view_for_read(view)?.this_key; - Ok(key.is_some()) - } - - pub fn ensure(&mut self, scope: Scope) -> Result { - let key = self.open_scope_for_write(scope)?; - if let None = key.this_key { - let _ = key.parent_key.create_subkey_with_flags( - regext::blank_path_guard(self.ext.dotted_inner())?, - PERM_RW, - )?; - Ok(true) - } else { - Ok(false) - } - } - - pub fn delete(&mut self, scope: Scope) -> Result<(), Error> { - let key = self.open_scope_for_write(scope)?; - key.parent_key - .delete_subkey_all(regext::blank_path_guard(self.ext.dotted_inner())?)?; - Ok(()) - } - - // YYC MARK: - // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes - // - // 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. - - fn open_view_for_getter(&self, view: View) -> Result { - self.open_view_for_read(view)? - .this_key - .ok_or(Error::InexistantKey) - } - - fn open_scope_for_setter(&self, scope: Scope) -> Result { - self.open_scope_for_write(scope)? - .this_key - .ok_or(Error::InexistantKey) - } - - const NAMEOF_DEFAULT: &str = ""; - - pub fn get_default(&self, view: View) -> Result, Error> { - let key = self.open_view_for_getter(view)?; - // Get value of it - let value = regext::try_get_value::(&key, Self::NAMEOF_DEFAULT)?; - // Transform it as result - Ok(value.map(|v| LosseProgId::from(v.as_str()))) - } - - pub fn set_default(&mut self, scope: Scope, pid: Option<&LosseProgId>) -> Result<(), Error> { - let key = self.open_scope_for_setter(scope)?; - - match pid { - Some(pid) => { - // Set value for this key - key.set_value(Self::NAMEOF_DEFAULT, &pid.to_string())?; - } - None => { - // Delete this key - key.delete_value(Self::NAMEOF_DEFAULT)?; - } - } - - Ok(()) - } - - const NAMEOF_OPEN_WITH_PROGIDS: &str = "OpenWithProgIds"; - - pub fn get_open_with_progids(&self, view: View) -> Result>, Error> { - let key = self.open_view_for_getter(view)?; - // Get OpenWithProgIds subkey - let open_with_progids_key = match regext::try_open_subkey_with_flags( - &key, - regext::blank_path_guard(Self::NAMEOF_OPEN_WITH_PROGIDS)?, - PERM_R, - )? { - Some(key) => key, - None => return Ok(None), - }; - // Fetch all sub-values - let key_names = regext::get_all_string_subkey_names(&open_with_progids_key)?; - // Map the result - let progids = key_names - .into_iter() - .map(|name| LosseProgId::from(name.as_str())) - .collect(); - // Return value - Ok(Some(progids)) - } - - /// - /// - /// If there is no "OpenWithProgIds" subkey, this function return false. - pub fn is_in_open_with_progids(&self, view: View, pid: &LosseProgId) -> Result { - let key = self.open_view_for_getter(view)?; - // Get OpenWithProgIds subkey - let open_with_progids_key = match regext::try_open_subkey_with_flags( - &key, - regext::blank_path_guard(Self::NAMEOF_OPEN_WITH_PROGIDS)?, - PERM_R, - )? { - Some(key) => key, - None => return Ok(false), - }; - // Check whether there is given ProgId - Ok(regext::try_get_value::(&open_with_progids_key, pid.to_string())?.is_some()) - } - - /// - /// - /// If there is no "OpenWithProgIds" subkey, this function will create it first, - /// then add your given ProgId into it. - pub fn add_into_open_with_progids( - &mut self, - scope: Scope, - pid: &LosseProgId, - ) -> Result<(), Error> { - todo!() - } - - /// - /// - /// If there is no "OpenWithProgIds" subkey, this function do nothing. - pub fn remove_from_open_with_progids( - &mut self, - scope: Scope, - pid: &LosseProgId, - ) -> Result<(), Error> { - todo!() - } -} - -// endregion - -// region: ProgId Key - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ProgIdKey { - progid: LosseProgId, -} - -impl ProgIdKey { - pub fn new(inner: LosseProgId) -> Self { - Self { progid: inner } - } - - pub fn inner(&self) -> &LosseProgId { - &self.progid - } -} - -impl ProgIdKey { - const FULL_CLASSES: &str = "Software\\Classes"; - const PARTIAL_CLASSES: &str = ""; - - fn open_key( - &self, - territory: OpenKeyTerritory, - purpose: OpenKeyPurpose, - ) -> Result { - // check privilege - check_privilege(territory, purpose)?; - // Fetch the permission - let perms = purpose.to_permission(); - - // navigate to ProgId container - let hk = match territory { - OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER), - OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE), - OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT), - }; - let classes = match territory { - OpenKeyTerritory::User | OpenKeyTerritory::System => { - hk.open_subkey_with_flags(regext::blank_path_guard(Self::FULL_CLASSES)?, perms)? - } - OpenKeyTerritory::Hybrid => { - hk.open_subkey_with_flags(regext::blank_path_guard(Self::PARTIAL_CLASSES)?, perms)? - } - }; - // open ProgId key if possible - let this_progid = regext::try_open_subkey_with_flags( - &classes, - regext::blank_path_guard(self.progid.to_string())?, - perms, - )?; - // okey - Ok(OpenedKey::new(classes, this_progid)) - } - - fn open_view_for_read(&self, view: View) -> Result { - self.open_key(view.into(), OpenKeyPurpose::Read) - } - - fn open_scope_for_write(&self, scope: Scope) -> Result { - self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) - } - - pub fn is_exist(&self, view: View) -> Result { - let key = self.open_view_for_read(view)?.this_key; - Ok(key.is_some()) - } - - pub fn ensure(&mut self, scope: Scope) -> Result { - let key = self.open_scope_for_write(scope)?; - if let None = key.this_key { - let _ = key.parent_key.create_subkey_with_flags( - regext::blank_path_guard(self.progid.to_string())?, - PERM_RW, - )?; - Ok(true) - } else { - Ok(false) - } - } - - pub fn delete(&mut self, scope: Scope) -> Result<(), Error> { - let key = self.open_scope_for_write(scope)?; - key.parent_key - .delete_subkey_all(regext::blank_path_guard(self.progid.to_string())?)?; - Ok(()) - } - - // YYC MARK: - // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations - // - // Currently we only support (Default), FriendlyTypeName and DefaultIcon - // to just cover the basic usage. - - fn open_view_for_getter(&self, view: View) -> Result { - self.open_view_for_read(view)? - .this_key - .ok_or(Error::InexistantKey) - } - - fn open_scope_for_setter(&self, scope: Scope) -> Result { - self.open_scope_for_write(scope)? - .this_key - .ok_or(Error::InexistantKey) - } - - const NAMEOF_DEFAULT: &str = ""; - - pub fn get_default(&self, view: View) -> Result, Error> { - todo!() - } - - /// - /// - /// The legacy way to set friendly name for this ProgId. - pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<(), Error> { - todo!() - } - - const NAMEOF_SHELL_VERB_PART1: &str = "shell"; - const NAMEOF_SHELL_VERB_PART3: &str = "command"; - - pub fn get_shell_verb(&self, view: View) -> Result { - todo!() - } - - pub fn set_shell_verb(&mut self, scope: Scope, sv: &ShellVerb) -> Result<(), Error> { - todo!() - } - - const NAMEOF_FRIENDLY_TYPE_NAME: &str = "FriendlyTypeName"; - - pub fn get_friendly_type_name(&self, view: View) -> Result, Error> { - todo!() - } - - /// - /// - /// Set this entry to a friendly name for the ProgID. - pub fn set_friendly_type_name( - &mut self, - scope: Scope, - name: Option<&StrResVariant>, - ) -> Result<(), Error> { - todo!() - } - - const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon"; - const NAMEOF_DEFAULT_ICON_PART2: &str = ""; - - pub fn get_default_icon(&self, view: View) -> Result, Error> { - todo!() - } - - pub fn set_default_icon( - &mut self, - scope: Scope, - icon: Option<&IconResVariant>, - ) -> Result<(), Error> { - todo!() - } -} +pub use app_path_key::AppPathsKey; +pub use applications_key::ApplicationsKey; +pub use ext_key::ExtKey; +pub use progid_key::ProgIdKey; // endregion diff --git a/wfassoc/src/lowlevel/app_path_key.rs b/wfassoc/src/lowlevel/app_path_key.rs new file mode 100644 index 0000000..0884ec9 --- /dev/null +++ b/wfassoc/src/lowlevel/app_path_key.rs @@ -0,0 +1,152 @@ +use super::{ + Error, OpenKeyPurpose, OpenKeyTerritory, OpenedKey, PERM_RW, Result, Scope, check_privilege, +}; +use crate::win32::{concept, regext}; +use winreg::RegKey; +use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; + +// region: App Paths Key + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AppPathsKey { + key_name: concept::FileName, +} + +impl AppPathsKey { + pub fn new(inner: concept::FileName) -> Self { + Self { key_name: inner } + } + + pub fn inner(&self) -> &concept::FileName { + &self.key_name + } +} + +impl AppPathsKey { + const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths"; + + fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result { + // check privilege + check_privilege(territory, purpose)?; + // Fetch the permission + let perms = purpose.to_permission(); + + // get the root key + let hk = match territory { + OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER), + OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE), + // There is no way that territory is hybrid. + OpenKeyTerritory::Hybrid => panic!("unexpected hybrid key territory"), + }; + // navigate to App Paths + let app_paths = + hk.open_subkey_with_flags(regext::blank_path_guard(Self::APP_PATHS)?, perms)?; + // open file name key if possible + let this_app = regext::try_open_subkey_with_flags( + &app_paths, + regext::blank_path_guard(self.key_name.inner())?, + perms, + )?; + + // okey + Ok(OpenedKey::new(app_paths, this_app)) + } + + fn open_scope_for_read(&self, scope: Scope) -> Result { + self.open_key(scope.into(), OpenKeyPurpose::Read) + } + + fn open_scope_for_write(&self, scope: Scope) -> Result { + self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) + } + + pub fn is_exist(&self, scope: Scope) -> Result { + let key = self.open_scope_for_read(scope)?.this_key; + Ok(key.is_some()) + } + + /// Ensure this application key is presented in App Paths. + /// + /// Return true if we newly create this key, + /// otherwise false indicating there already is an existing key. + pub fn ensure(&mut self, scope: Scope) -> Result { + let key = self.open_scope_for_write(scope)?; + if let None = key.this_key { + let _ = key.parent_key.create_subkey_with_flags( + regext::blank_path_guard(self.key_name.inner())?, + PERM_RW, + )?; + Ok(true) + } else { + Ok(false) + } + } + + /// Delete this application key from App Paths. + /// + /// If there is no such key in App Paths, + /// this function does nothing. + pub fn delete(&mut self, scope: Scope) -> Result<()> { + let key = self.open_scope_for_write(scope)?; + key.parent_key + .delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?; + Ok(()) + } + + fn open_scope_for_getter(&self, scope: Scope) -> Result { + self.open_scope_for_read(scope)? + .this_key + .ok_or(Error::InexistantKey) + } + + 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 = ""; + + /// + /// + /// This field point to the fully qualified path to 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. + 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(()) + } + + const NAMEOF_PATH: &str = "Path"; + + /// + /// + /// This field point to the added path for PATH environment variable. + /// Usually it 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. + 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)?; + Ok(()) + } +} + +// endregion diff --git a/wfassoc/src/lowlevel/applications_key.rs b/wfassoc/src/lowlevel/applications_key.rs new file mode 100644 index 0000000..d826732 --- /dev/null +++ b/wfassoc/src/lowlevel/applications_key.rs @@ -0,0 +1,343 @@ +use super::{ + Error, IconResVariant, OpenKeyPurpose, OpenKeyTerritory, OpenedKey, PERM_R, PERM_RW, Result, + Scope, ShellVerb, StrResVariant, View, check_privilege, +}; +use crate::win32::{concept, regext}; +use winreg::RegKey; +use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; + +// region: Applications Key + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ApplicationsKey { + key_name: concept::FileName, +} + +impl ApplicationsKey { + pub fn new(inner: concept::FileName) -> Self { + Self { key_name: inner } + } + + pub fn inner(&self) -> &concept::FileName { + &self.key_name + } +} + +impl ApplicationsKey { + const FULL_APPLICATIONS: &str = "Software\\Classes\\Applications"; + const PARTIAL_APPLICATIONS: &str = "Applications"; + + fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result { + // check privilege + check_privilege(territory, purpose)?; + // Fetch the permission + let perms = purpose.to_permission(); + + // get the root key + let hk = match territory { + OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER), + OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE), + OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT), + }; + let applications = match territory { + OpenKeyTerritory::User | OpenKeyTerritory::System => hk.open_subkey_with_flags( + regext::blank_path_guard(Self::FULL_APPLICATIONS)?, + perms, + )?, + OpenKeyTerritory::Hybrid => hk.open_subkey_with_flags( + regext::blank_path_guard(Self::PARTIAL_APPLICATIONS)?, + perms, + )?, + }; + // open app key if possible + let this_app = regext::try_open_subkey_with_flags( + &applications, + regext::blank_path_guard(self.key_name.inner())?, + perms, + )?; + // okey + Ok(OpenedKey::new(applications, this_app)) + } + + fn open_view_for_read(&self, view: View) -> Result { + self.open_key(view.into(), OpenKeyPurpose::Read) + } + + fn open_scope_for_write(&self, scope: Scope) -> Result { + self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) + } + + pub fn is_exist(&self, view: View) -> Result { + let key = self.open_view_for_read(view)?.this_key; + Ok(key.is_some()) + } + + pub fn ensure(&mut self, scope: Scope) -> Result { + let key = self.open_scope_for_write(scope)?; + if let None = key.this_key { + let _ = key.parent_key.create_subkey_with_flags( + regext::blank_path_guard(self.key_name.inner())?, + PERM_RW, + )?; + Ok(true) + } else { + Ok(false) + } + } + + pub fn delete(&mut self, scope: Scope) -> Result<()> { + let key = self.open_scope_for_write(scope)?; + key.parent_key + .delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?; + Ok(()) + } + + // YYC MARK: + // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-applications-subkey + + fn open_view_for_getter(&self, view: View) -> Result { + self.open_view_for_read(view)? + .this_key + .ok_or(Error::InexistantKey) + } + + fn open_scope_for_setter(&self, scope: Scope) -> Result { + self.open_scope_for_write(scope)? + .this_key + .ok_or(Error::InexistantKey) + } + + const NAMEOF_SHELL_VERB_PART1: &str = "shell"; + const NAMEOF_SHELL_VERB_PART3: &str = "command"; + const NAMEOF_SHELL_VERB_PART4: &str = ""; + + pub fn get_shell_verb(&self, view: View) -> Result> { + let key = self.open_view_for_getter(view)?; + + // Get shell subkey + let shell_key = match regext::try_open_subkey_with_flags( + &key, + regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART1)?, + PERM_R, + )? { + Some(key) => key, + None => return Ok(None), + }; + // Get verb subkey name, then get subkey itself. + let verb_key_name = match regext::get_sole_subkey_name(&shell_key)? { + Some(key) => key, + None => return Ok(None), + }; + let verb_key = match regext::try_open_subkey_with_flags( + &shell_key, + regext::blank_path_guard(&verb_key_name)?, + PERM_R, + )? { + Some(key) => key, + None => return Ok(None), + }; + // Get command subkey. + let command_key = match regext::try_open_subkey_with_flags( + &verb_key, + regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART3)?, + PERM_R, + )? { + Some(key) => key, + None => return Ok(None), + }; + // Get the default value of command subkey + let command_default_value = match regext::try_get_value::( + &command_key, + Self::NAMEOF_SHELL_VERB_PART4, + )? { + Some(value) => value, + None => return Ok(None), + }; + + // Okey, return value. + Ok(Some(ShellVerb::new( + verb_key_name.parse()?, + command_default_value.parse()?, + ))) + } + + pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> { + let key = self.open_scope_for_setter(scope)?; + + match sv { + Some(sv) => { + // Create shell subkey + let (shell_key, _) = key.create_subkey_with_flags( + regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART1)?, + PERM_RW, + )?; + // Create verb key + let (verb_key, _) = + shell_key.create_subkey_with_flags(sv.get_verb().inner(), PERM_RW)?; + // Create command key + let (command_key, _) = verb_key.create_subkey_with_flags( + regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART3)?, + PERM_RW, + )?; + // Set command key default value + command_key.set_value(Self::NAMEOF_SHELL_VERB_PART4, &sv.get_command().full())?; + } + None => { + // Delete shell and its all subkey. + key.delete_subkey_all(regext::blank_path_guard(Self::NAMEOF_SHELL_VERB_PART1)?)?; + } + } + + Ok(()) + } + + const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon"; + const NAMEOF_DEFAULT_ICON_PART2: &str = ""; + + pub fn get_default_icon(&self, view: View) -> Result> { + let key = self.open_view_for_getter(view)?; + // Get default icon subkey + let default_icon_key = match regext::try_open_subkey_with_flags( + &key, + regext::blank_path_guard(Self::NAMEOF_DEFAULT_ICON_PART1)?, + PERM_R, + )? { + Some(key) => key, + None => return Ok(None), + }; + // Get the default value of default icon subkey + let default_icon_default_value = + regext::try_get_value::(&default_icon_key, Self::NAMEOF_DEFAULT_ICON_PART2)?; + // Transform it as result + Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str()))) + } + + pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> { + let key = self.open_scope_for_setter(scope)?; + + match icon { + Some(icon) => { + // Create default icon subkey + let (default_icon_key, _) = key.create_subkey_with_flags( + regext::blank_path_guard(Self::NAMEOF_DEFAULT_ICON_PART1)?, + PERM_RW, + )?; + // Set default value of default icon subkey. + default_icon_key.set_value(Self::NAMEOF_DEFAULT_ICON_PART2, &icon.to_string())?; + } + None => { + // Delete shell and its all subkey. + key.delete_subkey_all(regext::blank_path_guard(Self::NAMEOF_DEFAULT_ICON_PART1)?)?; + } + } + + Ok(()) + } + + const NAMEOF_FRIENDLY_APP_NAME: &str = "FriendlyAppName"; + + pub fn get_friendly_app_name(&self, view: View) -> Result> { + let key = self.open_view_for_getter(view)?; + // Get value of it + let value = regext::try_get_value::(&key, Self::NAMEOF_FRIENDLY_APP_NAME)?; + // Transform it as result + Ok(value.map(|v| StrResVariant::from(v.as_str()))) + } + + pub fn set_friendly_app_name( + &mut self, + scope: Scope, + name: Option<&StrResVariant>, + ) -> Result<()> { + let key = self.open_scope_for_setter(scope)?; + + match name { + Some(name) => { + // Set value for this key + key.set_value(Self::NAMEOF_FRIENDLY_APP_NAME, &name.to_string())?; + } + None => { + // Delete this key + key.delete_value(Self::NAMEOF_FRIENDLY_APP_NAME)?; + } + } + + Ok(()) + } + + const NAMEOF_SUPPORTED_TYPES: &str = "SupportedTypes"; + + pub fn get_supported_types(&self, view: View) -> Result>> { + let key = self.open_view_for_getter(view)?; + // Get supported types subkey + let supported_types_key = match regext::try_open_subkey_with_flags( + &key, + regext::blank_path_guard(Self::NAMEOF_SUPPORTED_TYPES)?, + PERM_R, + )? { + Some(key) => key, + None => return Ok(None), + }; + // Fetch all sub-values + let key_names = regext::get_all_string_subkey_names(&supported_types_key)?; + // Map the result + let exts = key_names + .into_iter() + .map(|name| name.parse::()) + .collect::, _>>()?; + // Return value + Ok(Some(exts)) + } + + pub fn set_supported_types( + &mut self, + scope: Scope, + tys: Option<&[&concept::Ext]>, + ) -> Result<()> { + let key = self.open_scope_for_setter(scope)?; + + match tys { + Some(tys) => { + // Create supported types key + let (supported_types_key, _) = key.create_subkey_with_flags( + regext::blank_path_guard(Self::NAMEOF_SUPPORTED_TYPES)?, + PERM_RW, + )?; + // Clean all contents of this key + regext::clean_all_contents(&supported_types_key)?; + // Add file types one by one + for ty in tys { + supported_types_key.set_value(ty.dotted_inner(), &"")?; + } + } + None => { + // Delete this subkey. + key.delete_subkey_all(regext::blank_path_guard(Self::NAMEOF_SUPPORTED_TYPES)?)?; + } + } + + Ok(()) + } + + const NAMEOF_NO_OPEN_WITH: &str = "NoOpenWith"; + + 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)? { + Some(_) => Ok(true), + None => Ok(false), + } + } + + pub fn set_no_open_with(&mut self, scope: Scope, flag: bool) -> Result<()> { + let key = self.open_scope_for_setter(scope)?; + if flag { + key.set_value(Self::NAMEOF_NO_OPEN_WITH, &"")?; + } else { + key.delete_value(Self::NAMEOF_NO_OPEN_WITH)?; + } + Ok(()) + } +} + +// endregion diff --git a/wfassoc/src/lowlevel/ext_key.rs b/wfassoc/src/lowlevel/ext_key.rs new file mode 100644 index 0000000..d3585f1 --- /dev/null +++ b/wfassoc/src/lowlevel/ext_key.rs @@ -0,0 +1,217 @@ +use super::{ + Error, LosseProgId, OpenKeyPurpose, OpenKeyTerritory, OpenedKey, PERM_R, PERM_RW, Result, + Scope, View, check_privilege, +}; +use crate::win32::{concept, regext}; +use winreg::RegKey; +use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; + +// region: File Extension Key + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ExtKey { + ext: concept::Ext, +} + +impl ExtKey { + pub fn new(inner: concept::Ext) -> Self { + Self { ext: inner } + } + + pub fn inner(&self) -> &concept::Ext { + &self.ext + } +} + +impl ExtKey { + const FULL_CLASSES: &str = "Software\\Classes"; + const PARTIAL_CLASSES: &str = ""; + + fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result { + // check privilege + check_privilege(territory, purpose)?; + // Fetch the permission + let perms = purpose.to_permission(); + + // navigate to extension container + let hk = match territory { + OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER), + OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE), + OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT), + }; + let classes = match territory { + OpenKeyTerritory::User | OpenKeyTerritory::System => { + hk.open_subkey_with_flags(regext::blank_path_guard(Self::FULL_CLASSES)?, perms)? + } + OpenKeyTerritory::Hybrid => { + hk.open_subkey_with_flags(regext::blank_path_guard(Self::PARTIAL_CLASSES)?, perms)? + } + }; + // open extension key if possible + let this_ext = regext::try_open_subkey_with_flags( + &classes, + regext::blank_path_guard(self.ext.dotted_inner())?, + perms, + )?; + // okey + Ok(OpenedKey::new(classes, this_ext)) + } + + fn open_view_for_read(&self, view: View) -> Result { + self.open_key(view.into(), OpenKeyPurpose::Read) + } + + fn open_scope_for_write(&self, scope: Scope) -> Result { + self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) + } + + pub fn is_exist(&self, view: View) -> Result { + let key = self.open_view_for_read(view)?.this_key; + Ok(key.is_some()) + } + + pub fn ensure(&mut self, scope: Scope) -> Result { + let key = self.open_scope_for_write(scope)?; + if let None = key.this_key { + let _ = key.parent_key.create_subkey_with_flags( + regext::blank_path_guard(self.ext.dotted_inner())?, + PERM_RW, + )?; + Ok(true) + } else { + Ok(false) + } + } + + pub fn delete(&mut self, scope: Scope) -> Result<()> { + let key = self.open_scope_for_write(scope)?; + key.parent_key + .delete_subkey_all(regext::blank_path_guard(self.ext.dotted_inner())?)?; + Ok(()) + } + + // YYC MARK: + // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes + // + // 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. + + fn open_view_for_getter(&self, view: View) -> Result { + self.open_view_for_read(view)? + .this_key + .ok_or(Error::InexistantKey) + } + + fn open_scope_for_setter(&self, scope: Scope) -> Result { + self.open_scope_for_write(scope)? + .this_key + .ok_or(Error::InexistantKey) + } + + const NAMEOF_DEFAULT: &str = ""; + + pub fn get_default(&self, view: View) -> Result> { + let key = self.open_view_for_getter(view)?; + // Get value of it + let value = regext::try_get_value::(&key, Self::NAMEOF_DEFAULT)?; + // Transform it as result + Ok(value.map(|v| LosseProgId::from(v.as_str()))) + } + + pub fn set_default(&mut self, scope: Scope, pid: Option<&LosseProgId>) -> Result<()> { + let key = self.open_scope_for_setter(scope)?; + + match pid { + Some(pid) => { + // Set value for this key + key.set_value(Self::NAMEOF_DEFAULT, &pid.to_string())?; + } + None => { + // Delete this key + key.delete_value(Self::NAMEOF_DEFAULT)?; + } + } + + Ok(()) + } + + const NAMEOF_OPEN_WITH_PROGIDS: &str = "OpenWithProgIds"; + + pub fn get_open_with_progids(&self, view: View) -> Result>> { + let key = self.open_view_for_getter(view)?; + // Get OpenWithProgIds subkey + let open_with_progids_key = match regext::try_open_subkey_with_flags( + &key, + regext::blank_path_guard(Self::NAMEOF_OPEN_WITH_PROGIDS)?, + PERM_R, + )? { + Some(key) => key, + None => return Ok(None), + }; + // Fetch all sub-values + let key_names = regext::get_all_string_subkey_names(&open_with_progids_key)?; + // Map the result + let progids = key_names + .into_iter() + .map(|name| LosseProgId::from(name.as_str())) + .collect(); + // Return value + Ok(Some(progids)) + } + + /// + /// + /// If there is no "OpenWithProgIds" subkey, this function return false. + pub fn is_in_open_with_progids(&self, view: View, pid: &LosseProgId) -> Result { + let key = self.open_view_for_getter(view)?; + // Get OpenWithProgIds subkey + let open_with_progids_key = match regext::try_open_subkey_with_flags( + &key, + regext::blank_path_guard(Self::NAMEOF_OPEN_WITH_PROGIDS)?, + PERM_R, + )? { + Some(key) => key, + None => return Ok(false), + }; + // Check whether there is given ProgId + Ok(regext::try_get_value::(&open_with_progids_key, pid.to_string())?.is_some()) + } + + /// + /// + /// If there is no "OpenWithProgIds" subkey, this function will create it first, + /// then add your given ProgId into it. + pub fn add_into_open_with_progids(&mut self, scope: Scope, pid: &LosseProgId) -> Result<()> { + let key = self.open_scope_for_setter(scope)?; + // Get subkey + let (open_with_progids_key, _) = key.create_subkey_with_flags( + regext::blank_path_guard(Self::NAMEOF_OPEN_WITH_PROGIDS)?, + PERM_RW, + )?; + // Add value + open_with_progids_key.set_value(pid.to_string(), &"")?; + Ok(()) + } + + /// + /// + /// If there is no "OpenWithProgIds" subkey, this function do 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 + let open_with_progids_key = match regext::try_open_subkey_with_flags( + &key, + regext::blank_path_guard(Self::NAMEOF_OPEN_WITH_PROGIDS)?, + PERM_RW, + )? { + Some(key) => key, + None => return Ok(()), + }; + // Remove given key + open_with_progids_key.delete_value(pid.to_string())?; + Ok(()) + } +} + +// endregion diff --git a/wfassoc/src/lowlevel/progid_key.rs b/wfassoc/src/lowlevel/progid_key.rs new file mode 100644 index 0000000..d8de4f7 --- /dev/null +++ b/wfassoc/src/lowlevel/progid_key.rs @@ -0,0 +1,171 @@ +use super::{Error, Result, Scope, View, LosseProgId, OpenedKey, OpenKeyTerritory, OpenKeyPurpose, StrResVariant, IconResVariant, ShellVerb, check_privilege, PERM_R, PERM_RW}; +use crate::win32::{concept, regext}; +use winreg::RegKey; +use winreg::enums::{ + HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE +}; + +// region: ProgId Key + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ProgIdKey { + progid: LosseProgId, +} + +impl ProgIdKey { + pub fn new(inner: LosseProgId) -> Self { + Self { progid: inner } + } + + pub fn inner(&self) -> &LosseProgId { + &self.progid + } +} + +impl ProgIdKey { + const FULL_CLASSES: &str = "Software\\Classes"; + const PARTIAL_CLASSES: &str = ""; + + fn open_key( + &self, + territory: OpenKeyTerritory, + purpose: OpenKeyPurpose, + ) -> Result { + // check privilege + check_privilege(territory, purpose)?; + // Fetch the permission + let perms = purpose.to_permission(); + + // navigate to ProgId container + let hk = match territory { + OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER), + OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE), + OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT), + }; + let classes = match territory { + OpenKeyTerritory::User | OpenKeyTerritory::System => { + hk.open_subkey_with_flags(regext::blank_path_guard(Self::FULL_CLASSES)?, perms)? + } + OpenKeyTerritory::Hybrid => { + hk.open_subkey_with_flags(regext::blank_path_guard(Self::PARTIAL_CLASSES)?, perms)? + } + }; + // open ProgId key if possible + let this_progid = regext::try_open_subkey_with_flags( + &classes, + regext::blank_path_guard(self.progid.to_string())?, + perms, + )?; + // okey + Ok(OpenedKey::new(classes, this_progid)) + } + + fn open_view_for_read(&self, view: View) -> Result { + self.open_key(view.into(), OpenKeyPurpose::Read) + } + + fn open_scope_for_write(&self, scope: Scope) -> Result { + self.open_key(scope.into(), OpenKeyPurpose::ReadWrite) + } + + pub fn is_exist(&self, view: View) -> Result { + let key = self.open_view_for_read(view)?.this_key; + Ok(key.is_some()) + } + + pub fn ensure(&mut self, scope: Scope) -> Result { + let key = self.open_scope_for_write(scope)?; + if let None = key.this_key { + let _ = key.parent_key.create_subkey_with_flags( + regext::blank_path_guard(self.progid.to_string())?, + PERM_RW, + )?; + Ok(true) + } else { + Ok(false) + } + } + + pub fn delete(&mut self, scope: Scope) -> Result<()> { + let key = self.open_scope_for_write(scope)?; + key.parent_key + .delete_subkey_all(regext::blank_path_guard(self.progid.to_string())?)?; + Ok(()) + } + + // YYC MARK: + // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations + // + // Currently we only support (Default), FriendlyTypeName and DefaultIcon + // to just cover the basic usage. + + fn open_view_for_getter(&self, view: View) -> Result { + self.open_view_for_read(view)? + .this_key + .ok_or(Error::InexistantKey) + } + + fn open_scope_for_setter(&self, scope: Scope) -> Result { + self.open_scope_for_write(scope)? + .this_key + .ok_or(Error::InexistantKey) + } + + const NAMEOF_DEFAULT: &str = ""; + + pub fn get_default(&self, view: View) -> Result> { + todo!() + } + + /// + /// + /// The legacy way to set friendly name for this ProgId. + pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<()> { + todo!() + } + + const NAMEOF_SHELL_VERB_PART1: &str = "shell"; + const NAMEOF_SHELL_VERB_PART3: &str = "command"; + + pub fn get_shell_verb(&self, view: View) -> Result { + todo!() + } + + pub fn set_shell_verb(&mut self, scope: Scope, sv: &ShellVerb) -> Result<()> { + todo!() + } + + const NAMEOF_FRIENDLY_TYPE_NAME: &str = "FriendlyTypeName"; + + pub fn get_friendly_type_name(&self, view: View) -> Result> { + todo!() + } + + /// + /// + /// Set this entry to a friendly name for the ProgID. + pub fn set_friendly_type_name( + &mut self, + scope: Scope, + name: Option<&StrResVariant>, + ) -> Result<()> { + todo!() + } + + const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon"; + const NAMEOF_DEFAULT_ICON_PART2: &str = ""; + + pub fn get_default_icon(&self, view: View) -> Result> { + todo!() + } + + pub fn set_default_icon( + &mut self, + scope: Scope, + icon: Option<&IconResVariant>, + ) -> Result<()> { + todo!() + } +} + +// endregion