| /* |
| * 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; |
| 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; |
| |
| 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 | ENABLE_VIRTUAL_TERMINAL_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->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); |