]> git.lizzy.rs Git - rust.git/commitdiff
windows: Unicode console support.
authorPeter Atashian <retep998@gmail.com>
Fri, 4 Jul 2014 14:45:28 +0000 (10:45 -0400)
committerPeter Atashian <retep998@gmail.com>
Fri, 4 Jul 2014 14:45:28 +0000 (10:45 -0400)
Adds a WindowsTTY for libnative that converts between UTF-8 and UTF-16.

Signed-off-by: Peter Atashian <retep998@gmail.com>
src/libnative/io/c_win32.rs
src/libnative/io/mod.rs
src/libnative/io/tty_win32.rs [new file with mode: 0644]

index 802526c91969524a9cfd4023466b7fce9f93aa72..482155c339c9b5e0b7f6b0973d725b69287a4c87 100644 (file)
 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 {
@@ -165,3 +172,24 @@ pub mod kernel32 {
         })
     }
 }
+
+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;
+}
index f6764b8f26a790893853e1c88e716bb1bb51341e..ecdf4ad2c45f49b24af8653bc2d713bfed9dd1d3 100644 (file)
 #[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;
 
@@ -280,15 +284,27 @@ fn kill(&mut self, pid: libc::pid_t, signum: int) -> IoResult<()> {
     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,
             })
diff --git a/src/libnative/io/tty_win32.rs b/src/libnative/io/tty_win32.rs
new file mode 100644 (file)
index 0000000..72cf5e7
--- /dev/null
@@ -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 }
+}