mirror of
https://github.com/denoland/deno
synced 2024-08-27 12:00:17 +00:00
tty: Deno.setRaw(rid, mode) to turn on/off raw mode (#3958)
This commit is contained in:
parent
e53064c4f2
commit
5946808f66
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -454,6 +454,7 @@ dependencies = [
|
||||||
"nix",
|
"nix",
|
||||||
"notify",
|
"notify",
|
||||||
"os_pipe",
|
"os_pipe",
|
||||||
|
"pty",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"regex",
|
"regex",
|
||||||
"remove_dir_all",
|
"remove_dir_all",
|
||||||
|
@ -619,6 +620,17 @@ dependencies = [
|
||||||
"syn 1.0.14",
|
"syn 1.0.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e2b2decb0484e15560df3210cf0d78654bb0864b2c138977c07e377a1bae0e2"
|
||||||
|
dependencies = [
|
||||||
|
"kernel32-sys",
|
||||||
|
"libc",
|
||||||
|
"winapi 0.2.8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "failure"
|
name = "failure"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -1508,6 +1520,16 @@ dependencies = [
|
||||||
"unicode-xid 0.2.0",
|
"unicode-xid 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pty"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f50f3d255966981eb4e4c5df3e983e6f7d163221f547406d83b6a460ff5c5ee8"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "qadapt-spin"
|
name = "qadapt-spin"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
|
@ -72,3 +72,6 @@ nix = "0.14.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
os_pipe = "0.9.1"
|
os_pipe = "0.9.1"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
|
pty = "0.2"
|
||||||
|
|
|
@ -90,7 +90,6 @@ export {
|
||||||
dir,
|
dir,
|
||||||
env,
|
env,
|
||||||
exit,
|
exit,
|
||||||
isTTY,
|
|
||||||
execPath,
|
execPath,
|
||||||
hostname,
|
hostname,
|
||||||
loadavg,
|
loadavg,
|
||||||
|
@ -124,6 +123,7 @@ export { statSync, lstatSync, stat, lstat } from "./stat.ts";
|
||||||
export { symlinkSync, symlink } from "./symlink.ts";
|
export { symlinkSync, symlink } from "./symlink.ts";
|
||||||
export { connectTLS, listenTLS } from "./tls.ts";
|
export { connectTLS, listenTLS } from "./tls.ts";
|
||||||
export { truncateSync, truncate } from "./truncate.ts";
|
export { truncateSync, truncate } from "./truncate.ts";
|
||||||
|
export { isatty, setRaw } from "./tty.ts";
|
||||||
export { utimeSync, utime } from "./utime.ts";
|
export { utimeSync, utime } from "./utime.ts";
|
||||||
export { version } from "./version.ts";
|
export { version } from "./version.ts";
|
||||||
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file.ts";
|
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file.ts";
|
||||||
|
|
25
cli/js/lib.deno.ns.d.ts
vendored
25
cli/js/lib.deno.ns.d.ts
vendored
|
@ -30,16 +30,6 @@ declare namespace Deno {
|
||||||
|
|
||||||
export function runTests(opts?: RunTestsOptions): Promise<void>;
|
export function runTests(opts?: RunTestsOptions): Promise<void>;
|
||||||
|
|
||||||
/** Check if running in terminal.
|
|
||||||
*
|
|
||||||
* console.log(Deno.isTTY().stdout);
|
|
||||||
*/
|
|
||||||
export function isTTY(): {
|
|
||||||
stdin: boolean;
|
|
||||||
stdout: boolean;
|
|
||||||
stderr: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Get the loadavg. Requires the `--allow-env` flag.
|
/** Get the loadavg. Requires the `--allow-env` flag.
|
||||||
*
|
*
|
||||||
* console.log(Deno.loadavg());
|
* console.log(Deno.loadavg());
|
||||||
|
@ -492,6 +482,7 @@ declare namespace Deno {
|
||||||
seekSync(offset: number, whence: SeekMode): void;
|
seekSync(offset: number, whence: SeekMode): void;
|
||||||
close(): void;
|
close(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An instance of `File` for stdin. */
|
/** An instance of `File` for stdin. */
|
||||||
export const stdin: File;
|
export const stdin: File;
|
||||||
/** An instance of `File` for stdout. */
|
/** An instance of `File` for stdout. */
|
||||||
|
@ -555,6 +546,20 @@ declare namespace Deno {
|
||||||
/** Read-write. Behaves like `x` and allows to read from file. */
|
/** Read-write. Behaves like `x` and allows to read from file. */
|
||||||
| "x+";
|
| "x+";
|
||||||
|
|
||||||
|
// @url js/tty.d.ts
|
||||||
|
|
||||||
|
/** UNSTABLE: newly added API
|
||||||
|
*
|
||||||
|
* Check if a given resource is TTY
|
||||||
|
*/
|
||||||
|
export function isatty(rid: number): boolean;
|
||||||
|
|
||||||
|
/** UNSTABLE: newly added API
|
||||||
|
*
|
||||||
|
* Set TTY to be under raw mode or not.
|
||||||
|
*/
|
||||||
|
export function setRaw(rid: number, mode: boolean): void;
|
||||||
|
|
||||||
// @url js/buffer.d.ts
|
// @url js/buffer.d.ts
|
||||||
|
|
||||||
/** A Buffer is a variable-sized buffer of bytes with read() and write()
|
/** A Buffer is a variable-sized buffer of bytes with read() and write()
|
||||||
|
|
|
@ -3,13 +3,6 @@ import { sendSync } from "./dispatch_json.ts";
|
||||||
import { errors } from "./errors.ts";
|
import { errors } from "./errors.ts";
|
||||||
import * as util from "./util.ts";
|
import * as util from "./util.ts";
|
||||||
|
|
||||||
/** Check if running in terminal.
|
|
||||||
*
|
|
||||||
* console.log(Deno.isTTY().stdout);
|
|
||||||
*/
|
|
||||||
export function isTTY(): { stdin: boolean; stdout: boolean; stderr: boolean } {
|
|
||||||
return sendSync("op_is_tty");
|
|
||||||
}
|
|
||||||
/** Get the loadavg.
|
/** Get the loadavg.
|
||||||
* Requires the `--allow-env` flag.
|
* Requires the `--allow-env` flag.
|
||||||
*
|
*
|
||||||
|
|
|
@ -115,10 +115,6 @@ test(function osPid(): void {
|
||||||
assert(Deno.pid > 0);
|
assert(Deno.pid > 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(function osIsTTYSmoke(): void {
|
|
||||||
console.log(Deno.isTTY());
|
|
||||||
});
|
|
||||||
|
|
||||||
testPerm({ env: true }, function getDir(): void {
|
testPerm({ env: true }, function getDir(): void {
|
||||||
type supportOS = "mac" | "win" | "linux";
|
type supportOS = "mac" | "win" | "linux";
|
||||||
|
|
||||||
|
|
14
cli/js/tty.ts
Normal file
14
cli/js/tty.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { sendSync } from "./dispatch_json.ts";
|
||||||
|
|
||||||
|
/** Check if a given resource is TTY. */
|
||||||
|
export function isatty(rid: number): boolean {
|
||||||
|
return sendSync("op_isatty", { rid });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set TTY to be under raw mode or not. */
|
||||||
|
export function setRaw(rid: number, mode: boolean): void {
|
||||||
|
sendSync("op_set_raw", {
|
||||||
|
rid,
|
||||||
|
mode
|
||||||
|
});
|
||||||
|
}
|
22
cli/js/tty_test.ts
Normal file
22
cli/js/tty_test.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import { test, testPerm, assert } from "./test_util.ts";
|
||||||
|
|
||||||
|
// Note tests for Deno.setRaw is in integration tests.
|
||||||
|
|
||||||
|
testPerm({ read: true }, function isatty(): void {
|
||||||
|
// CI not under TTY, so cannot test stdin/stdout/stderr.
|
||||||
|
const f = Deno.openSync("cli/tests/hello.txt");
|
||||||
|
assert(!Deno.isatty(f.rid));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(function isattyError(): void {
|
||||||
|
let caught = false;
|
||||||
|
try {
|
||||||
|
// Absurdly large rid.
|
||||||
|
Deno.isatty(0x7fffffff);
|
||||||
|
} catch (e) {
|
||||||
|
caught = true;
|
||||||
|
assert(e instanceof Deno.errors.BadResource);
|
||||||
|
}
|
||||||
|
assert(caught);
|
||||||
|
});
|
|
@ -54,6 +54,7 @@ import "./text_encoding_test.ts";
|
||||||
import "./timers_test.ts";
|
import "./timers_test.ts";
|
||||||
import "./tls_test.ts";
|
import "./tls_test.ts";
|
||||||
import "./truncate_test.ts";
|
import "./truncate_test.ts";
|
||||||
|
import "./tty_test.ts";
|
||||||
import "./url_test.ts";
|
import "./url_test.ts";
|
||||||
import "./url_search_params_test.ts";
|
import "./url_search_params_test.ts";
|
||||||
import "./utime_test.ts";
|
import "./utime_test.ts";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
use super::dispatch_json::{Deserialize, JsonOp, Value};
|
use super::dispatch_json::{Deserialize, JsonOp, Value};
|
||||||
use super::io::StreamResource;
|
use super::io::{FileMetadata, StreamResource};
|
||||||
use crate::fs as deno_fs;
|
use crate::fs as deno_fs;
|
||||||
use crate::op_error::OpError;
|
use crate::op_error::OpError;
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
|
@ -125,9 +125,10 @@ fn op_open(
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let fs_file = open_options.open(filename).await?;
|
let fs_file = open_options.open(filename).await?;
|
||||||
let mut state = state_.borrow_mut();
|
let mut state = state_.borrow_mut();
|
||||||
let rid = state
|
let rid = state.resource_table.add(
|
||||||
.resource_table
|
"fsFile",
|
||||||
.add("fsFile", Box::new(StreamResource::FsFile(fs_file)));
|
Box::new(StreamResource::FsFile(fs_file, FileMetadata::default())),
|
||||||
|
);
|
||||||
Ok(json!(rid))
|
Ok(json!(rid))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -197,7 +198,7 @@ fn op_seek(
|
||||||
.ok_or_else(OpError::bad_resource)?;
|
.ok_or_else(OpError::bad_resource)?;
|
||||||
|
|
||||||
let tokio_file = match resource {
|
let tokio_file = match resource {
|
||||||
StreamResource::FsFile(ref file) => file,
|
StreamResource::FsFile(ref file, _) => file,
|
||||||
_ => return Err(OpError::bad_resource()),
|
_ => return Err(OpError::bad_resource()),
|
||||||
};
|
};
|
||||||
let mut file = futures::executor::block_on(tokio_file.try_clone())?;
|
let mut file = futures::executor::block_on(tokio_file.try_clone())?;
|
||||||
|
|
|
@ -57,7 +57,7 @@ pub fn init(i: &mut Isolate, s: &State) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_stdio() -> (StreamResource, StreamResource, StreamResource) {
|
pub fn get_stdio() -> (StreamResource, StreamResource, StreamResource) {
|
||||||
let stdin = StreamResource::Stdin(tokio::io::stdin());
|
let stdin = StreamResource::Stdin(tokio::io::stdin(), TTYMetadata::default());
|
||||||
let stdout = StreamResource::Stdout({
|
let stdout = StreamResource::Stdout({
|
||||||
let stdout = STDOUT_HANDLE
|
let stdout = STDOUT_HANDLE
|
||||||
.try_clone()
|
.try_clone()
|
||||||
|
@ -69,11 +69,25 @@ pub fn get_stdio() -> (StreamResource, StreamResource, StreamResource) {
|
||||||
(stdin, stdout, stderr)
|
(stdin, stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use nix::sys::termios;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TTYMetadata {
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub mode: Option<termios::Termios>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FileMetadata {
|
||||||
|
pub tty: TTYMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum StreamResource {
|
pub enum StreamResource {
|
||||||
Stdin(tokio::io::Stdin),
|
Stdin(tokio::io::Stdin, TTYMetadata),
|
||||||
Stdout(tokio::fs::File),
|
Stdout(tokio::fs::File),
|
||||||
Stderr(tokio::io::Stderr),
|
Stderr(tokio::io::Stderr),
|
||||||
FsFile(tokio::fs::File),
|
FsFile(tokio::fs::File, FileMetadata),
|
||||||
TcpStream(tokio::net::TcpStream),
|
TcpStream(tokio::net::TcpStream),
|
||||||
ServerTlsStream(Box<ServerTlsStream<TcpStream>>),
|
ServerTlsStream(Box<ServerTlsStream<TcpStream>>),
|
||||||
ClientTlsStream(Box<ClientTlsStream<TcpStream>>),
|
ClientTlsStream(Box<ClientTlsStream<TcpStream>>),
|
||||||
|
@ -101,8 +115,8 @@ impl DenoAsyncRead for StreamResource {
|
||||||
) -> Poll<Result<usize, OpError>> {
|
) -> Poll<Result<usize, OpError>> {
|
||||||
use StreamResource::*;
|
use StreamResource::*;
|
||||||
let mut f: Pin<Box<dyn AsyncRead>> = match self {
|
let mut f: Pin<Box<dyn AsyncRead>> = match self {
|
||||||
FsFile(f) => Box::pin(f),
|
FsFile(f, _) => Box::pin(f),
|
||||||
Stdin(f) => Box::pin(f),
|
Stdin(f, _) => Box::pin(f),
|
||||||
TcpStream(f) => Box::pin(f),
|
TcpStream(f) => Box::pin(f),
|
||||||
ClientTlsStream(f) => Box::pin(f),
|
ClientTlsStream(f) => Box::pin(f),
|
||||||
ServerTlsStream(f) => Box::pin(f),
|
ServerTlsStream(f) => Box::pin(f),
|
||||||
|
@ -203,7 +217,7 @@ impl DenoAsyncWrite for StreamResource {
|
||||||
) -> Poll<Result<usize, OpError>> {
|
) -> Poll<Result<usize, OpError>> {
|
||||||
use StreamResource::*;
|
use StreamResource::*;
|
||||||
let mut f: Pin<Box<dyn AsyncWrite>> = match self {
|
let mut f: Pin<Box<dyn AsyncWrite>> = match self {
|
||||||
FsFile(f) => Box::pin(f),
|
FsFile(f, _) => Box::pin(f),
|
||||||
Stdout(f) => Box::pin(f),
|
Stdout(f) => Box::pin(f),
|
||||||
Stderr(f) => Box::pin(f),
|
Stderr(f) => Box::pin(f),
|
||||||
TcpStream(f) => Box::pin(f),
|
TcpStream(f) => Box::pin(f),
|
||||||
|
@ -220,7 +234,7 @@ impl DenoAsyncWrite for StreamResource {
|
||||||
fn poll_flush(&mut self, cx: &mut Context) -> Poll<Result<(), OpError>> {
|
fn poll_flush(&mut self, cx: &mut Context) -> Poll<Result<(), OpError>> {
|
||||||
use StreamResource::*;
|
use StreamResource::*;
|
||||||
let mut f: Pin<Box<dyn AsyncWrite>> = match self {
|
let mut f: Pin<Box<dyn AsyncWrite>> = match self {
|
||||||
FsFile(f) => Box::pin(f),
|
FsFile(f, _) => Box::pin(f),
|
||||||
Stdout(f) => Box::pin(f),
|
Stdout(f) => Box::pin(f),
|
||||||
Stderr(f) => Box::pin(f),
|
Stderr(f) => Box::pin(f),
|
||||||
TcpStream(f) => Box::pin(f),
|
TcpStream(f) => Box::pin(f),
|
||||||
|
|
|
@ -28,5 +28,6 @@ pub mod runtime_compiler;
|
||||||
pub mod signal;
|
pub mod signal;
|
||||||
pub mod timers;
|
pub mod timers;
|
||||||
pub mod tls;
|
pub mod tls;
|
||||||
|
pub mod tty;
|
||||||
pub mod web_worker;
|
pub mod web_worker;
|
||||||
pub mod worker_host;
|
pub mod worker_host;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
use super::dispatch_json::{Deserialize, JsonOp, Value};
|
use super::dispatch_json::{Deserialize, JsonOp, Value};
|
||||||
use crate::op_error::OpError;
|
use crate::op_error::OpError;
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
use atty;
|
|
||||||
use deno_core::*;
|
use deno_core::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -12,7 +11,6 @@ use url::Url;
|
||||||
|
|
||||||
pub fn init(i: &mut Isolate, s: &State) {
|
pub fn init(i: &mut Isolate, s: &State) {
|
||||||
i.register_op("op_exit", s.stateful_json_op(op_exit));
|
i.register_op("op_exit", s.stateful_json_op(op_exit));
|
||||||
i.register_op("op_is_tty", s.stateful_json_op(op_is_tty));
|
|
||||||
i.register_op("op_env", s.stateful_json_op(op_env));
|
i.register_op("op_env", s.stateful_json_op(op_env));
|
||||||
i.register_op("op_exec_path", s.stateful_json_op(op_exec_path));
|
i.register_op("op_exec_path", s.stateful_json_op(op_exec_path));
|
||||||
i.register_op("op_set_env", s.stateful_json_op(op_set_env));
|
i.register_op("op_set_env", s.stateful_json_op(op_set_env));
|
||||||
|
@ -151,18 +149,6 @@ fn op_exit(
|
||||||
std::process::exit(args.code)
|
std::process::exit(args.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn op_is_tty(
|
|
||||||
_s: &State,
|
|
||||||
_args: Value,
|
|
||||||
_zero_copy: Option<ZeroCopyBuf>,
|
|
||||||
) -> Result<JsonOp, OpError> {
|
|
||||||
Ok(JsonOp::Sync(json!({
|
|
||||||
"stdin": atty::is(atty::Stream::Stdin),
|
|
||||||
"stdout": atty::is(atty::Stream::Stdout),
|
|
||||||
"stderr": atty::is(atty::Stream::Stderr),
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn op_loadavg(
|
fn op_loadavg(
|
||||||
state: &State,
|
state: &State,
|
||||||
_args: Value,
|
_args: Value,
|
||||||
|
|
|
@ -33,7 +33,7 @@ fn clone_file(rid: u32, state: &State) -> Result<std::fs::File, OpError> {
|
||||||
.get_mut::<StreamResource>(rid)
|
.get_mut::<StreamResource>(rid)
|
||||||
.ok_or_else(OpError::bad_resource)?;
|
.ok_or_else(OpError::bad_resource)?;
|
||||||
let file = match repr {
|
let file = match repr {
|
||||||
StreamResource::FsFile(ref mut file) => file,
|
StreamResource::FsFile(ref mut file, _) => file,
|
||||||
_ => return Err(OpError::bad_resource()),
|
_ => return Err(OpError::bad_resource()),
|
||||||
};
|
};
|
||||||
let tokio_file = futures::executor::block_on(file.try_clone())?;
|
let tokio_file = futures::executor::block_on(file.try_clone())?;
|
||||||
|
|
246
cli/ops/tty.rs
Normal file
246
cli/ops/tty.rs
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
use super::dispatch_json::JsonOp;
|
||||||
|
use super::io::StreamResource;
|
||||||
|
use crate::op_error::OpError;
|
||||||
|
use crate::ops::json_op;
|
||||||
|
use crate::state::State;
|
||||||
|
use atty;
|
||||||
|
use deno_core::*;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use nix::sys::termios;
|
||||||
|
use serde_derive::Deserialize;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use winapi::shared::minwindef::DWORD;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use winapi::um::wincon;
|
||||||
|
#[cfg(windows)]
|
||||||
|
const RAW_MODE_MASK: DWORD = wincon::ENABLE_LINE_INPUT
|
||||||
|
| wincon::ENABLE_ECHO_INPUT
|
||||||
|
| wincon::ENABLE_PROCESSED_INPUT;
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn get_windows_handle(
|
||||||
|
f: &std::fs::File,
|
||||||
|
) -> Result<std::os::windows::io::RawHandle, OpError> {
|
||||||
|
use std::os::windows::io::AsRawHandle;
|
||||||
|
use winapi::um::handleapi;
|
||||||
|
|
||||||
|
let handle = f.as_raw_handle();
|
||||||
|
if handle == handleapi::INVALID_HANDLE_VALUE {
|
||||||
|
return Err(OpError::from(std::io::Error::last_os_error()));
|
||||||
|
} else if handle.is_null() {
|
||||||
|
return Err(OpError::other("null handle".to_owned()));
|
||||||
|
}
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(i: &mut Isolate, s: &State) {
|
||||||
|
i.register_op("op_set_raw", s.core_op(json_op(s.stateful_op(op_set_raw))));
|
||||||
|
i.register_op("op_isatty", s.core_op(json_op(s.stateful_op(op_isatty))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
macro_rules! wincheck {
|
||||||
|
($funcall:expr) => {{
|
||||||
|
let rc = unsafe { $funcall };
|
||||||
|
if rc == 0 {
|
||||||
|
Err(OpError::from(std::io::Error::last_os_error()))?;
|
||||||
|
}
|
||||||
|
rc
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct SetRawArgs {
|
||||||
|
rid: u32,
|
||||||
|
mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn op_set_raw(
|
||||||
|
state_: &State,
|
||||||
|
args: Value,
|
||||||
|
_zero_copy: Option<ZeroCopyBuf>,
|
||||||
|
) -> Result<JsonOp, OpError> {
|
||||||
|
let args: SetRawArgs = serde_json::from_value(args)?;
|
||||||
|
let rid = args.rid;
|
||||||
|
let is_raw = args.mode;
|
||||||
|
|
||||||
|
// From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs
|
||||||
|
// and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs
|
||||||
|
// and https://github.com/crossterm-rs/crossterm/blob/e35d4d2c1cc4c919e36d242e014af75f6127ab50/src/terminal/sys/windows.rs
|
||||||
|
// Copyright (c) 2015 Katsu Kawakami & Rustyline authors. MIT license.
|
||||||
|
// Copyright (c) 2019 Timon. MIT license.
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
use std::os::windows::io::AsRawHandle;
|
||||||
|
use winapi::um::{consoleapi, handleapi};
|
||||||
|
|
||||||
|
let state = state_.borrow_mut();
|
||||||
|
let resource = state.resource_table.get::<StreamResource>(rid);
|
||||||
|
if resource.is_none() {
|
||||||
|
return Err(OpError::bad_resource());
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, only stdin.
|
||||||
|
let handle = match resource.unwrap() {
|
||||||
|
StreamResource::Stdin(_, _) => std::io::stdin().as_raw_handle(),
|
||||||
|
StreamResource::FsFile(f, _) => {
|
||||||
|
let tokio_file = futures::executor::block_on(f.try_clone())?;
|
||||||
|
let std_file = futures::executor::block_on(tokio_file.into_std());
|
||||||
|
std_file.as_raw_handle()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(OpError::other("Not supported".to_owned()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if handle == handleapi::INVALID_HANDLE_VALUE {
|
||||||
|
return Err(OpError::from(std::io::Error::last_os_error()));
|
||||||
|
} else if handle.is_null() {
|
||||||
|
return Err(OpError::other("null handle".to_owned()));
|
||||||
|
}
|
||||||
|
let mut original_mode: DWORD = 0;
|
||||||
|
wincheck!(consoleapi::GetConsoleMode(handle, &mut original_mode));
|
||||||
|
let new_mode = if is_raw {
|
||||||
|
original_mode & !RAW_MODE_MASK
|
||||||
|
} else {
|
||||||
|
original_mode | RAW_MODE_MASK
|
||||||
|
};
|
||||||
|
wincheck!(consoleapi::SetConsoleMode(handle, new_mode));
|
||||||
|
|
||||||
|
Ok(JsonOp::Sync(json!({})))
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
|
let mut state = state_.borrow_mut();
|
||||||
|
let resource = state.resource_table.get_mut::<StreamResource>(rid);
|
||||||
|
if resource.is_none() {
|
||||||
|
return Err(OpError::bad_resource());
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_raw {
|
||||||
|
let (raw_fd, maybe_tty_mode) = match resource.unwrap() {
|
||||||
|
StreamResource::Stdin(_, ref mut metadata) => {
|
||||||
|
(std::io::stdin().as_raw_fd(), &mut metadata.mode)
|
||||||
|
}
|
||||||
|
StreamResource::FsFile(f, ref mut metadata) => {
|
||||||
|
let tokio_file = futures::executor::block_on(f.try_clone())?;
|
||||||
|
let std_file = futures::executor::block_on(tokio_file.into_std());
|
||||||
|
(std_file.as_raw_fd(), &mut metadata.tty.mode)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(OpError::other("Not supported".to_owned()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if maybe_tty_mode.is_some() {
|
||||||
|
// Already raw. Skip.
|
||||||
|
return Ok(JsonOp::Sync(json!({})));
|
||||||
|
}
|
||||||
|
|
||||||
|
let original_mode = termios::tcgetattr(raw_fd)?;
|
||||||
|
let mut raw = original_mode.clone();
|
||||||
|
// Save original mode.
|
||||||
|
maybe_tty_mode.replace(original_mode);
|
||||||
|
|
||||||
|
raw.input_flags &= !(termios::InputFlags::BRKINT
|
||||||
|
| termios::InputFlags::ICRNL
|
||||||
|
| termios::InputFlags::INPCK
|
||||||
|
| termios::InputFlags::ISTRIP
|
||||||
|
| termios::InputFlags::IXON);
|
||||||
|
|
||||||
|
raw.control_flags |= termios::ControlFlags::CS8;
|
||||||
|
|
||||||
|
raw.local_flags &= !(termios::LocalFlags::ECHO
|
||||||
|
| termios::LocalFlags::ICANON
|
||||||
|
| termios::LocalFlags::IEXTEN
|
||||||
|
| termios::LocalFlags::ISIG);
|
||||||
|
raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1;
|
||||||
|
raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0;
|
||||||
|
termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?;
|
||||||
|
Ok(JsonOp::Sync(json!({})))
|
||||||
|
} else {
|
||||||
|
// Try restore saved mode.
|
||||||
|
let (raw_fd, maybe_tty_mode) = match resource.unwrap() {
|
||||||
|
StreamResource::Stdin(_, ref mut metadata) => {
|
||||||
|
(std::io::stdin().as_raw_fd(), &mut metadata.mode)
|
||||||
|
}
|
||||||
|
StreamResource::FsFile(f, ref mut metadata) => {
|
||||||
|
let tokio_file = futures::executor::block_on(f.try_clone())?;
|
||||||
|
let std_file = futures::executor::block_on(tokio_file.into_std());
|
||||||
|
(std_file.as_raw_fd(), &mut metadata.tty.mode)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(OpError::other("Not supported".to_owned()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mode) = maybe_tty_mode.take() {
|
||||||
|
termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(JsonOp::Sync(json!({})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct IsattyArgs {
|
||||||
|
rid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn op_isatty(
|
||||||
|
state_: &State,
|
||||||
|
args: Value,
|
||||||
|
_zero_copy: Option<ZeroCopyBuf>,
|
||||||
|
) -> Result<JsonOp, OpError> {
|
||||||
|
let args: IsattyArgs = serde_json::from_value(args)?;
|
||||||
|
let rid = args.rid;
|
||||||
|
|
||||||
|
let state = state_.borrow_mut();
|
||||||
|
if !state.resource_table.has(rid) {
|
||||||
|
return Err(OpError::bad_resource());
|
||||||
|
}
|
||||||
|
|
||||||
|
let resource = state.resource_table.get::<StreamResource>(rid);
|
||||||
|
if resource.is_none() {
|
||||||
|
return Ok(JsonOp::Sync(json!(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
match resource.unwrap() {
|
||||||
|
StreamResource::Stdin(_, _) => {
|
||||||
|
Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stdin))))
|
||||||
|
}
|
||||||
|
StreamResource::Stdout(_) => {
|
||||||
|
Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stdout))))
|
||||||
|
}
|
||||||
|
StreamResource::Stderr(_) => {
|
||||||
|
Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stderr))))
|
||||||
|
}
|
||||||
|
StreamResource::FsFile(f, _) => {
|
||||||
|
let tokio_file = futures::executor::block_on(f.try_clone())?;
|
||||||
|
let std_file = futures::executor::block_on(tokio_file.into_std());
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
use winapi::um::consoleapi;
|
||||||
|
|
||||||
|
let handle = get_windows_handle(&std_file)?;
|
||||||
|
let mut test_mode: DWORD = 0;
|
||||||
|
// If I cannot get mode out of console, it is not a console.
|
||||||
|
let result =
|
||||||
|
unsafe { consoleapi::GetConsoleMode(handle, &mut test_mode) != 0 };
|
||||||
|
Ok(JsonOp::Sync(json!(result)))
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
let raw_fd = std_file.as_raw_fd();
|
||||||
|
let result = unsafe { libc::isatty(raw_fd as libc::c_int) == 1 };
|
||||||
|
Ok(JsonOp::Sync(json!(result)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(JsonOp::Sync(json!(false))),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,61 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
#[cfg(unix)]
|
||||||
|
extern crate nix;
|
||||||
|
#[cfg(unix)]
|
||||||
|
extern crate pty;
|
||||||
extern crate tempfile;
|
extern crate tempfile;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
pub fn test_raw_tty() {
|
||||||
|
use pty::fork::*;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
let fork = Fork::from_ptmx().unwrap();
|
||||||
|
|
||||||
|
if let Ok(mut master) = fork.is_parent() {
|
||||||
|
let mut obytes: [u8; 100] = [0; 100];
|
||||||
|
let mut nread = master.read(&mut obytes).unwrap();
|
||||||
|
assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "S");
|
||||||
|
master.write_all(b"a").unwrap();
|
||||||
|
nread = master.read(&mut obytes).unwrap();
|
||||||
|
assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "A");
|
||||||
|
master.write_all(b"b").unwrap();
|
||||||
|
nread = master.read(&mut obytes).unwrap();
|
||||||
|
assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "B");
|
||||||
|
master.write_all(b"c").unwrap();
|
||||||
|
nread = master.read(&mut obytes).unwrap();
|
||||||
|
assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "C");
|
||||||
|
} else {
|
||||||
|
use deno::test_util::*;
|
||||||
|
use nix::sys::termios;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use std::process::*;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
// Turn off echo such that parent is reading works properly.
|
||||||
|
let stdin_fd = std::io::stdin().as_raw_fd();
|
||||||
|
let mut t = termios::tcgetattr(stdin_fd).unwrap();
|
||||||
|
t.local_flags.remove(termios::LocalFlags::ECHO);
|
||||||
|
termios::tcsetattr(stdin_fd, termios::SetArg::TCSANOW, &t).unwrap();
|
||||||
|
|
||||||
|
let deno_dir = TempDir::new().expect("tempdir fail");
|
||||||
|
let mut child = Command::new(deno_exe_path())
|
||||||
|
.env("DENO_DIR", deno_dir.path())
|
||||||
|
.current_dir(util::root_path())
|
||||||
|
.arg("run")
|
||||||
|
.arg("cli/tests/raw_mode.ts")
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to spawn script");
|
||||||
|
child.wait().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pattern_match() {
|
fn test_pattern_match() {
|
||||||
assert!(util::pattern_match("foo[BAR]baz", "foobarbaz", "[BAR]"));
|
assert!(util::pattern_match("foo[BAR]baz", "foobarbaz", "[BAR]"));
|
||||||
|
|
18
cli/tests/raw_mode.ts
Normal file
18
cli/tests/raw_mode.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
Deno.setRaw(0, true);
|
||||||
|
Deno.setRaw(0, true); // Can be called multiple times
|
||||||
|
|
||||||
|
Deno.stdout.writeSync(new TextEncoder().encode("S"));
|
||||||
|
|
||||||
|
const buf = new Uint8Array(3);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const nread = await Deno.stdin.read(buf);
|
||||||
|
if (nread === Deno.EOF) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
const data = new TextDecoder().decode(buf.subarray(0, nread));
|
||||||
|
Deno.stdout.writeSync(new TextEncoder().encode(data.toUpperCase()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.setRaw(0, false); // restores old mode.
|
||||||
|
Deno.setRaw(0, false); // Can be safely called multiple times
|
|
@ -219,6 +219,7 @@ impl MainWorker {
|
||||||
ops::resources::init(isolate, &state);
|
ops::resources::init(isolate, &state);
|
||||||
ops::signal::init(isolate, &state);
|
ops::signal::init(isolate, &state);
|
||||||
ops::timers::init(isolate, &state);
|
ops::timers::init(isolate, &state);
|
||||||
|
ops::tty::init(isolate, &state);
|
||||||
ops::worker_host::init(isolate, &state);
|
ops::worker_host::init(isolate, &state);
|
||||||
ops::web_worker::init(isolate, &state, &worker.internal_channels.sender);
|
ops::web_worker::init(isolate, &state, &worker.internal_channels.sender);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ pub struct ResourceTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceTable {
|
impl ResourceTable {
|
||||||
|
pub fn has(&self, rid: ResourceId) -> bool {
|
||||||
|
self.map.contains_key(&rid)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get<T: Resource>(&self, rid: ResourceId) -> Option<&T> {
|
pub fn get<T: Resource>(&self, rid: ResourceId) -> Option<&T> {
|
||||||
if let Some((_name, resource)) = self.map.get(&rid) {
|
if let Some((_name, resource)) = self.map.get(&rid) {
|
||||||
return resource.downcast_ref::<T>();
|
return resource.downcast_ref::<T>();
|
||||||
|
|
Loading…
Reference in a new issue