use super::super::{PathPickerError, PathPurpose};
use crate::compat::{String, Vec};
use std::os::windows::ffi::OsStrExt;
use std::path::PathBuf;
const PATH_BUFFER_LEN: usize = 4096;
const FILTER_ALL_FILES: &str = "All files\0*.*\0\0";
pub(crate) fn choose_path(
purpose: PathPurpose,
title: &str,
) -> Result<Option<PathBuf>, PathPickerError> {
match purpose {
PathPurpose::SelectDirectory => browse_for_folder(title),
PathPurpose::OpenFile | PathPurpose::SaveFile => file_dialog(purpose, title),
}
}
fn file_dialog(purpose: PathPurpose, title: &str) -> Result<Option<PathBuf>, PathPickerError> {
use winapi::um::commdlg::{
CommDlgExtendedError, GetOpenFileNameW, GetSaveFileNameW, OFN_EXPLORER, OFN_FILEMUSTEXIST,
OFN_NOCHANGEDIR, OFN_OVERWRITEPROMPT, OFN_PATHMUSTEXIST, OPENFILENAMEW,
};
let saving = matches!(purpose, PathPurpose::SaveFile);
let mut buffer = [0u16; PATH_BUFFER_LEN];
let title_wide: Vec<u16> = std::ffi::OsStr::new(title).encode_wide().chain(Some(0)).collect();
let filter_wide: Vec<u16> = FILTER_ALL_FILES.encode_utf16().collect();
let mut ofn: OPENFILENAMEW = unsafe { std::mem::zeroed() };
ofn.lStructSize = std::mem::size_of::<OPENFILENAMEW>() as u32;
ofn.lpstrFilter = filter_wide.as_ptr();
ofn.lpstrTitle = title_wide.as_ptr();
ofn.lpstrFile = buffer.as_mut_ptr();
ofn.nMaxFile = PATH_BUFFER_LEN as u32;
ofn.Flags = OFN_EXPLORER
| OFN_NOCHANGEDIR
| OFN_PATHMUSTEXIST
| if saving { OFN_OVERWRITEPROMPT } else { OFN_FILEMUSTEXIST };
let accepted = unsafe {
if saving {
GetSaveFileNameW(&mut ofn)
} else {
GetOpenFileNameW(&mut ofn)
}
};
if accepted == 0 {
let extended = unsafe { CommDlgExtendedError() };
return if extended == 0 { Ok(None) } else { Err(PathPickerError::Unavailable) };
}
Ok(Some(path_from_wide_buffer(&buffer)))
}
fn browse_for_folder(title: &str) -> Result<Option<PathBuf>, PathPickerError> {
use winapi::um::combaseapi::CoTaskMemFree;
use winapi::um::shlobj::SHGetPathFromIDListW;
#[repr(C)]
struct BrowseInfoW {
hwnd_owner: *mut core::ffi::c_void,
pidl_root: *mut core::ffi::c_void,
display_name: *mut u16,
label: *const u16,
flags: u32,
callback: *mut core::ffi::c_void,
callback_param: isize,
image: i32,
}
extern "system" {
fn SHBrowseForFolderW(info: *mut BrowseInfoW) -> *mut core::ffi::c_void;
}
const BIF_RETURNONLYFSDIRS: u32 = 0x0000_0001;
let label_wide: Vec<u16> = std::ffi::OsStr::new(title).encode_wide().chain(Some(0)).collect();
let mut browse: BrowseInfoW = unsafe { std::mem::zeroed() };
browse.label = label_wide.as_ptr();
browse.flags = BIF_RETURNONLYFSDIRS;
let item_id_list = unsafe { SHBrowseForFolderW(&mut browse) };
if item_id_list.is_null() {
return Ok(None);
}
let mut path_buffer = [0u16; PATH_BUFFER_LEN];
let resolved =
unsafe { SHGetPathFromIDListW(item_id_list as *const _, path_buffer.as_mut_ptr()) != 0 };
unsafe {
CoTaskMemFree(item_id_list as *mut winapi::ctypes::c_void);
}
if !resolved {
return Ok(None);
}
Ok(Some(path_from_wide_buffer(&path_buffer)))
}
fn path_from_wide_buffer(buffer: &[u16]) -> PathBuf {
let length = buffer.iter().position(|&unit| unit == 0).unwrap_or(buffer.len());
PathBuf::from(String::from_utf16_lossy(&buffer[..length]))
}