/* * QEMU System Emulator * * Copyright (c) 2003-2008 Fabrice Bellard * * 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 "qemu/osdep.h" #include "qapi/error.h" #include "qemu/main-loop.h" #include "qemu/module.h" #include "chardev/char-win.h" #include "chardev/char-win-stdio.h" #include "qom/object.h" struct WinStdioChardev { Chardev parent; HANDLE hStdIn; DWORD dwOldMode; HANDLE hInputReadyEvent; HANDLE hInputDoneEvent; HANDLE hInputThread; uint8_t win_stdio_buf; }; typedef struct WinStdioChardev WinStdioChardev; DECLARE_INSTANCE_CHECKER(WinStdioChardev, WIN_STDIO_CHARDEV, TYPE_CHARDEV_WIN_STDIO) static void win_stdio_wait_func(void *opaque) { Chardev *chr = CHARDEV(opaque); WinStdioChardev *stdio = WIN_STDIO_CHARDEV(opaque); INPUT_RECORD buf[4]; int ret; DWORD dwSize; int i; ret = ReadConsoleInput(stdio->hStdIn, buf, ARRAY_SIZE(buf), &dwSize); if (!ret) { /* Avoid error storm */ qemu_del_wait_object(stdio->hStdIn, NULL, NULL); return; } for (i = 0; i < dwSize; i++) { KEY_EVENT_RECORD *kev = &buf[i].Event.KeyEvent; if (buf[i].EventType == KEY_EVENT && kev->bKeyDown) { int j; if (kev->uChar.AsciiChar != 0) { for (j = 0; j < kev->wRepeatCount; j++) { if (qemu_chr_be_can_write(chr)) { uint8_t c = kev->uChar.AsciiChar; qemu_chr_be_write(chr, &c, 1); } } } } } } static DWORD WINAPI win_stdio_thread(LPVOID param) { WinStdioChardev *stdio = WIN_STDIO_CHARDEV(param); int ret; DWORD dwSize; while (1) { /* Wait for one byte */ ret = ReadFile(stdio->hStdIn, &stdio->win_stdio_buf, 1, &dwSize, NULL); /* Exit in case of error, continue if nothing read */ if (!ret) { break; } if (!dwSize) { continue; } /* Some terminal emulator returns \r\n for Enter, just pass \n */ if (stdio->win_stdio_buf == '\r') { continue; } /* Signal the main thread and wait until the byte was eaten */ if (!SetEvent(stdio->hInputReadyEvent)) { break; } if (WaitForSingleObject(stdio->hInputDoneEvent, INFINITE) != WAIT_OBJECT_0) { break; } } qemu_del_wait_object(stdio->hInputReadyEvent, NULL, NULL); return 0; } static void win_stdio_thread_wait_func(void *opaque) { Chardev *chr = CHARDEV(opaque); WinStdioChardev *stdio = WIN_STDIO_CHARDEV(opaque); if (qemu_chr_be_can_write(chr)) { qemu_chr_be_write(chr, &stdio->win_stdio_buf, 1); } SetEvent(stdio->hInputDoneEvent); } static void qemu_chr_set_echo_win_stdio(Chardev *chr, bool echo) { WinStdioChardev *stdio = WIN_STDIO_CHARDEV(chr); DWORD dwMode = 0; GetConsoleMode(stdio->hStdIn, &dwMode); if (echo) { SetConsoleMode(stdio->hStdIn, dwMode | ENABLE_ECHO_INPUT); } else { SetConsoleMode(stdio->hStdIn, dwMode & ~ENABLE_ECHO_INPUT); } } static void qemu_chr_open_stdio(Chardev *chr, ChardevBackend *backend, bool *be_opened, Error **errp) { ChardevStdio *opts = backend->u.stdio.data; bool stdio_allow_signal = !opts->has_signal || opts->signal; WinStdioChardev *stdio = WIN_STDIO_CHARDEV(chr); DWORD dwMode; int is_console = 0; stdio->hStdIn = GetStdHandle(STD_INPUT_HANDLE); if (stdio->hStdIn == INVALID_HANDLE_VALUE) { error_setg(errp, "cannot open stdio: invalid handle"); return; } is_console = GetConsoleMode(stdio->hStdIn, &dwMode) != 0; stdio->dwOldMode = dwMode; if (is_console) { if (qemu_add_wait_object(stdio->hStdIn, win_stdio_wait_func, chr)) { error_setg(errp, "qemu_add_wait_object: failed"); goto err1; } } else { DWORD dwId; stdio->hInputReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); stdio->hInputDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (stdio->hInputReadyEvent == INVALID_HANDLE_VALUE || stdio->hInputDoneEvent == INVALID_HANDLE_VALUE) { error_setg(errp, "cannot create event"); goto err2; } if (qemu_add_wait_object(stdio->hInputReadyEvent, win_stdio_thread_wait_func, chr)) { error_setg(errp, "qemu_add_wait_object: failed"); goto err2; } stdio->hInputThread = CreateThread(NULL, 0, win_stdio_thread, chr, 0, &dwId); if (stdio->hInputThread == INVALID_HANDLE_VALUE) { error_setg(errp, "cannot create stdio thread"); goto err3; } } dwMode |= ENABLE_LINE_INPUT; if (is_console) { /* set the terminal in raw mode */ /* ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS */ if (stdio_allow_signal) { dwMode |= ENABLE_PROCESSED_INPUT; } else { dwMode &= ~ENABLE_PROCESSED_INPUT; } } SetConsoleMode(stdio->hStdIn, dwMode); qemu_chr_set_echo_win_stdio(chr, false); return; err3: qemu_del_wait_object(stdio->hInputReadyEvent, NULL, NULL); err2: CloseHandle(stdio->hInputReadyEvent); CloseHandle(stdio->hInputDoneEvent); err1: qemu_del_wait_object(stdio->hStdIn, NULL, NULL); } static void char_win_stdio_finalize(Object *obj) { WinStdioChardev *stdio = WIN_STDIO_CHARDEV(obj); if (stdio->hStdIn != INVALID_HANDLE_VALUE) { SetConsoleMode(stdio->hStdIn, stdio->dwOldMode); } if (stdio->hInputReadyEvent != INVALID_HANDLE_VALUE) { CloseHandle(stdio->hInputReadyEvent); } if (stdio->hInputDoneEvent != INVALID_HANDLE_VALUE) { CloseHandle(stdio->hInputDoneEvent); } if (stdio->hInputThread != INVALID_HANDLE_VALUE) { TerminateThread(stdio->hInputThread, 0); } } static int win_stdio_write(Chardev *chr, const uint8_t *buf, int len) { HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); DWORD dwSize; int len1; len1 = len; while (len1 > 0) { if (!WriteFile(hStdOut, buf, len1, &dwSize, NULL)) { break; } buf += dwSize; len1 -= dwSize; } return len - len1; } static void char_win_stdio_class_init(ObjectClass *oc, void *data) { ChardevClass *cc = CHARDEV_CLASS(oc); cc->open = qemu_chr_open_stdio; cc->chr_write = win_stdio_write; cc->chr_set_echo = qemu_chr_set_echo_win_stdio; } static const TypeInfo char_win_stdio_type_info = { .name = TYPE_CHARDEV_WIN_STDIO, .parent = TYPE_CHARDEV, .instance_size = sizeof(WinStdioChardev), .instance_finalize = char_win_stdio_finalize, .class_init = char_win_stdio_class_init, .abstract = true, }; static void register_types(void) { type_register_static(&char_win_stdio_type_info); } type_init(register_types);