webview2-com-sys 0.3.0

Bindings generated with the windows crate for the WebView2 COM APIs
Documentation
fn main() -> webview2_nuget::Result<()> {
    windows::build! {
        Microsoft::Web::WebView2::Win32::*,
        Windows::Win32::{
            Devices::HumanInterfaceDevice::*,
            Foundation::*,
            Storage::StructuredStorage::{
                CreateStreamOnHGlobal,
                IStream,
            },
            Globalization::*,
            Graphics::{
                Dwm::*,
                Gdi::*,
            },
            System::{
                Com::{
                    CoCreateInstance,
                    CoInitializeEx,
                    CoTaskMemAlloc,
                    CoTaskMemFree,
                    CoUninitialize,
                    IDataObject,
                    IDropTarget,
                    OleInitialize,
                    OleUninitialize,
                    RegisterDragDrop,
                    RevokeDragDrop,
                    CLSCTX,
                    DROPEFFECT_COPY,
                    DROPEFFECT_NONE,
                    DVASPECT,
                    FORMATETC,
                    TYMED,
                },
                DataExchange::*,
                Diagnostics::Debug::{
                    FlashWindowEx,
                    FLASHWINFO,
                    FLASHWINFO_FLAGS,
                },
                LibraryLoader::*,
                Memory::{
                    GlobalAlloc,
                    GlobalFlags,
                    GlobalFree,
                    GlobalLock,
                    GlobalReAlloc,
                    GlobalSize,
                    GlobalUnlock,
                    GLOBAL_ALLOC_FLAGS,
                },
                SystemServices::{
                    CLIPBOARD_FORMATS,
                    DPI_AWARENESS_CONTEXT,
                    LANG_JAPANESE,
                    LANG_KOREAN,
                },
                Threading::{
                    MsgWaitForMultipleObjectsEx,
                    GetCurrentThreadId,
                    MSG_WAIT_FOR_MULTIPLE_OBJECTS_EX_FLAGS,
                    WAIT_RETURN_CAUSE,
                },
                WindowsProgramming::INFINITE,
                WinRT::EventRegistrationToken,
            },
            UI::{
                Accessibility::*,
                Controls::*,
                DisplayDevices::DEVMODEW,
                HiDpi::*,
                KeyboardAndMouseInput::*,
                PointerInput::*,
                Shell::*,
                TextServices::HKL,
                TouchInput::*,
                WindowsAndMessaging::*,
            },
        }
    };

    let package_root = webview2_nuget::install()?;
    webview2_nuget::update_windows(&package_root)?;
    webview2_nuget::update_browser_version(&package_root)?;
    webview2_nuget::update_callback_interfaces(&package_root)?;

    Ok(())
}

#[macro_use]
extern crate thiserror;

mod webview2_nuget {
    use std::{
        convert::From,
        env, fs,
        io::{self, Read, Write},
        path::{Path, PathBuf},
        process::Command,
    };

    use regex::Regex;

    include!("./src/browser_version.rs");
    include!("./src/callback_interfaces.rs");

    const WEBVIEW2_NAME: &str = "Microsoft.Web.WebView2";
    const WEBVIEW2_VERSION: &str = "1.0.961.33";

    #[cfg(not(windows))]
    pub fn install() -> Result<PathBuf> {
        get_manifest_dir()
    }

    #[cfg(windows)]
    pub fn install() -> Result<PathBuf> {
        let out_dir = get_out_dir()?;
        let install_root = match out_dir.to_str() {
            Some(path) => path,
            None => return Err(Error::MissingPath(out_dir)),
        };

        let mut package_root = out_dir.clone();
        package_root.push(format!("{}.{}", WEBVIEW2_NAME, WEBVIEW2_VERSION));

        if !check_nuget_dir(install_root)? {
            let mut nuget_path = get_manifest_dir()?;
            nuget_path.push("tools");
            nuget_path.push("nuget.exe");

            let nuget_tool = match nuget_path.to_str() {
                Some(path) => path,
                None => return Err(Error::MissingPath(nuget_path)),
            };

            Command::new(nuget_tool)
                .args(&[
                    "install",
                    WEBVIEW2_NAME,
                    "-OutputDirectory",
                    install_root,
                    "-Version",
                    WEBVIEW2_VERSION,
                ])
                .output()?;

            if !check_nuget_dir(install_root)? {
                return Err(Error::MissingPath(package_root));
            }
        }

        Ok(package_root)
    }

