From e8ff9a3e4fb407fa2a2aa99b78e702c7b3626d14 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Wed, 29 Apr 2026 13:11:12 +0800 Subject: [PATCH] feat: write some lowlevel code --- wfassoc-exec/src/runner.rs | 10 +- wfassoc/src/highlevel.rs | 12 ++ wfassoc/src/lowlevel.rs | 318 +++++++++++++++++++++++++++++++++---- 3 files changed, 306 insertions(+), 34 deletions(-) diff --git a/wfassoc-exec/src/runner.rs b/wfassoc-exec/src/runner.rs index a6fe8ae..2749446 100644 --- a/wfassoc-exec/src/runner.rs +++ b/wfassoc-exec/src/runner.rs @@ -57,7 +57,10 @@ fn run_status(program: wfassoc::Program, view: wfassoc::View) -> Result<()> { fn run_ext_link(program: wfassoc::Program, scope: wfassoc::Scope, exts: Vec) -> Result<()> { let exts = stringified_exts_to_indices(&program, exts)?; - todo!() + for index in exts { + program.link_ext(scope, index)?; + } + Ok(()) } fn run_ext_unlink( @@ -66,7 +69,10 @@ fn run_ext_unlink( exts: Vec, ) -> Result<()> { let exts = stringified_exts_to_indices(&program, exts)?; - todo!() + for index in exts { + program.link_ext(scope, index)?; + } + Ok(()) } fn run_ext_list( diff --git a/wfassoc/src/highlevel.rs b/wfassoc/src/highlevel.rs index cba00a1..1b51cd0 100644 --- a/wfassoc/src/highlevel.rs +++ b/wfassoc/src/highlevel.rs @@ -263,6 +263,18 @@ impl Program { pub fn is_registered(&self, view: View) -> Result { todo!() } + + pub fn link_ext(&self, scope: Scope, index: usize) -> Result<(), ProgramError> { + todo!() + } + + pub fn unlink_ext(&self, scope: Scope, index: usize) -> Result<(), ProgramError> { + todo!() + } + + pub fn ext_status(&self, view: View, index: usize) -> Result<(), ProgramError> { + todo!() + } } // endregion diff --git a/wfassoc/src/lowlevel.rs b/wfassoc/src/lowlevel.rs index 6df3987..3260f3c 100644 --- a/wfassoc/src/lowlevel.rs +++ b/wfassoc/src/lowlevel.rs @@ -1,20 +1,25 @@ -use crate::win32; +use crate::win32::{concept, regext, utilities}; 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, +}; // region: Error Type /// Error occurs in this module. #[derive(Debug, TeError)] pub enum Error { - #[error("can not perform this operation because lack essential privilege.")] + #[error("can not perform this operation because lack essential privilege")] NoPrivilege, - #[error("{0}")] + #[error("given registry key is inexistant")] + InexistantKey, + #[error("registry operation error: {0}")] BadRegOp(#[from] std::io::Error), #[error("{0}")] - UnexpectedBlankKey(#[from] win32::regext::BlankPathError), + UnexpectedBlankKey(#[from] regext::BlankPathError), } // endregion @@ -78,7 +83,7 @@ impl Scope { /// Check whether we have enough privilege when operating in current scope. /// If we have, simply return, otherwise return error. fn check_privilege(&self) -> Result<(), Error> { - if matches!(self, Self::System if !win32::utilities::has_privilege()) { + if matches!(self, Self::System if !utilities::has_privilege()) { Err(Error::NoPrivilege) } else { Ok(()) @@ -88,20 +93,161 @@ impl Scope { // endregion +// region: Utilities + +/// The purpose of opening this key. +enum OpenScopePurpose { + /// Only read something. + Read, + /// Need to write something. + ReadWrite, +} + +/// Internal used struct representing the result of opening scope or view. +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 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct AppPathsKey { - key: win32::concept::FileName, + key_name: concept::FileName, } impl AppPathsKey { - pub fn new(inner: win32::concept::FileName) -> Self { - Self { key: inner } + pub fn new(inner: concept::FileName) -> Self { + Self { key_name: inner } } - pub fn inner(&self) -> &win32::concept::FileName { - &self.key + pub fn inner(&self) -> &concept::FileName { + &self.key_name + } +} + +impl AppPathsKey { + const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths"; + + fn open_scope(&self, scope: Scope, purpose: OpenScopePurpose) -> Result { + // check privilege + if let OpenScopePurpose::ReadWrite = purpose { + scope.check_privilege()?; + } + // Fetch the permission + let perms = match purpose { + OpenScopePurpose::Read => KEY_READ, + OpenScopePurpose::ReadWrite => KEY_READ | KEY_WRITE, + }; + + // get the root key + let hk = match scope { + Scope::User => RegKey::predef(HKEY_CURRENT_USER), + Scope::System => RegKey::predef(HKEY_LOCAL_MACHINE), + }; + // navigate to App Paths + let app_paths = hk.open_subkey_with_flags(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_scope(scope, OpenScopePurpose::Read) + } + + fn open_scope_for_write(&self, scope: Scope) -> Result { + self.open_scope(scope, OpenScopePurpose::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())?, + KEY_READ | KEY_WRITE, + )?; + 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) + } + + const NAMEOF_PATH_TO_APPLICATION: &str = ""; + + pub fn get_path_to_application(&self, scope: Scope) -> Result { + let key = self.open_scope_for_getter(scope)?; + Ok(key.get_value(Self::NAMEOF_PATH_TO_APPLICATION)?) + } + + pub fn set_path_to_application(&mut self, scope: Scope, value: &str) -> Result<(), Error> { + let key = self.open_scope_for_setter(scope)?; + key.set_value(Self::NAMEOF_PATH_TO_APPLICATION, &value)?; + Ok(()) + } + + const NAMEOF_APPLICATION_DIRECTORY: &str = "Path"; + + pub fn get_application_directory(&self, scope: Scope) -> Result { + let key = self.open_scope_for_getter(scope)?; + Ok(key.get_value(Self::NAMEOF_APPLICATION_DIRECTORY)?) + } + + pub fn set_application_directory(&mut self, scope: Scope, value: &str) -> Result<(), Error> { + let key = self.open_scope_for_setter(scope)?; + key.set_value(Self::NAMEOF_APPLICATION_DIRECTORY, &value)?; + Ok(()) } } @@ -111,16 +257,120 @@ impl AppPathsKey { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ApplicationsKey { - key: win32::concept::FileName, + key_name: concept::FileName, } impl ApplicationsKey { - pub fn new(inner: win32::concept::FileName) -> Self { - Self { key: inner } + pub fn new(inner: concept::FileName) -> Self { + Self { key_name: inner } } - pub fn inner(&self) -> &win32::concept::FileName { - &self.key + 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_scope(&self, scope: Scope, purpose: OpenScopePurpose) -> Result { + // check privilege + if let OpenScopePurpose::ReadWrite = purpose { + scope.check_privilege()?; + } + // Fetch the permission + let perms = match purpose { + OpenScopePurpose::Read => KEY_READ, + OpenScopePurpose::ReadWrite => KEY_READ | KEY_WRITE, + }; + + // get the root key + let hk = match scope { + Scope::User => RegKey::predef(HKEY_CURRENT_USER), + Scope::System => RegKey::predef(HKEY_LOCAL_MACHINE), + }; + // navigate to Applications + let applications = hk.open_subkey_with_flags(Self::FULL_APPLICATIONS, perms)?; + // open extension 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(&self, view: View) -> Result { + // define the permission + let perms = KEY_READ; + + // navigate to extension container + let hk = match view { + View::User => RegKey::predef(HKEY_CURRENT_USER), + View::System => RegKey::predef(HKEY_LOCAL_MACHINE), + View::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT), + }; + let applications = match view { + View::User | View::System => { + hk.open_subkey_with_flags(Self::FULL_APPLICATIONS, perms)? + } + View::Hybrid => hk.open_subkey_with_flags(Self::PARTIAL_APPLICATIONS, perms)?, + }; + // open extension 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_view(view) + } + + fn open_scope_for_write(&self, scope: Scope) -> Result { + self.open_scope(scope, OpenScopePurpose::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())?, + KEY_READ | KEY_WRITE, + )?; + 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(()) + } + + 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) } } @@ -130,10 +380,12 @@ impl ApplicationsKey { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ExtKey { - ext: win32::concept::Ext, + ext: concept::Ext, } impl ExtKey { + const CLASSES: &str = "Software\\Classes"; + fn open_scope(&self, scope: Scope) -> Result, Error> { use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE}; @@ -145,15 +397,15 @@ impl ExtKey { Scope::System => RegKey::predef(HKEY_LOCAL_MACHINE), }; // navigate to classes - let classes = hk.open_subkey_with_flags("Software\\Classes", KEY_READ | KEY_WRITE)?; + let classes = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ | KEY_WRITE)?; // open extension key if possible - let thisext = win32::regext::try_open_subkey_with_flags( + let this_ext = regext::try_open_subkey_with_flags( &classes, - win32::regext::blank_path_guard(self.ext.dotted_inner())?, + regext::blank_path_guard(self.ext.dotted_inner())?, KEY_READ | KEY_WRITE, )?; // okey - Ok(thisext) + Ok(this_ext) } fn open_view(&self, view: View) -> Result, Error> { @@ -166,30 +418,26 @@ impl ExtKey { View::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT), }; let classes = match view { - View::User | View::System => { - hk.open_subkey_with_flags("Software\\Classes", KEY_READ)? - } + View::User | View::System => hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?, View::Hybrid => hk.open_subkey_with_flags("", KEY_READ)?, }; // open extension key if possible - let thisext = win32::regext::try_open_subkey_with_flags( + let this_ext = regext::try_open_subkey_with_flags( &classes, - win32::regext::blank_path_guard(self.ext.dotted_inner())?, + regext::blank_path_guard(self.ext.dotted_inner())?, KEY_READ, )?; // okey - Ok(thisext) + Ok(this_ext) } - - } impl ExtKey { - pub fn new(inner: win32::concept::Ext) -> Self { + pub fn new(inner: concept::Ext) -> Self { Self { ext: inner } } - pub fn inner(&self) -> &win32::concept::Ext { + pub fn inner(&self) -> &concept::Ext { &self.ext } } @@ -208,7 +456,7 @@ impl ExtKey { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum LosseProgId { Plain(String), - Strict(win32::concept::ProgId), + Strict(concept::ProgId), } impl Display for LosseProgId { @@ -223,7 +471,7 @@ impl Display for LosseProgId { impl From<&str> for LosseProgId { fn from(s: &str) -> Self { // match it for standard ProgId first - if let Ok(v) = win32::concept::ProgId::from_str(s) { + if let Ok(v) = concept::ProgId::from_str(s) { Self::Strict(v) } else { // fallback with other @@ -232,6 +480,12 @@ impl From<&str> for LosseProgId { } } +impl From for LosseProgId { + fn from(value: concept::ProgId) -> Self { + Self::Strict(value) + } +} + // endregion // region: ProgId Key