// Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include #include #include #include using namespace v8; namespace node { #define THROW_ERROR(msg) \ return ThrowException(Exception::Error(String::New(msg))); #define THROW_BAD_ARGS \ return ThrowException(Exception::TypeError(String::New("Bad argument"))); #define KEY(scancode, name) \ scancodes[scancode] = name; #define MAX_KEY VK_OEM_PERIOD static const char* scancodes[MAX_KEY + 1] = {0}; static Persistent name_symbol; static Persistent shift_symbol; static Persistent ctrl_symbol; static Persistent meta_symbol; static void init_scancode_table() { KEY(VK_CANCEL, "break") KEY(VK_BACK, "backspace") KEY(VK_TAB, "tab") KEY(VK_CLEAR, "clear") KEY(VK_RETURN, "enter") KEY(VK_PAUSE, "pause") KEY(VK_ESCAPE, "escape") KEY(VK_SPACE, "space") KEY(VK_PRIOR, "pageup") KEY(VK_NEXT, "pagedown") KEY(VK_END, "end") KEY(VK_HOME, "home") KEY(VK_LEFT, "left") KEY(VK_UP, "up") KEY(VK_RIGHT, "right") KEY(VK_DOWN, "down") KEY(VK_SELECT, "select") KEY(VK_PRINT, "print") KEY(VK_EXECUTE, "execute") KEY(VK_SNAPSHOT, "printscreen") KEY(VK_INSERT, "insert") KEY(VK_DELETE, "delete") KEY(VK_HELP, "help") KEY(VK_LWIN, "lwin") KEY(VK_RWIN, "rwin") KEY(VK_APPS, "apps") KEY(VK_SLEEP, "sleep") KEY(VK_NUMPAD0, "numpad0") KEY(VK_NUMPAD1, "numpad1") KEY(VK_NUMPAD2, "numpad2") KEY(VK_NUMPAD3, "numpad3") KEY(VK_NUMPAD4, "numpad4") KEY(VK_NUMPAD5, "numpad5") KEY(VK_NUMPAD6, "numpad6") KEY(VK_NUMPAD7, "numpad7") KEY(VK_NUMPAD8, "numpad8") KEY(VK_NUMPAD9, "numpad9") KEY(VK_MULTIPLY, "numpad*") KEY(VK_ADD, "numpad+") KEY(VK_SEPARATOR, "numpad,") KEY(VK_SUBTRACT, "numpad-") KEY(VK_DECIMAL, "numpad.") KEY(VK_DIVIDE, "numpad/") KEY(VK_F1, "f1") KEY(VK_F2, "f2") KEY(VK_F3, "f3") KEY(VK_F4, "f4") KEY(VK_F5, "f5") KEY(VK_F6, "f6") KEY(VK_F7, "f7") KEY(VK_F8, "f8") KEY(VK_F9, "f9") KEY(VK_F10, "f10") KEY(VK_F11, "f11") KEY(VK_F12, "f12") KEY(VK_F13, "f13") KEY(VK_F14, "f14") KEY(VK_F15, "f15") KEY(VK_F16, "f16") KEY(VK_F17, "f17") KEY(VK_F18, "f18") KEY(VK_F19, "f19") KEY(VK_F20, "f20") KEY(VK_F21, "f21") KEY(VK_F22, "f22") KEY(VK_F23, "f23") KEY(VK_F24, "f24") KEY(VK_OEM_PLUS, "+") KEY(VK_OEM_MINUS, "-") KEY(VK_OEM_COMMA, ",") KEY(VK_OEM_PERIOD, ".") // Letter keys have the ascii code of their uppercase equivalent as a scan code for (int i = 0; i < 26; i++) { char *name = new char[2]; name[0] = 'a' + i; name[1] = '\0'; KEY('A' + i, name) } // Number keys have their ascii code as scan code for (int i = '0'; i <= '9'; i++) { char *name = new char[2]; name[0] = i; name[1] = '\0'; KEY(i, name) } } /* * Flush stdout and stderr on node exit * Not necessary on windows, so a no-op */ void Stdio::Flush() { } /* * STDERR should always be blocking */ static Handle WriteError(const Arguments& args) { HandleScope scope; if (args.Length() < 1) return Undefined(); String::Utf8Value msg(args[0]->ToString()); fprintf(stderr, "%s", reinterpret_cast(*msg)); return Undefined(); } static Handle IsATTY(const Arguments& args) { HandleScope scope; int fd = args[0]->IntegerValue(); DWORD result; int r = GetConsoleMode((HANDLE)_get_osfhandle(fd), &result); return scope.Close(r ? True() : False()); } /* Whether stdio is currently in raw mode */ /* -1 means that it has not been set */ static int rawMode = -1; static void setRawMode(int newMode) { DWORD flags; BOOL result; if (newMode != rawMode) { if (newMode) { // raw input flags = ENABLE_WINDOW_INPUT; } else { // input not raw, but still processing enough messages to make the // tty watcher work (this mode is not the windows default) flags = ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT; } result = SetConsoleMode((HANDLE)_get_osfhandle(STDIN_FILENO), flags); if (result) { rawMode = newMode; } } } static Handle SetRawMode(const Arguments& args) { HandleScope scope; int newMode = !args[0]->IsFalse(); setRawMode(newMode); if (newMode != rawMode) { return ThrowException(ErrnoException(GetLastError(), "EnableRawMode")); } return scope.Close(rawMode ? True() : False()); } void Stdio::DisableRawMode(int fd) { if (rawMode == 1) setRawMode(0); } static Handle OpenStdin(const Arguments& args) { HandleScope scope; setRawMode(0); // init into nonraw mode return scope.Close(Integer::New(STDIN_FILENO)); } static Handle IsStdinBlocking(const Arguments& args) { // On windows stdin always blocks return True(); } static Handle IsStdoutBlocking(const Arguments& args) { // On windows stdout always blocks return True(); } static Handle WriteTTY(const Arguments& args) { HandleScope scope; int fd, len; DWORD written; HANDLE handle; if (!args[0]->IsNumber()) THROW_BAD_ARGS fd = args[0]->IntegerValue(); handle = (HANDLE)_get_osfhandle(fd); Handle data = args[1]->ToString(); String::Value buf(data); len = data->Length(); if (!WriteConsoleW(handle, reinterpret_cast(*buf), len, &written, NULL)) return ThrowException(ErrnoException(GetLastError(), "WriteConsole")); return scope.Close(Integer::New(written)); } static Handle CloseTTY(const Arguments& args) { HandleScope scope; int fd = args[0]->IntegerValue(); if (close(fd) < 0) return ThrowException(ErrnoException(errno, "close")); return Undefined(); } // process.binding('stdio').getWindowSize(fd); // returns [row, col] static Handle GetWindowSize (const Arguments& args) { HandleScope scope; int fd; HANDLE handle; CONSOLE_SCREEN_BUFFER_INFO info; if (!args[0]->IsNumber()) THROW_BAD_ARGS fd = args[0]->IntegerValue(); handle = (HANDLE)_get_osfhandle(fd); if (!GetConsoleScreenBufferInfo(handle, &info)) return ThrowException(ErrnoException(GetLastError(), "GetConsoleScreenBufferInfo")); Local ret = Array::New(2); ret->Set(0, Integer::New(static_cast(info.dwSize.Y))); ret->Set(1, Integer::New(static_cast(info.dwSize.X))); return scope.Close(ret); } /* moveCursor(fd, dx, dy) */ /* cursorTo(fd, x, y) */ template static Handle SetCursor(const Arguments& args) { HandleScope scope; int fd; COORD size, pos; HANDLE handle; CONSOLE_SCREEN_BUFFER_INFO info; if (!args[0]->IsNumber()) THROW_BAD_ARGS fd = args[0]->IntegerValue(); handle = (HANDLE)_get_osfhandle(fd); if (!GetConsoleScreenBufferInfo(handle, &info)) return ThrowException(ErrnoException(GetLastError(), "GetConsoleScreenBufferInfo")); pos = info.dwCursorPosition; if (relative) { if (args[1]->IsNumber()) pos.X += static_cast(args[1]->Int32Value()); if (args[2]->IsNumber()) pos.Y += static_cast(args[2]->Int32Value()); } else { if (args[1]->IsNumber()) pos.X = static_cast(args[1]->Int32Value()); if (args[2]->IsNumber()) pos.Y = static_cast(args[2]->Int32Value()); } size = info.dwSize; if (pos.X >= size.X) pos.X = size.X - 1; if (pos.X < 0) pos.X = 0; if (pos.Y >= size.Y) pos.Y = size.Y - 1; if (pos.Y < 0) pos.Y = 0; if (!SetConsoleCursorPosition(handle, pos)) return ThrowException(ErrnoException(GetLastError(), "SetConsoleCursorPosition")); return Undefined(); } /* * ClearLine(fd, direction) * direction: * -1: from cursor leftward * 0: entire line * 1: from cursor to right */ static Handle ClearLine(const Arguments& args) { HandleScope scope; int fd, dir; short x1, x2, count; WCHAR *buf; COORD pos; HANDLE handle; CONSOLE_SCREEN_BUFFER_INFO info; DWORD res, written, mode, oldmode; if (!args[0]->IsNumber()) THROW_BAD_ARGS fd = args[0]->IntegerValue(); handle = (HANDLE)_get_osfhandle(fd); if (args[1]->IsNumber()) dir = args[1]->IntegerValue(); if (!GetConsoleScreenBufferInfo(handle, &info)) return ThrowException(ErrnoException(GetLastError(), "GetConsoleScreenBufferInfo")); x1 = dir <= 0 ? 0 : info.dwCursorPosition.X; x2 = dir >= 0 ? info.dwSize.X - 1: info.dwCursorPosition.X; count = x2 - x1 + 1; if (x1 != info.dwCursorPosition.X) { pos.Y = info.dwCursorPosition.Y; pos.X = x1; if (!SetConsoleCursorPosition(handle, pos)) return ThrowException(ErrnoException(GetLastError(), "SetConsoleCursorPosition")); } if (!GetConsoleMode(handle, &oldmode)) return ThrowException(ErrnoException(GetLastError(), "GetConsoleMode")); // Disable wrapping at eol because otherwise windows scrolls the console // when clearing the last line of the console mode = oldmode & ~ENABLE_WRAP_AT_EOL_OUTPUT; if (!SetConsoleMode(handle, mode)) return ThrowException(ErrnoException(GetLastError(), "SetConsoleMode")); buf = new WCHAR[count]; for (short i = 0; i < count; i++) { buf[i] = L' '; } res = WriteConsoleW(handle, buf, count, &written, NULL); delete[] buf; if (!res) return ThrowException(ErrnoException(GetLastError(), "WriteConsole")); if (!SetConsoleCursorPosition(handle, info.dwCursorPosition)) return ThrowException(ErrnoException(GetLastError(), "SetConsoleCursorPosition")); if (!SetConsoleMode(handle, oldmode)) return ThrowException(ErrnoException(GetLastError(), "SetConsoleMode")); return Undefined(); } /* TTY watcher data */ bool tty_watcher_initialized = false; HANDLE tty_handle; HANDLE tty_wait_handle; void *tty_error_callback; void *tty_keypress_callback; void *tty_resize_callback; static ev_async tty_avail_notifier; static void CALLBACK tty_want_poll(void *context, BOOLEAN didTimeout) { assert(!didTimeout); ev_async_send(EV_DEFAULT_UC_ &tty_avail_notifier); } static void tty_watcher_arm() { // Register a new wait handle before dropping the old one, because // otherwise windows might destroy and recreate the wait thread. // MSDN promises that thread pool threads are kept alive when they're idle, // but apparently this does not apply to wait threads. Sigh. HANDLE old_wait_handle = tty_wait_handle; tty_wait_handle = NULL; if (ev_is_active(&tty_avail_notifier)) { if (!RegisterWaitForSingleObject(&tty_wait_handle, tty_handle, tty_want_poll, NULL, INFINITE, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE)) ThrowException(ErrnoException(GetLastError(), "RegisterWaitForSingleObject")); } if (old_wait_handle != NULL) { if (!UnregisterWait(old_wait_handle) && GetLastError() != ERROR_IO_PENDING) ThrowException(ErrnoException(GetLastError(), "UnregisterWait")); } } static void tty_watcher_disarm() { DWORD result; if (tty_wait_handle != NULL) { result = UnregisterWait(tty_wait_handle); tty_wait_handle = NULL; if (!result && GetLastError() != ERROR_IO_PENDING) ThrowException(ErrnoException(GetLastError(), "UnregisterWait")); } } static void tty_watcher_start() { if (!ev_is_active(&tty_avail_notifier)) { ev_async_start(EV_DEFAULT_UC_ &tty_avail_notifier); tty_watcher_arm(); } } static void tty_watcher_stop() { if (ev_is_active(&tty_avail_notifier)) { tty_watcher_disarm(); ev_async_stop(EV_DEFAULT_UC_ &tty_avail_notifier); } } static inline void tty_emit_error(Handle err) { HandleScope scope; Handle global = v8::Context::GetCurrent()->Global(); Handle *handler = cb_unwrap(tty_error_callback); Handle argv[1] = { err }; (*handler)->Call(global, 1, argv); } static void tty_poll(EV_P_ ev_async *watcher, int revents) { assert(watcher == &tty_avail_notifier); assert(revents == EV_ASYNC); HandleScope scope; TryCatch try_catch; Handle global = v8::Context::GetCurrent()->Global(); Handle *callback; INPUT_RECORD input; KEY_EVENT_RECORD k; const char *keyName; DWORD i, j, numev, read; Handle argv[2]; Handle key; if (!GetNumberOfConsoleInputEvents(tty_handle, &numev)) { tty_emit_error(ErrnoException(GetLastError(), "GetNumberOfConsoleInputEvents")); numev = 0; } for (i = numev; i > 0 && ev_is_active(EV_DEFAULT_UC_ &tty_avail_notifier); i--) { if (!ReadConsoleInputW(tty_handle, &input, 1, &read)) { tty_emit_error(ErrnoException(GetLastError(), "ReadConsoleInputW")); break; } switch (input.EventType) { case KEY_EVENT: // Skip if no callback set if (!tty_keypress_callback) break; k = input.Event.KeyEvent; // Ignore keyup if (!k.bKeyDown) break; // Try to find a symbolic name for the key keyName = (k.wVirtualKeyCode <= MAX_KEY) ? scancodes[k.wVirtualKeyCode] : 0; // The key must have a symbolic name or a char or both if (k.uChar.UnicodeChar == 0 && keyName == 0) break; // Set the event name and character argv[0] = k.uChar.UnicodeChar ? String::New(reinterpret_cast(&k.uChar.UnicodeChar), 1) : Undefined(); // Set the key info, if any if (keyName) { key = Object::New(); key->Set(name_symbol, String::New(keyName)); key->Set(shift_symbol, Boolean::New(k.dwControlKeyState & SHIFT_PRESSED)); key->Set(ctrl_symbol, Boolean::New(k.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))); key->Set(meta_symbol, Boolean::New(k.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))); argv[1] = key; } else { argv[1] = Undefined(); } callback = cb_unwrap(tty_keypress_callback); j = k.wRepeatCount; do { (*callback)->Call(global, 2, argv); } while (--j > 0 && ev_is_active(EV_DEFAULT_UC_ &tty_avail_notifier)); break; case WINDOW_BUFFER_SIZE_EVENT: if (!tty_resize_callback) break; callback = cb_unwrap(tty_resize_callback); (*callback)->Call(global, 0, argv); break; } } // Rearm the watcher tty_watcher_arm(); // Emit fatal errors and unhandled error events if (try_catch.HasCaught()) { FatalException(try_catch); } } /* StartTTYWatcher(fd, onError, onKeypress, onResize) */ static Handle InitTTYWatcher(const Arguments& args) { HandleScope scope; if (tty_watcher_initialized) THROW_ERROR("TTY watcher already initialized") if (!args[0]->IsNumber()) THROW_BAD_ARGS; tty_handle = (HANDLE)_get_osfhandle(args[0]->IntegerValue()); if (!args[1]->IsFunction()) THROW_BAD_ARGS; tty_error_callback = cb_persist(args[1]); tty_keypress_callback = args[2]->IsFunction() ? cb_persist(args[2]) : NULL; tty_resize_callback = args[3]->IsFunction() ? cb_persist(args[3]) : NULL; ev_async_init(EV_DEFAULT_UC_ &tty_avail_notifier, tty_poll); tty_watcher_initialized = true; tty_wait_handle = NULL; return Undefined(); } static Handle DestroyTTYWatcher(const Arguments& args) { if (!tty_watcher_initialized) THROW_ERROR("TTY watcher not initialized") tty_watcher_stop(); if (tty_error_callback != NULL) cb_destroy(cb_unwrap(tty_error_callback)); if (tty_keypress_callback != NULL) cb_destroy(cb_unwrap(tty_keypress_callback)); if (tty_resize_callback != NULL) cb_destroy(cb_unwrap(tty_resize_callback)); tty_watcher_initialized = false; return Undefined(); } static Handle StartTTYWatcher(const Arguments& args) { if (!tty_watcher_initialized) THROW_ERROR("TTY watcher not initialized") tty_watcher_start(); return Undefined(); } static Handle StopTTYWatcher(const Arguments& args) { if (!tty_watcher_initialized) THROW_ERROR("TTY watcher not initialized") tty_watcher_stop(); return Undefined(); } void Stdio::Initialize(v8::Handle target) { init_scancode_table(); name_symbol = NODE_PSYMBOL("name"); shift_symbol = NODE_PSYMBOL("shift"); ctrl_symbol = NODE_PSYMBOL("ctrl"); meta_symbol = NODE_PSYMBOL("meta"); target->Set(String::NewSymbol("stdoutFD"), Integer::New(STDOUT_FILENO)); target->Set(String::NewSymbol("stderrFD"), Integer::New(STDERR_FILENO)); target->Set(String::NewSymbol("stdinFD"), Integer::New(STDIN_FILENO)); NODE_SET_METHOD(target, "writeError", WriteError); NODE_SET_METHOD(target, "isatty", IsATTY); NODE_SET_METHOD(target, "isStdoutBlocking", IsStdoutBlocking); NODE_SET_METHOD(target, "isStdinBlocking", IsStdinBlocking); NODE_SET_METHOD(target, "setRawMode", SetRawMode); NODE_SET_METHOD(target, "openStdin", OpenStdin); NODE_SET_METHOD(target, "writeTTY", WriteTTY); NODE_SET_METHOD(target, "closeTTY", CloseTTY); NODE_SET_METHOD(target, "moveCursor", SetCursor); NODE_SET_METHOD(target, "cursorTo", SetCursor); NODE_SET_METHOD(target, "clearLine", ClearLine); NODE_SET_METHOD(target, "getWindowSize", GetWindowSize); NODE_SET_METHOD(target, "initTTYWatcher", InitTTYWatcher); NODE_SET_METHOD(target, "destroyTTYWatcher", DestroyTTYWatcher); NODE_SET_METHOD(target, "startTTYWatcher", StartTTYWatcher); NODE_SET_METHOD(target, "stopTTYWatcher", StopTTYWatcher); } } // namespace node NODE_MODULE(node_stdio, node::Stdio::Initialize);