    fn get_out_dir() -> Result<PathBuf> {
        Ok(PathBuf::from(env::var("OUT_DIR")?))
    }

    fn get_manifest_dir() -> Result<PathBuf> {
        Ok(PathBuf::from(env::var("CARGO_MANIFEST_DIR")?))
    }

    #[cfg(windows)]
    fn check_nuget_dir(install_root: &str) -> Result<bool> {
        let nuget_path = format!("{}.{}", WEBVIEW2_NAME, WEBVIEW2_VERSION);
        let mut dir_iter = fs::read_dir(install_root)?.filter(|dir| match dir {
            Ok(dir) => match dir.file_type() {
                Ok(file_type) => {
                    file_type.is_dir()
                        && match dir.file_name().to_str() {
                            Some(name) => name.eq_ignore_ascii_case(&nuget_path),
                            None => false,
                        }
                }
                Err(_) => false,
            },
            Err(_) => false,
        });
        Ok(dir_iter.next().is_some())
    }

    #[cfg(not(windows))]
    pub fn update_windows(_: &Path) -> Result<()> {
        Ok(())
    }

    #[cfg(windows)]
    pub fn update_windows(package_root: &Path) -> Result<()> {
        const WEBVIEW2_STATIC_LIB: &str = "WebView2LoaderStatic.lib";
        const WEBVIEW2_TARGETS: &[&str] = &["arm64", "x64", "x86"];

        let mut workspace_windows_dir = get_workspace_dir()?;
        workspace_windows_dir.push(".windows");

        let mut bindings_windows_dir = get_manifest_dir()?;
        bindings_windows_dir.push(".windows");

        let mut native_dir = package_root.to_path_buf();
        native_dir.push("build");
        native_dir.push("native");
        for target in WEBVIEW2_TARGETS {
            let mut lib_src = native_dir.clone();
            lib_src.push(target);
            lib_src.push(WEBVIEW2_STATIC_LIB);

            let mut lib_dest = workspace_windows_dir.clone();
            lib_dest.push(target);
            lib_dest.push(WEBVIEW2_STATIC_LIB);
            eprintln!("Copy from {:?} -> {:?}", lib_src, lib_dest);
            fs::copy(lib_src.as_path(), lib_dest.as_path())?;

            let mut lib_dest = bindings_windows_dir.clone();
            lib_dest.push(target);
            lib_dest.push(WEBVIEW2_STATIC_LIB);
            eprintln!("Copy from {:?} -> {:?}", lib_src, lib_dest);
            fs::copy(lib_src.as_path(), lib_dest.as_path())?;
        }

        Ok(())
    }

    fn get_workspace_dir() -> Result<PathBuf> {
        use serde::Deserialize;

        #[derive(Deserialize)]
        struct CargoMetadata {
            workspace_root: String,
        }

        let output = Command::new(env::var("CARGO")?)
            .args(&["metadata", "--format-version=1", "--no-deps", "--offline"])
            .output()?;

        let metadata: CargoMetadata = serde_json::from_slice(&output.stdout)?;

        Ok(PathBuf::from(metadata.workspace_root))
    }

    #[cfg(not(windows))]
    pub fn update_browser_version(_: &PathBuf) -> Result<bool> {
        Ok(false)
    }

