use std::path::PathBuf;
#[must_use]
pub fn path_from_wide(units: &[u16]) -> Option<PathBuf> {
let end = units
.iter()
.position(|unit| *unit == 0)
.unwrap_or(units.len());
let units = &units[..end];
if units.is_empty() {
return None;
}
Some(decode(units))
}
#[cfg(target_os = "windows")]
fn decode(units: &[u16]) -> PathBuf {
use std::os::windows::ffi::OsStringExt;
PathBuf::from(std::ffi::OsString::from_wide(units))
}
#[cfg(not(target_os = "windows"))]
fn decode(units: &[u16]) -> PathBuf {
PathBuf::from(String::from_utf16_lossy(units))
}
#[cfg(target_os = "windows")]
pub(super) use system::{accept_files, query_files, take_drop};
#[cfg(target_os = "windows")]
mod system {
use std::path::PathBuf;
use super::super::ffi::{self, Handle, Point, value};
use super::path_from_wide;
pub(in super::super) fn accept_files(hwnd: Handle, accept: bool) {
unsafe { ffi::DragAcceptFiles(hwnd, i32::from(accept)) };
}
pub(in super::super) unsafe fn take_drop(hdrop: Handle) -> (Vec<PathBuf>, Point) {
let paths = unsafe { query_files(hdrop) };
let mut point = Point::default();
unsafe { ffi::DragQueryPoint(hdrop, &raw mut point) };
unsafe { ffi::DragFinish(hdrop) };
(paths, point)
}
pub(in super::super) unsafe fn query_files(hdrop: Handle) -> Vec<PathBuf> {
let count = unsafe { ffi::DragQueryFileW(hdrop, value::DRAG_QUERY_COUNT, ptr_null(), 0) };
let mut paths = Vec::with_capacity(count as usize);
for index in 0..count {
let length = unsafe { ffi::DragQueryFileW(hdrop, index, ptr_null(), 0) };
if length == 0 {
continue;
}
let mut buffer = vec![0u16; length as usize + 1];
let written = unsafe {
ffi::DragQueryFileW(hdrop, index, buffer.as_mut_ptr(), buffer.len() as u32)
};
if let Some(path) = path_from_wide(&buffer[..(written as usize).min(buffer.len())]) {
paths.push(path);
}
}
paths
}
const fn ptr_null() -> *mut u16 {
core::ptr::null_mut()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn wide(text: &str) -> Vec<u16> {
let mut units: Vec<u16> = text.encode_utf16().collect();
units.push(0);
units
}
#[test]
fn a_queried_path_stops_at_the_terminator_and_keeps_everything_before_it() {
assert_eq!(
path_from_wide(&wide(r"C:\Users\dev\My Scene.ron")),
Some(PathBuf::from(r"C:\Users\dev\My Scene.ron"))
);
let mut roomy = wide(r"C:\a.png");
roomy.extend_from_slice(&[0; 12]);
assert_eq!(path_from_wide(&roomy), Some(PathBuf::from(r"C:\a.png")));
let exact: Vec<u16> = r"C:\b.png".encode_utf16().collect();
assert_eq!(path_from_wide(&exact), Some(PathBuf::from(r"C:\b.png")));
}
#[test]
fn an_empty_entry_is_not_a_path() {
assert_eq!(path_from_wide(&[]), None);
assert_eq!(path_from_wide(&[0]), None);
assert_eq!(path_from_wide(&[0, 0, 0]), None);
}
#[test]
fn a_unc_path_and_a_non_ascii_name_both_survive() {
assert_eq!(
path_from_wide(&wide(r"\\server\share\level.ron")),
Some(PathBuf::from(r"\\server\share\level.ron"))
);
assert_eq!(
path_from_wide(&wide(r"C:\プロジェクト\🎮.png")),
Some(PathBuf::from(r"C:\プロジェクト\🎮.png")),
"a surrogate pair is one character on both sides of the conversion"
);
}
}