tuifw-screen-winapi 0.1.5

Text User Interface Framework. Basic text screen implementation for Win platform.
Documentation
#![cfg(windows)]

#![deny(warnings)]
use std::char::{self};
use std::io::{self};
use std::mem::{size_of, MaybeUninit};
use std::num::NonZeroU16;
use std::ptr::{null, null_mut};
use std::str::{self};
use tuifw_screen_base::*;
use tuifw_screen_base::Screen as base_Screen;
use either::{Either, Right, Left};
use winapi::ctypes::*;
use winapi::shared::minwindef::*;
use winapi::shared::ntdef::{CHAR, HANDLE};
use winapi::um::wincontypes::*;
use winapi::um::wincontypes::INPUT_RECORD_Event;
use winapi::um::wincon::*;
use winapi::um::consoleapi::*;
use winapi::um::processenv::{GetStdHandle};
use winapi::um::stringapiset::WideCharToMultiByte;
use winapi::um::winnls::*;
use winapi::um::winuser::*;
use winapi::um::winbase::{STD_OUTPUT_HANDLE, STD_INPUT_HANDLE};
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use num_traits::identities::Zero;
use unicode_segmentation::UnicodeSegmentation;

fn no_zero<Z: Zero>(r: Z) -> io::Result<Z> {
    if r.is_zero() {
        Err(io::Error::last_os_error())
    } else {
        Ok(r)
    }
}

fn valid_handle(h: HANDLE) -> io::Result<HANDLE> {
    if h == INVALID_HANDLE_VALUE {
        Err(io::Error::last_os_error())
    } else {
        Ok(h)
    }
}

pub struct Screen {
    h_input: HANDLE,
    input_mode: DWORD,
    h_output: HANDLE,
    output_mode: DWORD,
    output_cp: UINT,
    wctmb_flags: DWORD,
    buf: Vec<CHAR_INFO>,
    size_: Vector,
    invalidated: Rect,
}

impl Screen {
    pub fn new() -> io::Result<Self> {
        let h_input = valid_handle(unsafe { GetStdHandle(STD_INPUT_HANDLE) })?;
        let h_output = valid_handle(unsafe { GetStdHandle(STD_OUTPUT_HANDLE) })?;
        let mut input_mode: DWORD = 0;
        no_zero(unsafe { GetConsoleMode(h_input, &mut input_mode as *mut _) })?;
        let mut output_mode: DWORD = 0;
        no_zero(unsafe { GetConsoleMode(h_output, &mut output_mode as *mut _) })?;
        no_zero(unsafe { SetConsoleMode(h_input, ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT) })?;
        if let Err(e) = no_zero(unsafe { SetConsoleMode(h_output, 0) }) {
            no_zero(unsafe { SetConsoleMode(h_input, input_mode) }).unwrap();
            return Err(e);
        }
        let mut s = Screen {
            h_input,
            h_output,
            input_mode,
            output_mode,
            output_cp: 0,
            wctmb_flags: WC_COMPOSITECHECK | WC_DISCARDNS,
            buf: Vec::new(),
            size_: Vector::null(),
            invalidated: Rect { tl: Point { x: 0, y: 0 }, size: Vector::null() }
        };
        s.output_cp = no_zero(unsafe { GetConsoleOutputCP() })?;
        let test_char = 0u16;
        if unsafe { WideCharToMultiByte(
            s.output_cp,
            s.wctmb_flags,
            &test_char as *const _,
            1,
            null_mut(),
            0,
            null(),
            null_mut()
        ) } == 0 {
            s.wctmb_flags = 0;
        }
        s.resize()?;
        Ok(s)
    }

    fn resize(&mut self) -> io::Result<()> {
        let mut ci = CONSOLE_SCREEN_BUFFER_INFO {
            dwSize: COORD { X: 0, Y: 0 },
            dwCursorPosition: COORD { X: 0, Y: 0 },
            wAttributes: 0,
            srWindow: SMALL_RECT { Left: 0, Top: 0, Right: 0, Bottom: 0 },
            dwMaximumWindowSize: COORD { X: 0, Y: 0 }
        };
        no_zero(unsafe { GetConsoleScreenBufferInfo(self.h_output, &mut ci as *mut _) })?;
        let mut space = CHAR_INFO {
            Attributes: 0,
            Char: CHAR_INFO_Char::default()
        };
        *unsafe {space.Char.AsciiChar_mut() } = b' ' as CHAR;
        assert!(size_of::<usize>() >= 4);
        self.buf.resize((ci.dwSize.X as usize) * (ci.dwSize.Y as usize), space);
        self.size_ = Vector { x: ci.dwSize.X, y: ci.dwSize.Y };
        self.invalidated = Rect { tl: Point { x: 0, y: 0 }, size: self.size_ };
        Ok(())
    }

