Skip to content

Commit

Permalink
windows: Unicode console support.
Browse files Browse the repository at this point in the history
Adds a WindowsTTY for libnative that converts between UTF-8 and UTF-16.

Signed-off-by: Peter Atashian <retep998@gmail.com>
  • Loading branch information
retep998 committed Jul 4, 2014
1 parent 169c988 commit a34fd5a
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 3 deletions.
28 changes: 28 additions & 0 deletions src/libnative/io/c_win32.rs
Expand Up @@ -19,6 +19,13 @@ pub static WSASYS_STATUS_LEN: uint = 128;
pub static FIONBIO: libc::c_long = 0x8004667e;
static FD_SETSIZE: uint = 64;
pub static MSG_DONTWAIT: libc::c_int = 0;
pub static ERROR_ILLEGAL_CHARACTER: libc::c_int = 582;
pub static ENABLE_ECHO_INPUT: libc::DWORD = 0x4;
pub static ENABLE_EXTENDED_FLAGS: libc::DWORD = 0x80;
pub static ENABLE_INSERT_MODE: libc::DWORD = 0x20;
pub static ENABLE_LINE_INPUT: libc::DWORD = 0x2;
pub static ENABLE_PROCESSED_INPUT: libc::DWORD = 0x1;
pub static ENABLE_QUICK_EDIT_MODE: libc::DWORD = 0x40;

#[repr(C)]
pub struct WSADATA {
Expand Down Expand Up @@ -165,3 +172,24 @@ pub mod compat {
})
}
}

extern "system" {
// FIXME - pInputControl should be PCONSOLE_READCONSOLE_CONTROL
pub fn ReadConsoleW(hConsoleInput: libc::HANDLE,
lpBuffer: libc::LPVOID,
nNumberOfCharsToRead: libc::DWORD,
lpNumberOfCharsRead: libc::LPDWORD,
pInputControl: libc::LPVOID) -> libc::BOOL;

pub fn WriteConsoleW(hConsoleOutput: libc::HANDLE,
lpBuffer: libc::types::os::arch::extra::LPCVOID,
nNumberOfCharsToWrite: libc::DWORD,
lpNumberOfCharsWritten: libc::LPDWORD,
lpReserved: libc::LPVOID) -> libc::BOOL;

pub fn GetConsoleMode(hConsoleHandle: libc::HANDLE,
lpMode: libc::LPDWORD) -> libc::BOOL;

pub fn SetConsoleMode(hConsoleHandle: libc::HANDLE,
lpMode: libc::DWORD) -> libc::BOOL;
}
22 changes: 19 additions & 3 deletions src/libnative/io/mod.rs
Expand Up @@ -69,6 +69,10 @@ pub mod pipe;
#[path = "pipe_win32.rs"]
pub mod pipe;

#[cfg(windows)]
#[path = "tty_win32.rs"]
mod tty;

#[cfg(unix)] #[path = "c_unix.rs"] mod c;
#[cfg(windows)] #[path = "c_win32.rs"] mod c;

