remove webview2_com dependency

This commit is contained in:
Caelan Sayler
2025-01-11 08:51:08 +00:00
committed by Caelan
parent a0229c749c
commit bc34f00699
8 changed files with 531 additions and 132 deletions

View File

@@ -78,11 +78,12 @@ waitpid-any.workspace = true
fs_extra.workspace = true
memmap2.workspace = true
image.workspace = true
winsafe = { version = "0.0.20", features = ["gui"] }
windows = { version = "0.58", default-features = false, features = [
winsafe.workspace = true
windows = { workspace = true, features = [
"Win32_Foundation",
"Win32_Security",
"Win32_System_Com",
"Win32_Globalization",
"Win32_UI",
"Win32_UI_Shell",
"Win32_System_Threading",
@@ -105,7 +106,7 @@ windows = { version = "0.58", default-features = false, features = [
"Wdk_System",
"Wdk_System_Threading",
] }
webview2-com = "0.34"
webview2-com-sys.workspace = true
libloading.workspace = true
strsim.workspace = true
same-file.workspace = true

View File

@@ -6,6 +6,7 @@ pub mod splash;
pub mod known_path;
pub mod strings;
pub mod registry;
pub mod webview2;
mod self_delete;
mod shortcuts;

View File

@@ -547,18 +547,8 @@ impl RuntimeInfo for WebView2Info {
}
fn is_installed(&self) -> bool {
// https://github.com/myhrmans/figma-content-length-bug/blob/980b5ce03171218904782f9ab590857d6c7de700/src/webview/webview2/mod.rs#L752
use webview2_com::{Microsoft::Web::WebView2::Win32::*, *};
use windows::core::{PCWSTR, PWSTR};
let mut versioninfo = PWSTR::null();
let result = unsafe { GetAvailableCoreWebView2BrowserVersionString(PCWSTR::null(), &mut versioninfo) };
if result.is_err() || versioninfo == PWSTR::null() {
return false;
}
let version = take_pwstr(versioninfo);
if version.len() > 0 {
let result = super::webview2::get_webview_version();
if let Some(version) = result {
info!("WebView2 version: {}", version);
true
} else {
@@ -710,7 +700,7 @@ fn test_parse_dotnet_version() {
assert_eq!(info.architecture, RuntimeArch::X64);
assert_eq!(info.runtime_type, DotnetRuntimeType::WindowsDesktop);
let info = parse_dotnet_version("net8-sdk").unwrap();
let info = parse_dotnet_version("net8-sdk").unwrap();
assert_eq!(info.version, "8.0.0");
assert_eq!(info.architecture, RuntimeArch::X64);
assert_eq!(info.runtime_type, DotnetRuntimeType::Sdk);

View File

@@ -6,11 +6,11 @@ use anyhow::{anyhow, bail, Result};
use glob::glob;
use same_file::is_same_file;
use velopack::{locator::ShortcutLocationFlags, locator::VelopackLocator};
use windows::core::{Interface, GUID, PCWSTR, PROPVARIANT};
use windows::core::{Interface, GUID, PCWSTR};
use windows::Win32::Storage::EnhancedStorage::PKEY_AppUserModel_ID;
use windows::Win32::System::Com::{
CoCreateInstance, CoInitializeEx, CoUninitialize, IPersistFile, StructuredStorage::InitPropVariantFromStringVector,
CLSCTX_ALL, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, STGM_READWRITE,
StructuredStorage::PROPVARIANT, CLSCTX_ALL, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, STGM_READWRITE,
};
use windows::Win32::UI::Shell::{
IShellItem, IShellLinkW, IStartMenuPinnedList, PropertiesSystem::IPropertyStore, SHCreateItemFromParsingName, ShellLink, StartMenuPin,
@@ -153,8 +153,7 @@ unsafe fn unsafe_update_app_manifest_lnks(next_app: &VelopackLocator, previous_a
let target_path = if let Some(parent) = path.parent() {
parent.join(shortcut_file_name)
} else {
match get_path_for_shortcut_location(&app_id, &app_title, &app_authors, flag)
{
match get_path_for_shortcut_location(&app_id, &app_title, &app_authors, flag) {
Ok(p) => p,
Err(e) => {
error!("Failed to get desired path for shortcut location: {:?} ({})", flag, e);
@@ -526,7 +525,6 @@ impl Lnk {
Ok(self.pf.Save(output, true)?)
}
pub unsafe fn open_write<P: AsRef<Path>>(link_path: P) -> Result<Lnk> {
let link_path = link_path.as_ref().to_string_lossy().to_string();
let link: IShellLinkW = create_instance(&ShellLink)?;
@@ -575,7 +573,7 @@ fn test_unpin_shortcut() {
unsafe_unpin_lnk_from_start(path)?;
Ok(())
})
.unwrap();
.unwrap();
}
}
#[test]
@@ -597,7 +595,7 @@ fn test_shortcut_intense_intermittent() {
l.save_as(&p1).unwrap();
Ok(())
})
.unwrap();
.unwrap();
}
assert!(lnk_path.exists());
util::retry_io(|| std::fs::remove_file(&lnk_path)).unwrap();
@@ -632,7 +630,7 @@ fn test_can_resolve_existing_shortcut() {
assert_eq!(target, "C:\\Users\\Caelan\\AppData\\Local\\Discord\\Update.exe");
Ok(())
})
.unwrap();
.unwrap();
}
}
@@ -708,6 +706,6 @@ fn test_shortcut_full_integration() {
assert!(!start_menu_subfolder.exists());
Ok(())
})
.unwrap();
.unwrap();
}
}

View File

@@ -8,7 +8,7 @@ pub fn string_to_u16<P: AsRef<str>>(input: P) -> Vec<u16> {
pub fn pwstr_to_string(input: PWSTR) -> Result<String> {
unsafe {
let hstring = input.to_hstring()?;
let hstring = input.to_hstring();
let string = hstring.to_string_lossy();
Ok(string.trim_end_matches('\0').to_string())
}
@@ -16,7 +16,7 @@ pub fn pwstr_to_string(input: PWSTR) -> Result<String> {
pub fn pcwstr_to_string(input: PCWSTR) -> Result<String> {
unsafe {
let hstring = input.to_hstring()?;
let hstring = input.to_hstring();
let string = hstring.to_string_lossy();
Ok(string.trim_end_matches('\0').to_string())
}
@@ -24,7 +24,7 @@ pub fn pcwstr_to_string(input: PCWSTR) -> Result<String> {
pub fn u16_to_string<T: AsRef<[u16]>>(input: T) -> Result<String> {
let input = input.as_ref();
let hstring = HSTRING::from_wide(input)?;
let hstring = HSTRING::from_wide(input);
let string = hstring.to_string_lossy();
Ok(string.trim_end_matches('\0').to_string())
}

View File

@@ -0,0 +1,173 @@
use std::{fmt::Display, marker::PhantomData, mem, ptr};
use windows::{
core::{Param, HRESULT, PCWSTR, PWSTR},
Win32::{Globalization::lstrlenW, System::Com},
};
/// RAII holder for a [`PWSTR`] which is allocated with [`Com::CoTaskMemAlloc`] and freed
/// with [`Com::CoTaskMemFree`] when dropped.
pub struct CoTaskMemPWSTR<'a>(PWSTR, PhantomData<&'a PWSTR>);
/// Constant guard object tied to the lifetime of the [`CoTaskMemPWSTR`] so that it
/// is safe to dereference the [`PCWSTR`] as long as both are still in scope.
pub struct CoTaskMemRef<'a>(PCWSTR, PhantomData<&'a PCWSTR>);
impl<'a> CoTaskMemRef<'a> {
pub fn as_pcwstr(&self) -> &PCWSTR {
&self.0
}
}
impl<'a> From<&'a CoTaskMemPWSTR<'a>> for CoTaskMemRef<'a> {
fn from(value: &'a CoTaskMemPWSTR<'a>) -> Self {
Self(PCWSTR::from_raw(value.0.as_ptr()), PhantomData)
}
}
/// Mutable guard object tied to the lifetime of the [`CoTaskMemPWSTR`] so that it
/// is safe to dereference the [`PWSTR`] as long as both are still in scope.
pub struct CoTaskMemMut<'a>(&'a PWSTR);
impl<'a> CoTaskMemMut<'a> {
pub fn as_pwstr(&mut self) -> &'a PWSTR {
self.0
}
}
impl<'a> From<&'a mut CoTaskMemPWSTR<'a>> for CoTaskMemMut<'a> {
fn from(value: &'a mut CoTaskMemPWSTR<'a>) -> Self {
Self(&value.0)
}
}
impl<'a> CoTaskMemPWSTR<'a> {
/// Get a mutable [`PWSTR`] guard which borrows the pointer.
pub fn as_mut(&'a mut self) -> CoTaskMemMut<'a> {
From::from(self)
}
/// Get a constant [`PCWSTR`] guard which borrows the pointer.
pub fn as_ref(&'a self) -> CoTaskMemRef<'a> {
From::from(self)
}
/// Take the [`PWSTR`] pointer and hand off ownership so that it is not freed when the `CoTaskMemPWSTR` is dropped.
pub fn take(&mut self) -> PWSTR {
let result = self.0;
self.0 = PWSTR::null();
result
}
}
impl<'a> Drop for CoTaskMemPWSTR<'a> {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe {
Com::CoTaskMemFree(Some(self.0.as_ptr() as *mut _ as *const _));
}
}
}
}
impl<'a> Default for CoTaskMemPWSTR<'a> {
fn default() -> Self {
Self(PWSTR::null(), PhantomData)
}
}
impl<'a> From<PWSTR> for CoTaskMemPWSTR<'a> {
fn from(value: PWSTR) -> Self {
Self(value, PhantomData)
}
}
impl<'a> From<&str> for CoTaskMemPWSTR<'a> {
fn from(value: &str) -> Self {
match value {
"" => Default::default(),
value => {
let encoded: Vec<_> = value.encode_utf16().chain(std::iter::once(0)).collect();
unsafe {
let mut buffer = Com::CoTaskMemAlloc(encoded.len() * mem::size_of::<u16>()) as *mut u16;
let result = PWSTR::from_raw(buffer);
for char in encoded {
*buffer = char;
buffer = buffer.add(1);
}
Self(result, PhantomData)
}
}
}
}
}
impl<'a> Display for CoTaskMemPWSTR<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = string_from_pcwstr(self.as_ref().as_pcwstr());
f.write_str(value.as_str())
}
}
/// Copy a [`PCWSTR`] from an input param to a [`String`].
pub fn string_from_pcwstr(source: &PCWSTR) -> String {
if source.0.is_null() {
String::new()
} else {
let len = unsafe { lstrlenW(*source) };
if len > 0 {
unsafe {
let buffer = ptr::slice_from_raw_parts(source.0, len as usize);
String::from_utf16_lossy(&*buffer)
}
} else {
String::new()
}
}
}
/// Copy a [`PWSTR`] allocated with [`Com::CoTaskMemAlloc`] from an input param to a [`String`]
/// and free the original buffer with [`Com::CoTaskMemFree`].
pub fn take_pwstr(source: PWSTR) -> String {
CoTaskMemPWSTR::from(source).to_string()
}
/// Allocate a [`PWSTR`] with [`Com::CoTaskMemAlloc`] and copy a [`&str`] into it.
pub fn pwstr_from_str(source: &str) -> PWSTR {
CoTaskMemPWSTR::from(source).take()
}
// https://github.com/myhrmans/figma-content-length-bug/blob/980b5ce03171218904782f9ab590857d6c7de700/src/webview/webview2/mod.rs#L752
pub fn get_webview_version() -> Option<String> {
// #[cfg_attr(target_env = "msvc", link(name = "WebView2LoaderStatic", kind = "static"))]
// #[cfg_attr(not(target_env = "msvc"), link(name = "WebView2Loader.dll"))]
#[link(name = "WebView2LoaderStatic", kind = "static")]
extern "system" {
pub fn GetAvailableCoreWebView2BrowserVersionString(browserexecutablefolder: PCWSTR, versioninfo: *mut PWSTR) -> HRESULT;
}
let browserexecutablefolder = PCWSTR::null();
let mut versioninfo = PWSTR::null();
let hr = unsafe { GetAvailableCoreWebView2BrowserVersionString(browserexecutablefolder.param().abi(), &mut versioninfo).ok() };
if hr.is_err() || versioninfo.is_null() {
return None;
}
let str = CoTaskMemPWSTR::from(versioninfo).to_string();
if str.is_empty() {
None
} else {
Some(str)
}
}
#[test]
fn test_webview_version() {
let version = get_webview_version();
assert!(version.is_some());
}