blob: a6e260eb964d765cc18839027657f88cc14f18eb [file] [log] [blame]
balrog4d3b6f62008-02-10 16:33:14 +00001/*
2 * QEMU curses/ncurses display driver
3 *
4 * Copyright (c) 2005 Andrzej Zaborowski <balrog@zabor.org>
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 */
Markus Armbruster0b8fa322019-05-23 16:35:07 +020024
Peter Maydelle16f4c82016-01-29 17:49:51 +000025#include "qemu/osdep.h"
balrog4d3b6f62008-02-10 16:33:14 +000026
27#ifndef _WIN32
balrog4d3b6f62008-02-10 16:33:14 +000028#include <sys/ioctl.h>
29#include <termios.h>
30#endif
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010031#include <locale.h>
32#include <wchar.h>
33#include <langinfo.h>
34#include <iconv.h>
balrog4d3b6f62008-02-10 16:33:14 +000035
Fei Liab4f9312018-10-17 10:26:50 +020036#include "qapi/error.h"
Markus Armbruster0b8fa322019-05-23 16:35:07 +020037#include "qemu/module.h"
Paolo Bonzini28ecbae2012-11-28 12:06:30 +010038#include "ui/console.h"
Gerd Hoffmanncd100322013-12-04 13:40:20 +010039#include "ui/input.h"
Paolo Bonzini9c17d612012-12-17 18:20:04 +010040#include "sysemu/sysemu.h"
blueswir1511d2b12009-03-07 15:32:56 +000041
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020042/* KEY_EVENT is defined in wincon.h and in curses.h. Avoid redefinition. */
43#undef KEY_EVENT
44#include <curses.h>
45#undef KEY_EVENT
46
balrog4d3b6f62008-02-10 16:33:14 +000047#define FONT_HEIGHT 16
48#define FONT_WIDTH 8
49
Samuel Thibault459a7072019-03-04 22:05:32 +010050enum maybe_keycode {
51 CURSES_KEYCODE,
52 CURSES_CHAR,
53 CURSES_CHAR_OR_KEYCODE,
54};
55
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010056static DisplayChangeListener *dcl;
Anthony Liguoric227f092009-10-01 16:12:16 -050057static console_ch_t screen[160 * 100];
balrog4d3b6f62008-02-10 16:33:14 +000058static WINDOW *screenpad = NULL;
59static int width, height, gwidth, gheight, invalidate;
60static int px, py, sminx, sminy, smaxx, smaxy;
61
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010062static const char *font_charset = "CP437";
63static cchar_t vga_to_curses[256];
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +090064
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010065static void curses_update(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010066 int x, int y, int w, int h)
balrog4d3b6f62008-02-10 16:33:14 +000067{
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020068 console_ch_t *line;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010069 cchar_t curses_line[width];
Samuel Thibault962cf8f2019-04-27 20:33:07 +020070 wchar_t wch[CCHARW_MAX];
71 attr_t attrs;
72 short colors;
73 int ret;
balrog4d3b6f62008-02-10 16:33:14 +000074
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020075 line = screen + y * width;
76 for (h += y; y < h; y ++, line += width) {
77 for (x = 0; x < width; x++) {
78 chtype ch = line[x] & 0xff;
79 chtype at = line[x] & ~0xff;
Samuel Thibault962cf8f2019-04-27 20:33:07 +020080 ret = getcchar(&vga_to_curses[ch], wch, &attrs, &colors, NULL);
81 if (ret == ERR || wch[0] == 0) {
82 wch[0] = ch;
83 wch[1] = 0;
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020084 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +020085 setcchar(&curses_line[x], wch, at, 0, NULL);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020086 }
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010087 mvwadd_wchnstr(screenpad, y, 0, curses_line, width);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020088 }
balrog4d3b6f62008-02-10 16:33:14 +000089
90 pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1);
91 refresh();
92}
93
94static void curses_calc_pad(void)
95{
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +010096 if (qemu_console_is_fixedsize(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +000097 width = gwidth;
98 height = gheight;
99 } else {
100 width = COLS;
101 height = LINES;
102 }
103
104 if (screenpad)
105 delwin(screenpad);
106
107 clear();
108 refresh();
109
110 screenpad = newpad(height, width);
111
112 if (width > COLS) {
113 px = (width - COLS) / 2;
114 sminx = 0;
115 smaxx = COLS;
116 } else {
117 px = 0;
118 sminx = (COLS - width) / 2;
119 smaxx = sminx + width;
120 }
121
122 if (height > LINES) {
123 py = (height - LINES) / 2;
124 sminy = 0;
125 smaxy = LINES;
126 } else {
127 py = 0;
128 sminy = (LINES - height) / 2;
129 smaxy = sminy + height;
130 }
131}
132
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100133static void curses_resize(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100134 int width, int height)
balrog4d3b6f62008-02-10 16:33:14 +0000135{
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200136 if (width == gwidth && height == gheight) {
balrog4d3b6f62008-02-10 16:33:14 +0000137 return;
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200138 }
balrog4d3b6f62008-02-10 16:33:14 +0000139
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200140 gwidth = width;
141 gheight = height;
balrog4d3b6f62008-02-10 16:33:14 +0000142
143 curses_calc_pad();
144}
145
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100146#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE)
147static volatile sig_atomic_t got_sigwinch;
148static void curses_winch_check(void)
balrog4d3b6f62008-02-10 16:33:14 +0000149{
150 struct winsize {
151 unsigned short ws_row;
152 unsigned short ws_col;
153 unsigned short ws_xpixel; /* unused */
154 unsigned short ws_ypixel; /* unused */
155 } ws;
156
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100157 if (!got_sigwinch) {
balrog4d3b6f62008-02-10 16:33:14 +0000158 return;
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100159 }
160 got_sigwinch = false;
161
162 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
163 return;
164 }
balrog4d3b6f62008-02-10 16:33:14 +0000165
166 resize_term(ws.ws_row, ws.ws_col);
balrog4d3b6f62008-02-10 16:33:14 +0000167 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000168}
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100169
170static void curses_winch_handler(int signum)
171{
172 got_sigwinch = true;
173}
174
175static void curses_winch_init(void)
176{
177 struct sigaction old, winch = {
178 .sa_handler = curses_winch_handler,
179 };
180 sigaction(SIGWINCH, &winch, &old);
181}
182#else
183static void curses_winch_check(void) {}
184static void curses_winch_init(void) {}
balrog4d3b6f62008-02-10 16:33:14 +0000185#endif
186
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100187static void curses_cursor_position(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100188 int x, int y)
balrog4d3b6f62008-02-10 16:33:14 +0000189{
190 if (x >= 0) {
191 x = sminx + x - px;
192 y = sminy + y - py;
193
194 if (x >= 0 && y >= 0 && x < COLS && y < LINES) {
195 move(y, x);
196 curs_set(1);
197 /* it seems that curs_set(1) must always be called before
198 * curs_set(2) for the latter to have effect */
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100199 if (!qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000200 curs_set(2);
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100201 }
balrog4d3b6f62008-02-10 16:33:14 +0000202 return;
203 }
204 }
205
206 curs_set(0);
207}
208
209/* generic keyboard conversion */
210
211#include "curses_keys.h"
balrog4d3b6f62008-02-10 16:33:14 +0000212
Anthony Liguoric227f092009-10-01 16:12:16 -0500213static kbd_layout_t *kbd_layout = NULL;
balrog4d3b6f62008-02-10 16:33:14 +0000214
Samuel Thibault459a7072019-03-04 22:05:32 +0100215static wint_t console_getch(enum maybe_keycode *maybe_keycode)
216{
217 wint_t ret;
218 switch (get_wch(&ret)) {
219 case KEY_CODE_YES:
220 *maybe_keycode = CURSES_KEYCODE;
221 break;
222 case OK:
223 *maybe_keycode = CURSES_CHAR;
224 break;
225 case ERR:
226 ret = -1;
227 break;
228 }
229 return ret;
230}
231
232static int curses2foo(const int _curses2foo[], const int _curseskey2foo[],
233 int chr, enum maybe_keycode maybe_keycode)
234{
235 int ret = -1;
236 if (maybe_keycode == CURSES_CHAR) {
237 if (chr < CURSES_CHARS) {
238 ret = _curses2foo[chr];
239 }
240 } else {
241 if (chr < CURSES_KEYS) {
242 ret = _curseskey2foo[chr];
243 }
244 if (ret == -1 && maybe_keycode == CURSES_CHAR_OR_KEYCODE &&
245 chr < CURSES_CHARS) {
246 ret = _curses2foo[chr];
247 }
248 }
249 return ret;
250}
251
252#define curses2keycode(chr, maybe_keycode) \
253 curses2foo(_curses2keycode, _curseskey2keycode, chr, maybe_keycode)
254#define curses2keysym(chr, maybe_keycode) \
255 curses2foo(_curses2keysym, _curseskey2keysym, chr, maybe_keycode)
256#define curses2qemu(chr, maybe_keycode) \
257 curses2foo(_curses2qemu, _curseskey2qemu, chr, maybe_keycode)
258
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100259static void curses_refresh(DisplayChangeListener *dcl)
balrog4d3b6f62008-02-10 16:33:14 +0000260{
Peter Maydell99a9ef42016-08-11 15:23:27 +0100261 int chr, keysym, keycode, keycode_alt;
Samuel Thibault459a7072019-03-04 22:05:32 +0100262 enum maybe_keycode maybe_keycode;
balrog4d3b6f62008-02-10 16:33:14 +0000263
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100264 curses_winch_check();
265
balrog4d3b6f62008-02-10 16:33:14 +0000266 if (invalidate) {
267 clear();
268 refresh();
269 curses_calc_pad();
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100270 graphic_hw_invalidate(NULL);
balrog4d3b6f62008-02-10 16:33:14 +0000271 invalidate = 0;
272 }
273
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100274 graphic_hw_text_update(NULL, screen);
balrog4d3b6f62008-02-10 16:33:14 +0000275
balrog4d3b6f62008-02-10 16:33:14 +0000276 while (1) {
277 /* while there are any pending key strokes to process */
Samuel Thibault459a7072019-03-04 22:05:32 +0100278 chr = console_getch(&maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000279
Samuel Thibault459a7072019-03-04 22:05:32 +0100280 if (chr == -1)
balrog4d3b6f62008-02-10 16:33:14 +0000281 break;
282
balrogb1314cf2008-02-22 18:21:28 +0000283#ifdef KEY_RESIZE
balrog4d3b6f62008-02-10 16:33:14 +0000284 /* this shouldn't occur when we use a custom SIGWINCH handler */
Samuel Thibault459a7072019-03-04 22:05:32 +0100285 if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) {
balrog4d3b6f62008-02-10 16:33:14 +0000286 clear();
287 refresh();
288 curses_calc_pad();
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100289 curses_update(dcl, 0, 0, width, height);
balrog4d3b6f62008-02-10 16:33:14 +0000290 continue;
291 }
balrogb1314cf2008-02-22 18:21:28 +0000292#endif
balrog4d3b6f62008-02-10 16:33:14 +0000293
Samuel Thibault459a7072019-03-04 22:05:32 +0100294 keycode = curses2keycode(chr, maybe_keycode);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100295 keycode_alt = 0;
balrog4d3b6f62008-02-10 16:33:14 +0000296
Samuel Thibault633786f2019-03-03 18:25:57 +0100297 /* alt or esc key */
balrog4d3b6f62008-02-10 16:33:14 +0000298 if (keycode == 1) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100299 enum maybe_keycode next_maybe_keycode;
300 int nextchr = console_getch(&next_maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000301
Samuel Thibault459a7072019-03-04 22:05:32 +0100302 if (nextchr != -1) {
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100303 chr = nextchr;
Samuel Thibault459a7072019-03-04 22:05:32 +0100304 maybe_keycode = next_maybe_keycode;
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100305 keycode_alt = ALT;
Samuel Thibault459a7072019-03-04 22:05:32 +0100306 keycode = curses2keycode(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000307
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100308 if (keycode != -1) {
309 keycode |= ALT;
balrog4d3b6f62008-02-10 16:33:14 +0000310
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100311 /* process keys reserved for qemu */
312 if (keycode >= QEMU_KEY_CONSOLE0 &&
313 keycode < QEMU_KEY_CONSOLE0 + 9) {
314 erase();
315 wnoutrefresh(stdscr);
316 console_select(keycode - QEMU_KEY_CONSOLE0);
balrog4d3b6f62008-02-10 16:33:14 +0000317
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100318 invalidate = 1;
319 continue;
320 }
balrog4d3b6f62008-02-10 16:33:14 +0000321 }
322 }
323 }
324
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100325 if (kbd_layout) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100326 keysym = curses2keysym(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000327
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100328 if (keysym == -1) {
Samuel Thibaultd03703c2010-10-19 19:48:20 +0200329 if (chr < ' ') {
330 keysym = chr + '@';
331 if (keysym >= 'A' && keysym <= 'Z')
332 keysym += 'a' - 'A';
333 keysym |= KEYSYM_CNTRL;
334 } else
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100335 keysym = chr;
336 }
337
Gerd Hoffmannabb4f2c2018-02-22 08:05:13 +0100338 keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK,
Gerd Hoffmann19c1b9f2019-01-22 10:28:14 +0100339 NULL, false);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100340 if (keycode == 0)
341 continue;
342
343 keycode |= (keysym & ~KEYSYM_MASK) >> 16;
344 keycode |= keycode_alt;
balrog4d3b6f62008-02-10 16:33:14 +0000345 }
346
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100347 if (keycode == -1)
348 continue;
349
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100350 if (qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000351 /* since terminals don't know about key press and release
352 * events, we need to emit both for each key received */
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100353 if (keycode & SHIFT) {
354 qemu_input_event_send_key_number(NULL, SHIFT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200355 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100356 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100357 if (keycode & CNTRL) {
358 qemu_input_event_send_key_number(NULL, CNTRL_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200359 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100360 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100361 if (keycode & ALT) {
362 qemu_input_event_send_key_number(NULL, ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200363 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100364 }
365 if (keycode & ALTGR) {
366 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200367 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100368 }
369
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400370 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200371 qemu_input_event_send_key_delay(0);
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400372 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200373 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100374
375 if (keycode & ALTGR) {
376 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200377 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100378 }
379 if (keycode & ALT) {
380 qemu_input_event_send_key_number(NULL, ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200381 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100382 }
383 if (keycode & CNTRL) {
384 qemu_input_event_send_key_number(NULL, CNTRL_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200385 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100386 }
387 if (keycode & SHIFT) {
388 qemu_input_event_send_key_number(NULL, SHIFT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200389 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100390 }
balrog4d3b6f62008-02-10 16:33:14 +0000391 } else {
Samuel Thibault459a7072019-03-04 22:05:32 +0100392 keysym = curses2qemu(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000393 if (keysym == -1)
394 keysym = chr;
395
396 kbd_put_keysym(keysym);
397 }
398 }
399}
400
Blue Swirlaaf12c22010-03-21 19:44:06 +0000401static void curses_atexit(void)
balrog4d3b6f62008-02-10 16:33:14 +0000402{
403 endwin();
404}
405
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200406/*
407 * In the following:
408 * - fch is the font glyph number
409 * - uch is the unicode value
410 * - wch is the wchar_t value (may not be unicode, e.g. on BSD/solaris)
411 * - mbch is the native local-dependent multibyte representation
412 */
413
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100414/* Setup wchar glyph for one UCS-2 char */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200415static void convert_ucs(unsigned char fch, uint16_t uch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100416{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200417 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200418 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200419 char *puch, *pmbch;
420 size_t such, smbch;
421 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100422
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200423 puch = (char *) &uch;
424 pmbch = (char *) mbch;
425 such = sizeof(uch);
426 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100427
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200428 if (iconv(conv, &puch, &such, &pmbch, &smbch) == (size_t) -1) {
429 fprintf(stderr, "Could not convert 0x%04x "
430 "from UCS-2 to a multibyte character: %s\n",
431 uch, strerror(errno));
432 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100433 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200434
435 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200436 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200437 fprintf(stderr, "Could not convert 0x%04x "
438 "from a multibyte character to wchar_t: %s\n",
439 uch, strerror(errno));
440 return;
441 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200442
443 wch[1] = 0;
444 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100445}
446
447/* Setup wchar glyph for one font character */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200448static void convert_font(unsigned char fch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100449{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200450 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200451 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200452 char *pfch, *pmbch;
453 size_t sfch, smbch;
454 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100455
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200456 pfch = (char *) &fch;
457 pmbch = (char *) &mbch;
458 sfch = sizeof(fch);
459 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100460
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200461 if (iconv(conv, &pfch, &sfch, &pmbch, &smbch) == (size_t) -1) {
462 fprintf(stderr, "Could not convert font glyph 0x%02x "
463 "from %s to a multibyte character: %s\n",
464 fch, font_charset, strerror(errno));
465 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100466 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200467
468 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200469 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200470 fprintf(stderr, "Could not convert font glyph 0x%02x "
471 "from a multibyte character to wchar_t: %s\n",
472 fch, strerror(errno));
473 return;
474 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200475
476 wch[1] = 0;
477 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100478}
479
480/* Convert one wchar to UCS-2 */
481static uint16_t get_ucs(wchar_t wch, iconv_t conv)
482{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200483 char mbch[MB_LEN_MAX];
484 uint16_t uch;
485 char *pmbch, *puch;
486 size_t smbch, such;
487 mbstate_t ps;
488 int ret;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100489
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200490 memset(&ps, 0, sizeof(ps));
491 ret = wcrtomb(mbch, wch, &ps);
492 if (ret == -1) {
Max Reitzdc3c8712019-05-27 16:25:40 +0200493 fprintf(stderr, "Could not convert 0x%04lx "
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200494 "from wchar_t to a multibyte character: %s\n",
Max Reitzdc3c8712019-05-27 16:25:40 +0200495 (unsigned long)wch, strerror(errno));
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100496 return 0xFFFD;
497 }
498
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200499 pmbch = (char *) mbch;
500 puch = (char *) &uch;
501 smbch = ret;
502 such = sizeof(uch);
503
504 if (iconv(conv, &pmbch, &smbch, &puch, &such) == (size_t) -1) {
Max Reitzdc3c8712019-05-27 16:25:40 +0200505 fprintf(stderr, "Could not convert 0x%04lx "
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200506 "from a multibyte character to UCS-2 : %s\n",
Max Reitzdc3c8712019-05-27 16:25:40 +0200507 (unsigned long)wch, strerror(errno));
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200508 return 0xFFFD;
509 }
510
511 return uch;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100512}
513
514/*
515 * Setup mapping for vga to curses line graphics.
516 */
517static void font_setup(void)
518{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200519 iconv_t ucs2_to_nativecharset;
520 iconv_t nativecharset_to_ucs2;
521 iconv_t font_conv;
522 int i;
523
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100524 /*
525 * Control characters are normally non-printable, but VGA does have
526 * well-known glyphs for them.
527 */
528 static uint16_t control_characters[0x20] = {
529 0x0020,
530 0x263a,
531 0x263b,
532 0x2665,
533 0x2666,
534 0x2663,
535 0x2660,
536 0x2022,
537 0x25d8,
538 0x25cb,
539 0x25d9,
540 0x2642,
541 0x2640,
542 0x266a,
543 0x266b,
544 0x263c,
545 0x25ba,
546 0x25c4,
547 0x2195,
548 0x203c,
549 0x00b6,
550 0x00a7,
551 0x25ac,
552 0x21a8,
553 0x2191,
554 0x2193,
555 0x2192,
556 0x2190,
557 0x221f,
558 0x2194,
559 0x25b2,
560 0x25bc
561 };
562
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200563 ucs2_to_nativecharset = iconv_open(nl_langinfo(CODESET), "UCS-2");
564 if (ucs2_to_nativecharset == (iconv_t) -1) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100565 fprintf(stderr, "Could not convert font glyphs from UCS-2: '%s'\n",
566 strerror(errno));
567 exit(1);
568 }
569
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200570 nativecharset_to_ucs2 = iconv_open("UCS-2", nl_langinfo(CODESET));
571 if (nativecharset_to_ucs2 == (iconv_t) -1) {
572 iconv_close(ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100573 fprintf(stderr, "Could not convert font glyphs to UCS-2: '%s'\n",
574 strerror(errno));
575 exit(1);
576 }
577
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200578 font_conv = iconv_open(nl_langinfo(CODESET), font_charset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100579 if (font_conv == (iconv_t) -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200580 iconv_close(ucs2_to_nativecharset);
581 iconv_close(nativecharset_to_ucs2);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100582 fprintf(stderr, "Could not convert font glyphs from %s: '%s'\n",
583 font_charset, strerror(errno));
584 exit(1);
585 }
586
587 /* Control characters */
588 for (i = 0; i <= 0x1F; i++) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200589 convert_ucs(i, control_characters[i], ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100590 }
591
592 for (i = 0x20; i <= 0xFF; i++) {
593 convert_font(i, font_conv);
594 }
595
596 /* DEL */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200597 convert_ucs(0x7F, 0x2302, ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100598
599 if (strcmp(nl_langinfo(CODESET), "UTF-8")) {
600 /* Non-Unicode capable, use termcap equivalents for those available */
601 for (i = 0; i <= 0xFF; i++) {
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200602 wchar_t wch[CCHARW_MAX];
603 attr_t attr;
604 short color;
605 int ret;
606
607 ret = getcchar(&vga_to_curses[i], wch, &attr, &color, NULL);
608 if (ret == ERR)
609 continue;
610
611 switch (get_ucs(wch[0], nativecharset_to_ucs2)) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100612 case 0x00a3:
613 vga_to_curses[i] = *WACS_STERLING;
614 break;
615 case 0x2591:
616 vga_to_curses[i] = *WACS_BOARD;
617 break;
618 case 0x2592:
619 vga_to_curses[i] = *WACS_CKBOARD;
620 break;
621 case 0x2502:
622 vga_to_curses[i] = *WACS_VLINE;
623 break;
624 case 0x2524:
625 vga_to_curses[i] = *WACS_RTEE;
626 break;
627 case 0x2510:
628 vga_to_curses[i] = *WACS_URCORNER;
629 break;
630 case 0x2514:
631 vga_to_curses[i] = *WACS_LLCORNER;
632 break;
633 case 0x2534:
634 vga_to_curses[i] = *WACS_BTEE;
635 break;
636 case 0x252c:
637 vga_to_curses[i] = *WACS_TTEE;
638 break;
639 case 0x251c:
640 vga_to_curses[i] = *WACS_LTEE;
641 break;
642 case 0x2500:
643 vga_to_curses[i] = *WACS_HLINE;
644 break;
645 case 0x253c:
646 vga_to_curses[i] = *WACS_PLUS;
647 break;
648 case 0x256c:
649 vga_to_curses[i] = *WACS_LANTERN;
650 break;
651 case 0x256a:
652 vga_to_curses[i] = *WACS_NEQUAL;
653 break;
654 case 0x2518:
655 vga_to_curses[i] = *WACS_LRCORNER;
656 break;
657 case 0x250c:
658 vga_to_curses[i] = *WACS_ULCORNER;
659 break;
660 case 0x2588:
661 vga_to_curses[i] = *WACS_BLOCK;
662 break;
663 case 0x03c0:
664 vga_to_curses[i] = *WACS_PI;
665 break;
666 case 0x00b1:
667 vga_to_curses[i] = *WACS_PLMINUS;
668 break;
669 case 0x2265:
670 vga_to_curses[i] = *WACS_GEQUAL;
671 break;
672 case 0x2264:
673 vga_to_curses[i] = *WACS_LEQUAL;
674 break;
675 case 0x00b0:
676 vga_to_curses[i] = *WACS_DEGREE;
677 break;
678 case 0x25a0:
679 vga_to_curses[i] = *WACS_BULLET;
680 break;
681 case 0x2666:
682 vga_to_curses[i] = *WACS_DIAMOND;
683 break;
684 case 0x2192:
685 vga_to_curses[i] = *WACS_RARROW;
686 break;
687 case 0x2190:
688 vga_to_curses[i] = *WACS_LARROW;
689 break;
690 case 0x2191:
691 vga_to_curses[i] = *WACS_UARROW;
692 break;
693 case 0x2193:
694 vga_to_curses[i] = *WACS_DARROW;
695 break;
696 case 0x23ba:
697 vga_to_curses[i] = *WACS_S1;
698 break;
699 case 0x23bb:
700 vga_to_curses[i] = *WACS_S3;
701 break;
702 case 0x23bc:
703 vga_to_curses[i] = *WACS_S7;
704 break;
705 case 0x23bd:
706 vga_to_curses[i] = *WACS_S9;
707 break;
708 }
709 }
710 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200711 iconv_close(ucs2_to_nativecharset);
712 iconv_close(nativecharset_to_ucs2);
Samuel Thibaulta9fda242019-03-14 18:25:24 +0100713 iconv_close(font_conv);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100714}
715
balrog4d3b6f62008-02-10 16:33:14 +0000716static void curses_setup(void)
717{
718 int i, colour_default[8] = {
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900719 [QEMU_COLOR_BLACK] = COLOR_BLACK,
720 [QEMU_COLOR_BLUE] = COLOR_BLUE,
721 [QEMU_COLOR_GREEN] = COLOR_GREEN,
722 [QEMU_COLOR_CYAN] = COLOR_CYAN,
723 [QEMU_COLOR_RED] = COLOR_RED,
724 [QEMU_COLOR_MAGENTA] = COLOR_MAGENTA,
725 [QEMU_COLOR_YELLOW] = COLOR_YELLOW,
726 [QEMU_COLOR_WHITE] = COLOR_WHITE,
balrog4d3b6f62008-02-10 16:33:14 +0000727 };
728
729 /* input as raw as possible, let everything be interpreted
730 * by the guest system */
731 initscr(); noecho(); intrflush(stdscr, FALSE);
732 nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE);
733 start_color(); raw(); scrollok(stdscr, FALSE);
Samuel Thibault633786f2019-03-03 18:25:57 +0100734 set_escdelay(25);
balrog4d3b6f62008-02-10 16:33:14 +0000735
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900736 /* Make color pair to match color format (3bits bg:3bits fg) */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900737 for (i = 0; i < 64; i++) {
balrog4d3b6f62008-02-10 16:33:14 +0000738 init_pair(i, colour_default[i & 7], colour_default[i >> 3]);
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900739 }
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900740 /* Set default color for more than 64 for safety. */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900741 for (i = 64; i < COLOR_PAIRS; i++) {
742 init_pair(i, COLOR_WHITE, COLOR_BLACK);
743 }
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +0900744
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100745 font_setup();
balrog4d3b6f62008-02-10 16:33:14 +0000746}
747
748static void curses_keyboard_setup(void)
749{
balrog4d3b6f62008-02-10 16:33:14 +0000750#if defined(__APPLE__)
751 /* always use generic keymaps */
752 if (!keyboard_layout)
753 keyboard_layout = "en-us";
754#endif
755 if(keyboard_layout) {
Fei Liab4f9312018-10-17 10:26:50 +0200756 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
757 &error_fatal);
balrog4d3b6f62008-02-10 16:33:14 +0000758 }
balrog4d3b6f62008-02-10 16:33:14 +0000759}
760
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100761static const DisplayChangeListenerOps dcl_ops = {
762 .dpy_name = "curses",
763 .dpy_text_update = curses_update,
764 .dpy_text_resize = curses_resize,
765 .dpy_refresh = curses_refresh,
766 .dpy_text_cursor = curses_cursor_position,
767};
768
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100769static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
balrog4d3b6f62008-02-10 16:33:14 +0000770{
771#ifndef _WIN32
772 if (!isatty(1)) {
773 fprintf(stderr, "We need a terminal output\n");
774 exit(1);
775 }
776#endif
777
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100778 setlocale(LC_CTYPE, "");
779 if (opts->u.curses.charset) {
780 font_charset = opts->u.curses.charset;
781 }
balrog4d3b6f62008-02-10 16:33:14 +0000782 curses_setup();
783 curses_keyboard_setup();
Anthony Liguori28695482010-03-21 14:13:02 -0500784 atexit(curses_atexit);
balrog4d3b6f62008-02-10 16:33:14 +0000785
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100786 curses_winch_init();
balrog4d3b6f62008-02-10 16:33:14 +0000787
Markus Armbrusterfedf0d32015-11-03 17:12:03 +0100788 dcl = g_new0(DisplayChangeListener, 1);
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100789 dcl->ops = &dcl_ops;
Gerd Hoffmann52090892013-04-23 15:44:31 +0200790 register_displaychangelistener(dcl);
balrog4d3b6f62008-02-10 16:33:14 +0000791
792 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000793}
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100794
795static QemuDisplay qemu_display_curses = {
796 .type = DISPLAY_TYPE_CURSES,
797 .init = curses_display_init,
798};
799
800static void register_curses(void)
801{
802 qemu_display_register(&qemu_display_curses);
803}
804
805type_init(register_curses);