Remove C++ shortcut code

This commit is contained in:
Caelan Sayler
2023-12-22 11:14:08 +00:00
parent 79bf338efd
commit d8459636f6
8 changed files with 893 additions and 135 deletions

86
src/Rust/Cargo.lock generated
View File

@@ -91,9 +91,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.75"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
[[package]]
name = "as-slice"
@@ -215,7 +215,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.41",
"syn 2.0.42",
]
[[package]]
@@ -229,10 +229,10 @@ name = "clowd_squirrel"
version = "0.1.0"
dependencies = [
"anyhow",
"cc",
"chrono",
"clap",
"codesign-verify",
"com",
"derivative",
"file-rotate",
"fs_extra",
@@ -253,6 +253,7 @@ dependencies = [
"strum",
"ureq",
"wait-timeout",
"widestring",
"windows",
"windows-sys 0.52.0",
"winres",
@@ -284,6 +285,37 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "com"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a30a2b2a013da986dc5cc3eda3d19c0d59d53f835be1b2356eb8d00f000c793"
dependencies = [
"com_macros",
]
[[package]]
name = "com_macros"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7606b05842fea68ddcc89e8053b8860ebcb2a0ba8d6abfe3a148e5d5a8d3f0c1"
dependencies = [
"com_macros_support",
"proc-macro2",
"syn 1.0.109",
]
[[package]]
name = "com_macros_support"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97e9a6d20f4ac8830e309a455d7e9416e65c6af5a97c88c55fbb4c2012e107da"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -320,9 +352,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f"
dependencies = [
"cfg-if",
]
@@ -599,9 +631,9 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "memmap2"
version = "0.9.0"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92"
dependencies = [
"libc",
]
@@ -728,7 +760,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.41",
"syn 2.0.42",
]
[[package]]
@@ -766,9 +798,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pkg-config"
version = "0.3.27"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
[[package]]
name = "png"
@@ -806,9 +838,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
dependencies = [
"unicode-ident",
]
@@ -988,7 +1020,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.41",
"syn 2.0.42",
]
[[package]]
@@ -1065,7 +1097,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.41",
"syn 2.0.42",
]
[[package]]
@@ -1081,9 +1113,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.41"
version = "2.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
dependencies = [
"proc-macro2",
"quote",
@@ -1114,9 +1146,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
dependencies = [
"deranged",
"itoa",
@@ -1136,9 +1168,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
dependencies = [
"time-core",
]
@@ -1273,7 +1305,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.41",
"syn 2.0.42",
"wasm-bindgen-shared",
]
@@ -1295,7 +1327,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.41",
"syn 2.0.42",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -1312,6 +1344,12 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]]
name = "widestring"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
[[package]]
name = "winapi"
version = "0.3.9"
@@ -1581,7 +1619,7 @@ dependencies = [
[[package]]
name = "winsafe"
version = "0.0.18"
source = "git+https://github.com/caesay/winsafe.git?branch=cs/persistfile-and-lnk#1d03f22443547441ed08a16081fa4b5d60a45da4"
source = "git+https://github.com/caesay/winsafe.git?branch=cs/only-ipersistfile#e9aadd0b7d8337364865039bf098e73be40d11a1"
[[package]]
name = "xml"

View File

@@ -36,7 +36,7 @@ memmap2 = "0.9"
pretty-bytes-rust = "0.3"
xml = "0.8"
os_info = { git = "https://github.com/stanislav-tkach/os_info.git", branch = "master", default-features = false } # public releases don't yet have processor arch info
winsafe = { git = "https://github.com/caesay/winsafe.git", branch = "cs/persistfile-and-lnk", features = [
winsafe = { git = "https://github.com/caesay/winsafe.git", branch = "cs/only-ipersistfile", features = [
"kernel",
"version",
"user",
@@ -90,8 +90,9 @@ remove_dir_all = { git = "https://github.com/caesay/remove_dir_all.git", feature
glob = "0.3"
normpath = "1.0.1"
codesign-verify = { git = "https://github.com/caesay/codesign-verify-rs.git" }
com = "0.2.0"
widestring = "1.0.2"
[build-dependencies]
winres = "0.1"
semver = "1.0"
cc = "1.0"
semver = "1.0"

View File

@@ -2,13 +2,13 @@ use semver;
extern crate winres;
fn main() {
cc::Build::new()
.cpp(true)
.file("src/platform/windows/shortcuts.cpp")
.define("UNICODE", None)
.define("_UNICODE", None)
.compile("lib_shortcuts");
println!("cargo:rerun-if-changed=src/platform/windows/shortcuts.cpp");
// cc::Build::new()
// .cpp(true)
// .file("src/platform/windows/shortcuts.cpp")
// .define("UNICODE", None)
// .define("_UNICODE", None)
// .compile("lib_shortcuts");
// println!("cargo:rerun-if-changed=src/platform/windows/shortcuts.cpp");
let ver = env!("CARGO_PKG_VERSION");
let ver = semver::Version::parse(&ver).unwrap();

View File

@@ -48,7 +48,7 @@ pub fn show_restart_required(app: &Manifest) {
}
let hwnd = w::HWND::GetDesktopWindow();
let _ = w::task_dlg::warn(
let _ = warn(
&hwnd,
format!("{} Setup {}", app.title, app.version).as_str(),
Some("Restart Required"),
@@ -62,7 +62,7 @@ pub fn show_missing_dependencies_dialog(app: &Manifest, depedency_string: &str)
}
let hwnd = w::HWND::GetDesktopWindow();
w::task_dlg::ok_cancel(
ok_cancel(
&hwnd,
format!("{} Setup {}", app.title, app.version).as_str(),
Some(format!("{} has missing system dependencies.", app.title).as_str()),
@@ -157,3 +157,130 @@ extern "system" fn task_dialog_callback(_: w::HWND, msg: co::TDN, _: usize, _: i
}
return co::HRESULT::S_OK; // close dialog on button press
}
pub fn error(
hparent: &w::HWND,
title: &str,
header: Option<&str>,
body: &str,
) -> w::HrResult<()>
{
generate(hparent, title, header, body, None,
co::TDCBF::OK, co::TD_ICON::ERROR)
.map(|_| ())
}
pub fn warn(
hparent: &w::HWND,
title: &str,
header: Option<&str>,
body: &str,
) -> w::HrResult<()>
{
generate(hparent, title, header, body, None,
co::TDCBF::OK, co::TD_ICON::WARNING)
.map(|_| ())
}
pub fn info(
hparent: &w::HWND,
title: &str,
header: Option<&str>,
body: &str,
) -> w::HrResult<()>
{
generate(hparent, title, header, body, None,
co::TDCBF::OK, co::TD_ICON::INFORMATION)
.map(|_| ())
}
#[must_use]
pub fn ok_cancel(
hparent: &w::HWND,
title: &str,
header: Option<&str>,
body: &str,
ok_text: Option<&str>,
) -> w::HrResult<bool>
{
let mut btns = co::TDCBF::CANCEL;
if ok_text.is_none() {
btns |= co::TDCBF::OK;
}
generate(hparent, title, header, body, ok_text,
btns, co::TD_ICON::WARNING)
.map(|dlg_id| dlg_id == co::DLGID::OK)
}
pub fn yes_no(
hparent: &w::HWND,
title: &str,
header: Option<&str>,
body: &str,
) -> w::HrResult<bool>
{
generate(hparent, title, header, body, None,
co::TDCBF::YES | co::TDCBF::NO, co::TD_ICON::WARNING)
.map(|dlg_id| dlg_id == co::DLGID::YES)
}
pub fn yes_no_cancel(
hparent: &w::HWND,
title: &str,
header: Option<&str>,
body: &str,
) -> w::HrResult<co::DLGID>
{
generate(hparent, title, header, body, None,
co::TDCBF::YES | co::TDCBF::NO | co::TDCBF::CANCEL, co::TD_ICON::WARNING)
}
fn generate(
hparent: &w::HWND,
title: &str,
header: Option<&str>,
body: &str,
ok_text: Option<&str>,
btns: co::TDCBF,
ico: co::TD_ICON,
) -> w::HrResult<co::DLGID>
{
let mut ok_text_buf = WString::from_opt_str(ok_text);
let mut custom_btns = if ok_text.is_some() {
let mut td_btn = w::TASKDIALOG_BUTTON::default();
td_btn.set_nButtonID(co::DLGID::OK.into());
td_btn.set_pszButtonText(Some(&mut ok_text_buf));
let mut custom_btns = Vec::with_capacity(1);
custom_btns.push(td_btn);
custom_btns
} else {
Vec::<w::TASKDIALOG_BUTTON>::default()
};
let mut tdc = w::TASKDIALOGCONFIG::default();
tdc.hwndParent = unsafe { hparent.raw_copy() };
tdc.dwFlags = co::TDF::ALLOW_DIALOG_CANCELLATION | co::TDF::POSITION_RELATIVE_TO_WINDOW;
tdc.dwCommonButtons = btns;
tdc.set_pszMainIcon(w::IconIdTdicon::Tdicon(ico));
if ok_text.is_some() {
tdc.set_pButtons(Some(&mut custom_btns));
}
let mut title_buf = WString::from_str(title);
tdc.set_pszWindowTitle(Some(&mut title_buf));
let mut header_buf = WString::from_opt_str(header);
if header.is_some() {
tdc.set_pszMainInstruction(Some(&mut header_buf));
}
let mut body_buf = WString::from_str(body);
tdc.set_pszContent(Some(&mut body_buf));
w::TaskDialogIndirect(&tdc, None)
.map(|(dlg_id, _)| dlg_id)
}

View File

@@ -0,0 +1,504 @@
use std::path::{Path, PathBuf};
use std::{ffi::c_void, os::raw::c_int};
use widestring::U16CString;
use com::{
com_interface,
runtime::create_instance,
interfaces::iunknown::IUnknown,
sys::{HRESULT, IID, FAILED},
};
// unfortunately, paths/descriptions/etc. of ShellLinks are all
// constrained to `MAX_PATH`.
// TODO: figure out how this works with LongPathAware?
const MAX_PATH: usize = 260;
const INFOTIPSIZE: usize = 1024;
#[derive(Debug)]
pub struct SimpleError(String);
impl SimpleError {
pub unsafe fn ret(self, p: *mut *mut XString) {
let xs = XString { data: self.0 };
*p = Box::into_raw(Box::new(xs));
}
}
impl<T> From<T> for SimpleError
where
T: std::error::Error,
{
fn from(e: T) -> Self {
Self(format!("{}", e))
}
}
struct NulTerminatedUtf16(Vec<u16>);
impl NulTerminatedUtf16 {
fn to_u16_c_string(self, _method: &str) -> Result<U16CString, SimpleError> {
let res = U16CString::from_vec_truncate(self.0);
Ok(res)
}
fn to_string(self, method: &str) -> Result<String, SimpleError> {
let res = self
.to_u16_c_string(method)?
.to_string()
.map_err(|_| SimpleError(format!("invalid utf-16 data in {}", method)))?;
Ok(res)
}
fn to_path_buf(self, method: &str) -> Result<PathBuf, SimpleError> {
let res = self.to_u16_c_string(method)?;
let res = res.to_os_string();
Ok(res.into())
}
}
pub trait IntoStringSimple {
fn into_string_simple(self, method: &str) -> Result<String, SimpleError>;
}
impl IntoStringSimple for PathBuf {
fn into_string_simple(self, method: &str) -> Result<String, SimpleError> {
let s = self.into_os_string();
let s = s
.into_string()
.map_err(|_| SimpleError(format!("invalid utf-16 data in {} result", method)))?;
Ok(s)
}
}
trait Checked
where
Self: Sized + Copy,
{
fn check(self, method: &str) -> Result<(), SimpleError>;
fn format(self, method: &str) -> SimpleError;
}
impl Checked for HRESULT {
fn check(self, method: &str) -> Result<(), SimpleError> {
if FAILED(self) {
Err(self.format(method))
} else {
Ok(())
}
}
fn format(self, method: &str) -> SimpleError {
SimpleError(format!(
"{} returned system error {} (0x{:x})",
method, self, self
))
}
}
#[repr(i32)]
pub enum ReturnCode {
Ok = 0,
Error = 1,
}
/// Handle for an IShellLink instance
pub struct ShellLink {
instance: com::ComRc<dyn IShellLinkW>,
}
/// Exchange string: passes Rust strings to C
pub struct XString {
data: String,
}
impl From<String> for XString {
fn from(data: String) -> Self {
Self { data }
}
}
impl ShellLink {
pub fn new() -> Result<Self, SimpleError> {
let instance = create_instance::<dyn IShellLinkW>(&CLSID_SHELL_LINK)
.map_err(|hr| hr.check("create_instance<IShellLinkW>").unwrap_err())?;
Ok(Self {
instance,
})
}
pub fn load<P: AsRef<Path>>(&self, path: P) -> Result<(), SimpleError> {
let pf = self.instance.get_interface::<dyn IPersistFile>().unwrap();
let path = U16CString::from_os_str(path.as_ref())?;
unsafe { pf.load(path.as_ptr(), STGM_READ) }.check("IPersistFile::Load")?;
Ok(())
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), SimpleError> {
let pf = self.instance.get_interface::<dyn IPersistFile>().unwrap();
let path = U16CString::from_os_str(path.as_ref())?;
unsafe { pf.save(path.as_ptr(), false) }.check("IPersistFile::Save")?;
Ok(())
}
pub fn get_path(&self) -> Result<PathBuf, SimpleError> {
let mut v = vec![0u16; MAX_PATH + 1];
unsafe {
self.instance.get_path(
v.as_mut_ptr(),
v.len() as _,
std::ptr::null_mut(),
SLGP_RAWPATH,
)
}
.check("IShellLinkW::GetPath")?;
Ok(NulTerminatedUtf16(v).to_path_buf("IShellLinkW::GetPath")?)
}
/// Sets the shell link's path (what it points to)
pub fn set_path<P: AsRef<Path>>(&self, path: P) -> Result<(), SimpleError> {
let path = U16CString::from_os_str(path.as_ref())?;
unsafe { self.instance.set_path(path.as_ptr()) }.check("IShellLinkW::SetPath")?;
Ok(())
}
pub fn get_arguments(&self) -> Result<String, SimpleError> {
let mut v = vec![0u16; INFOTIPSIZE + 1];
unsafe { self.instance.get_arguments(v.as_mut_ptr(), v.len() as _) }
.check("IShellLinkW::GetArguments")?;
Ok(NulTerminatedUtf16(v).to_string("IShellLinkW::GetArguments")?)
}
pub fn set_arguments(&self, path: &str) -> Result<(), SimpleError> {
let path = U16CString::from_str(path)?;
unsafe { self.instance.set_arguments(path.as_ptr()) }.check("IShellLinkW::SetArguments")?;
Ok(())
}
pub fn get_description(&self) -> Result<String, SimpleError> {
let mut v = vec![0u16; INFOTIPSIZE + 1];
unsafe { self.instance.get_description(v.as_mut_ptr(), v.len() as _) }
.check("IShellLinkW::GetDescription")?;
Ok(NulTerminatedUtf16(v).to_string("IShellLinkW::GetDescription")?)
}
pub fn set_description(&self, path: &str) -> Result<(), SimpleError> {
let path = U16CString::from_str(path)?;
unsafe { self.instance.set_description(path.as_ptr()) }
.check("IShellLinkW::SetDescription")?;
Ok(())
}
pub fn get_working_directory(&self) -> Result<PathBuf, SimpleError> {
let mut v = vec![0u16; INFOTIPSIZE + 1];
unsafe {
self.instance
.get_working_directory(v.as_mut_ptr(), v.len() as _)
}
.check("IShellLinkW::GetWorkingDirectory")?;
Ok(NulTerminatedUtf16(v).to_path_buf("IShellLinkW::GetWorkingDirectory")?)
}
pub fn set_working_directory<P: AsRef<Path>>(&self, path: P) -> Result<(), SimpleError> {
let path = U16CString::from_os_str(path.as_ref().as_os_str())?;
unsafe { self.instance.set_working_directory(path.as_ptr()) }
.check("IShellLinkW::SetWorkingDirectory")?;
Ok(())
}
pub fn get_icon_location(&self) -> Result<(PathBuf, i32), SimpleError> {
let mut v = vec![0u16; INFOTIPSIZE + 1];
let mut index = 0;
unsafe {
self.instance
.get_icon_location(v.as_mut_ptr(), v.len() as _, &mut index)
}
.check("IShellLinkW::GetIconLocation")?;
let s = NulTerminatedUtf16(v).to_path_buf("IShellLinkW::GetIconLocation")?;
Ok((s, index as _))
}
pub fn set_icon_location<P: AsRef<Path>>(
&self,
path: P,
index: i32,
) -> Result<(), SimpleError> {
let path = U16CString::from_os_str(path.as_ref().as_os_str())?;
unsafe { self.instance.set_icon_location(path.as_ptr(), index as _) }
.check("IShellLinkW::SetIconLocation")?;
Ok(())
}
}
macro_rules! checked {
($x: expr, $p_err: ident) => {
match $x {
Err(e) => {
SimpleError::from(e).ret($p_err);
return ReturnCode::Error;
}
Ok(x) => x,
}
};
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_new(
p_link: *mut *mut ShellLink,
p_err: *mut *mut XString,
) -> ReturnCode {
let sl = checked!(ShellLink::new(), p_err);
*p_link = Box::into_raw(Box::new(sl));
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_load(
link: *mut ShellLink,
path_data: *mut u8,
path_len: usize,
p_err: *mut *mut XString,
) -> ReturnCode {
let v = std::slice::from_raw_parts(path_data, path_len);
let path = checked!(std::str::from_utf8(v), p_err);
checked!((*link).load(path), p_err);
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_save(
link: *mut ShellLink,
path_data: *mut u8,
path_len: usize,
p_err: *mut *mut XString,
) -> ReturnCode {
let v = std::slice::from_raw_parts(path_data, path_len);
let path = checked!(std::str::from_utf8(v), p_err);
checked!((*link).save(path), p_err);
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_get_path(
link: *mut ShellLink,
path: *mut *mut XString,
p_err: *mut *mut XString,
) -> ReturnCode {
let res = checked!((*link).get_path(), p_err);
let res: String = checked!(res.into_string_simple("shell_link_get_path"), p_err);
*path = Box::into_raw(Box::new(res.into()));
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_set_path(
link: *mut ShellLink,
path_data: *mut u8,
path_len: usize,
p_err: *mut *mut XString,
) -> ReturnCode {
let v = std::slice::from_raw_parts(path_data, path_len);
let path = checked!(std::str::from_utf8(v), p_err);
checked!((*link).set_path(path), p_err);
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_get_arguments(
link: *mut ShellLink,
arguments: *mut *mut XString,
p_err: *mut *mut XString,
) -> ReturnCode {
let res = checked!((*link).get_arguments(), p_err);
*arguments = Box::into_raw(Box::new(XString::from(res)));
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_set_arguments(
link: *mut ShellLink,
arguments_data: *mut u8,
arguments_len: usize,
p_err: *mut *mut XString,
) -> ReturnCode {
let v = std::slice::from_raw_parts(arguments_data, arguments_len);
let arguments = checked!(std::str::from_utf8(v), p_err);
checked!((*link).set_arguments(arguments), p_err);
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_get_description(
link: *mut ShellLink,
description: *mut *mut XString,
p_err: *mut *mut XString,
) -> ReturnCode {
let res = checked!((*link).get_description(), p_err);
*description = Box::into_raw(Box::new(XString::from(res)));
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_set_description(
link: *mut ShellLink,
description_data: *mut u8,
description_len: usize,
p_err: *mut *mut XString,
) -> ReturnCode {
let v = std::slice::from_raw_parts(description_data, description_len);
let description = checked!(std::str::from_utf8(v), p_err);
checked!((*link).set_description(description), p_err);
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_get_working_directory(
link: *mut ShellLink,
working_directory: *mut *mut XString,
p_err: *mut *mut XString,
) -> ReturnCode {
let res = checked!((*link).get_working_directory(), p_err);
let res: String = checked!(
res.into_string_simple("shell_link_get_working_directory"),
p_err
);
*working_directory = Box::into_raw(Box::new(res.into()));
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_set_working_directory(
link: *mut ShellLink,
working_directory_data: *mut u8,
working_directory_len: usize,
p_err: *mut *mut XString,
) -> ReturnCode {
let v = std::slice::from_raw_parts(working_directory_data, working_directory_len);
let working_directory = checked!(std::str::from_utf8(v), p_err);
checked!((*link).set_working_directory(working_directory), p_err);
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_get_icon_location(
link: *mut ShellLink,
icon_location: *mut *mut XString,
icon_index: *mut i32,
p_err: *mut *mut XString,
) -> ReturnCode {
let (res, res_index) = checked!((*link).get_icon_location(), p_err);
let res = checked!(
res.into_string_simple("shell_link_get_icon_location"),
p_err
);
*icon_location = Box::into_raw(Box::new(res.into()));
*icon_index = res_index;
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_set_icon_location(
link: *mut ShellLink,
icon_location_data: *mut u8,
icon_location_len: usize,
icon_index: i32,
p_err: *mut *mut XString,
) -> ReturnCode {
let v = std::slice::from_raw_parts(icon_location_data, icon_location_len);
let icon_location = checked!(std::str::from_utf8(v), p_err);
checked!((*link).set_icon_location(icon_location, icon_index), p_err);
ReturnCode::Ok
}
#[no_mangle]
pub unsafe extern "C" fn shell_link_free(link: *mut ShellLink) {
drop(Box::from_raw(link))
}
#[no_mangle]
pub unsafe extern "C" fn xstring_free(xs: *mut XString) {
drop(Box::from_raw(xs))
}
#[no_mangle]
pub unsafe extern "C" fn xstring_data(xs: *mut XString) -> *const u8 {
(*xs).data.as_ptr()
}
#[no_mangle]
pub unsafe extern "C" fn xstring_len(xs: *mut XString) -> usize {
(*xs).data.len()
}
pub type DWORD = u32;
pub const SLGP_RAWPATH: DWORD = 0x4;
pub const STGM_READ: DWORD = 0x0;
#[com_interface("000214F9-0000-0000-C000-000000000046")]
pub trait IShellLinkW: IUnknown {
// DO NOT REORDER THESE - they must match the ABI
unsafe fn get_path(
&self,
psz_file: *mut u16,
cch: c_int,
pfd: *const c_void,
fflags: DWORD,
) -> HRESULT;
unsafe fn get_id_list(&self) -> !;
unsafe fn set_id_list(&self) -> !;
unsafe fn get_description(&self, psz_name: *mut u16, cch: c_int) -> HRESULT;
unsafe fn set_description(&self, psz_name: *const u16) -> HRESULT;
unsafe fn get_working_directory(&self, psz_dir: *mut u16, cch: c_int) -> HRESULT;
unsafe fn set_working_directory(&self, psz_dir: *const u16) -> HRESULT;
unsafe fn get_arguments(&self, psz_args: *mut u16, cch: c_int) -> HRESULT;
unsafe fn set_arguments(&self, psz_args: *const u16) -> HRESULT;
unsafe fn get_hotkey(&self) -> !;
unsafe fn set_hotkey(&self) -> !;
unsafe fn get_show_cmd(&self) -> !;
unsafe fn set_show_cmd(&self) -> !;
unsafe fn get_icon_location(
&self,
psz_icon_path: *mut u16,
cch: c_int,
pi_icon: *mut c_int,
) -> HRESULT;
unsafe fn set_icon_location(&self, psz_icon_path: *const u16, i_icon: c_int) -> HRESULT;
unsafe fn set_relative_path(&self) -> !;
unsafe fn resolve(&self) -> !;
unsafe fn set_path(&self, psz_file: *const u16) -> HRESULT;
}
#[com_interface("0000010C-0000-0000-C000-000000000046")]
pub trait IPersist: IUnknown {
// DO NOT REORDER THESE - they must match the ABI
unsafe fn get_class_id(&self, pclass_id: *mut IID) -> HRESULT;
}
#[com_interface("0000010B-0000-0000-C000-000000000046")]
pub trait IPersistFile: IPersist {
// DO NOT REORDER THESE - they must match the ABI
unsafe fn is_dirty(&self) -> !;
unsafe fn load(&self, psz_file_name: *const u16, mode: DWORD) -> HRESULT;
unsafe fn save(&self, psz_file_name: *const u16, fremember: bool) -> HRESULT;
unsafe fn save_completed(&self) -> !;
unsafe fn get_cur_file(&self) -> !;
}
pub const CLSID_SHELL_LINK: IID = IID {
data1: 0x00021401,
data2: 0x0000,
data3: 0x0000,
data4: [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46],
};

View File

@@ -2,6 +2,7 @@ mod dialogs;
mod self_delete;
mod shortcuts;
mod util;
mod ishelllink;
pub use dialogs::*;
pub use self_delete::*;

View File

@@ -1,65 +0,0 @@
#include "windows.h"
#include "winnls.h"
#include "shobjidl.h"
#include "objbase.h"
#include "objidl.h"
#include "shlguid.h"
#include "strsafe.h"
// all ripped from the following link and then modified
// https://learn.microsoft.com/en-us/windows/win32/shell/links
extern "C" HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink, LPCWSTR lpszWorkDir)
{
HRESULT hres;
IShellLink* psl;
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
if (SUCCEEDED(hres)) {
IPersistFile* ppf;
psl->SetPath(lpszPathObj);
psl->SetWorkingDirectory(lpszWorkDir);
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
if (SUCCEEDED(hres)) {
hres = ppf->Save(lpszPathLink, TRUE);
ppf->Release();
}
psl->Release();
}
return hres;
}
extern "C" HRESULT ResolveLink(LPWSTR lpszLinkFile, LPWSTR lpszPath, int iPathBufferSize, LPWSTR lpszWorkDir, int iWorkDirBufferSize)
{
HRESULT hres;
IShellLink* psl;
WCHAR szGotPath[MAX_PATH];
WCHAR szWorkDir[MAX_PATH];
WIN32_FIND_DATA wfd;
*lpszPath = 0; // Assume failure
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
if (SUCCEEDED(hres)) {
IPersistFile* ppf;
hres = psl->QueryInterface(IID_IPersistFile, (void**)&ppf);
if (SUCCEEDED(hres)) {
hres = ppf->Load(lpszLinkFile, STGM_READ);
if (SUCCEEDED(hres)) {
hres = psl->Resolve(0, 0x2 | 0x1 | (1 << 16));
if (SUCCEEDED(hres)) {
hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATA*)&wfd, SLGP_UNCPRIORITY);
if (SUCCEEDED(hres)) {
hres = psl->GetWorkingDirectory(szWorkDir, MAX_PATH);
if (SUCCEEDED(hres)) {
hres = StringCbCopy(lpszPath, iPathBufferSize, szGotPath);
if (SUCCEEDED(hres)) {
hres = StringCbCopy(lpszWorkDir, iWorkDirBufferSize, szWorkDir);
}
}
}
}
}
ppf->Release();
}
psl->Release();
}
return hres;
}

View File

@@ -1,52 +1,88 @@
use anyhow::Result;
use anyhow::{anyhow, Result};
use glob::glob;
use std::path::Path;
use winsafe::{self as w, co, HrResult, WString};
use winsafe::{self as w, co, prelude::*};
use super::ishelllink::*;
use crate::platform;
use crate::util;
type PCSTR = *const u16;
extern "C" {
fn CreateLink(lpszPathObj: PCSTR, lpszPathLink: PCSTR, lpszWorkDir: PCSTR) -> u32;
fn ResolveLink(lpszLinkFile: PCSTR, lpszPath: PCSTR, iPathBufferSize: i32, lpszWorkDir: PCSTR, iWorkDirBufferSize: i32) -> u32;
}
// type PCSTR = *const u16;
// extern "C" {
// fn CreateLink(lpszPathObj: PCSTR, lpszPathLink: PCSTR, lpszWorkDir: PCSTR) -> u32;
// fn ResolveLink(lpszLinkFile: PCSTR, lpszPath: PCSTR, iPathBufferSize: i32, lpszWorkDir: PCSTR, iWorkDirBufferSize: i32) -> u32;
// }
pub fn resolve_lnk(link_path: &str) -> HrResult<(String, String)> {
let _comguard = w::CoInitializeEx(co::COINIT::APARTMENTTHREADED)?;
pub fn resolve_lnk(link_path: &str) -> Result<(String, String)> {
let _comguard = w::CoInitializeEx(co::COINIT::APARTMENTTHREADED | co::COINIT::DISABLE_OLE1DDE)?;
_resolve_lnk(link_path)
}
pub fn create_lnk(output: &str, target: &str, work_dir: &str) -> HrResult<()> {
let _comguard = w::CoInitializeEx(co::COINIT::APARTMENTTHREADED)?;
pub fn create_lnk(output: &str, target: &str, work_dir: &str) -> w::HrResult<()> {
let _comguard = w::CoInitializeEx(co::COINIT::APARTMENTTHREADED | co::COINIT::DISABLE_OLE1DDE)?;
_create_lnk(output, target, work_dir)
}
fn _create_lnk(output: &str, target: &str, work_dir: &str) -> HrResult<()> {
let _comguard = w::CoInitializeEx(co::COINIT::APARTMENTTHREADED)?;
let hr = unsafe { CreateLink(WString::from_str(target).as_ptr(), WString::from_str(output).as_ptr(), WString::from_str(work_dir).as_ptr()) };
match unsafe { co::HRESULT::from_raw(hr) } {
co::HRESULT::S_OK => Ok(()),
hr => Err(hr),
}
fn _create_lnk(output: &str, target: &str, work_dir: &str) -> w::HrResult<()> {
let me = w::CoCreateInstance::<w::IShellLink>(&co::CLSID::ShellLink, None, co::CLSCTX::INPROC_SERVER)?;
me.SetPath(target)?;
me.SetWorkingDirectory(work_dir)?;
let pf = me.QueryInterface::<w::IPersistFile>()?;
pf.Save(Some(output), true)?;
Ok(())
// let _comguard = w::CoInitializeEx(co::COINIT::APARTMENTTHREADED)?;
// let hr = unsafe { CreateLink(WString::from_str(target).as_ptr(), WString::from_str(output).as_ptr(), WString::from_str(work_dir).as_ptr()) };
// match unsafe { co::HRESULT::from_raw(hr) } {
// co::HRESULT::S_OK => Ok(()),
// hr => Err(hr),
// }
}
fn _resolve_lnk(link_path: &str) -> HrResult<(String, String)> {
let mut path_buf = WString::new_alloc_buf(1024);
let mut work_dir_buf = WString::new_alloc_buf(1024);
let hr = unsafe {
ResolveLink(
WString::from_str(link_path).as_ptr(),
path_buf.as_mut_ptr(),
path_buf.buf_len() as _,
work_dir_buf.as_mut_ptr(),
work_dir_buf.buf_len() as _,
)
};
match unsafe { co::HRESULT::from_raw(hr) } {
co::HRESULT::S_OK => Ok((path_buf.to_string(), work_dir_buf.to_string())),
hr => Err(hr),
}
fn _resolve_lnk(link_path: &str) -> Result<(String, String)> {
let lnk = ShellLink::new().map_err(|e| anyhow!("Failed to create ShellLink: {:?}", e))?;
lnk.load(link_path).map_err(|e| anyhow!("Failed to load link: {:?}", e))?;
let path = lnk.get_path().map_err(|e| anyhow!("Failed to get link path: {:?}", e))?;
let work = lnk.get_working_directory().map_err(|e| anyhow!("Failed to get link working directory: {:?}", e))?;
Ok((path.to_string_lossy().to_string(), work.to_string_lossy().to_string()))
// let me = w::CoCreateInstance::<w::IShellLink>(&co::CLSID::ShellLink, None, co::CLSCTX::INPROC_SERVER)?;
// let pf = me.QueryInterface::<w::IPersistFile>()?;
// pf.Load(link_path, co::STGM::READ)?;
// info!("5");
// // let flags_with_timeout = unsafe { co::SLR::from_raw(65539) };
// info!("{:?}", w::HWND::NULL.ptr());
// me.Resolve(&w::HWND::NULL, co::SLR::NO_UI);
// info!("6");
// let path = me.GetPath(None, co::SLGP::UNCPRIORITY)?;
// let workdir = me.GetWorkingDirectory()?;
// info!("7");
// Ok((path, workdir))
// let me = w::CoCreateInstance::<w::IShellLink>(&co::CLSID::ShellLink, None, co::CLSCTX::INPROC_SERVER)?;
// let pf = me.QueryInterface::<w::IPersistFile>()?;
// pf.Load(file_path, co::STGM::READ)?;
// let flags = (co::SLR::NO_UI | co::SLR::ANY_MATCH).raw() | (1 << 16);
// me.Resolve(&w::HWND::NULL, flags_with_timeout)?;
// Ok(Box::new(Lnk { me, pf }))
// let mut path_buf = WString::new_alloc_buf(1024);
// let mut work_dir_buf = WString::new_alloc_buf(1024);
// let hr = unsafe {
// ResolveLink(
// WString::from_str(link_path).as_ptr(),
// path_buf.as_mut_ptr(),
// path_buf.buf_len() as _,
// work_dir_buf.as_mut_ptr(),
// work_dir_buf.buf_len() as _,
// )
// };
// match unsafe { co::HRESULT::from_raw(hr) } {
// co::HRESULT::S_OK => Ok((path_buf.to_string(), work_dir_buf.to_string())),
// hr => Err(hr),
// }
}
pub fn remove_all_for_root_dir<P: AsRef<Path>>(root_dir: P) -> Result<()> {
@@ -113,3 +149,119 @@ fn shortcut_full_integration_test() {
remove_all_for_root_dir(root).unwrap();
assert!(!link_location.exists());
}
// pub struct Lnk {
// me: w::IShellLink,
// pf: w::IPersistFile,
// }
// pub trait ShellLinkReadOnly {
// fn get_arguments(&self) -> w::HrResult<String>;
// fn get_description(&self) -> w::HrResult<String>;
// fn get_icon_location(&self) -> w::HrResult<(String, i32)>;
// fn get_path(&self) -> w::HrResult<String>;
// fn get_show_cmd(&self) -> w::HrResult<co::SW>;
// fn get_working_directory(&self) -> w::HrResult<String>;
// }
// pub trait ShellLinkWriteOnly {
// fn set_arguments(&mut self, path: &str) -> w::HrResult<()>;
// fn set_description(&mut self, path: &str) -> w::HrResult<()>;
// fn set_icon_location(&mut self, path: &str, index: i32) -> w::HrResult<()>;
// fn set_path(&mut self, path: &str) -> w::HrResult<()>;
// fn set_show_cmd(&mut self, show_cmd: co::SW) -> w::HrResult<()>;
// fn set_working_directory(&mut self, path: &str) -> w::HrResult<()>;
// fn save(&self) -> w::HrResult<()>;
// fn save_as(&self, path: &str) -> w::HrResult<()>;
// }
// impl ShellLinkReadOnly for Lnk {
// fn get_arguments(&self) -> w::HrResult<String> {
// Ok(self.me.GetArguments()?)
// }
// fn get_description(&self) -> w::HrResult<String> {
// Ok(self.me.GetDescription()?)
// }
// fn get_icon_location(&self) -> w::HrResult<(String, i32)> {
// Ok(self.me.GetIconLocation()?)
// }
// fn get_path(&self) -> w::HrResult<String> {
// Ok(self.me.GetPath(None, co::SLGP::UNCPRIORITY)?)
// }
// fn get_show_cmd(&self) -> w::HrResult<co::SW> {
// Ok(self.me.GetShowCmd()?)
// }
// fn get_working_directory(&self) -> w::HrResult<String> {
// Ok(self.me.GetWorkingDirectory()?)
// }
// }
// impl ShellLinkWriteOnly for Lnk {
// fn set_arguments(&mut self, path: &str) -> w::HrResult<()> {
// Ok(self.me.SetArguments(path)?)
// }
// fn set_description(&mut self, path: &str) -> w::HrResult<()> {
// Ok(self.me.SetDescription(path)?)
// }
// fn set_icon_location(&mut self, path: &str, index: i32) -> w::HrResult<()> {
// Ok(self.me.SetIconLocation(path, index)?)
// }
// fn set_path(&mut self, path: &str) -> w::HrResult<()> {
// Ok(self.me.SetPath(path)?)
// }
// fn set_show_cmd(&mut self, show_cmd: co::SW) -> w::HrResult<()> {
// Ok(self.me.SetShowCmd(show_cmd)?)
// }
// fn set_working_directory(&mut self, path: &str) -> w::HrResult<()> {
// Ok(self.me.SetWorkingDirectory(path)?)
// }
// fn save(&self) -> w::HrResult<()> {
// Ok(self.pf.Save(None, true)?)
// }
// fn save_as(&self, path: &str) -> w::HrResult<()> {
// Ok(self.pf.Save(Some(path), true)?)
// }
// }
// pub trait ShellLinkReadWrite: ShellLinkReadOnly + ShellLinkWriteOnly {}
// impl ShellLinkReadWrite for Lnk {}
// impl Lnk {
// pub fn open_read(file_path: &str) -> w::HrResult<Box<dyn ShellLinkReadOnly>> {
// let me = w::CoCreateInstance::<w::IShellLink>(&co::CLSID::ShellLink, None, co::CLSCTX::INPROC_SERVER)?;
// let pf = me.QueryInterface::<w::IPersistFile>()?;
// pf.Load(file_path, co::STGM::READ)?;
// let flags = (co::SLR::NO_UI | co::SLR::ANY_MATCH).raw() | (1 << 16);
// let flags_with_timeout = unsafe { co::SLR::from_raw(flags) };
// me.Resolve(&w::HWND::NULL, flags_with_timeout)?;
// Ok(Box::new(Lnk { me, pf }))
// }
// pub fn open_write(file_path: &str) -> w::HrResult<Box<dyn ShellLinkReadWrite>> {
// let me = w::CoCreateInstance::<w::IShellLink>(&co::CLSID::ShellLink, None, co::CLSCTX::INPROC_SERVER)?;
// let pf = me.QueryInterface::<w::IPersistFile>()?;
// pf.Load(file_path, co::STGM::READWRITE)?;
// let flags = (co::SLR::NO_UI | co::SLR::ANY_MATCH).raw() | (1 << 16);
// let flags_with_timeout = unsafe { co::SLR::from_raw(flags) };
// me.Resolve(&w::HWND::NULL, flags_with_timeout)?;
// Ok(Box::new(Lnk { me, pf }))
// }
// pub fn create_new() -> w::HrResult<Box<dyn ShellLinkReadWrite>> {
// let me = w::CoCreateInstance::<w::IShellLink>(&co::CLSID::ShellLink, None, co::CLSCTX::INPROC_SERVER)?;
// let pf = me.QueryInterface::<w::IPersistFile>()?;
// Ok(Box::new(Lnk { me, pf }))
// }
// }