about summary refs log tree commit diff
path: root/library/term/src/win.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/term/src/win.rs')
-rw-r--r--library/term/src/win.rs220
1 files changed, 220 insertions, 0 deletions
diff --git a/library/term/src/win.rs b/library/term/src/win.rs
new file mode 100644
index 00000000000..c24cf9518aa
--- /dev/null
+++ b/library/term/src/win.rs
@@ -0,0 +1,220 @@
+//! Windows console handling
+
+// FIXME (#13400): this is only a tiny fraction of the Windows console api
+
+use std::io;
+use std::io::prelude::*;
+
+use crate::color;
+use crate::Attr;
+use crate::Terminal;
+
+/// A Terminal implementation that uses the Win32 Console API.
+pub struct WinConsole<T> {
+    buf: T,
+    def_foreground: color::Color,
+    def_background: color::Color,
+    foreground: color::Color,
+    background: color::Color,
+}
+
+type SHORT = i16;
+type WORD = u16;
+type DWORD = u32;
+type BOOL = i32;
+type HANDLE = *mut u8;
+
+#[allow(non_snake_case)]
+#[repr(C)]
+struct SMALL_RECT {
+    Left: SHORT,
+    Top: SHORT,
+    Right: SHORT,
+    Bottom: SHORT,
+}
+
+#[allow(non_snake_case)]
+#[repr(C)]
+struct COORD {
+    X: SHORT,
+    Y: SHORT,
+}
+
+#[allow(non_snake_case)]
+#[repr(C)]
+struct CONSOLE_SCREEN_BUFFER_INFO {
+    dwSize: COORD,
+    dwCursorPosition: COORD,
+    wAttributes: WORD,
+    srWindow: SMALL_RECT,
+    dwMaximumWindowSize: COORD,
+}
+
+#[allow(non_snake_case)]
+#[link(name = "kernel32")]
+extern "system" {
+    fn SetConsoleTextAttribute(handle: HANDLE, attr: WORD) -> BOOL;
+    fn GetStdHandle(which: DWORD) -> HANDLE;
+    fn GetConsoleScreenBufferInfo(handle: HANDLE, info: *mut CONSOLE_SCREEN_BUFFER_INFO) -> BOOL;
+}
+
+fn color_to_bits(color: color::Color) -> u16 {
+    // magic numbers from mingw-w64's wincon.h
+
+    let bits = match color % 8 {
+        color::BLACK => 0,
+        color::BLUE => 0x1,
+        color::GREEN => 0x2,
+        color::RED => 0x4,
+        color::YELLOW => 0x2 | 0x4,
+        color::MAGENTA => 0x1 | 0x4,
+        color::CYAN => 0x1 | 0x2,
+        color::WHITE => 0x1 | 0x2 | 0x4,
+        _ => unreachable!(),
+    };
+
+    if color >= 8 { bits | 0x8 } else { bits }
+}
+
+fn bits_to_color(bits: u16) -> color::Color {
+    let color = match bits & 0x7 {
+        0 => color::BLACK,
+        0x1 => color::BLUE,
+        0x2 => color::GREEN,
+        0x4 => color::RED,
+        0x6 => color::YELLOW,
+        0x5 => color::MAGENTA,
+        0x3 => color::CYAN,
+        0x7 => color::WHITE,
+        _ => unreachable!(),
+    };
+
+    color | (u32::from(bits) & 0x8) // copy the hi-intensity bit
+}
+
+impl<T: Write + Send + 'static> WinConsole<T> {
+    fn apply(&mut self) {
+        let _unused = self.buf.flush();
+        let mut accum: WORD = 0;
+        accum |= color_to_bits(self.foreground);
+        accum |= color_to_bits(self.background) << 4;
+
+        unsafe {
+            // Magic -11 means stdout, from
+            // https://docs.microsoft.com/en-us/windows/console/getstdhandle
+            //
+            // You may be wondering, "but what about stderr?", and the answer
+            // to that is that setting terminal attributes on the stdout
+            // handle also sets them for stderr, since they go to the same
+            // terminal! Admittedly, this is fragile, since stderr could be
+            // redirected to a different console. This is good enough for
+            // rustc though. See #13400.
+            let out = GetStdHandle(-11i32 as DWORD);
+            SetConsoleTextAttribute(out, accum);
+        }
+    }
+
+    /// Returns `None` whenever the terminal cannot be created for some reason.
+    pub fn new(out: T) -> io::Result<WinConsole<T>> {
+        use std::mem::MaybeUninit;
+
+        let fg;
+        let bg;
+        unsafe {
+            let mut buffer_info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFO>::uninit();
+            if GetConsoleScreenBufferInfo(GetStdHandle(-11i32 as DWORD), buffer_info.as_mut_ptr())
+                != 0
+            {
+                let buffer_info = buffer_info.assume_init();
+                fg = bits_to_color(buffer_info.wAttributes);
+                bg = bits_to_color(buffer_info.wAttributes >> 4);
+            } else {
+                fg = color::WHITE;
+                bg = color::BLACK;
+            }
+        }
+        Ok(WinConsole {
+            buf: out,
+            def_foreground: fg,
+            def_background: bg,
+            foreground: fg,
+            background: bg,
+        })
+    }
+}
+
+impl<T: Write> Write for WinConsole<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.buf.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.buf.flush()
+    }
+}
+
+impl<T: Write + Send + 'static> Terminal for WinConsole<T> {
+    type Output = T;
+
+    fn fg(&mut self, color: color::Color) -> io::Result<bool> {
+        self.foreground = color;
+        self.apply();
+
+        Ok(true)
+    }
+
+    fn bg(&mut self, color: color::Color) -> io::Result<bool> {
+        self.background = color;
+        self.apply();
+
+        Ok(true)
+    }
+
+    fn attr(&mut self, attr: Attr) -> io::Result<bool> {
+        match attr {
+            Attr::ForegroundColor(f) => {
+                self.foreground = f;
+                self.apply();
+                Ok(true)
+            }
+            Attr::BackgroundColor(b) => {
+                self.background = b;
+                self.apply();
+                Ok(true)
+            }
+            _ => Ok(false),
+        }
+    }
+
+    fn supports_attr(&self, attr: Attr) -> bool {
+        // it claims support for underscore and reverse video, but I can't get
+        // it to do anything -cmr
+        match attr {
+            Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => true,
+            _ => false,
+        }
+    }
+
+    fn reset(&mut self) -> io::Result<bool> {
+        self.foreground = self.def_foreground;
+        self.background = self.def_background;
+        self.apply();
+
+        Ok(true)
+    }
+
+    fn get_ref(&self) -> &T {
+        &self.buf
+    }
+
+    fn get_mut(&mut self) -> &mut T {
+        &mut self.buf
+    }
+
+    fn into_inner(self) -> T
+    where
+        Self: Sized,
+    {
+        self.buf
+    }
+}