    fn encode_grapheme(output_cp: UINT, wctmb_flags: DWORD, g: &str) -> Either<u8, (u8, u8)> {
        let g = g.encode_utf16().collect::<Vec<_>>();
        let len = g.len() as isize as _;
        let n = no_zero(unsafe { WideCharToMultiByte(output_cp, wctmb_flags, g.as_ptr(), len, null_mut(), 0, null(), null_mut()) }).unwrap();
        let mut buf: Vec<MaybeUninit<u8>> = vec![MaybeUninit::uninit(); n as c_uint as usize];
        no_zero(unsafe { WideCharToMultiByte(output_cp, wctmb_flags, g.as_ptr(), len, buf.as_mut_ptr() as *mut _, n, null(), null_mut()) }).unwrap();
        unsafe { 
            if IsDBCSLeadByteEx(output_cp, buf[0].assume_init()) != 0 {
                Right((buf[0].assume_init(), buf[1].assume_init()))
            } else {
                Left(buf[0].assume_init())
            }
        }
    }
}

impl Drop for Screen {
    fn drop(&mut self) {
        unsafe {
            no_zero(SetConsoleMode(self.h_output, self.output_mode)).unwrap();
            no_zero(SetConsoleMode(self.h_input, self.input_mode)).unwrap();
        }
    }
}

fn attr_w(fg: Color, bg: Option<Color>, attr: Attr) -> WORD {
    let bg = bg.unwrap_or(Color::Black);
    let (fg, bg) = if attr.contains(Attr::REVERSE) { (bg, fg) } else { (fg, bg) };
    let fg = match fg {
        Color::Black => 0,
        Color::Red => FOREGROUND_RED,
        Color::Green => FOREGROUND_GREEN,
        Color::Yellow => FOREGROUND_RED | FOREGROUND_GREEN,
        Color::Blue => FOREGROUND_BLUE,
        Color::Magenta => FOREGROUND_RED | FOREGROUND_BLUE,
        Color::Cyan => FOREGROUND_BLUE | FOREGROUND_GREEN,
        Color::White => FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
    };
    let bg = match bg {
        Color::Black => 0,
        Color::Red => BACKGROUND_RED,
        Color::Green => BACKGROUND_GREEN,
        Color::Yellow => BACKGROUND_RED | BACKGROUND_GREEN,
        Color::Blue => BACKGROUND_BLUE,
        Color::Magenta => BACKGROUND_RED | BACKGROUND_BLUE,
        Color::Cyan => BACKGROUND_BLUE | BACKGROUND_GREEN,
        Color::White => BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED
    };
    const REVERSE_INTENSITY: Attr = unsafe { Attr::from_bits_unchecked(Attr::INTENSITY.bits() | Attr::REVERSE.bits()) };
    let attr = match attr {
        Attr::INTENSITY => FOREGROUND_INTENSITY,
        REVERSE_INTENSITY => BACKGROUND_INTENSITY,
        _ => 0
    };
    fg | bg | attr
}

impl base_Screen for Screen {
    type Error = io::Error;

    fn size(&self) -> Vector { self.size_ }

