edit/sys/
windows.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use std::ffi::{CStr, OsString, c_void};
5use std::fmt::Write as _;
6use std::fs::{self, File};
7use std::mem::MaybeUninit;
8use std::os::windows::io::{AsRawHandle as _, FromRawHandle};
9use std::path::{Path, PathBuf};
10use std::ptr::{self, NonNull, null, null_mut};
11use std::{mem, time};
12
13use windows_sys::Win32::Storage::FileSystem;
14use windows_sys::Win32::System::Diagnostics::Debug;
15use windows_sys::Win32::System::{Console, IO, LibraryLoader, Memory, Threading};
16use windows_sys::Win32::{Foundation, Globalization};
17use windows_sys::w;
18
19use crate::apperr;
20use crate::arena::{Arena, ArenaString, scratch_arena};
21use crate::helpers::*;
22
23type ReadConsoleInputExW = unsafe extern "system" fn(
24    h_console_input: Foundation::HANDLE,
25    lp_buffer: *mut Console::INPUT_RECORD,
26    n_length: u32,
27    lp_number_of_events_read: *mut u32,
28    w_flags: u16,
29) -> Foundation::BOOL;
30
31unsafe extern "system" fn read_console_input_ex_placeholder(
32    _: Foundation::HANDLE,
33    _: *mut Console::INPUT_RECORD,
34    _: u32,
35    _: *mut u32,
36    _: u16,
37) -> Foundation::BOOL {
38    panic!();
39}
40
41const CONSOLE_READ_NOWAIT: u16 = 0x0002;
42
43const INVALID_CONSOLE_MODE: u32 = u32::MAX;
44
45struct State {
46    read_console_input_ex: ReadConsoleInputExW,
47    stdin: Foundation::HANDLE,
48    stdout: Foundation::HANDLE,
49    stdin_cp_old: u32,
50    stdout_cp_old: u32,
51    stdin_mode_old: u32,
52    stdout_mode_old: u32,
53    leading_surrogate: u16,
54    inject_resize: bool,
55    wants_exit: bool,
56}
57
58static mut STATE: State = State {
59    read_console_input_ex: read_console_input_ex_placeholder,
60    stdin: null_mut(),
61    stdout: null_mut(),
62    stdin_cp_old: 0,
63    stdout_cp_old: 0,
64    stdin_mode_old: INVALID_CONSOLE_MODE,
65    stdout_mode_old: INVALID_CONSOLE_MODE,
66    leading_surrogate: 0,
67    inject_resize: false,
68    wants_exit: false,
69};
70
71extern "system" fn console_ctrl_handler(_ctrl_type: u32) -> Foundation::BOOL {
72    unsafe {
73        STATE.wants_exit = true;
74        IO::CancelIoEx(STATE.stdin, null());
75    }
76    1
77}
78
79/// Initializes the platform-specific state.
80pub fn init() -> apperr::Result<Deinit> {
81    unsafe {
82        // Get the stdin and stdout handles first, so that if this function fails,
83        // we at least got something to use for `write_stdout`.
84        STATE.stdin = Console::GetStdHandle(Console::STD_INPUT_HANDLE);
85        STATE.stdout = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE);
86
87        // Reopen stdin if it's redirected (= piped input).
88        if !ptr::eq(STATE.stdin, Foundation::INVALID_HANDLE_VALUE)
89            && matches!(
90                FileSystem::GetFileType(STATE.stdin),
91                FileSystem::FILE_TYPE_DISK | FileSystem::FILE_TYPE_PIPE
92            )
93        {
94            STATE.stdin = FileSystem::CreateFileW(
95                w!("CONIN$"),
96                Foundation::GENERIC_READ | Foundation::GENERIC_WRITE,
97                FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_WRITE,
98                null_mut(),
99                FileSystem::OPEN_EXISTING,
100                0,
101                null_mut(),
102            );
103        }
104
105        if ptr::eq(STATE.stdin, Foundation::INVALID_HANDLE_VALUE)
106            || ptr::eq(STATE.stdout, Foundation::INVALID_HANDLE_VALUE)
107        {
108            return Err(get_last_error());
109        }
110
111        unsafe fn load_read_func(module: *const u16) -> apperr::Result<ReadConsoleInputExW> {
112            unsafe { get_module(module).and_then(|m| get_proc_address(m, c"ReadConsoleInputExW")) }
113        }
114
115        // `kernel32.dll` doesn't exist on OneCore variants of Windows.
116        // NOTE: `kernelbase.dll` is NOT a stable API to rely on. In our case it's the best option though.
117        //
118        // This is written as two nested `match` statements so that we can return the error from the first
119        // `load_read_func` call if it fails. The kernel32.dll lookup may contain some valid information,
120        // while the kernelbase.dll lookup may not, since it's not a stable API.
121        STATE.read_console_input_ex = match load_read_func(w!("kernel32.dll")) {
122            Ok(func) => func,
123            Err(err) => match load_read_func(w!("kernelbase.dll")) {
124                Ok(func) => func,
125                Err(_) => return Err(err),
126            },
127        };
128
129        Ok(Deinit)
130    }
131}
132
133pub struct Deinit;
134
135impl Drop for Deinit {
136    fn drop(&mut self) {
137        unsafe {
138            if STATE.stdin_cp_old != 0 {
139                Console::SetConsoleCP(STATE.stdin_cp_old);
140                STATE.stdin_cp_old = 0;
141            }
142            if STATE.stdout_cp_old != 0 {
143                Console::SetConsoleOutputCP(STATE.stdout_cp_old);
144                STATE.stdout_cp_old = 0;
145            }
146            if STATE.stdin_mode_old != INVALID_CONSOLE_MODE {
147                Console::SetConsoleMode(STATE.stdin, STATE.stdin_mode_old);
148                STATE.stdin_mode_old = INVALID_CONSOLE_MODE;
149            }
150            if STATE.stdout_mode_old != INVALID_CONSOLE_MODE {
151                Console::SetConsoleMode(STATE.stdout, STATE.stdout_mode_old);
152                STATE.stdout_mode_old = INVALID_CONSOLE_MODE;
153            }
154        }
155    }
156}
157
158/// Switches the terminal into raw mode, etc.
159pub fn switch_modes() -> apperr::Result<()> {
160    unsafe {
161        check_bool_return(Console::SetConsoleCtrlHandler(Some(console_ctrl_handler), 1))?;
162
163        STATE.stdin_cp_old = Console::GetConsoleCP();
164        STATE.stdout_cp_old = Console::GetConsoleOutputCP();
165        check_bool_return(Console::GetConsoleMode(STATE.stdin, &raw mut STATE.stdin_mode_old))?;
166        check_bool_return(Console::GetConsoleMode(STATE.stdout, &raw mut STATE.stdout_mode_old))?;
167
168        check_bool_return(Console::SetConsoleCP(Globalization::CP_UTF8))?;
169        check_bool_return(Console::SetConsoleOutputCP(Globalization::CP_UTF8))?;
170        check_bool_return(Console::SetConsoleMode(
171            STATE.stdin,
172            Console::ENABLE_WINDOW_INPUT
173                | Console::ENABLE_EXTENDED_FLAGS
174                | Console::ENABLE_VIRTUAL_TERMINAL_INPUT,
175        ))?;
176        check_bool_return(Console::SetConsoleMode(
177            STATE.stdout,
178            Console::ENABLE_PROCESSED_OUTPUT
179                | Console::ENABLE_WRAP_AT_EOL_OUTPUT
180                | Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING
181                | Console::DISABLE_NEWLINE_AUTO_RETURN,
182        ))?;
183
184        Ok(())
185    }
186}
187
188/// During startup we need to get the window size from the terminal.
189/// Because I didn't want to type a bunch of code, this function tells
190/// [`read_stdin`] to inject a fake sequence, which gets picked up by
191/// the input parser and provided to the TUI code.
192pub fn inject_window_size_into_stdin() {
193    unsafe {
194        STATE.inject_resize = true;
195    }
196}
197
198fn get_console_size() -> Option<Size> {
199    unsafe {
200        let mut info: Console::CONSOLE_SCREEN_BUFFER_INFOEX = mem::zeroed();
201        info.cbSize = mem::size_of::<Console::CONSOLE_SCREEN_BUFFER_INFOEX>() as u32;
202        if Console::GetConsoleScreenBufferInfoEx(STATE.stdout, &mut info) == 0 {
203            return None;
204        }
205
206        let w = (info.srWindow.Right - info.srWindow.Left + 1).max(1) as CoordType;
207        let h = (info.srWindow.Bottom - info.srWindow.Top + 1).max(1) as CoordType;
208        Some(Size { width: w, height: h })
209    }
210}
211
212/// Reads from stdin.
213///
214/// # Returns
215///
216/// * `None` if there was an error reading from stdin.
217/// * `Some("")` if the given timeout was reached.
218/// * Otherwise, it returns the read, non-empty string.
219pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option<ArenaString<'_>> {
220    let scratch = scratch_arena(Some(arena));
221
222    // On startup we're asked to inject a window size so that the UI system can layout the elements.
223    // --> Inject a fake sequence for our input parser.
224    let mut resize_event = None;
225    if unsafe { STATE.inject_resize } {
226        unsafe { STATE.inject_resize = false };
227        timeout = time::Duration::ZERO;
228        resize_event = get_console_size();
229    }
230
231    let read_poll = timeout != time::Duration::MAX; // there is a timeout -> don't block in read()
232    let input_buf = scratch.alloc_uninit_slice(4 * KIBI);
233    let mut input_buf_cap = input_buf.len();
234    let utf16_buf = scratch.alloc_uninit_slice(4 * KIBI);
235    let mut utf16_buf_len = 0;
236
237    // If there was a leftover leading surrogate from the last read, we prepend it to the buffer.
238    if unsafe { STATE.leading_surrogate } != 0 {
239        utf16_buf[0] = MaybeUninit::new(unsafe { STATE.leading_surrogate });
240        utf16_buf_len = 1;
241        input_buf_cap -= 1;
242        unsafe { STATE.leading_surrogate = 0 };
243    }
244
245    // Read until there's either a timeout or we have something to process.
246    loop {
247        if timeout != time::Duration::MAX {
248            let beg = time::Instant::now();
249
250            match unsafe { Threading::WaitForSingleObject(STATE.stdin, timeout.as_millis() as u32) }
251            {
252                // Ready to read? Continue with reading below.
253                Foundation::WAIT_OBJECT_0 => {}
254                // Timeout? Skip reading entirely.
255                Foundation::WAIT_TIMEOUT => break,
256                // Error? Tell the caller stdin is broken.
257                _ => return None,
258            }
259
260            timeout = timeout.saturating_sub(beg.elapsed());
261        }
262
263        // Read from stdin.
264        let input = unsafe {
265            // If we had a `inject_resize`, we don't want to block indefinitely for other pending input on startup,
266            // but are still interested in any other pending input that may be waiting for us.
267            let flags = if read_poll { CONSOLE_READ_NOWAIT } else { 0 };
268            let mut read = 0;
269            let ok = (STATE.read_console_input_ex)(
270                STATE.stdin,
271                input_buf[0].as_mut_ptr(),
272                input_buf_cap as u32,
273                &mut read,
274                flags,
275            );
276            if ok == 0 || STATE.wants_exit {
277                return None;
278            }
279            input_buf[..read as usize].assume_init_ref()
280        };
281
282        // Convert Win32 input records into UTF16.
283        for inp in input {
284            match inp.EventType as u32 {
285                Console::KEY_EVENT => {
286                    let event = unsafe { &inp.Event.KeyEvent };
287                    let ch = unsafe { event.uChar.UnicodeChar };
288                    if event.bKeyDown != 0 && ch != 0 {
289                        utf16_buf[utf16_buf_len] = MaybeUninit::new(ch);
290                        utf16_buf_len += 1;
291                    }
292                }
293                Console::WINDOW_BUFFER_SIZE_EVENT => {
294                    let event = unsafe { &inp.Event.WindowBufferSizeEvent };
295                    let w = event.dwSize.X as CoordType;
296                    let h = event.dwSize.Y as CoordType;
297                    // Windows is prone to sending broken/useless `WINDOW_BUFFER_SIZE_EVENT`s.
298                    // E.g. starting conhost will emit 3 in a row. Skip rendering in that case.
299                    if w > 0 && h > 0 {
300                        resize_event = Some(Size { width: w, height: h });
301                    }
302                }
303                _ => {}
304            }
305        }
306
307        if resize_event.is_some() || utf16_buf_len != 0 {
308            break;
309        }
310    }
311
312    const RESIZE_EVENT_FMT_MAX_LEN: usize = 16; // "\x1b[8;65535;65535t"
313    let resize_event_len = if resize_event.is_some() { RESIZE_EVENT_FMT_MAX_LEN } else { 0 };
314    // +1 to account for a potential `STATE.leading_surrogate`.
315    let utf8_max_len = (utf16_buf_len + 1) * 3;
316    let mut text = ArenaString::new_in(arena);
317    text.reserve(utf8_max_len + resize_event_len);
318
319    // Now prepend our previously extracted resize event.
320    if let Some(resize_event) = resize_event {
321        // If I read xterm's documentation correctly, CSI 18 t reports the window size in characters.
322        // CSI 8 ; height ; width t is the response. Of course, we didn't send the request,
323        // but we can use this fake response to trigger the editor to resize itself.
324        _ = write!(text, "\x1b[8;{};{}t", resize_event.height, resize_event.width);
325    }
326
327    // If the input ends with a lone lead surrogate, we need to remember it for the next read.
328    if utf16_buf_len > 0 {
329        unsafe {
330            let last_char = utf16_buf[utf16_buf_len - 1].assume_init();
331            if (0xD800..0xDC00).contains(&last_char) {
332                STATE.leading_surrogate = last_char;
333                utf16_buf_len -= 1;
334            }
335        }
336    }
337
338    // Convert the remaining input to UTF8, the sane encoding.
339    if utf16_buf_len > 0 {
340        unsafe {
341            let vec = text.as_mut_vec();
342            let spare = vec.spare_capacity_mut();
343
344            let len = Globalization::WideCharToMultiByte(
345                Globalization::CP_UTF8,
346                0,
347                utf16_buf[0].as_ptr(),
348                utf16_buf_len as i32,
349                spare.as_mut_ptr() as *mut _,
350                spare.len() as i32,
351                null(),
352                null_mut(),
353            );
354
355            if len > 0 {
356                vec.set_len(vec.len() + len as usize);
357            }
358        }
359    }
360
361    text.shrink_to_fit();
362    Some(text)
363}
364
365/// Writes a string to stdout.
366///
367/// Use this instead of `print!` or `println!` to avoid
368/// the overhead of Rust's stdio handling. Don't need that.
369pub fn write_stdout(text: &str) {
370    unsafe {
371        let mut offset = 0;
372
373        while offset < text.len() {
374            let ptr = text.as_ptr().add(offset);
375            let write = (text.len() - offset).min(GIBI) as u32;
376            let mut written = 0;
377            let ok = FileSystem::WriteFile(STATE.stdout, ptr, write, &mut written, null_mut());
378            offset += written as usize;
379            if ok == 0 || written == 0 {
380                break;
381            }
382        }
383    }
384}
385
386/// Check if the stdin handle is redirected to a file, etc.
387///
388/// # Returns
389///
390/// * `Some(file)` if stdin is redirected.
391/// * Otherwise, `None`.
392pub fn open_stdin_if_redirected() -> Option<File> {
393    unsafe {
394        let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE);
395        // Did we reopen stdin during `init()`?
396        if !std::ptr::eq(STATE.stdin, handle) { Some(File::from_raw_handle(handle)) } else { None }
397    }
398}
399
400pub fn drives() -> impl Iterator<Item = char> {
401    unsafe {
402        let mut mask = FileSystem::GetLogicalDrives();
403        std::iter::from_fn(move || {
404            let bit = mask.trailing_zeros();
405            if bit >= 26 {
406                None
407            } else {
408                mask &= !(1 << bit);
409                Some((b'A' + bit as u8) as char)
410            }
411        })
412    }
413}
414
415/// A unique identifier for a file.
416pub enum FileId {
417    Id(FileSystem::FILE_ID_INFO),
418    Path(PathBuf),
419}
420
421impl PartialEq for FileId {
422    fn eq(&self, other: &Self) -> bool {
423        match (self, other) {
424            (Self::Id(left), Self::Id(right)) => {
425                // Lowers to an efficient word-wise comparison.
426                const SIZE: usize = std::mem::size_of::<FileSystem::FILE_ID_INFO>();
427                let a: &[u8; SIZE] = unsafe { mem::transmute(left) };
428                let b: &[u8; SIZE] = unsafe { mem::transmute(right) };
429                a == b
430            }
431            (Self::Path(left), Self::Path(right)) => left == right,
432            _ => false,
433        }
434    }
435}
436
437impl Eq for FileId {}
438
439/// Returns a unique identifier for the given file by handle or path.
440pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result<FileId> {
441    let file = match file {
442        Some(f) => f,
443        None => &File::open(path)?,
444    };
445
446    file_id_from_handle(file).or_else(|_| Ok(FileId::Path(std::fs::canonicalize(path)?)))
447}
448
449fn file_id_from_handle(file: &File) -> apperr::Result<FileId> {
450    unsafe {
451        let mut info = MaybeUninit::<FileSystem::FILE_ID_INFO>::uninit();
452        check_bool_return(FileSystem::GetFileInformationByHandleEx(
453            file.as_raw_handle(),
454            FileSystem::FileIdInfo,
455            info.as_mut_ptr() as *mut _,
456            mem::size_of::<FileSystem::FILE_ID_INFO>() as u32,
457        ))?;
458        Ok(FileId::Id(info.assume_init()))
459    }
460}
461
462/// Canonicalizes the given path.
463///
464/// This differs from [`fs::canonicalize`] in that it strips the `\\?\` UNC
465/// prefix on Windows. This is because it's confusing/ugly when displaying it.
466pub fn canonicalize(path: &Path) -> std::io::Result<PathBuf> {
467    let mut path = fs::canonicalize(path)?;
468    let path = path.as_mut_os_string();
469    let mut path = mem::take(path).into_encoded_bytes();
470
471    if path.len() > 6 && &path[0..4] == br"\\?\" && path[4].is_ascii_uppercase() && path[5] == b':'
472    {
473        path.drain(0..4);
474    }
475
476    let path = unsafe { OsString::from_encoded_bytes_unchecked(path) };
477    let path = PathBuf::from(path);
478    Ok(path)
479}
480
481/// Reserves a virtual memory region of the given size.
482/// To commit the memory, use [`virtual_commit`].
483/// To release the memory, use [`virtual_release`].
484///
485/// # Safety
486///
487/// This function is unsafe because it uses raw pointers.
488/// Don't forget to release the memory when you're done with it or you'll leak it.
489pub unsafe fn virtual_reserve(size: usize) -> apperr::Result<NonNull<u8>> {
490    unsafe {
491        #[allow(unused_assignments, unused_mut)]
492        let mut base = null_mut();
493
494        // In debug builds, we use fixed addresses to aid in debugging.
495        // Makes it possible to immediately tell which address space a pointer belongs to.
496        #[cfg(all(debug_assertions, not(target_pointer_width = "32")))]
497        {
498            static mut S_BASE_GEN: usize = 0x0000100000000000; // 16 TiB
499            S_BASE_GEN += 0x0000001000000000; // 64 GiB
500            base = S_BASE_GEN as *mut _;
501        }
502
503        check_ptr_return(Memory::VirtualAlloc(
504            base,
505            size,
506            Memory::MEM_RESERVE,
507            Memory::PAGE_READWRITE,
508        ) as *mut u8)
509    }
510}
511
512/// Releases a virtual memory region of the given size.
513///
514/// # Safety
515///
516/// This function is unsafe because it uses raw pointers.
517/// Make sure to only pass pointers acquired from [`virtual_reserve`].
518pub unsafe fn virtual_release(base: NonNull<u8>, _size: usize) {
519    unsafe {
520        // NOTE: `VirtualFree` fails if the pointer isn't
521        // a valid base address or if the size isn't zero.
522        Memory::VirtualFree(base.as_ptr() as *mut _, 0, Memory::MEM_RELEASE);
523    }
524}
525
526/// Commits a virtual memory region of the given size.
527///
528/// # Safety
529///
530/// This function is unsafe because it uses raw pointers.
531/// Make sure to only pass pointers acquired from [`virtual_reserve`]
532/// and to pass a size less than or equal to the size passed to [`virtual_reserve`].
533pub unsafe fn virtual_commit(base: NonNull<u8>, size: usize) -> apperr::Result<()> {
534    unsafe {
535        check_ptr_return(Memory::VirtualAlloc(
536            base.as_ptr() as *mut _,
537            size,
538            Memory::MEM_COMMIT,
539            Memory::PAGE_READWRITE,
540        ))
541        .map(|_| ())
542    }
543}
544
545unsafe fn get_module(name: *const u16) -> apperr::Result<NonNull<c_void>> {
546    unsafe { check_ptr_return(LibraryLoader::GetModuleHandleW(name)) }
547}
548
549unsafe fn load_library(name: *const u16) -> apperr::Result<NonNull<c_void>> {
550    unsafe {
551        check_ptr_return(LibraryLoader::LoadLibraryExW(
552            name,
553            null_mut(),
554            LibraryLoader::LOAD_LIBRARY_SEARCH_SYSTEM32,
555        ))
556    }
557}
558
559/// Loads a function from a dynamic library.
560///
561/// # Safety
562///
563/// This function is highly unsafe as it requires you to know the exact type
564/// of the function you're loading. No type checks whatsoever are performed.
565//
566// It'd be nice to constrain T to std::marker::FnPtr, but that's unstable.
567pub unsafe fn get_proc_address<T>(handle: NonNull<c_void>, name: &CStr) -> apperr::Result<T> {
568    unsafe {
569        let ptr = LibraryLoader::GetProcAddress(handle.as_ptr(), name.as_ptr() as *const u8);
570        if let Some(ptr) = ptr { Ok(mem::transmute_copy(&ptr)) } else { Err(get_last_error()) }
571    }
572}
573
574/// Loads the "common" portion of ICU4C.
575pub fn load_libicuuc() -> apperr::Result<NonNull<c_void>> {
576    unsafe { load_library(w!("icuuc.dll")) }
577}
578
579/// Loads the internationalization portion of ICU4C.
580pub fn load_libicui18n() -> apperr::Result<NonNull<c_void>> {
581    unsafe { load_library(w!("icuin.dll")) }
582}
583
584/// Returns a list of preferred languages for the current user.
585pub fn preferred_languages(arena: &Arena) -> Vec<ArenaString<'_>, &Arena> {
586    // If the GetUserPreferredUILanguages() don't fit into 512 characters,
587    // honestly, just give up. How many languages do you realistically need?
588    const LEN: usize = 512;
589
590    let scratch = scratch_arena(Some(arena));
591    let mut res = Vec::new_in(arena);
592
593    // Get the list of preferred languages via `GetUserPreferredUILanguages`.
594    let langs = unsafe {
595        let buf = scratch.alloc_uninit_slice(LEN);
596        let mut len = buf.len() as u32;
597        let mut num = 0;
598
599        let ok = Globalization::GetUserPreferredUILanguages(
600            Globalization::MUI_LANGUAGE_NAME,
601            &mut num,
602            buf[0].as_mut_ptr(),
603            &mut len,
604        );
605
606        if ok == 0 || num == 0 {
607            len = 0;
608        }
609
610        // Drop the terminating double-null character.
611        len = len.saturating_sub(1);
612
613        buf[..len as usize].assume_init_ref()
614    };
615
616    // Convert UTF16 to UTF8.
617    let langs = wide_to_utf8(&scratch, langs);
618
619    // Split the null-delimited string into individual chunks
620    // and copy them into the given arena.
621    res.extend(
622        langs
623            .split_terminator('\0')
624            .filter(|s| !s.is_empty())
625            .map(|s| ArenaString::from_str(arena, s)),
626    );
627    res
628}
629
630fn wide_to_utf8<'a>(arena: &'a Arena, wide: &[u16]) -> ArenaString<'a> {
631    let mut res = ArenaString::new_in(arena);
632    res.reserve(wide.len() * 3);
633
634    let len = unsafe {
635        Globalization::WideCharToMultiByte(
636            Globalization::CP_UTF8,
637            0,
638            wide.as_ptr(),
639            wide.len() as i32,
640            res.as_mut_ptr() as *mut _,
641            res.capacity() as i32,
642            null(),
643            null_mut(),
644        )
645    };
646    if len > 0 {
647        unsafe { res.as_mut_vec().set_len(len as usize) };
648    }
649
650    res.shrink_to_fit();
651    res
652}
653
654#[cold]
655fn get_last_error() -> apperr::Error {
656    unsafe { gle_to_apperr(Foundation::GetLastError()) }
657}
658
659#[inline]
660const fn gle_to_apperr(gle: u32) -> apperr::Error {
661    apperr::Error::new_sys(if gle == 0 { 0x8000FFFF } else { 0x80070000 | gle })
662}
663
664#[inline]
665pub(crate) fn io_error_to_apperr(err: std::io::Error) -> apperr::Error {
666    gle_to_apperr(err.raw_os_error().unwrap_or(0) as u32)
667}
668
669/// Formats a platform error code into a human-readable string.
670pub fn apperr_format(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result {
671    unsafe {
672        let mut ptr: *mut u8 = null_mut();
673        let len = Debug::FormatMessageA(
674            Debug::FORMAT_MESSAGE_ALLOCATE_BUFFER
675                | Debug::FORMAT_MESSAGE_FROM_SYSTEM
676                | Debug::FORMAT_MESSAGE_IGNORE_INSERTS,
677            null(),
678            code,
679            0,
680            &mut ptr as *mut *mut _ as *mut _,
681            0,
682            null_mut(),
683        );
684
685        write!(f, "Error {code:#08x}")?;
686
687        if len > 0 {
688            let msg = str_from_raw_parts(ptr, len as usize);
689            let msg = msg.trim_ascii();
690            let msg = msg.replace(['\r', '\n'], " ");
691            write!(f, ": {msg}")?;
692            Foundation::LocalFree(ptr as *mut _);
693        }
694
695        Ok(())
696    }
697}
698
699/// Checks if the given error is a "file not found" error.
700pub fn apperr_is_not_found(err: apperr::Error) -> bool {
701    err == gle_to_apperr(Foundation::ERROR_FILE_NOT_FOUND)
702}
703
704fn check_bool_return(ret: Foundation::BOOL) -> apperr::Result<()> {
705    if ret == 0 { Err(get_last_error()) } else { Ok(()) }
706}
707
708fn check_ptr_return<T>(ret: *mut T) -> apperr::Result<NonNull<T>> {
709    NonNull::new(ret).ok_or_else(get_last_error)
710}