Expand Down Expand Up @@ -280,15 +284,27 @@ impl rtio::IoFactory for IoFactory {
fn pipe_open(&mut self, fd: c_int) -> IoResult<Box<rtio::RtioPipe + Send>> {
Ok(box file::FileDesc::new(fd, true) as Box<rtio::RtioPipe + Send>)
}
#[cfg(unix)]
fn tty_open(&mut self, fd: c_int, _readable: bool)
-> IoResult<Box<rtio::RtioTTY + Send>> {
#[cfg(unix)] use ERROR = libc::ENOTTY;
#[cfg(windows)] use ERROR = libc::ERROR_INVALID_HANDLE;
if unsafe { libc::isatty(fd) } != 0 {
Ok(box file::FileDesc::new(fd, true) as Box<rtio::RtioTTY + Send>)
} else {
Err(IoError {
code: ERROR as uint,
code: libc::ENOTTY as uint,
extra: 0,
detail: None,
})
}
}
#[cfg(windows)]
fn tty_open(&mut self, fd: c_int, _readable: bool)
-> IoResult<Box<rtio::RtioTTY + Send>> {
if tty::is_tty(fd) {
Ok(box tty::WindowsTTY::new(fd) as Box<rtio::RtioTTY + Send>)
} else {
Err(IoError {
code: libc::ERROR_INVALID_HANDLE as uint,
extra: 0,
detail: None,
})
Expand Down
156 changes: 156 additions & 0 deletions src/libnative/io/tty_win32.rs
@@ -0,0 +1,156 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Windows specific console TTY implementation
//!
//! This module contains the implementation of a Windows specific console TTY.
//! Also converts between UTF-16 and UTF-8. Windows has very poor support for
//! UTF-8 and some functions will fail. In particular ReadFile and ReadConsole
//! will fail when the codepage is set to UTF-8 and a unicode character is
//! entered.
//!
//! FIXME
//! This implementation does not account for codepoints that are split across
//! multiple reads and writes. Also, this implementation does not expose a way
//! to read/write UTF-16 directly. When/if Rust receives a Reader/Writer
//! wrapper that performs encoding/decoding, this implementation should switch
//! to working in raw UTF-16, with such a wrapper around it.

use super::c::{ReadConsoleW, WriteConsoleW, GetConsoleMode, SetConsoleMode};
use super::c::{ERROR_ILLEGAL_CHARACTER};
use super::c::{ENABLE_ECHO_INPUT, ENABLE_EXTENDED_FLAGS};
use super::c::{ENABLE_INSERT_MODE, ENABLE_LINE_INPUT};
use super::c::{ENABLE_PROCESSED_INPUT, ENABLE_QUICK_EDIT_MODE};
use libc::{c_int, HANDLE, LPDWORD, DWORD, LPVOID};
use libc::{get_osfhandle, CloseHandle};
use libc::types::os::arch::extra::LPCVOID;
use std::io::MemReader;
use std::ptr;
use std::rt::rtio::{IoResult, IoError, RtioTTY};
use std::str::{from_utf16, from_utf8};

fn invalid_encoding() -> IoError {
IoError {
code: ERROR_ILLEGAL_CHARACTER as uint,
extra: 0,
detail: Some("text was not valid unicode".to_string()),
}
}

pub fn is_tty(fd: c_int) -> bool {
let mut out: DWORD = 0;
// If this function doesn't fail then fd is a TTY
match unsafe { GetConsoleMode(get_osfhandle(fd) as HANDLE,
&mut out as LPDWORD) } {
0 => false,
_ => true,
}
}

pub struct WindowsTTY {
closeme: bool,
handle: HANDLE,
utf8: MemReader,
}

impl WindowsTTY {
pub fn new(fd: c_int) -> WindowsTTY {
// If the file descriptor is one of stdin, stderr, or stdout
// then it should not be closed by us
let closeme = match fd {
0..2 => false,
_ => true,
};
let handle = unsafe { get_osfhandle(fd) as HANDLE };
WindowsTTY {
handle: handle,
utf8: MemReader::new(Vec::new()),
closeme: closeme,
}
}
}

impl Drop for WindowsTTY {
fn drop(&mut self) {
if self.closeme {
// Nobody cares about the return value
let _ = unsafe { CloseHandle(self.handle) };
}
}
}

impl RtioTTY for WindowsTTY {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
// Read more if the buffer is empty
if self.utf8.eof() {
let mut utf16 = Vec::from_elem(0x1000, 0u16);
let mut num: DWORD = 0;
match unsafe { ReadConsoleW(self.handle,
utf16.as_mut_ptr() as LPVOID,
utf16.len() as u32,
&mut num as LPDWORD,
ptr::mut_null()) } {
0 => return Err(super::last_error()),
_ => (),
};
utf16.truncate(num as uint);
let utf8 = match from_utf16(utf16.as_slice()) {
Some(utf8) => utf8.into_bytes(),
None => return Err(invalid_encoding()),
};
self.utf8 = MemReader::new(utf8);
}
// MemReader shouldn't error here since we just filled it
Ok(self.utf8.read(buf).unwrap())
}

fn write(&mut self, buf: &[u8]) -> IoResult<()> {
let utf16 = match from_utf8(buf) {
Some(utf8) => utf8.to_utf16(),
None => return Err(invalid_encoding()),
};
let mut num: DWORD = 0;
match unsafe { WriteConsoleW(self.handle,
utf16.as_ptr() as LPCVOID,
utf16.len() as u32,
&mut num as LPDWORD,
ptr::mut_null()) } {
0 => Err(super::last_error()),
_ => Ok(()),
}
}

fn set_raw(&mut self, raw: bool) -> IoResult<()> {
// FIXME
// Somebody needs to decide on which of these flags we want
match unsafe { SetConsoleMode(self.handle,
match raw {
true => 0,
false => ENABLE_ECHO_INPUT | ENABLE_EXTENDED_FLAGS |
ENABLE_INSERT_MODE | ENABLE_LINE_INPUT |
ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE,
}) } {
0 => Err(super::last_error()),
_ => Ok(()),
}
}

fn get_winsize(&mut self) -> IoResult<(int, int)> {
// FIXME
// Get console buffer via CreateFile with CONOUT$
// Make a CONSOLE_SCREEN_BUFFER_INFO
// Call GetConsoleScreenBufferInfo
// Maybe call GetLargestConsoleWindowSize instead?
Err(super::unimpl())
}

// Let us magically declare this as a TTY
fn isatty(&self) -> bool { true }
}

5 comments on commit a34fd5a

@bors
Copy link
Contributor

@bors bors commented on a34fd5a Jul 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saw approval from alexcrichton
at retep998@a34fd5a

@bors
Copy link
Contributor

@bors bors commented on a34fd5a Jul 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merging retep998/rust/master = a34fd5a into auto

@bors
Copy link
Contributor

@bors bors commented on a34fd5a Jul 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retep998/rust/master = a34fd5a merged ok, testing candidate = c9e2ca0

@bors
Copy link
Contributor

@bors bors commented on a34fd5a Jul 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fast-forwarding master to auto = c9e2ca0

Please sign in to comment.