    fn out(&mut self, p: Point, fg: Color, bg: Option<Color>, attr: Attr, text: &str) -> Option<i16> {
        let size = self.size_;
        let output_cp = self.output_cp;
        let wctmb_flags = self.wctmb_flags;
        if p.y < 0 || p.y >= size.y {
            let n = text.graphemes(true).map(|g| Self::encode_grapheme(output_cp, wctmb_flags, g)).map(|g| if g.is_left() { 1 } else { 2 }).fold(0u32, |a, b| a.saturating_add(b));
            return if n > u16::MAX as u32 { None } else { Some(n as u16 as i16) };
        }
        let line = (p.y as u16 as usize) * (size.x as u16 as usize);
        let line = &mut self.buf[line .. line + size.x as u16 as usize];
        let attr = attr_w(fg, bg, attr);
        let mut x = p.x;
        let invalidated_l = if x > 0 && x < size.x {
            if line[x as u16 as usize].Attributes & COMMON_LVB_TRAILING_BYTE != 0 {
                let col = &mut line[(x as u16 as usize) - 1];
                debug_assert!(col.Attributes & COMMON_LVB_LEADING_BYTE != 0);
                col.Attributes &= !COMMON_LVB_LEADING_BYTE;
                *unsafe { col.Char.AsciiChar_mut() } = b' ' as CHAR;
                x - 1
            } else {
                x
            }
        } else {
            x
        };
        let mut n = 0u32;
        for g in text.graphemes(true).map(|g| Self::encode_grapheme(output_cp, wctmb_flags, g)) {
            let mut g = g;
            let g_width = if g.is_left() { 1 } else { 2 };
            n = n.saturating_add(g_width as u16 as u32);
            if x < 0 {
                if x + g_width > 0 {
                    debug_assert_eq!(x + g_width, 1);
                    x = 0;
                    g = Left(b' ');
                }
            }
            if x >= 0 && x < size.x {
                if g.is_right() && x == size.x - 1 {
                    g = Left(b' ');
                }
                match g {
                    Left(c) => {
                        let col = &mut line[x as u16 as usize];
                        col.Attributes = attr;
                        *unsafe { col.Char.AsciiChar_mut() } = c as CHAR;
                        x += 1;
                    },
                    Right((l, t)) => {
                        let col = &mut line[x as u16 as usize];
                        col.Attributes = attr | COMMON_LVB_LEADING_BYTE;
                        *unsafe { col.Char.AsciiChar_mut() } = l as CHAR;
                        x += 1;
                        let col = &mut line[x as u16 as usize];
                        col.Attributes = attr | COMMON_LVB_TRAILING_BYTE;
                        *unsafe { col.Char.AsciiChar_mut() } = t as CHAR;
                        x += 1;
                    }
                }
            } else {
                x = x.saturating_add(g_width);
            };
        }
        if x >= 0 && x < size.x {
            let col = &mut line[x as u16 as usize];
            if col.Attributes & COMMON_LVB_TRAILING_BYTE != 0 {
                col.Attributes &= !COMMON_LVB_TRAILING_BYTE;
                *unsafe { col.Char.AsciiChar_mut() } = b' ' as CHAR;
                x += 1;
            }
        }
        self.invalidated = self.invalidated
            .union(Rect::with_tl_br(Point { x: invalidated_l, y: p.y }, Point { x, y: p.y + 1 }))
            .unwrap().right().unwrap()
            .intersect(Rect { tl: Point { x: 0, y: 0 }, size: self.size_ })
        ;
        if n > u16::MAX as u32 { None } else { Some(n as u16 as i16) }
    }

