1
0

feat: add Windows-specific modules and enhance program registration

- Add new modules for Windows command line handling (wincmd) and registry extensions (winreg_extra)
- Replace Manner struct with simple String and add identifier validation
- Update WFAdd function signature and add new WFStartup/WFShutdown functions
- Implement ExpandString wrapper for registry operations
This commit is contained in:
2025-10-18 23:11:33 +08:00
parent 6e9f91d0e6
commit 4f0f9670cb
4 changed files with 137 additions and 45 deletions

View File

@ -6,11 +6,14 @@ compile_error!("Crate wfassoc is only supported on Windows.");
pub mod assoc;
pub mod utilities;
pub mod wincmd;
pub mod winreg_extra;
use indexmap::{IndexMap, IndexSet};
use regex::Regex;
use std::ffi::OsStr;
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use thiserror::Error as TeError;
use winreg::RegKey;
use winreg::enums::{
@ -31,6 +34,8 @@ pub enum Error {
#[error("no administrative privilege")]
NoPrivilege,
#[error("given identifier \"{0}\" of application is invalid")]
BadIdentifier(String),
#[error("given full path to application is invalid")]
BadFullAppPath,
#[error("manner \"{0}\" is already registered")]
@ -118,33 +123,6 @@ impl From<Scope> for View {
// endregion
// region: Manner
/// The struct representing a program manner.
/// Manner usually mean the way to open files,
/// or more preciously, the consititution of command arguments passed to program.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Manner {
// TODO: use specialized WinArg instead.
argv: String,
}
impl Manner {
fn new(argv: &str) -> Self {
Self {
argv: argv.to_string(),
}
}
}
impl Display for Manner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.argv)
}
}
// endregion
// region: Program
/// The struct representing a complete program for registration and unregistration.
@ -155,7 +133,7 @@ pub struct Program {
/// The fully qualified path to the application.
full_path: PathBuf,
/// The collection holding all manners of this program.
manners: IndexSet<Manner>,
manners: IndexSet<String>,
/// The collection holding all file extensions supported by this program.
/// The key is file estension and value is its associated manner for opening it.
exts: IndexMap<assoc::Ext, Token>,
@ -168,16 +146,21 @@ impl Program {
/// If should only contain digits and alphabet chars,
/// and should not start with any digits.
/// For example, "MyApp" is okey but following names are not okey:
///
///
/// - `My App`
/// - `3DViewer`
/// - `我的Qt程序` (means "My Qt App" in English)
///
///
/// More preciously, `identifier` will be used as the vendor part of ProgId.
///
///
/// `full_path` is the fully qualified path to the application.
pub fn new(identifier: &str, full_path: &Path) -> Result<Self> {
// TODO: Add checker for identifier
// Check identifier
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9]*$").unwrap());
if !RE.is_match(identifier) {
return Err(Error::BadIdentifier(identifier.to_string()));
}
// Everything is okey, build self.
Ok(Self {
identifier: identifier.to_string(),
full_path: full_path.to_path_buf(),
@ -188,8 +171,9 @@ impl Program {
/// Add manner provided by this program.
pub fn add_manner(&mut self, manner: &str) -> Result<Token> {
// TODO: Use wincmd::CmdArgs instead of String.
// Create manner from string
let manner = Manner::new(manner);
let manner = manner.to_string();
// Backup a stringfied manner for error output.
let manner_str = manner.to_string();
// Insert manner.
@ -197,19 +181,19 @@ impl Program {
if self.manners.insert(manner) {
Ok(idx)
} else {
Err(Error::DupExt(manner_str))
Err(Error::DupManner(manner_str))
}
}
/// Get the reference to manner with given token.
pub fn get_manner(&self, token: Token) -> Option<&Manner> {
self.manners.get_index(token)
/// Get the string display of manner represented by given token
pub fn get_manner_str(&self, token: Token) -> Option<String> {
self.manners.get_index(token).map(|s| s.clone())
}
/// Add file extension supported by this program and its associated manner.
pub fn add_ext(&mut self, ext: &str, token: Token) -> Result<Token> {
// Check manner token
if let None = self.get_manner(token) {
if let None = self.manners.get_index(token) {
return Err(Error::InvalidAssocManner);
}
@ -226,9 +210,9 @@ impl Program {
}
}
/// Get the reference to file extension with given token.
pub fn get_ext(&self, token: Token) -> Option<&assoc::Ext> {
self.exts.get_index(token).map(|p| p.0)
/// Get the string display of file extension represented by given token
pub fn get_ext_str(&self, token: Token) -> Option<String> {
self.exts.get_index(token).map(|p| p.0.to_string())
}
}
@ -369,7 +353,7 @@ impl Program {
}
/// Remove this program from the default "open with" of given extension.
///
///
/// If the default "open with" of given extension is not our program,
/// this function do nothing.
pub fn unlink_ext(&self, ext: Token) -> Result<()> {

18
wfassoc/src/wincmd.rs Normal file
View File

@ -0,0 +1,18 @@
//! This module involve Windows command line stuff, like argument splittor and path,
//! because they are different with POSIX standard.
// region: Cmd Path
pub struct CmdPath {
}
// endregion
// region: Cmd Arguments
pub struct CmdArgs {
}
// endregion

View File

@ -0,0 +1,74 @@
//! This module expand `winreg` crate to make it more suit for this crate.
// region: Expand String
/// The struct basically is the alias of String, but make a slight difference with it,
/// to make they are different when use it with String as generic argument.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExpandString(String);
impl ExpandString {
/// Construct new ExpandString.
pub fn new(s: String) -> Self {
Self(s)
}
/// Create from &str
pub fn from_str(s: &str) -> Self {
Self(s.to_string())
}
/// Get reference to internal String.
pub fn as_str(&self) -> &str {
&self.0
}
/// Get mutable reference to internal String.
pub fn as_mut_str(&mut self) -> &mut String {
&mut self.0
}
/// Comsule self, return internal String.
pub fn into_inner(self) -> String {
self.0
}
}
// Implement Deref trait to make it can be used like &str
use std::ops::Deref;
impl Deref for ExpandString {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
// Implement DerefMut trait
use std::ops::DerefMut;
impl DerefMut for ExpandString {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
// Implement From/Into trait for explicit convertion
impl From<String> for ExpandString {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl From<ExpandString> for String {
fn from(expand: ExpandString) -> Self {
expand.into_inner()
}
}
impl From<&str> for ExpandString {
fn from(s: &str) -> Self {
Self::from_str(s)
}
}
// endregion

View File

@ -76,6 +76,22 @@ fn clear_last_error() {
// endregion
#[unsafe(no_mangle)]
pub extern "C" fn WFAdd(left: u32, right: u32) -> u32 {
left + right
pub extern "C" fn WFStartup() -> bool {
false
}
#[unsafe(no_mangle)]
pub extern "C" fn WFShutdown() -> bool {
false
}
#[unsafe(no_mangle)]
pub extern "C" fn WFGetLastError() -> *const c_char {
get_last_error()
}
#[unsafe(no_mangle)]
pub extern "C" fn WFAdd(left: u32, right: u32, rv: *mut u32) -> bool {
unsafe { *rv = left + right; }
return true;
}