    #[cfg(windows)]
    pub fn update_browser_version(package_root: &Path) -> Result<bool> {
        let version = get_target_broweser_version(package_root)?;
        if version == CORE_WEBVIEW_TARGET_PRODUCT_VERSION {
            return Ok(false);
        }

        let mut source_path = get_manifest_dir()?;
        source_path.push("src");
        source_path.push("browser_version.rs");
        let mut source_file = fs::File::create(source_path)?;
        writeln!(
            source_file,
            r#"pub const CORE_WEBVIEW_TARGET_PRODUCT_VERSION: &str = "{}";"#,
            version
        )?;
        Ok(true)
    }

    #[cfg(windows)]
    fn get_target_broweser_version(package_root: &Path) -> Result<String> {
        let mut include_path = package_root.to_path_buf();
        include_path.push("build");
        include_path.push("native");
        include_path.push("include");
        include_path.push("WebView2EnvironmentOptions.h");
        let mut contents = String::new();
        fs::File::open(include_path)?.read_to_string(&mut contents)?;
        let pattern =
            Regex::new(r#"^\s*#define\s+CORE_WEBVIEW_TARGET_PRODUCT_VERSION\s+L"(.*)"\s*$"#)?;
        match contents
            .lines()
            .filter_map(|line| pattern.captures(line))
            .filter_map(|captures| captures.get(1))
            .next()
        {
            Some(capture) => Ok(capture.as_str().into()),
            None => Err(Error::MissingVersion),
        }
    }

    #[cfg(not(windows))]
    pub fn update_callback_interfaces(_: &PathBuf) -> Result<bool> {
        Ok(false)
    }

    #[cfg(windows)]
    pub fn update_callback_interfaces(package_root: &Path) -> Result<bool> {
        let interfaces = get_callback_interfaces(package_root)?;
        let declared = all_declared().into_iter().map(String::from).collect();
        if interfaces == declared {
            return Ok(false);
        }

        let mut source_path = get_manifest_dir()?;
        source_path.push("src");
        source_path.push("callback_interfaces.rs");
        let mut source_file = fs::File::create(source_path)?;
        writeln!(
            source_file,
            r#"use std::collections::BTreeSet;

/// Generate a list of all `ICoreWebView2...Handler` interfaces declared in `WebView2.h`. This is
/// for testing purposes to make sure they are all covered in [callback.rs](../../src/callback.rs).
pub fn all_declared() -> BTreeSet<&'static str> {{
    let mut interfaces = BTreeSet::new();
"#,
        )?;
        for interface in interfaces {
            writeln!(source_file, r#"    interfaces.insert("{}");"#, interface)?;
        }
        writeln!(
            source_file,
            r#"
    interfaces
}}"#
        )?;
        Ok(true)
    }

    #[cfg(windows)]
    fn get_callback_interfaces(package_root: &Path) -> Result<BTreeSet<String>> {
        let mut include_path = package_root.to_path_buf();
        include_path.push("build");
        include_path.push("native");
        include_path.push("include");
        include_path.push("WebView2.h");
        let mut contents = String::new();
        fs::File::open(include_path)?.read_to_string(&mut contents)?;
        let pattern =
            Regex::new(r#"^\s*typedef\s+interface\s+(ICoreWebView2[A-Za-z0-9]+Handler)\s+"#)?;
        let interfaces: BTreeSet<String> = contents
            .lines()
            .filter_map(|line| pattern.captures(line))
            .filter_map(|captures| captures.get(1))
            .map(|match_1| String::from(match_1.as_str()))
            .collect();
        if interfaces.is_empty() {
            Err(Error::MissingTypedef)
        } else {
            Ok(interfaces)
        }
    }

    #[derive(Debug, Error)]
    pub enum Error {
        #[error(transparent)]
        Io(#[from] io::Error),
        #[error(transparent)]
        Var(#[from] env::VarError),
        #[error(transparent)]
        Json(#[from] serde_json::Error),
        #[error(transparent)]
        Regex(#[from] regex::Error),
        #[error("Missing Version")]
        MissingVersion,
        #[error("Missing Typedef")]
        MissingTypedef,
        #[error("Missing Path")]
        MissingPath(PathBuf),
    }

    pub type Result<T> = std::result::Result<T, Error>;
}