From 9d3467ea263edd9428246f73ce93efda18d424e4 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sun, 17 May 2026 14:28:29 +0800 Subject: [PATCH] feat: add application subkey shell verb function --- wfassoc/src/lowlevel.rs | 90 +++++++++++++++++++++++++++++++++++-- wfassoc/src/win32/regext.rs | 24 ++++------ 2 files changed, 94 insertions(+), 20 deletions(-) diff --git a/wfassoc/src/lowlevel.rs b/wfassoc/src/lowlevel.rs index 633284b..36cc065 100644 --- a/wfassoc/src/lowlevel.rs +++ b/wfassoc/src/lowlevel.rs @@ -20,14 +20,16 @@ pub enum Error { #[error("registry operation error: {0}")] BadRegOp(#[from] std::io::Error), #[error("{0}")] - BadSoleSubKey(#[from] regext::OnlySubKeyError), - #[error("{0}")] UnexpectedBlankKey(#[from] regext::BlankPathError), #[error("{0}")] LoadIconRc(#[from] concept::LoadIconRcError), #[error("{0}")] LoadStrRc(#[from] concept::LoadStrRcError), + #[error("{0}")] + ParseVerb(#[from] concept::ParseVerbError), + #[error("{0}")] + ParseCmdLine(#[from] concept::ParseCmdLineError), } // endregion @@ -458,6 +460,9 @@ impl AppPathsKey { .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 = ""; /// @@ -592,6 +597,9 @@ impl ApplicationsKey { 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 @@ -606,13 +614,83 @@ impl ApplicationsKey { 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> { - todo!() + let key = self.open_view_for_getter(view)?; + + // Get shell subkey + let shell_key = match regext::try_open_subkey_with_flags( + &key, + Self::NAMEOF_SHELL_VERB_PART1, + KEY_READ, + )? { + 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, &verb_key_name, KEY_READ)? { + Some(key) => key, + None => return Ok(None), + }; + // Get command subkey. + let command_key = match regext::try_open_subkey_with_flags( + &verb_key, + Self::NAMEOF_SHELL_VERB_PART3, + KEY_READ, + )? { + 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> { - todo!() + let key = self.open_scope_for_setter(scope)?; + + match sv { + Some(sv) => { + // Create shell subkey + let (shell_key, _) = key.create_subkey_with_flags( + Self::NAMEOF_SHELL_VERB_PART1, + KEY_READ | KEY_WRITE, + )?; + // Create verb key + let (verb_key, _) = shell_key + .create_subkey_with_flags(sv.get_verb().inner(), KEY_READ | KEY_WRITE)?; + // Create command key + let (command_key, _) = verb_key.create_subkey_with_flags( + Self::NAMEOF_SHELL_VERB_PART3, + KEY_READ | KEY_WRITE, + )?; + // 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"; @@ -771,6 +849,8 @@ impl ExtKey { } // YYC MARK: + // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes + // // 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. @@ -916,6 +996,8 @@ impl ProgIdKey { } // 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. diff --git a/wfassoc/src/win32/regext.rs b/wfassoc/src/win32/regext.rs index 02ea06a..50edbf0 100644 --- a/wfassoc/src/win32/regext.rs +++ b/wfassoc/src/win32/regext.rs @@ -61,34 +61,26 @@ pub fn try_get_value>( } } -/// The error occurs when fetching the name of only subkey. -#[derive(Debug, TeError)] -pub enum OnlySubKeyError { - #[error("registry operation error: {0}")] - Io(#[from] std::io::Error), - - #[error("there is no any subkey in given RegKey")] - NoSubKey, - #[error("there is more than one subkey in given RegKey")] - TooManySubKey, -} - /// Get the name of only subkey in given key. /// +/// If there is only one subkey in given key, the return value is its name. +/// If there is no any subkey, or has multiple subkeys, return None instead. +/// If error occurs when fetching data, return Err(_). +/// /// This is usually used for ShellVerb fetching. -pub fn get_sole_subkey_name(regkey: &RegKey) -> Result { +pub fn get_sole_subkey_name(regkey: &RegKey) -> std::io::Result> { let mut subkey_enumerator = regkey.enum_keys(); // Get first one. let rv = match subkey_enumerator.next() { Some(key) => key?, - None => return Err(OnlySubKeyError::NoSubKey), + None => return Ok(None), }; // Check whether there is second one. match subkey_enumerator.next() { - Some(_) => Err(OnlySubKeyError::TooManySubKey), - None => Ok(rv), + Some(_) => Ok(None), + None => Ok(Some(rv)), } }