ErichDonGubler / termion

A bindless library for controlling terminals/TTY.
MIT License
4 stars 1 forks source link

Windows - Step 1 - Implement the get_tty() function #2

Open mattmccarty opened 6 years ago

mattmccarty commented 6 years ago

This is the backbone of the asyncronise portion of the code base, so I think we should implement this first. I have put together a draft which is included below. I'm new to rust, so we need to figure out the best way to return the data.

The function that consumes the returned data is async::async_stdin.

use std::io;
use std::ptr::null_mut;
use std::marker::Sized;
use ::sys::widestring::WideString;

use ::sys::kernel32::{
    GetStdHandle,
    GetConsoleMode,
    SetConsoleMode,
    PeekConsoleInputW,
    GetNumberOfConsoleInputEvents,
    ReadFile,
};
use ::sys::kernel32::{
    CreateFileW,
};
use ::sys::kernel32::{
    CloseHandle,
};
use ::sys::winapi::fileapi::{
    OPEN_EXISTING,
};
use ::sys::winapi::winnt::{
    GENERIC_READ,
    GENERIC_WRITE,
    FILE_SHARE_READ,
};
use ::sys::winapi::wincon::{
    ENABLE_PROCESSED_OUTPUT,
    ENABLE_WRAP_AT_EOL_OUTPUT,
    ENABLE_LINE_INPUT,
    ENABLE_PROCESSED_INPUT,
    ENABLE_ECHO_INPUT,
};
use ::sys::winapi::winbase::{
    STD_INPUT_HANDLE,
    STD_OUTPUT_HANDLE,
};
use ::sys::winapi::{
    FALSE, DWORD, HANDLE, INVALID_HANDLE_VALUE, LPWSTR, LPVOID
};

// once winapi 0.3 is available
// use winapi::wincon::{ENABLE_VIRTUAL_TERMINAL_PROCESSING};
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: DWORD = 0x0004;

/// Is this stream a TTY?
pub fn is_tty(stream: &HANDLE) -> bool {
    if *stream == INVALID_HANDLE_VALUE {
        return false;
    };

    let mut read: DWORD = 0;
    if unsafe { PeekConsoleInputW(*stream, null_mut(), 0, &mut read) == 0 } {
        return false;
    };

    return true;
}

/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
pub fn get_tty() -> io::Result<Box<io::Read>> {
    unsafe {
        // UTF-16 encoded CONIN$ file
        let conin_file: Vec<u16> = "CONIN$\0".encode_utf16().collect();
        let hconin    : HANDLE   = CreateFileW(conin_file.as_ptr(),
            GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, null_mut(), OPEN_EXISTING, 0, null_mut()
        );

        if !is_tty(&hconin) || hconin == INVALID_HANDLE_VALUE {
            return Ok(Box::new("".as_bytes()) as Box<io::Read>);
        };

        let mut events: DWORD = 0;
        if GetNumberOfConsoleInputEvents(hconin, &mut events) == 0 {
            CloseHandle(hconin);
            return Ok(Box::new("".as_bytes()) as Box<io::Read>);
        }

        let mut dw_out: DWORD = 0;
        if GetConsoleMode(hconin, &mut dw_out) == 0 {
            return Ok(Box::new("".as_bytes()) as Box<io::Read>);
        }

        SetConsoleMode(hconin, dw_out & ENABLE_LINE_INPUT);

        let mut file_buffer = Vec::new();
        let mut file_read  : DWORD       = 0;
        ReadFile(hconin, (&mut file_buffer.as_ptr() as &mut LPVOID) as LPVOID, events, &mut file_read, null_mut());

        let widestr: WideString = WideString::from_ptr(file_buffer.as_ptr(), file_read as usize);
        Ok(return Ok(Box::new("".as_bytes()) as Box<io::Read>))
    }
}
ErichDonGubler commented 6 years ago

@mattmccarty: My bet is that we're going to want to make unsafe blocks more granular than you have, but we can handle that when we actually try to land this. :)

Another question: how does this compare to the get_tty that @mcgoo implemented before and that I had to rebase? I unfortunately have little domain knowledge with the terminal APIs exposed by Windows. I know where to find them, but it's hard to know when I might actually need something...

mattmccarty commented 6 years ago

@ErichDonGubler Regarding the unsafe blocks, I'll trust your rust knowledge. I'm new to rust, so I have some learning to do.

Regarding your second question, it's hard for me to recall because I did the research back in December and put this together. I believe that by getting the stream using "CONIN$" with CreateFile will return the actual console regardless if the stream has been redirected.

Btw, I feel your pain. I'm not an expert Windows guy either. I typically use linux. I made the switch from developing for Windows to linux (and the web) about 10 years ago. I've got some catching up to do.

ErichDonGubler commented 6 years ago

I've started working on this in the windows-is_tty branch (which should probably be renamed to something like windows-tty). Your implementation of the is_tty looks like it works -- looking forward to getting get_tty working with you! :)

mattmccarty commented 6 years ago

get_tty() is now working at least with ConEmu. cmd.exe panics and throws an error when a key is pressed. I will fix that next.