    fn update(&mut self, cursor: Option<Point>, wait: bool) -> Result<Option<Event>, Self::Error> {
        if !self.invalidated.is_empty() {
            let mut region = SMALL_RECT {
                Top: self.invalidated.t(),
                Left: self.invalidated.l(),
                Right: self.invalidated.r() - 1,
                Bottom: self.invalidated.b() - 1
            };
            no_zero(unsafe { WriteConsoleOutputA(
                self.h_output,
                self.buf.as_ptr(),
                COORD { X: self.size_.x, Y: self.size_.y },
                COORD { X: region.Left, Y: region.Top },
                &mut region as *mut _
            ) })?;
            self.invalidated.size = Vector::null();
        }
        let cursor = cursor.and_then(|cursor| {
            if (Rect { tl: Point { x: 0, y: 0 }, size: self.size() }).contains(cursor) {
                Some(cursor)
            } else {
                None
            }
        });
        let mut ci = CONSOLE_CURSOR_INFO { dwSize: 0, bVisible: FALSE };
        no_zero(unsafe { GetConsoleCursorInfo(self.h_output, &mut ci as *mut _) })?;
        ci.bVisible = if cursor.is_some() { TRUE } else { FALSE };
        no_zero(unsafe { SetConsoleCursorInfo(self.h_output, &ci as *const _) })?;
        let (count, key, c, ctrl, alt) = loop {
            if !wait {
                let mut n: DWORD = 0;
                no_zero(unsafe { GetNumberOfConsoleInputEvents(self.h_input, &mut n as *mut _) })?;
                if n == 0 { return Ok(None); }
            }
            let mut input = INPUT_RECORD {
                EventType: 0,
                Event: INPUT_RECORD_Event::default()
            };
            let mut readed: DWORD = 0;
            no_zero(unsafe { ReadConsoleInputW(self.h_input, &mut input as *mut _, 1, &mut readed as *mut _) })?;
            assert_eq!(readed, 1);
            match input.EventType {
                WINDOW_BUFFER_SIZE_EVENT => {
                    self.resize()?;
                    return Ok(Some(Event::Resize));
                },
                KEY_EVENT => {
                    let e = unsafe { input.Event.KeyEvent() };
                    if e.bKeyDown != 0 {
                        break (
                            NonZeroU16::new(e.wRepeatCount).unwrap(),
                            e.wVirtualKeyCode,
                            *unsafe { e.uChar.UnicodeChar() },
                            e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0,
                            e.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0
                        );
                    }
                },
                _ => unreachable!()
            }
        };
        Ok(match key as i32 {
            VK_RETURN => Some(Event::Key(count, Key::Enter)),
            VK_TAB => Some(Event::Key(count, Key::Tab)),
            VK_PRIOR => Some(Event::Key(count, Key::PageUp)),
            VK_NEXT => Some(Event::Key(count, Key::PageDown)),
            VK_HOME => Some(Event::Key(count, Key::Home)),
            VK_END => Some(Event::Key(count, Key::End)),
            VK_DOWN => Some(Event::Key(count, Key::Down)),
            VK_UP => Some(Event::Key(count, Key::Up)),
            VK_LEFT => Some(Event::Key(count, Key::Left)),
            VK_RIGHT => Some(Event::Key(count, Key::Right)),
            VK_DELETE => Some(Event::Key(count, Key::Delete)),
            VK_INSERT => Some(Event::Key(count, Key::Insert)),
            VK_F1 => Some(Event::Key(count, Key::F1)),
            VK_F2 => Some(Event::Key(count, Key::F2)),
            VK_F3 => Some(Event::Key(count, Key::F3)),
            VK_F4 => Some(Event::Key(count, Key::F4)),
            VK_F5 => Some(Event::Key(count, Key::F5)),
            VK_F6 => Some(Event::Key(count, Key::F6)),
            VK_F7 => Some(Event::Key(count, Key::F7)),
            VK_F8 => Some(Event::Key(count, Key::F8)),
            VK_F9 => Some(Event::Key(count, Key::F9)),
            VK_F10 => Some(Event::Key(count, Key::F10)),
            VK_F11 => Some(Event::Key(count, Key::F11)),
            VK_F12 => Some(Event::Key(count, Key::F12)),
            VK_ESCAPE => Some(Event::Key(count, Key::Escape)),
            VK_BACK => Some(Event::Key(count, Key::Backspace)),
            0x32 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::At))),
            0x41 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::A))),
            0x42 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::B))),
            0x43 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::C))),
            0x44 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::D))),
            0x45 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::E))),
            0x46 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::F))),
            0x47 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::G))),
            0x48 if ctrl => Some(Event::Key(count, Key::Backspace)),
            0x49 if ctrl => Some(Event::Key(count, Key::Tab)),
            0x4A if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::J))),
            0x4B if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::K))),
            0x4C if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::L))),
            0x4D if ctrl => Some(Event::Key(count, Key::Enter)),
            0x4E if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::N))),
            0x4F if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::O))),
            0x50 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::P))),
            0x51 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Q))),
            0x52 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::R))),
            0x53 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::S))),
            0x54 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::T))),
            0x55 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::U))),
            0x56 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::V))),
            0x57 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::W))),
            0x58 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::X))),
            0x59 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Y))),
            0x5A if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Z))),
            VK_OEM_4 if ctrl => Some(Event::Key(count, Key::Escape)),
            VK_OEM_5 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Backslash))),
            VK_OEM_6 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Bracket))),
            0x36 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Caret))),
            VK_OEM_MINUS if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Underscore))),
            VK_OEM_2 if ctrl => Some(Event::Key(count, Key::Backspace)),
            _ => {
                assert!(c < 0xD800 || c >= 0xDC00);
                let c = if c >= 0xDC00 && c < 0xE000 {
                    assert_eq!(count.get(), 1);
                    let mut input = INPUT_RECORD {
                        EventType: 0,
                        Event: INPUT_RECORD_Event::default()
                    };
                    let mut n: DWORD = 0;
                    no_zero(unsafe { GetNumberOfConsoleInputEvents(self.h_input, &mut n as *mut _) })?;
                    assert_ne!(n, 0);
                    let mut readed: DWORD = 0;
                    no_zero(unsafe { ReadConsoleInputW(self.h_input, &mut input as *mut _, 1, &mut readed as *mut _) })?;
                    assert_eq!(readed, 1);
                    assert_eq!(input.EventType, KEY_EVENT);
                    let e = unsafe { input.Event.KeyEvent() };
                    assert!(e.bKeyDown != 0);
                    assert_eq!(e.wRepeatCount, 1);
                    let h = *unsafe { e.uChar.UnicodeChar() };
                    assert!(h >= 0xD800 && h < 0xDC00);
                    ((h as u32 - 0xD800) << 10) | (c as u32 - 0xDC00)
                } else {
                    c as u32
                };
                let c = char::from_u32(c).unwrap();
                if c >= ' ' && c != '\x7F' {
                    if alt {
                        Some(Event::Key(count, Key::Alt(c)))
                    } else {
                        Some(Event::Key(count, Key::Char(c)))
                    }
                } else {
                    None
                }
            }
        })
    }
}