refactor: seperate lowlevel into 4 individual submodules
This commit is contained in:
File diff suppressed because it is too large
Load Diff
152
wfassoc/src/lowlevel/app_path_key.rs
Normal file
152
wfassoc/src/lowlevel/app_path_key.rs
Normal file
@@ -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<OpenedKey> {
|
||||
// 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<OpenedKey> {
|
||||
self.open_key(scope.into(), OpenKeyPurpose::Read)
|
||||
}
|
||||
|
||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||
}
|
||||
|
||||
pub fn is_exist(&self, scope: Scope) -> Result<bool> {
|
||||
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<bool> {
|
||||
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<RegKey> {
|
||||
self.open_scope_for_read(scope)?
|
||||
.this_key
|
||||
.ok_or(Error::InexistantKey)
|
||||
}
|
||||
|
||||
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 = "";
|
||||
|
||||
///
|
||||
///
|
||||
/// This field point to the fully qualified path to 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.
|
||||
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<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.
|
||||
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
|
||||
343
wfassoc/src/lowlevel/applications_key.rs
Normal file
343
wfassoc/src/lowlevel/applications_key.rs
Normal file
@@ -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<OpenedKey> {
|
||||
// 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<OpenedKey> {
|
||||
self.open_key(view.into(), OpenKeyPurpose::Read)
|
||||
}
|
||||
|
||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||
}
|
||||
|
||||
pub fn is_exist(&self, view: View) -> Result<bool> {
|
||||
let key = self.open_view_for_read(view)?.this_key;
|
||||
Ok(key.is_some())
|
||||
}
|
||||
|
||||
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||
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<RegKey> {
|
||||
self.open_view_for_read(view)?
|
||||
.this_key
|
||||
.ok_or(Error::InexistantKey)
|
||||
}
|
||||
|
||||
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||
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<Option<ShellVerb>> {
|
||||
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::<String, _>(
|
||||
&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<Option<IconResVariant>> {
|
||||
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::<String, _>(&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<Option<StrResVariant>> {
|
||||
let key = self.open_view_for_getter(view)?;
|
||||
// Get value of it
|
||||
let value = regext::try_get_value::<String, _>(&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<Option<Vec<concept::Ext>>> {
|
||||
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::<concept::Ext>())
|
||||
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||
// 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<bool> {
|
||||
let key = self.open_view_for_getter(view)?;
|
||||
match regext::try_get_value::<String, _>(&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
|
||||
217
wfassoc/src/lowlevel/ext_key.rs
Normal file
217
wfassoc/src/lowlevel/ext_key.rs
Normal file
@@ -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<OpenedKey> {
|
||||
// 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<OpenedKey> {
|
||||
self.open_key(view.into(), OpenKeyPurpose::Read)
|
||||
}
|
||||
|
||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||
}
|
||||
|
||||
pub fn is_exist(&self, view: View) -> Result<bool> {
|
||||
let key = self.open_view_for_read(view)?.this_key;
|
||||
Ok(key.is_some())
|
||||
}
|
||||
|
||||
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||
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<RegKey> {
|
||||
self.open_view_for_read(view)?
|
||||
.this_key
|
||||
.ok_or(Error::InexistantKey)
|
||||
}
|
||||
|
||||
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||
self.open_scope_for_write(scope)?
|
||||
.this_key
|
||||
.ok_or(Error::InexistantKey)
|
||||
}
|
||||
|
||||
const NAMEOF_DEFAULT: &str = "";
|
||||
|
||||
pub fn get_default(&self, view: View) -> Result<Option<LosseProgId>> {
|
||||
let key = self.open_view_for_getter(view)?;
|
||||
// Get value of it
|
||||
let value = regext::try_get_value::<String, _>(&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<Option<Vec<LosseProgId>>> {
|
||||
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<bool> {
|
||||
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::<String, _>(&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
|
||||
171
wfassoc/src/lowlevel/progid_key.rs
Normal file
171
wfassoc/src/lowlevel/progid_key.rs
Normal file
@@ -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<OpenedKey> {
|
||||
// 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<OpenedKey> {
|
||||
self.open_key(view.into(), OpenKeyPurpose::Read)
|
||||
}
|
||||
|
||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||
}
|
||||
|
||||
pub fn is_exist(&self, view: View) -> Result<bool> {
|
||||
let key = self.open_view_for_read(view)?.this_key;
|
||||
Ok(key.is_some())
|
||||
}
|
||||
|
||||
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||
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<RegKey> {
|
||||
self.open_view_for_read(view)?
|
||||
.this_key
|
||||
.ok_or(Error::InexistantKey)
|
||||
}
|
||||
|
||||
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||
self.open_scope_for_write(scope)?
|
||||
.this_key
|
||||
.ok_or(Error::InexistantKey)
|
||||
}
|
||||
|
||||
const NAMEOF_DEFAULT: &str = "";
|
||||
|
||||
pub fn get_default(&self, view: View) -> Result<Option<StrResVariant>> {
|
||||
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<ShellVerb> {
|
||||
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<Option<StrResVariant>> {
|
||||
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<Option<IconResVariant>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn set_default_icon(
|
||||
&mut self,
|
||||
scope: Scope,
|
||||
icon: Option<&IconResVariant>,
|
||||
) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
Reference in New Issue
Block a user