use crate::types::*;
#[macro_export]
macro_rules! w {
($s:literal) => {
{
const S: &[u8] = $s.as_bytes();
const LEN: usize = S.len() + 1;
const UTF16: [u16; LEN] = {
let mut out = [0u16; LEN];
let mut i = 0;
while i < S.len() {
out[i] = unsafe { *S.as_ptr().add(i) } as u16;
i += 1;
}
out[LEN - 1] = 0;
out
};
&UTF16 as &[u16]
}
};
}
pub fn to_wstring(value: &str) -> Vec<u16> {
value.encode_utf16().chain(std::iter::once(0)).collect()
}
pub trait ToPcwstr {
fn to_pcwstr(&self) -> Vec<u16>;
}
impl ToPcwstr for str {
fn to_pcwstr(&self) -> Vec<u16> { to_wstring(self) }
}
impl ToPcwstr for String {
fn to_pcwstr(&self) -> Vec<u16> { to_wstring(self) }
}
pub fn u64_to_wstring(mut val: u64) -> Vec<u16> {
if val == 0 { return vec![0x0030, 0x0000]; } let mut buf = Vec::with_capacity(24);
while val > 0 {
let digit = (val % 10) as u16;
buf.push(0x0030 + digit);
val /= 10;
}
buf.reverse();
buf.push(0x0000);
buf
}
pub unsafe fn fmt_u64(template: &[u16], value: u64) -> Vec<u16> {
let mut buffer = [0u16; 128];
unsafe {
crate::types::wsprintfW(buffer.as_mut_ptr(), template.as_ptr(), value);
}
let len = (0..128).take_while(|&i| buffer[i] != 0).count();
buffer[..=len].to_vec()
}
pub unsafe fn fmt_u32(value: u32) -> Vec<u16> {
let mut buffer = [0u16; 32];
let fmt = crate::w!("%u");
unsafe {
crate::types::wsprintfW(buffer.as_mut_ptr(), fmt.as_ptr(), value);
}
let len = (0..32).take_while(|&i| buffer[i] != 0).count();
buffer[..=len].to_vec()
}
pub unsafe fn fmt_u32_padded(value: u32) -> Vec<u16> {
let mut buffer = [0u16; 32];
let fmt = crate::w!("%02u");
unsafe {
crate::types::wsprintfW(buffer.as_mut_ptr(), fmt.as_ptr(), value);
}
let len = (0..32).take_while(|&i| buffer[i] != 0).count();
buffer[..=len].to_vec()
}
pub unsafe fn fmt_progress(current: u64, total: u64) -> Vec<u16> {
let mut buffer = [0u16; 128];
let fmt = crate::w!("%I64u / %I64u");
unsafe {
crate::types::wsprintfW(buffer.as_mut_ptr(), fmt.as_ptr(), current, total);
}
let len = (0..128).take_while(|&i| buffer[i] != 0).count();
buffer[..=len].to_vec()
}
pub unsafe fn fmt_timestamp(ts: u64) -> Vec<u16> {
let s = ts % 86400;
let h = s / 3600;
let m = (s % 3600) / 60;
let s = s % 60;
let mut buffer = [0u16; 32];
let fmt = crate::w!("[%02u:%02u:%02u]");
unsafe {
crate::types::wsprintfW(buffer.as_mut_ptr(), fmt.as_ptr(), h, m, s);
}
let len = (0..32).take_while(|&i| buffer[i] != 0).count();
buffer[..=len].to_vec()
}
pub fn concat_wstrings(parts: &[&[u16]]) -> Vec<u16> {
let total_len = parts.iter().map(|p| {
if p.last() == Some(&0) { p.len() - 1 } else { p.len() }
}).sum::<usize>();
let mut res = Vec::with_capacity(total_len + 1);
for part in parts {
if part.is_empty() { continue; }
let len = if part.last() == Some(&0) { part.len() - 1 } else { part.len() };
res.extend_from_slice(&part[..len]);
}
res.push(0);
res
}
pub fn format_size(bytes: u64) -> Vec<u16> {
let mut buffer: [u16; 32] = [0; 32];
unsafe {
let size_i64 = if bytes > i64::MAX as u64 {
i64::MAX
} else {
bytes as i64
};
let ptr = StrFormatByteSizeW(size_i64, buffer.as_mut_ptr(), buffer.len() as u32);
if ptr.is_null() {
return vec![0];
}
let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
buffer[..=len].to_vec() }
}
pub fn reveal_path_in_explorer(path: &str) {
let select_prefix = w!("/select,\"");
let path_w = to_wstring(path); let suffix = w!("\"");
let args = concat_wstrings(&[select_prefix, &path_w[..], suffix]);
unsafe {
ShellExecuteW(
std::ptr::null_mut(),
w!("open").as_ptr(),
w!("explorer.exe").as_ptr(),
args.as_ptr(),
std::ptr::null(),
SW_SHOWNORMAL
);
}
}
pub fn calculate_saved_percentage(logical: u64, disk: u64) -> f64 {
if logical == 0 { return 0.0; }
if disk >= logical { return 0.0; }
100.0 - ((disk as f64 / logical as f64) * 100.0)
}
pub fn calculate_ratio_string(logical: u64, disk: u64) -> Vec<u16> {
if logical == 0 { return w!("-").to_vec(); }
let saved = if logical > disk { logical - disk } else { 0 };
let ratio_10x = (saved as u128 * 1000) / (logical as u128);
let whole = (ratio_10x / 10) as u32;
let decimal = (ratio_10x % 10) as u32;
let mut buffer = [0u16; 32];
let fmt = crate::w!("%u.%u%%");
unsafe {
crate::types::wsprintfW(buffer.as_mut_ptr(), fmt.as_ptr(), whole, decimal);
}
let len = (0..32).take_while(|&i| buffer[i] != 0).count();
buffer[..=len].to_vec()
}
pub struct PathBuffer {
buf: Vec<u16>,
}
impl PathBuffer {
pub fn with_capacity(capacity: usize) -> Self {
let cap = std::cmp::max(capacity, 1024);
Self { buf: Vec::with_capacity(cap) }
}
pub fn from(s: &str) -> Self {
let mut pb = Self::with_capacity(s.len() + 10);
let needs_prefix = s.len() > 240 && s.contains(':') && !s.starts_with("\\\\?\\");
if needs_prefix {
pb.buf.extend_from_slice(w!("\\\\?\\"));
}
pb.push_normalized(s);
pb
}
pub fn push(&mut self, s: &str) {
self.push_normalized(s);
}
fn push_normalized(&mut self, s: &str) {
if self.buf.is_empty() {
for c in s.chars() {
if c == '/' {
self.buf.push(b'\\' as u16);
} else {
let mut buf = [0u16; 2];
let encoded = c.encode_utf16(&mut buf);
self.buf.extend_from_slice(encoded);
}
}
self.buf.push(0);
return;
}
self.pop_null();
if !self.buf.is_empty() && *self.buf.last().unwrap() != b'\\' as u16 {
self.buf.push(b'\\' as u16);
}
for c in s.chars() {
if c == '/' {
self.buf.push(b'\\' as u16);
} else {
let mut buf = [0u16; 2];
let encoded = c.encode_utf16(&mut buf);
self.buf.extend_from_slice(encoded);
}
}
self.buf.push(0);
}
pub fn push_u16_slice(&mut self, slice: &[u16]) {
if self.buf.is_empty() {
let len = slice.iter().position(|&c| c == 0).unwrap_or(slice.len());
self.buf.extend_from_slice(&slice[..len]);
self.buf.push(0);
return;
}
self.pop_null();
if !self.buf.is_empty() && *self.buf.last().unwrap() != b'\\' as u16 {
self.buf.push(b'\\' as u16);
}
let len = slice.iter().position(|&c| c == 0).unwrap_or(slice.len());
self.buf.extend_from_slice(&slice[..len]);
self.buf.push(0);
}
pub fn truncate(&mut self, len: usize) {
if len >= self.buf.len() { return; }
self.buf.truncate(len);
if self.buf.is_empty() || *self.buf.last().unwrap() != 0 {
self.buf.push(0);
}
}
pub fn as_ptr(&self) -> *const u16 {
if self.buf.is_empty() {
return [0u16].as_ptr();
}
self.buf.as_ptr()
}
pub fn len(&self) -> usize {
if self.buf.is_empty() { 0 } else { self.buf.len() - 1 }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn pop_null(&mut self) {
if let Some(&0) = self.buf.last() {
self.buf.pop();
}
}
pub fn to_string_lossy(&self) -> String {
if self.buf.is_empty() { return String::new(); }
let len = if self.buf.last() == Some(&0) { self.buf.len() - 1 } else { self.buf.len() };
String::from_utf16_lossy(&self.buf[..len])
}
}
pub mod matcher {
pub fn is_match(pattern: &str, text: &str) -> bool {
let p_chars: Vec<char> = pattern.chars().collect();
let t_chars: Vec<char> = text.chars().collect();
match_slice(&p_chars, &t_chars)
}
fn match_slice(p: &[char], t: &[char]) -> bool {
if p.is_empty() {
return t.is_empty();
}
match p[0] {
'*' => {
let mut next_p = 1;
while next_p < p.len() && p[next_p] == '*' {
next_p += 1;
}
if next_p == p.len() {
return true;
}
for i in 0..=t.len() {
if match_slice(&p[next_p..], &t[i..]) {
return true;
}
}
false
},
'?' => {
if t.is_empty() { return false; }
match_slice(&p[1..], &t[1..])
},
'^' => {
if t.is_empty() { return false; }
if p[0] == t[0] {
match_slice(&p[1..], &t[1..])
} else {
false
}
},
c => {
if t.is_empty() { return false; }
if c == t[0] {
match_slice(&p[1..], &t[1..])
} else {
false
}
}
}
}
}