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:
@ -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
18
wfassoc/src/wincmd.rs
Normal 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
|
||||
74
wfassoc/src/winreg_extra.rs
Normal file
74
wfassoc/src/winreg_extra.rs
Normal 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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user