blob: 81d419879ede7631cc71dd8c7cfab5f11e07d549 [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 */
Peter Maydelle16f4c82016-01-29 17:49:51 +000024#include "qemu/osdep.h"
balrog4d3b6f62008-02-10 16:33:14 +000025
26#ifndef _WIN32
balrog4d3b6f62008-02-10 16:33:14 +000027#include <sys/ioctl.h>
28#include <termios.h>
29#endif
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010030#include <locale.h>
31#include <wchar.h>
32#include <langinfo.h>
33#include <iconv.h>
balrog4d3b6f62008-02-10 16:33:14 +000034
Fei Liab4f9312018-10-17 10:26:50 +020035#include "qapi/error.h"
blueswir1511d2b12009-03-07 15:32:56 +000036#include "qemu-common.h"
Paolo Bonzini28ecbae2012-11-28 12:06:30 +010037#include "ui/console.h"
Gerd Hoffmanncd100322013-12-04 13:40:20 +010038#include "ui/input.h"
Paolo Bonzini9c17d612012-12-17 18:20:04 +010039#include "sysemu/sysemu.h"
blueswir1511d2b12009-03-07 15:32:56 +000040
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020041/* KEY_EVENT is defined in wincon.h and in curses.h. Avoid redefinition. */
42#undef KEY_EVENT
43#include <curses.h>
44#undef KEY_EVENT
45
balrog4d3b6f62008-02-10 16:33:14 +000046#define FONT_HEIGHT 16
47#define FONT_WIDTH 8
48
Samuel Thibault459a7072019-03-04 22:05:32 +010049enum maybe_keycode {
50 CURSES_KEYCODE,
51 CURSES_CHAR,
52 CURSES_CHAR_OR_KEYCODE,
53};
54
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010055static DisplayChangeListener *dcl;
Anthony Liguoric227f092009-10-01 16:12:16 -050056static console_ch_t screen[160 * 100];
balrog4d3b6f62008-02-10 16:33:14 +000057static WINDOW *screenpad = NULL;
58static int width, height, gwidth, gheight, invalidate;
59static int px, py, sminx, sminy, smaxx, smaxy;
60
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010061static const char *font_charset = "CP437";
62static cchar_t vga_to_curses[256];
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +090063
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010064static void curses_update(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010065 int x, int y, int w, int h)
balrog4d3b6f62008-02-10 16:33:14 +000066{
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020067 console_ch_t *line;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010068 cchar_t curses_line[width];
balrog4d3b6f62008-02-10 16:33:14 +000069
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020070 line = screen + y * width;
71 for (h += y; y < h; y ++, line += width) {
72 for (x = 0; x < width; x++) {
73 chtype ch = line[x] & 0xff;
74 chtype at = line[x] & ~0xff;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010075 if (vga_to_curses[ch].chars[0]) {
76 curses_line[x] = vga_to_curses[ch];
77 } else {
Samuel Thibaulta5489ae2019-03-15 14:09:32 +010078 curses_line[x] = (cchar_t) {
79 .chars[0] = ch,
80 };
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020081 }
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010082 curses_line[x].attr |= at;
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020083 }
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010084 mvwadd_wchnstr(screenpad, y, 0, curses_line, width);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020085 }
balrog4d3b6f62008-02-10 16:33:14 +000086
87 pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1);
88 refresh();
89}
90
91static void curses_calc_pad(void)
92{
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +010093 if (qemu_console_is_fixedsize(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +000094 width = gwidth;
95 height = gheight;
96 } else {
97 width = COLS;
98 height = LINES;
99 }
100
101 if (screenpad)
102 delwin(screenpad);
103
104 clear();
105 refresh();
106
107 screenpad = newpad(height, width);
108
109 if (width > COLS) {
110 px = (width - COLS) / 2;
111 sminx = 0;
112 smaxx = COLS;
113 } else {
114 px = 0;
115 sminx = (COLS - width) / 2;
116 smaxx = sminx + width;
117 }
118
119 if (height > LINES) {
120 py = (height - LINES) / 2;
121 sminy = 0;
122 smaxy = LINES;
123 } else {
124 py = 0;
125 sminy = (LINES - height) / 2;
126 smaxy = sminy + height;
127 }
128}
129
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100130static void curses_resize(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100131 int width, int height)
balrog4d3b6f62008-02-10 16:33:14 +0000132{
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200133 if (width == gwidth && height == gheight) {
balrog4d3b6f62008-02-10 16:33:14 +0000134 return;
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200135 }
balrog4d3b6f62008-02-10 16:33:14 +0000136
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200137 gwidth = width;
138 gheight = height;
balrog4d3b6f62008-02-10 16:33:14 +0000139
140 curses_calc_pad();
141}
142
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100143#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE)
144static volatile sig_atomic_t got_sigwinch;
145static void curses_winch_check(void)
balrog4d3b6f62008-02-10 16:33:14 +0000146{
147 struct winsize {
148 unsigned short ws_row;
149 unsigned short ws_col;
150 unsigned short ws_xpixel; /* unused */
151 unsigned short ws_ypixel; /* unused */
152 } ws;
153
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100154 if (!got_sigwinch) {
balrog4d3b6f62008-02-10 16:33:14 +0000155 return;
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100156 }
157 got_sigwinch = false;
158
159 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
160 return;
161 }
balrog4d3b6f62008-02-10 16:33:14 +0000162
163 resize_term(ws.ws_row, ws.ws_col);
balrog4d3b6f62008-02-10 16:33:14 +0000164 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000165}
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100166
167static void curses_winch_handler(int signum)
168{
169 got_sigwinch = true;
170}
171
172static void curses_winch_init(void)
173{
174 struct sigaction old, winch = {
175 .sa_handler = curses_winch_handler,
176 };
177 sigaction(SIGWINCH, &winch, &old);
178}
179#else
180static void curses_winch_check(void) {}
181static void curses_winch_init(void) {}
balrog4d3b6f62008-02-10 16:33:14 +0000182#endif
183
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100184static void curses_cursor_position(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100185 int x, int y)
balrog4d3b6f62008-02-10 16:33:14 +0000186{
187 if (x >= 0) {
188 x = sminx + x - px;
189 y = sminy + y - py;
190
191 if (x >= 0 && y >= 0 && x < COLS && y < LINES) {
192 move(y, x);
193 curs_set(1);
194 /* it seems that curs_set(1) must always be called before
195 * curs_set(2) for the latter to have effect */
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100196 if (!qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000197 curs_set(2);
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100198 }
balrog4d3b6f62008-02-10 16:33:14 +0000199 return;
200 }
201 }
202
203 curs_set(0);
204}
205
206/* generic keyboard conversion */
207
208#include "curses_keys.h"
balrog4d3b6f62008-02-10 16:33:14 +0000209
Anthony Liguoric227f092009-10-01 16:12:16 -0500210static kbd_layout_t *kbd_layout = NULL;
balrog4d3b6f62008-02-10 16:33:14 +0000211
Samuel Thibault459a7072019-03-04 22:05:32 +0100212static wint_t console_getch(enum maybe_keycode *maybe_keycode)
213{
214 wint_t ret;
215 switch (get_wch(&ret)) {
216 case KEY_CODE_YES:
217 *maybe_keycode = CURSES_KEYCODE;
218 break;
219 case OK:
220 *maybe_keycode = CURSES_CHAR;
221 break;
222 case ERR:
223 ret = -1;
224 break;
225 }
226 return ret;
227}
228
229static int curses2foo(const int _curses2foo[], const int _curseskey2foo[],
230 int chr, enum maybe_keycode maybe_keycode)
231{
232 int ret = -1;
233 if (maybe_keycode == CURSES_CHAR) {
234 if (chr < CURSES_CHARS) {
235 ret = _curses2foo[chr];
236 }
237 } else {
238 if (chr < CURSES_KEYS) {
239 ret = _curseskey2foo[chr];
240 }
241 if (ret == -1 && maybe_keycode == CURSES_CHAR_OR_KEYCODE &&
242 chr < CURSES_CHARS) {
243 ret = _curses2foo[chr];
244 }
245 }
246 return ret;
247}
248
249#define curses2keycode(chr, maybe_keycode) \
250 curses2foo(_curses2keycode, _curseskey2keycode, chr, maybe_keycode)
251#define curses2keysym(chr, maybe_keycode) \
252 curses2foo(_curses2keysym, _curseskey2keysym, chr, maybe_keycode)
253#define curses2qemu(chr, maybe_keycode) \
254 curses2foo(_curses2qemu, _curseskey2qemu, chr, maybe_keycode)
255
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100256static void curses_refresh(DisplayChangeListener *dcl)
balrog4d3b6f62008-02-10 16:33:14 +0000257{
Peter Maydell99a9ef42016-08-11 15:23:27 +0100258 int chr, keysym, keycode, keycode_alt;
Samuel Thibault459a7072019-03-04 22:05:32 +0100259 enum maybe_keycode maybe_keycode;
balrog4d3b6f62008-02-10 16:33:14 +0000260
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100261 curses_winch_check();
262
balrog4d3b6f62008-02-10 16:33:14 +0000263 if (invalidate) {
264 clear();
265 refresh();
266 curses_calc_pad();
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100267 graphic_hw_invalidate(NULL);
balrog4d3b6f62008-02-10 16:33:14 +0000268 invalidate = 0;
269 }
270
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100271 graphic_hw_text_update(NULL, screen);
balrog4d3b6f62008-02-10 16:33:14 +0000272
balrog4d3b6f62008-02-10 16:33:14 +0000273 while (1) {
274 /* while there are any pending key strokes to process */
Samuel Thibault459a7072019-03-04 22:05:32 +0100275 chr = console_getch(&maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000276
Samuel Thibault459a7072019-03-04 22:05:32 +0100277 if (chr == -1)
balrog4d3b6f62008-02-10 16:33:14 +0000278 break;
279
balrogb1314cf2008-02-22 18:21:28 +0000280#ifdef KEY_RESIZE
balrog4d3b6f62008-02-10 16:33:14 +0000281 /* this shouldn't occur when we use a custom SIGWINCH handler */
Samuel Thibault459a7072019-03-04 22:05:32 +0100282 if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) {
balrog4d3b6f62008-02-10 16:33:14 +0000283 clear();
284 refresh();
285 curses_calc_pad();
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100286 curses_update(dcl, 0, 0, width, height);
balrog4d3b6f62008-02-10 16:33:14 +0000287 continue;
288 }
balrogb1314cf2008-02-22 18:21:28 +0000289#endif
balrog4d3b6f62008-02-10 16:33:14 +0000290
Samuel Thibault459a7072019-03-04 22:05:32 +0100291 keycode = curses2keycode(chr, maybe_keycode);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100292 keycode_alt = 0;
balrog4d3b6f62008-02-10 16:33:14 +0000293
Samuel Thibault633786f2019-03-03 18:25:57 +0100294 /* alt or esc key */
balrog4d3b6f62008-02-10 16:33:14 +0000295 if (keycode == 1) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100296 enum maybe_keycode next_maybe_keycode;
297 int nextchr = console_getch(&next_maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000298
Samuel Thibault459a7072019-03-04 22:05:32 +0100299 if (nextchr != -1) {
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100300 chr = nextchr;
Samuel Thibault459a7072019-03-04 22:05:32 +0100301 maybe_keycode = next_maybe_keycode;
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100302 keycode_alt = ALT;
Samuel Thibault459a7072019-03-04 22:05:32 +0100303 keycode = curses2keycode(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000304
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100305 if (keycode != -1) {
306 keycode |= ALT;
balrog4d3b6f62008-02-10 16:33:14 +0000307
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100308 /* process keys reserved for qemu */
309 if (keycode >= QEMU_KEY_CONSOLE0 &&
310 keycode < QEMU_KEY_CONSOLE0 + 9) {
311 erase();
312 wnoutrefresh(stdscr);
313 console_select(keycode - QEMU_KEY_CONSOLE0);
balrog4d3b6f62008-02-10 16:33:14 +0000314
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100315 invalidate = 1;
316 continue;
317 }
balrog4d3b6f62008-02-10 16:33:14 +0000318 }
319 }
320 }
321
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100322 if (kbd_layout) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100323 keysym = curses2keysym(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000324
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100325 if (keysym == -1) {
Samuel Thibaultd03703c2010-10-19 19:48:20 +0200326 if (chr < ' ') {
327 keysym = chr + '@';
328 if (keysym >= 'A' && keysym <= 'Z')
329 keysym += 'a' - 'A';
330 keysym |= KEYSYM_CNTRL;
331 } else
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100332 keysym = chr;
333 }
334
Gerd Hoffmannabb4f2c2018-02-22 08:05:13 +0100335 keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK,
Gerd Hoffmann19c1b9f2019-01-22 10:28:14 +0100336 NULL, false);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100337 if (keycode == 0)
338 continue;
339
340 keycode |= (keysym & ~KEYSYM_MASK) >> 16;
341 keycode |= keycode_alt;
balrog4d3b6f62008-02-10 16:33:14 +0000342 }
343
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100344 if (keycode == -1)
345 continue;
346
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100347 if (qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000348 /* since terminals don't know about key press and release
349 * events, we need to emit both for each key received */
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100350 if (keycode & SHIFT) {
351 qemu_input_event_send_key_number(NULL, SHIFT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200352 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100353 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100354 if (keycode & CNTRL) {
355 qemu_input_event_send_key_number(NULL, CNTRL_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200356 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100357 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100358 if (keycode & ALT) {
359 qemu_input_event_send_key_number(NULL, ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200360 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100361 }
362 if (keycode & ALTGR) {
363 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200364 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100365 }
366
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400367 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200368 qemu_input_event_send_key_delay(0);
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400369 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200370 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100371
372 if (keycode & ALTGR) {
373 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200374 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100375 }
376 if (keycode & ALT) {
377 qemu_input_event_send_key_number(NULL, ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200378 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100379 }
380 if (keycode & CNTRL) {
381 qemu_input_event_send_key_number(NULL, CNTRL_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200382 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100383 }
384 if (keycode & SHIFT) {
385 qemu_input_event_send_key_number(NULL, SHIFT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200386 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100387 }
balrog4d3b6f62008-02-10 16:33:14 +0000388 } else {
Samuel Thibault459a7072019-03-04 22:05:32 +0100389 keysym = curses2qemu(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000390 if (keysym == -1)
391 keysym = chr;
392
393 kbd_put_keysym(keysym);
394 }
395 }
396}
397
Blue Swirlaaf12c22010-03-21 19:44:06 +0000398static void curses_atexit(void)
balrog4d3b6f62008-02-10 16:33:14 +0000399{
400 endwin();
401}
402
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200403/*
404 * In the following:
405 * - fch is the font glyph number
406 * - uch is the unicode value
407 * - wch is the wchar_t value (may not be unicode, e.g. on BSD/solaris)
408 * - mbch is the native local-dependent multibyte representation
409 */
410
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100411/* Setup wchar glyph for one UCS-2 char */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200412static void convert_ucs(unsigned char fch, uint16_t uch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100413{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200414 char mbch[MB_LEN_MAX];
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100415 wchar_t wch;
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200416 char *puch, *pmbch;
417 size_t such, smbch;
418 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100419
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200420 puch = (char *) &uch;
421 pmbch = (char *) mbch;
422 such = sizeof(uch);
423 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100424
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200425 if (iconv(conv, &puch, &such, &pmbch, &smbch) == (size_t) -1) {
426 fprintf(stderr, "Could not convert 0x%04x "
427 "from UCS-2 to a multibyte character: %s\n",
428 uch, strerror(errno));
429 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100430 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200431
432 memset(&ps, 0, sizeof(ps));
433 if (mbrtowc(&wch, mbch, sizeof(mbch) - smbch, &ps) == -1) {
434 fprintf(stderr, "Could not convert 0x%04x "
435 "from a multibyte character to wchar_t: %s\n",
436 uch, strerror(errno));
437 return;
438 }
439 vga_to_curses[fch].chars[0] = wch;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100440}
441
442/* Setup wchar glyph for one font character */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200443static void convert_font(unsigned char fch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100444{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200445 char mbch[MB_LEN_MAX];
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100446 wchar_t wch;
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200447 char *pfch, *pmbch;
448 size_t sfch, smbch;
449 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100450
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200451 pfch = (char *) &fch;
452 pmbch = (char *) &mbch;
453 sfch = sizeof(fch);
454 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100455
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200456 if (iconv(conv, &pfch, &sfch, &pmbch, &smbch) == (size_t) -1) {
457 fprintf(stderr, "Could not convert font glyph 0x%02x "
458 "from %s to a multibyte character: %s\n",
459 fch, font_charset, strerror(errno));
460 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100461 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200462
463 memset(&ps, 0, sizeof(ps));
464 if (mbrtowc(&wch, mbch, sizeof(mbch) - smbch, &ps) == -1) {
465 fprintf(stderr, "Could not convert font glyph 0x%02x "
466 "from a multibyte character to wchar_t: %s\n",
467 fch, strerror(errno));
468 return;
469 }
470 vga_to_curses[fch].chars[0] = wch;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100471}
472
473/* Convert one wchar to UCS-2 */
474static uint16_t get_ucs(wchar_t wch, iconv_t conv)
475{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200476 char mbch[MB_LEN_MAX];
477 uint16_t uch;
478 char *pmbch, *puch;
479 size_t smbch, such;
480 mbstate_t ps;
481 int ret;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100482
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200483 memset(&ps, 0, sizeof(ps));
484 ret = wcrtomb(mbch, wch, &ps);
485 if (ret == -1) {
486 fprintf(stderr, "Could not convert 0x%04x "
487 "from wchar_t to a multibyte character: %s\n",
488 wch, strerror(errno));
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100489 return 0xFFFD;
490 }
491
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200492 pmbch = (char *) mbch;
493 puch = (char *) &uch;
494 smbch = ret;
495 such = sizeof(uch);
496
497 if (iconv(conv, &pmbch, &smbch, &puch, &such) == (size_t) -1) {
498 fprintf(stderr, "Could not convert 0x%04x "
499 "from a multibyte character to UCS-2 : %s\n",
500 wch, strerror(errno));
501 return 0xFFFD;
502 }
503
504 return uch;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100505}
506
507/*
508 * Setup mapping for vga to curses line graphics.
509 */
510static void font_setup(void)
511{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200512 iconv_t ucs2_to_nativecharset;
513 iconv_t nativecharset_to_ucs2;
514 iconv_t font_conv;
515 int i;
516
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100517 /*
518 * Control characters are normally non-printable, but VGA does have
519 * well-known glyphs for them.
520 */
521 static uint16_t control_characters[0x20] = {
522 0x0020,
523 0x263a,
524 0x263b,
525 0x2665,
526 0x2666,
527 0x2663,
528 0x2660,
529 0x2022,
530 0x25d8,
531 0x25cb,
532 0x25d9,
533 0x2642,
534 0x2640,
535 0x266a,
536 0x266b,
537 0x263c,
538 0x25ba,
539 0x25c4,
540 0x2195,
541 0x203c,
542 0x00b6,
543 0x00a7,
544 0x25ac,
545 0x21a8,
546 0x2191,
547 0x2193,
548 0x2192,
549 0x2190,
550 0x221f,
551 0x2194,
552 0x25b2,
553 0x25bc
554 };
555
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200556 ucs2_to_nativecharset = iconv_open(nl_langinfo(CODESET), "UCS-2");
557 if (ucs2_to_nativecharset == (iconv_t) -1) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100558 fprintf(stderr, "Could not convert font glyphs from UCS-2: '%s'\n",
559 strerror(errno));
560 exit(1);
561 }
562
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200563 nativecharset_to_ucs2 = iconv_open("UCS-2", nl_langinfo(CODESET));
564 if (nativecharset_to_ucs2 == (iconv_t) -1) {
565 iconv_close(ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100566 fprintf(stderr, "Could not convert font glyphs to UCS-2: '%s'\n",
567 strerror(errno));
568 exit(1);
569 }
570
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200571 font_conv = iconv_open(nl_langinfo(CODESET), font_charset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100572 if (font_conv == (iconv_t) -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200573 iconv_close(ucs2_to_nativecharset);
574 iconv_close(nativecharset_to_ucs2);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100575 fprintf(stderr, "Could not convert font glyphs from %s: '%s'\n",
576 font_charset, strerror(errno));
577 exit(1);
578 }
579
580 /* Control characters */
581 for (i = 0; i <= 0x1F; i++) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200582 convert_ucs(i, control_characters[i], ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100583 }
584
585 for (i = 0x20; i <= 0xFF; i++) {
586 convert_font(i, font_conv);
587 }
588
589 /* DEL */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200590 convert_ucs(0x7F, 0x2302, ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100591
592 if (strcmp(nl_langinfo(CODESET), "UTF-8")) {
593 /* Non-Unicode capable, use termcap equivalents for those available */
594 for (i = 0; i <= 0xFF; i++) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200595 switch (get_ucs(vga_to_curses[i].chars[0], nativecharset_to_ucs2)) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100596 case 0x00a3:
597 vga_to_curses[i] = *WACS_STERLING;
598 break;
599 case 0x2591:
600 vga_to_curses[i] = *WACS_BOARD;
601 break;
602 case 0x2592:
603 vga_to_curses[i] = *WACS_CKBOARD;
604 break;
605 case 0x2502:
606 vga_to_curses[i] = *WACS_VLINE;
607 break;
608 case 0x2524:
609 vga_to_curses[i] = *WACS_RTEE;
610 break;
611 case 0x2510:
612 vga_to_curses[i] = *WACS_URCORNER;
613 break;
614 case 0x2514:
615 vga_to_curses[i] = *WACS_LLCORNER;
616 break;
617 case 0x2534:
618 vga_to_curses[i] = *WACS_BTEE;
619 break;
620 case 0x252c:
621 vga_to_curses[i] = *WACS_TTEE;
622 break;
623 case 0x251c:
624 vga_to_curses[i] = *WACS_LTEE;
625 break;
626 case 0x2500:
627 vga_to_curses[i] = *WACS_HLINE;
628 break;
629 case 0x253c:
630 vga_to_curses[i] = *WACS_PLUS;
631 break;
632 case 0x256c:
633 vga_to_curses[i] = *WACS_LANTERN;
634 break;
635 case 0x256a:
636 vga_to_curses[i] = *WACS_NEQUAL;
637 break;
638 case 0x2518:
639 vga_to_curses[i] = *WACS_LRCORNER;
640 break;
641 case 0x250c:
642 vga_to_curses[i] = *WACS_ULCORNER;
643 break;
644 case 0x2588:
645 vga_to_curses[i] = *WACS_BLOCK;
646 break;
647 case 0x03c0:
648 vga_to_curses[i] = *WACS_PI;
649 break;
650 case 0x00b1:
651 vga_to_curses[i] = *WACS_PLMINUS;
652 break;
653 case 0x2265:
654 vga_to_curses[i] = *WACS_GEQUAL;
655 break;
656 case 0x2264:
657 vga_to_curses[i] = *WACS_LEQUAL;
658 break;
659 case 0x00b0:
660 vga_to_curses[i] = *WACS_DEGREE;
661 break;
662 case 0x25a0:
663 vga_to_curses[i] = *WACS_BULLET;
664 break;
665 case 0x2666:
666 vga_to_curses[i] = *WACS_DIAMOND;
667 break;
668 case 0x2192:
669 vga_to_curses[i] = *WACS_RARROW;
670 break;
671 case 0x2190:
672 vga_to_curses[i] = *WACS_LARROW;
673 break;
674 case 0x2191:
675 vga_to_curses[i] = *WACS_UARROW;
676 break;
677 case 0x2193:
678 vga_to_curses[i] = *WACS_DARROW;
679 break;
680 case 0x23ba:
681 vga_to_curses[i] = *WACS_S1;
682 break;
683 case 0x23bb:
684 vga_to_curses[i] = *WACS_S3;
685 break;
686 case 0x23bc:
687 vga_to_curses[i] = *WACS_S7;
688 break;
689 case 0x23bd:
690 vga_to_curses[i] = *WACS_S9;
691 break;
692 }
693 }
694 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200695 iconv_close(ucs2_to_nativecharset);
696 iconv_close(nativecharset_to_ucs2);
Samuel Thibaulta9fda242019-03-14 18:25:24 +0100697 iconv_close(font_conv);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100698}
699
balrog4d3b6f62008-02-10 16:33:14 +0000700static void curses_setup(void)
701{
702 int i, colour_default[8] = {
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900703 [QEMU_COLOR_BLACK] = COLOR_BLACK,
704 [QEMU_COLOR_BLUE] = COLOR_BLUE,
705 [QEMU_COLOR_GREEN] = COLOR_GREEN,
706 [QEMU_COLOR_CYAN] = COLOR_CYAN,
707 [QEMU_COLOR_RED] = COLOR_RED,
708 [QEMU_COLOR_MAGENTA] = COLOR_MAGENTA,
709 [QEMU_COLOR_YELLOW] = COLOR_YELLOW,
710 [QEMU_COLOR_WHITE] = COLOR_WHITE,
balrog4d3b6f62008-02-10 16:33:14 +0000711 };
712
713 /* input as raw as possible, let everything be interpreted
714 * by the guest system */
715 initscr(); noecho(); intrflush(stdscr, FALSE);
716 nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE);
717 start_color(); raw(); scrollok(stdscr, FALSE);
Samuel Thibault633786f2019-03-03 18:25:57 +0100718 set_escdelay(25);
balrog4d3b6f62008-02-10 16:33:14 +0000719
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900720 /* Make color pair to match color format (3bits bg:3bits fg) */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900721 for (i = 0; i < 64; i++) {
balrog4d3b6f62008-02-10 16:33:14 +0000722 init_pair(i, colour_default[i & 7], colour_default[i >> 3]);
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900723 }
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900724 /* Set default color for more than 64 for safety. */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900725 for (i = 64; i < COLOR_PAIRS; i++) {
726 init_pair(i, COLOR_WHITE, COLOR_BLACK);
727 }
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +0900728
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100729 font_setup();
balrog4d3b6f62008-02-10 16:33:14 +0000730}
731
732static void curses_keyboard_setup(void)
733{
balrog4d3b6f62008-02-10 16:33:14 +0000734#if defined(__APPLE__)
735 /* always use generic keymaps */
736 if (!keyboard_layout)
737 keyboard_layout = "en-us";
738#endif
739 if(keyboard_layout) {
Fei Liab4f9312018-10-17 10:26:50 +0200740 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
741 &error_fatal);
balrog4d3b6f62008-02-10 16:33:14 +0000742 }
balrog4d3b6f62008-02-10 16:33:14 +0000743}
744
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100745static const DisplayChangeListenerOps dcl_ops = {
746 .dpy_name = "curses",
747 .dpy_text_update = curses_update,
748 .dpy_text_resize = curses_resize,
749 .dpy_refresh = curses_refresh,
750 .dpy_text_cursor = curses_cursor_position,
751};
752
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100753static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
balrog4d3b6f62008-02-10 16:33:14 +0000754{
755#ifndef _WIN32
756 if (!isatty(1)) {
757 fprintf(stderr, "We need a terminal output\n");
758 exit(1);
759 }
760#endif
761
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100762 setlocale(LC_CTYPE, "");
763 if (opts->u.curses.charset) {
764 font_charset = opts->u.curses.charset;
765 }
balrog4d3b6f62008-02-10 16:33:14 +0000766 curses_setup();
767 curses_keyboard_setup();
Anthony Liguori28695482010-03-21 14:13:02 -0500768 atexit(curses_atexit);
balrog4d3b6f62008-02-10 16:33:14 +0000769
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100770 curses_winch_init();
balrog4d3b6f62008-02-10 16:33:14 +0000771
Markus Armbrusterfedf0d32015-11-03 17:12:03 +0100772 dcl = g_new0(DisplayChangeListener, 1);
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100773 dcl->ops = &dcl_ops;
Gerd Hoffmann52090892013-04-23 15:44:31 +0200774 register_displaychangelistener(dcl);
balrog4d3b6f62008-02-10 16:33:14 +0000775
776 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000777}
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100778
779static QemuDisplay qemu_display_curses = {
780 .type = DISPLAY_TYPE_CURSES,
781 .init = curses_display_init,
782};
783
784static void register_curses(void)
785{
786 qemu_display_register(&qemu_display_curses);
787}
788
789type_init(register_curses);