1
//! Helper functions for handling the Windows console from a GUI context.
2
//!
3
//! Windows subsystem applications must explicitly attach to an existing console
4
//! before stdio works, and if not available, create their own if they wish to
5
//! print anything.
6
//!
7
//! These functions enable that, primarily for the purposes of displaying Rust
8
//! panics.
9

            
10
use std::result::Result;
11

            
12
use merc_utilities::MercError;
13
#[cfg(windows)]
14
use winapi::um::consoleapi::AllocConsole;
15

            
16
#[cfg(windows)]
17
use winapi::um::wincon::ATTACH_PARENT_PROCESS;
18
#[cfg(windows)]
19
use winapi::um::wincon::AttachConsole;
20
#[cfg(windows)]
21
use winapi::um::wincon::FreeConsole;
22
#[cfg(windows)]
23
use winapi::um::wincon::GetConsoleWindow;
24

            
25
pub struct Console {
26
    #[cfg(windows)]
27
    attached: bool,
28
}
29

            
30
/// Initialises the console and returns a [`Console`] guard.
31
///
32
/// On Windows this either attaches to the parent process' console, attaches to
33
/// an existing console, or allocates a fresh one when none is available, so that
34
/// `println!` and panic output are visible from a windows-subsystem binary.
35
/// Dropping the returned guard frees a console that this call allocated. On
36
/// other platforms it is a no-op and the guard does nothing.
37
pub fn init_console() -> Result<Console, MercError> {
38
    #[cfg(windows)]
39
    unsafe {
40
        // SAFETY: These console functions take no pointers and are safe to call
41
        // in any process state; the only soundness obligation is to balance an
42
        // allocated console with a single `FreeConsole`, which the `Drop` impl
43
        // does (guarded by `attached` so we never free a pre-existing console).
44
        // Check if we're attached to an existing Windows console
45
        if GetConsoleWindow().is_null() {
46
            // Try to attach to an existing Windows console.
47
            //
48
            // It's normally a no-brainer to call this - it just makes println! and friends
49
            // work as expected, without cluttering the screen with a console in the general
50
            // case.
51
            if AttachConsole(ATTACH_PARENT_PROCESS) == 0 {
52
                // Try to attach to a console, and if not, allocate ourselves a new one.
53
                if AllocConsole() != 0 {
54
                    Ok(Console { attached: false })
55
                } else {
56
                    Err("Failed to attach to a console, and to create one".into())
57
                }
58
            } else {
59
                // We attached to an existing console.
60
                Ok(Console { attached: true })
61
            }
62
        } else {
63
            // The program was started with a console attached.
64
            Ok(Console { attached: true })
65
        }
66
    }
67

            
68
    #[cfg(not(windows))]
69
    {
70
        Ok(Console {})
71
    }
72
}
73

            
74
impl Drop for Console {
75
    fn drop(&mut self) {
76
        // Free the allocated console, when it was not attached.
77
        #[cfg(windows)]
78
        if !self.attached {
79
            // SAFETY: `FreeConsole` takes no arguments; `attached == false`
80
            // means `init_console` allocated this console, so freeing it here
81
            // balances that allocation exactly once.
82
            unsafe { FreeConsole() };
83
        }
84
    }
85
}