diff --git a/Cargo.lock b/Cargo.lock index 23485fa..45316f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,6 +61,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + [[package]] name = "bumpalo" version = "3.19.0" @@ -119,6 +125,59 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "comfy-table" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "document-features", + "parking_lot", + "rustix", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "heck" version = "0.5.0" @@ -141,6 +200,33 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "libc" +version = "0.2.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.28" @@ -165,6 +251,29 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -183,6 +292,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.11.3" @@ -212,12 +330,37 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "strsim" version = "0.11.1" @@ -261,6 +404,18 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "utf8parse" version = "0.2.2" @@ -360,10 +515,33 @@ name = "wfassoc_exec" version = "0.1.0" dependencies = [ "clap", + "comfy-table", "thiserror", "wfassoc", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-link" version = "0.2.0" diff --git a/wfassoc/src/lib.rs b/wfassoc/src/lib.rs index 4c1cf91..07346f5 100644 --- a/wfassoc/src/lib.rs +++ b/wfassoc/src/lib.rs @@ -1,6 +1,9 @@ //! This crate provide utilities fetching and manilupating Windows file association. //! All code under crate are following Microsoft document: https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper +#[cfg(not(target_os = "windows"))] +compile_error!("Crate wfassoc is only supported on Windows."); + use regex::Regex; use std::fmt::Display; use std::path::PathBuf; @@ -8,9 +11,6 @@ use std::str::FromStr; use std::sync::LazyLock; use thiserror::Error as TeError; -#[cfg(not(target_os = "windows"))] -compile_error!("Crate wfassoc is only supported on Windows."); - // region: Error Types /// All possible error occurs in this crate. @@ -29,6 +29,7 @@ pub enum Error { // endregion /// The scope where wfassoc will operate. +#[derive(Debug, Copy, Clone)] pub enum Scope { /// Scope for current user. User, @@ -40,17 +41,17 @@ pub enum Scope { /// /// It usually means that checking whether current process is running as Administrator. /// Return true if it is, otherwise false. -/// +/// /// Reference: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership pub fn has_privilege() -> bool { - use windows_sys::core::BOOL; use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::Security::{ - SECURITY_NT_AUTHORITY, PSID, AllocateAndInitializeSid, CheckTokenMembership, FreeSid + AllocateAndInitializeSid, CheckTokenMembership, FreeSid, PSID, SECURITY_NT_AUTHORITY, }; - use windows_sys::Win32::System::SystemServices:: { - SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS + use windows_sys::Win32::System::SystemServices::{ + DOMAIN_ALIAS_RID_ADMINS, SECURITY_BUILTIN_DOMAIN_RID, }; + use windows_sys::core::BOOL; let nt_authority = SECURITY_NT_AUTHORITY.clone(); let mut administrators_group: PSID = PSID::default(); @@ -75,13 +76,8 @@ pub fn has_privilege() -> bool { } let mut is_member: BOOL = BOOL::default(); - let success: BOOL = unsafe { - CheckTokenMembership( - HANDLE::default(), - administrators_group, - &mut is_member, - ) - }; + let success: BOOL = + unsafe { CheckTokenMembership(HANDLE::default(), administrators_group, &mut is_member) }; unsafe { FreeSid(administrators_group); @@ -90,7 +86,7 @@ pub fn has_privilege() -> bool { if success == 0 { panic!("Win32 CheckTokenMembership() failed"); } - + is_member != 0 } @@ -113,6 +109,10 @@ impl FileExt { None => Err(ParseFileExtError::new()), } } + + pub fn query(&self, scope: Scope) -> Option { + FileExtAssoc::new(self, scope) + } } /// The error occurs when try parsing string into FileExt. @@ -140,6 +140,55 @@ impl FromStr for FileExt { } } +/// The association infomations of specific file extension. +pub struct FileExtAssoc { + default: String, + open_with_progids: Vec, +} + +impl FileExtAssoc { + fn new(file_ext: &FileExt, scope: Scope) -> Option { + let hk = match scope { + Scope::User => winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER), + Scope::System => winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE), + }; + let classes = hk + .open_subkey_with_flags("Software\\Classes", winreg::enums::KEY_READ) + .unwrap(); + let thisext = + match classes.open_subkey_with_flags(file_ext.to_string(), winreg::enums::KEY_READ) { + Ok(v) => v, + Err(e) => return None, + }; + + let default = thisext.get_value("").unwrap_or(String::new()); + let open_with_progids = if let Ok(progids) = + thisext.open_subkey_with_flags("OpenWithProdIds", winreg::enums::KEY_READ) + { + progids.enum_keys().map(|x| x.unwrap()).collect() + } else { + Vec::new() + }; + + Some(Self { + default, + open_with_progids, + }) + } + + pub fn get_default(&self) -> &str { + &self.default + } + + pub fn len_open_with_progid(&self) -> usize { + self.open_with_progids.len() + } + + pub fn iter_open_with_progids(&self) -> impl Iterator { + self.open_with_progids.iter().map(|s| s.as_str()) + } +} + // endregion // region: Executable Resource diff --git a/wfassoc_exec/Cargo.toml b/wfassoc_exec/Cargo.toml index 69b6cc7..ee2c440 100644 --- a/wfassoc_exec/Cargo.toml +++ b/wfassoc_exec/Cargo.toml @@ -10,3 +10,4 @@ license = "SPDX:MIT" thiserror = { workspace = true } wfassoc = { path="../wfassoc" } clap = { version="4.5.48", features=["derive"]} +comfy-table = "7.2.1" diff --git a/wfassoc_exec/src/main.rs b/wfassoc_exec/src/main.rs index 8a566d3..20195d7 100644 --- a/wfassoc_exec/src/main.rs +++ b/wfassoc_exec/src/main.rs @@ -1,7 +1,8 @@ use clap::{Parser, Subcommand}; +use comfy_table::Table; use std::process; -use wfassoc::{Error as WfError}; use thiserror::Error as TeError; +use wfassoc::{Error as WfError, FileExt, Scope}; // region: Basic Types @@ -10,7 +11,7 @@ use thiserror::Error as TeError; enum Error { /// Error from wfassoc core. #[error("{0}")] - Core(#[from] WfError) + Core(#[from] WfError), } /// Result type used in this executable. @@ -89,9 +90,40 @@ fn run_unregister(cli: Cli) -> Result<()> { } fn run_query(cli: Cli) -> Result<()> { + let exts = [ + ".jpg", ".jfif", ".gif", ".bmp", ".png", ".ico", ".jpeg", ".tif", ".tiff", ".webp", ".svg", + ".kra", ".xcf", ".avif", ".qoi", ".apng", ".exr", + ]; + + let mut table = Table::new(); + table.set_header(["Extension", "Default Open", "Open With"]); + for ext in exts.iter().map(|e| FileExt::new(e).unwrap()) { + if let Some(ext_assoc) = ext.query(Scope::User) { + if ext_assoc.len_open_with_progid() == 0 { + table.add_row([ext.to_string().as_str(), ext_assoc.get_default(), ""]); + } else { + for (i, open_with_entry) in ext_assoc.iter_open_with_progids().enumerate() { + if i == 0 { + table.add_row([ + ext.to_string().as_str(), + ext_assoc.get_default(), + open_with_entry, + ]); + } else { + table.add_row(["", "", open_with_entry]); + } + } + } + } else { + table.add_row([ext.to_string().as_str(), "", ""]); + } + } + + println!("{table}"); + // let program = Program::new(); // program.query()?; - println!("Has privilege: {}", wfassoc::has_privilege()); + //println!("Has privilege: {}", wfassoc::has_privilege()); Ok(()) }