Skip to content

Commit

Permalink
Windows: watcher for tty events
Browse files Browse the repository at this point in the history
  • Loading branch information
piscisaureus authored and ry committed Jan 19, 2011
1 parent 4f8dd28 commit b0eed65
Showing 1 changed file with 354 additions and 0 deletions.
354 changes: 354 additions & 0 deletions src/node_stdio_win32.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,109 @@ namespace node {
#define THROW_BAD_ARGS \
return ThrowException(Exception::TypeError(String::New("Bad argument")));

#define KEY(scancode, name) \
scancodes[scancode] = name;
#define MAX_KEY VK_RMENU

static const char* scancodes[MAX_KEY + 1] = {0};

static Persistent<String> name_symbol;
static Persistent<String> shift_symbol;
static Persistent<String> ctrl_symbol;
static Persistent<String> 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
Expand Down Expand Up @@ -142,7 +245,254 @@ static Handle<Value> GetWindowSize (const Arguments& args) {
}


/* 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<Value> err) {
HandleScope scope;
Handle<Object> global = v8::Context::GetCurrent()->Global();
Handle<Function> *handler = cb_unwrap(tty_error_callback);
Handle<Value> 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<Object> global = v8::Context::GetCurrent()->Global();
Handle<Function> *callback;
INPUT_RECORD input;
KEY_EVENT_RECORD k;
const char *keyName;
DWORD i, j, numev, read;
Handle<Value> argv[2];
Handle<Object> 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<uint16_t*>(&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;
argv[0] = Integer::New(input.Event.WindowBufferSizeEvent.dwSize.Y);
argv[1] = Integer::New(input.Event.WindowBufferSizeEvent.dwSize.X);
callback = cb_unwrap(tty_resize_callback);
(*callback)->Call(global, 2, 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<Value> 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<Value> 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<Value> StartTTYWatcher(const Arguments& args) {
if (!tty_watcher_initialized)
THROW_ERROR("TTY watcher not initialized")

tty_watcher_start();
return Undefined();
}


static Handle<Value> StopTTYWatcher(const Arguments& args) {
if (!tty_watcher_initialized)
THROW_ERROR("TTY watcher not initialized")

tty_watcher_stop();
return Undefined();
}


void Stdio::Initialize(v8::Handle<v8::Object> 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));
Expand All @@ -154,6 +504,10 @@ void Stdio::Initialize(v8::Handle<v8::Object> target) {
NODE_SET_METHOD(target, "setRawMode", SetRawMode);
NODE_SET_METHOD(target, "openStdin", OpenStdin);
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);
}


Expand Down

0 comments on commit b0eed65

Please sign in to comment.