|
| 1 | +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT |
| 2 | +// file at the top-level directory of this distribution and at |
| 3 | +// http://rust-lang.org/COPYRIGHT. |
| 4 | +// |
| 5 | +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 6 | +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 7 | +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 8 | +// option. This file may not be copied, modified, or distributed |
| 9 | +// except according to those terms. |
| 10 | + |
| 11 | +//! Windows specific console TTY implementation |
| 12 | +//! |
| 13 | +//! This module contains the implementation of a Windows specific console TTY. |
| 14 | +//! Also converts between UTF-16 and UTF-8. Windows has very poor support for |
| 15 | +//! UTF-8 and some functions will fail. In particular ReadFile and ReadConsole |
| 16 | +//! will fail when the codepage is set to UTF-8 and a unicode character is |
| 17 | +//! entered. |
| 18 | +//! |
| 19 | +//! FIXME |
| 20 | +//! This implementation does not account for codepoints that are split across |
| 21 | +//! multiple reads and writes. Also, this implementation does not expose a way |
| 22 | +//! to read/write UTF-16 directly. When/if Rust receives a Reader/Writer |
| 23 | +//! wrapper that performs encoding/decoding, this implementation should switch |
| 24 | +//! to working in raw UTF-16, with such a wrapper around it. |
| 25 | +
|
| 26 | +use super::c::{ReadConsoleW, WriteConsoleW, GetConsoleMode, SetConsoleMode}; |
| 27 | +use super::c::{ERROR_ILLEGAL_CHARACTER}; |
| 28 | +use super::c::{ENABLE_ECHO_INPUT, ENABLE_EXTENDED_FLAGS}; |
| 29 | +use super::c::{ENABLE_INSERT_MODE, ENABLE_LINE_INPUT}; |
| 30 | +use super::c::{ENABLE_PROCESSED_INPUT, ENABLE_QUICK_EDIT_MODE}; |
| 31 | +use libc::{c_int, HANDLE, LPDWORD, DWORD, LPVOID}; |
| 32 | +use libc::{get_osfhandle, CloseHandle}; |
| 33 | +use libc::types::os::arch::extra::LPCVOID; |
| 34 | +use std::io::MemReader; |
| 35 | +use std::ptr; |
| 36 | +use std::rt::rtio::{IoResult, IoError, RtioTTY}; |
| 37 | +use std::str::{from_utf16, from_utf8}; |
| 38 | + |
| 39 | +fn invalid_encoding() -> IoError { |
| 40 | + IoError { |
| 41 | + code: ERROR_ILLEGAL_CHARACTER as uint, |
| 42 | + extra: 0, |
| 43 | + detail: Some("text was not valid unicode".to_string()), |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +pub fn is_tty(fd: c_int) -> bool { |
| 48 | + let mut out: DWORD = 0; |
| 49 | + // If this function doesn't fail then fd is a TTY |
| 50 | + match unsafe { GetConsoleMode(get_osfhandle(fd) as HANDLE, |
| 51 | + &mut out as LPDWORD) } { |
| 52 | + 0 => false, |
| 53 | + _ => true, |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +pub struct WindowsTTY { |
| 58 | + closeme: bool, |
| 59 | + handle: HANDLE, |
| 60 | + utf8: MemReader, |
| 61 | +} |
| 62 | + |
| 63 | +impl WindowsTTY { |
| 64 | + pub fn new(fd: c_int) -> WindowsTTY { |
| 65 | + // If the file descriptor is one of stdin, stderr, or stdout |
| 66 | + // then it should not be closed by us |
| 67 | + let closeme = match fd { |
| 68 | + 0..2 => false, |
| 69 | + _ => true, |
| 70 | + }; |
| 71 | + let handle = unsafe { get_osfhandle(fd) as HANDLE }; |
| 72 | + WindowsTTY { |
| 73 | + handle: handle, |
| 74 | + utf8: MemReader::new(Vec::new()), |
| 75 | + closeme: closeme, |
| 76 | + } |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +impl Drop for WindowsTTY { |
| 81 | + fn drop(&mut self) { |
| 82 | + if self.closeme { |
| 83 | + // Nobody cares about the return value |
| 84 | + let _ = unsafe { CloseHandle(self.handle) }; |
| 85 | + } |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +impl RtioTTY for WindowsTTY { |
| 90 | + fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { |
| 91 | + // Read more if the buffer is empty |
| 92 | + if self.utf8.eof() { |
| 93 | + let mut utf16 = Vec::from_elem(0x1000, 0u16); |
| 94 | + let mut num: DWORD = 0; |
| 95 | + match unsafe { ReadConsoleW(self.handle, |
| 96 | + utf16.as_mut_ptr() as LPVOID, |
| 97 | + utf16.len() as u32, |
| 98 | + &mut num as LPDWORD, |
| 99 | + ptr::mut_null()) } { |
| 100 | + 0 => return Err(super::last_error()), |
| 101 | + _ => (), |
| 102 | + }; |
| 103 | + utf16.truncate(num as uint); |
| 104 | + let utf8 = match from_utf16(utf16.as_slice()) { |
| 105 | + Some(utf8) => utf8.into_bytes(), |
| 106 | + None => return Err(invalid_encoding()), |
| 107 | + }; |
| 108 | + self.utf8 = MemReader::new(utf8); |
| 109 | + } |
| 110 | + // MemReader shouldn't error here since we just filled it |
| 111 | + Ok(self.utf8.read(buf).unwrap()) |
| 112 | + } |
| 113 | + |
| 114 | + fn write(&mut self, buf: &[u8]) -> IoResult<()> { |
| 115 | + let utf16 = match from_utf8(buf) { |
| 116 | + Some(utf8) => utf8.to_utf16(), |
| 117 | + None => return Err(invalid_encoding()), |
| 118 | + }; |
| 119 | + let mut num: DWORD = 0; |
| 120 | + match unsafe { WriteConsoleW(self.handle, |
| 121 | + utf16.as_ptr() as LPCVOID, |
| 122 | + utf16.len() as u32, |
| 123 | + &mut num as LPDWORD, |
| 124 | + ptr::mut_null()) } { |
| 125 | + 0 => Err(super::last_error()), |
| 126 | + _ => Ok(()), |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + fn set_raw(&mut self, raw: bool) -> IoResult<()> { |
| 131 | + // FIXME |
| 132 | + // Somebody needs to decide on which of these flags we want |
| 133 | + match unsafe { SetConsoleMode(self.handle, |
| 134 | + match raw { |
| 135 | + true => 0, |
| 136 | + false => ENABLE_ECHO_INPUT | ENABLE_EXTENDED_FLAGS | |
| 137 | + ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | |
| 138 | + ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE, |
| 139 | + }) } { |
| 140 | + 0 => Err(super::last_error()), |
| 141 | + _ => Ok(()), |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + fn get_winsize(&mut self) -> IoResult<(int, int)> { |
| 146 | + // FIXME |
| 147 | + // Get console buffer via CreateFile with CONOUT$ |
| 148 | + // Make a CONSOLE_SCREEN_BUFFER_INFO |
| 149 | + // Call GetConsoleScreenBufferInfo |
| 150 | + // Maybe call GetLargestConsoleWindowSize instead? |
| 151 | + Err(super::unimpl()) |
| 152 | + } |
| 153 | + |
| 154 | + // Let us magically declare this as a TTY |
| 155 | + fn isatty(&self) -> bool { true } |
| 156 | +} |
0 commit comments