xref: /openbmc/qemu/chardev/char-win-stdio.c (revision 8779fccb)
1 /*
2  * QEMU System Emulator
3  *
4  * Copyright (c) 2003-2008 Fabrice Bellard
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 #include "qemu/osdep.h"
25 #include "qapi/error.h"
26 #include "char-win.h"
27 #include "char-win-stdio.h"
28 
29 typedef struct {
30     Chardev parent;
31     HANDLE  hStdIn;
32     HANDLE  hInputReadyEvent;
33     HANDLE  hInputDoneEvent;
34     HANDLE  hInputThread;
35     uint8_t win_stdio_buf;
36 } WinStdioChardev;
37 
38 #define WIN_STDIO_CHARDEV(obj)                                          \
39     OBJECT_CHECK(WinStdioChardev, (obj), TYPE_CHARDEV_WIN_STDIO)
40 
41 static void win_stdio_wait_func(void *opaque)
42 {
43     Chardev *chr = CHARDEV(opaque);
44     WinStdioChardev *stdio = WIN_STDIO_CHARDEV(opaque);
45     INPUT_RECORD       buf[4];
46     int                ret;
47     DWORD              dwSize;
48     int                i;
49 
50     ret = ReadConsoleInput(stdio->hStdIn, buf, ARRAY_SIZE(buf), &dwSize);
51 
52     if (!ret) {
53         /* Avoid error storm */
54         qemu_del_wait_object(stdio->hStdIn, NULL, NULL);
55         return;
56     }
57 
58     for (i = 0; i < dwSize; i++) {
59         KEY_EVENT_RECORD *kev = &buf[i].Event.KeyEvent;
60 
61         if (buf[i].EventType == KEY_EVENT && kev->bKeyDown) {
62             int j;
63             if (kev->uChar.AsciiChar != 0) {
64                 for (j = 0; j < kev->wRepeatCount; j++) {
65                     if (qemu_chr_be_can_write(chr)) {
66                         uint8_t c = kev->uChar.AsciiChar;
67                         qemu_chr_be_write(chr, &c, 1);
68                     }
69                 }
70             }
71         }
72     }
73 }
74 
75 static DWORD WINAPI win_stdio_thread(LPVOID param)
76 {
77     WinStdioChardev *stdio = WIN_STDIO_CHARDEV(param);
78     int                ret;
79     DWORD              dwSize;
80 
81     while (1) {
82 
83         /* Wait for one byte */
84         ret = ReadFile(stdio->hStdIn, &stdio->win_stdio_buf, 1, &dwSize, NULL);
85 
86         /* Exit in case of error, continue if nothing read */
87         if (!ret) {
88             break;
89         }
90         if (!dwSize) {
91             continue;
92         }
93 
94         /* Some terminal emulator returns \r\n for Enter, just pass \n */
95         if (stdio->win_stdio_buf == '\r') {
96             continue;
97         }
98 
99         /* Signal the main thread and wait until the byte was eaten */
100         if (!SetEvent(stdio->hInputReadyEvent)) {
101             break;
102         }
103         if (WaitForSingleObject(stdio->hInputDoneEvent, INFINITE)
104             != WAIT_OBJECT_0) {
105             break;
106         }
107     }
108 
109     qemu_del_wait_object(stdio->hInputReadyEvent, NULL, NULL);
110     return 0;
111 }
112 
113 static void win_stdio_thread_wait_func(void *opaque)
114 {
115     Chardev *chr = CHARDEV(opaque);
116     WinStdioChardev *stdio = WIN_STDIO_CHARDEV(opaque);
117 
118     if (qemu_chr_be_can_write(chr)) {
119         qemu_chr_be_write(chr, &stdio->win_stdio_buf, 1);
120     }
121 
122     SetEvent(stdio->hInputDoneEvent);
123 }
124 
125 static void qemu_chr_set_echo_win_stdio(Chardev *chr, bool echo)
126 {
127     WinStdioChardev *stdio = WIN_STDIO_CHARDEV(chr);
128     DWORD              dwMode = 0;
129 
130     GetConsoleMode(stdio->hStdIn, &dwMode);
131 
132     if (echo) {
133         SetConsoleMode(stdio->hStdIn, dwMode | ENABLE_ECHO_INPUT);
134     } else {
135         SetConsoleMode(stdio->hStdIn, dwMode & ~ENABLE_ECHO_INPUT);
136     }
137 }
138 
139 static void qemu_chr_open_stdio(Chardev *chr,
140                                 ChardevBackend *backend,
141                                 bool *be_opened,
142                                 Error **errp)
143 {
144     WinStdioChardev *stdio = WIN_STDIO_CHARDEV(chr);
145     DWORD              dwMode;
146     int                is_console = 0;
147 
148     stdio->hStdIn = GetStdHandle(STD_INPUT_HANDLE);
149     if (stdio->hStdIn == INVALID_HANDLE_VALUE) {
150         error_setg(errp, "cannot open stdio: invalid handle");
151         return;
152     }
153 
154     is_console = GetConsoleMode(stdio->hStdIn, &dwMode) != 0;
155 
156     if (is_console) {
157         if (qemu_add_wait_object(stdio->hStdIn,
158                                  win_stdio_wait_func, chr)) {
159             error_setg(errp, "qemu_add_wait_object: failed");
160             goto err1;
161         }
162     } else {
163         DWORD   dwId;
164 
165         stdio->hInputReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
166         stdio->hInputDoneEvent  = CreateEvent(NULL, FALSE, FALSE, NULL);
167         if (stdio->hInputReadyEvent == INVALID_HANDLE_VALUE
168             || stdio->hInputDoneEvent == INVALID_HANDLE_VALUE) {
169             error_setg(errp, "cannot create event");
170             goto err2;
171         }
172         if (qemu_add_wait_object(stdio->hInputReadyEvent,
173                                  win_stdio_thread_wait_func, chr)) {
174             error_setg(errp, "qemu_add_wait_object: failed");
175             goto err2;
176         }
177         stdio->hInputThread     = CreateThread(NULL, 0, win_stdio_thread,
178                                                chr, 0, &dwId);
179 
180         if (stdio->hInputThread == INVALID_HANDLE_VALUE) {
181             error_setg(errp, "cannot create stdio thread");
182             goto err3;
183         }
184     }
185 
186     dwMode |= ENABLE_LINE_INPUT;
187 
188     if (is_console) {
189         /* set the terminal in raw mode */
190         /* ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS */
191         dwMode |= ENABLE_PROCESSED_INPUT;
192     }
193 
194     SetConsoleMode(stdio->hStdIn, dwMode);
195 
196     qemu_chr_set_echo_win_stdio(chr, false);
197 
198     return;
199 
200 err3:
201     qemu_del_wait_object(stdio->hInputReadyEvent, NULL, NULL);
202 err2:
203     CloseHandle(stdio->hInputReadyEvent);
204     CloseHandle(stdio->hInputDoneEvent);
205 err1:
206     qemu_del_wait_object(stdio->hStdIn, NULL, NULL);
207 }
208 
209 static void char_win_stdio_finalize(Object *obj)
210 {
211     WinStdioChardev *stdio = WIN_STDIO_CHARDEV(obj);
212 
213     if (stdio->hInputReadyEvent != INVALID_HANDLE_VALUE) {
214         CloseHandle(stdio->hInputReadyEvent);
215     }
216     if (stdio->hInputDoneEvent != INVALID_HANDLE_VALUE) {
217         CloseHandle(stdio->hInputDoneEvent);
218     }
219     if (stdio->hInputThread != INVALID_HANDLE_VALUE) {
220         TerminateThread(stdio->hInputThread, 0);
221     }
222 }
223 
224 static int win_stdio_write(Chardev *chr, const uint8_t *buf, int len)
225 {
226     HANDLE  hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
227     DWORD   dwSize;
228     int     len1;
229 
230     len1 = len;
231 
232     while (len1 > 0) {
233         if (!WriteFile(hStdOut, buf, len1, &dwSize, NULL)) {
234             break;
235         }
236         buf  += dwSize;
237         len1 -= dwSize;
238     }
239 
240     return len - len1;
241 }
242 
243 static void char_win_stdio_class_init(ObjectClass *oc, void *data)
244 {
245     ChardevClass *cc = CHARDEV_CLASS(oc);
246 
247     cc->open = qemu_chr_open_stdio;
248     cc->chr_write = win_stdio_write;
249     cc->chr_set_echo = qemu_chr_set_echo_win_stdio;
250 }
251 
252 static const TypeInfo char_win_stdio_type_info = {
253     .name = TYPE_CHARDEV_WIN_STDIO,
254     .parent = TYPE_CHARDEV,
255     .instance_size = sizeof(WinStdioChardev),
256     .instance_finalize = char_win_stdio_finalize,
257     .class_init = char_win_stdio_class_init,
258     .abstract = true,
259 };
260 
261 static void register_types(void)
262 {
263     type_register_static(&char_win_stdio_type_info);
264 }
265 
266 type_init(register_types);
267