blob: 1f3fcabb004bbb6d61e7c303d305d315905a2825 [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];
Samuel Thibault962cf8f2019-04-27 20:33:07 +020069 wchar_t wch[CCHARW_MAX];
70 attr_t attrs;
71 short colors;
72 int ret;
balrog4d3b6f62008-02-10 16:33:14 +000073
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020074 line = screen + y * width;
75 for (h += y; y < h; y ++, line += width) {
76 for (x = 0; x < width; x++) {
77 chtype ch = line[x] & 0xff;
78 chtype at = line[x] & ~0xff;
Samuel Thibault962cf8f2019-04-27 20:33:07 +020079 ret = getcchar(&vga_to_curses[ch], wch, &attrs, &colors, NULL);
80 if (ret == ERR || wch[0] == 0) {
81 wch[0] = ch;
82 wch[1] = 0;
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020083 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +020084 setcchar(&curses_line[x], wch, at, 0, NULL);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020085 }
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010086 mvwadd_wchnstr(screenpad, y, 0, curses_line, width);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020087 }
balrog4d3b6f62008-02-10 16:33:14 +000088
89 pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1);
90 refresh();
91}
92
93static void curses_calc_pad(void)
94{
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +010095 if (qemu_console_is_fixedsize(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +000096 width = gwidth;
97 height = gheight;
98 } else {
99 width = COLS;
100 height = LINES;
101 }
102
103 if (screenpad)
104 delwin(screenpad);
105
106 clear();
107 refresh();
108
109 screenpad = newpad(height, width);
110
111 if (width > COLS) {
112 px = (width - COLS) / 2;
113 sminx = 0;
114 smaxx = COLS;
115 } else {
116 px = 0;
117 sminx = (COLS - width) / 2;
118 smaxx = sminx + width;
119 }
120
121 if (height > LINES) {
122 py = (height - LINES) / 2;
123 sminy = 0;
124 smaxy = LINES;
125 } else {
126 py = 0;
127 sminy = (LINES - height) / 2;
128 smaxy = sminy + height;
129 }
130}
131
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100132static void curses_resize(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100133 int width, int height)
balrog4d3b6f62008-02-10 16:33:14 +0000134{
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200135 if (width == gwidth && height == gheight) {
balrog4d3b6f62008-02-10 16:33:14 +0000136 return;
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200137 }
balrog4d3b6f62008-02-10 16:33:14 +0000138
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200139 gwidth = width;
140 gheight = height;
balrog4d3b6f62008-02-10 16:33:14 +0000141
142 curses_calc_pad();
143}
144
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100145#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE)
146static volatile sig_atomic_t got_sigwinch;
147static void curses_winch_check(void)
balrog4d3b6f62008-02-10 16:33:14 +0000148{
149 struct winsize {
150 unsigned short ws_row;
151 unsigned short ws_col;
152 unsigned short ws_xpixel; /* unused */
153 unsigned short ws_ypixel; /* unused */
154 } ws;
155
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100156 if (!got_sigwinch) {
balrog4d3b6f62008-02-10 16:33:14 +0000157 return;
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100158 }
159 got_sigwinch = false;
160
161 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
162 return;
163 }
balrog4d3b6f62008-02-10 16:33:14 +0000164
165 resize_term(ws.ws_row, ws.ws_col);
balrog4d3b6f62008-02-10 16:33:14 +0000166 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000167}
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100168
169static void curses_winch_handler(int signum)
170{
171 got_sigwinch = true;
172}
173
174static void curses_winch_init(void)
175{
176 struct sigaction old, winch = {
177 .sa_handler = curses_winch_handler,
178 };
179 sigaction(SIGWINCH, &winch, &old);
180}
181#else
182static void curses_winch_check(void) {}
183static void curses_winch_init(void) {}
balrog4d3b6f62008-02-10 16:33:14 +0000184#endif
185
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100186static void curses_cursor_position(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100187 int x, int y)
balrog4d3b6f62008-02-10 16:33:14 +0000188{
189 if (x >= 0) {
190 x = sminx + x - px;
191 y = sminy + y - py;
192
193 if (x >= 0 && y >= 0 && x < COLS && y < LINES) {
194 move(y, x);
195 curs_set(1);
196 /* it seems that curs_set(1) must always be called before
197 * curs_set(2) for the latter to have effect */
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100198 if (!qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000199 curs_set(2);
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100200 }
balrog4d3b6f62008-02-10 16:33:14 +0000201 return;
202 }
203 }
204
205 curs_set(0);
206}
207
208/* generic keyboard conversion */
209
210#include "curses_keys.h"
balrog4d3b6f62008-02-10 16:33:14 +0000211
Anthony Liguoric227f092009-10-01 16:12:16 -0500212static kbd_layout_t *kbd_layout = NULL;
balrog4d3b6f62008-02-10 16:33:14 +0000213
Samuel Thibault459a7072019-03-04 22:05:32 +0100214static wint_t console_getch(enum maybe_keycode *maybe_keycode)
215{
216 wint_t ret;
217 switch (get_wch(&ret)) {
218 case KEY_CODE_YES:
219 *maybe_keycode = CURSES_KEYCODE;
220 break;
221 case OK:
222 *maybe_keycode = CURSES_CHAR;
223 break;
224 case ERR:
225 ret = -1;
226 break;
227 }
228 return ret;
229}
230
231static int curses2foo(const int _curses2foo[], const int _curseskey2foo[],
232 int chr, enum maybe_keycode maybe_keycode)
233{
234 int ret = -1;
235 if (maybe_keycode == CURSES_CHAR) {
236 if (chr < CURSES_CHARS) {
237 ret = _curses2foo[chr];
238 }
239 } else {
240 if (chr < CURSES_KEYS) {
241 ret = _curseskey2foo[chr];
242 }
243 if (ret == -1 && maybe_keycode == CURSES_CHAR_OR_KEYCODE &&
244 chr < CURSES_CHARS) {
245 ret = _curses2foo[chr];
246 }
247 }
248 return ret;
249}
250
251#define curses2keycode(chr, maybe_keycode) \
252 curses2foo(_curses2keycode, _curseskey2keycode, chr, maybe_keycode)
253#define curses2keysym(chr, maybe_keycode) \
254 curses2foo(_curses2keysym, _curseskey2keysym, chr, maybe_keycode)
255#define curses2qemu(chr, maybe_keycode) \
256 curses2foo(_curses2qemu, _curseskey2qemu, chr, maybe_keycode)
257
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100258static void curses_refresh(DisplayChangeListener *dcl)
balrog4d3b6f62008-02-10 16:33:14 +0000259{
Peter Maydell99a9ef42016-08-11 15:23:27 +0100260 int chr, keysym, keycode, keycode_alt;
Samuel Thibault459a7072019-03-04 22:05:32 +0100261 enum maybe_keycode maybe_keycode;
balrog4d3b6f62008-02-10 16:33:14 +0000262
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100263 curses_winch_check();
264
balrog4d3b6f62008-02-10 16:33:14 +0000265 if (invalidate) {
266 clear();
267 refresh();
268 curses_calc_pad();
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100269 graphic_hw_invalidate(NULL);
balrog4d3b6f62008-02-10 16:33:14 +0000270 invalidate = 0;
271 }
272
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100273 graphic_hw_text_update(NULL, screen);
balrog4d3b6f62008-02-10 16:33:14 +0000274
balrog4d3b6f62008-02-10 16:33:14 +0000275 while (1) {
276 /* while there are any pending key strokes to process */
Samuel Thibault459a7072019-03-04 22:05:32 +0100277 chr = console_getch(&maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000278
Samuel Thibault459a7072019-03-04 22:05:32 +0100279 if (chr == -1)
balrog4d3b6f62008-02-10 16:33:14 +0000280 break;
281
balrogb1314cf2008-02-22 18:21:28 +0000282#ifdef KEY_RESIZE
balrog4d3b6f62008-02-10 16:33:14 +0000283 /* this shouldn't occur when we use a custom SIGWINCH handler */
Samuel Thibault459a7072019-03-04 22:05:32 +0100284 if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) {
balrog4d3b6f62008-02-10 16:33:14 +0000285 clear();
286 refresh();
287 curses_calc_pad();
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100288 curses_update(dcl, 0, 0, width, height);
balrog4d3b6f62008-02-10 16:33:14 +0000289 continue;
290 }
balrogb1314cf2008-02-22 18:21:28 +0000291#endif
balrog4d3b6f62008-02-10 16:33:14 +0000292
Samuel Thibault459a7072019-03-04 22:05:32 +0100293 keycode = curses2keycode(chr, maybe_keycode);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100294 keycode_alt = 0;
balrog4d3b6f62008-02-10 16:33:14 +0000295
Samuel Thibault633786f2019-03-03 18:25:57 +0100296 /* alt or esc key */
balrog4d3b6f62008-02-10 16:33:14 +0000297 if (keycode == 1) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100298 enum maybe_keycode next_maybe_keycode;
299 int nextchr = console_getch(&next_maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000300
Samuel Thibault459a7072019-03-04 22:05:32 +0100301 if (nextchr != -1) {
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100302 chr = nextchr;
Samuel Thibault459a7072019-03-04 22:05:32 +0100303 maybe_keycode = next_maybe_keycode;
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100304 keycode_alt = ALT;
Samuel Thibault459a7072019-03-04 22:05:32 +0100305 keycode = curses2keycode(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000306
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100307 if (keycode != -1) {
308 keycode |= ALT;
balrog4d3b6f62008-02-10 16:33:14 +0000309
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100310 /* process keys reserved for qemu */
311 if (keycode >= QEMU_KEY_CONSOLE0 &&
312 keycode < QEMU_KEY_CONSOLE0 + 9) {
313 erase();
314 wnoutrefresh(stdscr);
315 console_select(keycode - QEMU_KEY_CONSOLE0);
balrog4d3b6f62008-02-10 16:33:14 +0000316
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100317 invalidate = 1;
318 continue;
319 }
balrog4d3b6f62008-02-10 16:33:14 +0000320 }
321 }
322 }
323
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100324 if (kbd_layout) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100325 keysym = curses2keysym(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000326
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100327 if (keysym == -1) {
Samuel Thibaultd03703c2010-10-19 19:48:20 +0200328 if (chr < ' ') {
329 keysym = chr + '@';
330 if (keysym >= 'A' && keysym <= 'Z')
331 keysym += 'a' - 'A';
332 keysym |= KEYSYM_CNTRL;
333 } else
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100334 keysym = chr;
335 }
336
Gerd Hoffmannabb4f2c2018-02-22 08:05:13 +0100337 keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK,
Gerd Hoffmann19c1b9f2019-01-22 10:28:14 +0100338 NULL, false);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100339 if (keycode == 0)
340 continue;
341
342 keycode |= (keysym & ~KEYSYM_MASK) >> 16;
343 keycode |= keycode_alt;
balrog4d3b6f62008-02-10 16:33:14 +0000344 }
345
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100346 if (keycode == -1)
347 continue;
348
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100349 if (qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000350 /* since terminals don't know about key press and release
351 * events, we need to emit both for each key received */
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100352 if (keycode & SHIFT) {
353 qemu_input_event_send_key_number(NULL, SHIFT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200354 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100355 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100356 if (keycode & CNTRL) {
357 qemu_input_event_send_key_number(NULL, CNTRL_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200358 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100359 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100360 if (keycode & ALT) {
361 qemu_input_event_send_key_number(NULL, ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200362 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100363 }
364 if (keycode & ALTGR) {
365 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200366 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100367 }
368
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400369 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200370 qemu_input_event_send_key_delay(0);
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400371 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200372 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100373
374 if (keycode & ALTGR) {
375 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200376 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100377 }
378 if (keycode & ALT) {
379 qemu_input_event_send_key_number(NULL, ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200380 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100381 }
382 if (keycode & CNTRL) {
383 qemu_input_event_send_key_number(NULL, CNTRL_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200384 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100385 }
386 if (keycode & SHIFT) {
387 qemu_input_event_send_key_number(NULL, SHIFT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200388 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100389 }
balrog4d3b6f62008-02-10 16:33:14 +0000390 } else {
Samuel Thibault459a7072019-03-04 22:05:32 +0100391 keysym = curses2qemu(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000392 if (keysym == -1)
393 keysym = chr;
394
395 kbd_put_keysym(keysym);
396 }
397 }
398}
399
Blue Swirlaaf12c22010-03-21 19:44:06 +0000400static void curses_atexit(void)
balrog4d3b6f62008-02-10 16:33:14 +0000401{
402 endwin();
403}
404
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200405/*
406 * In the following:
407 * - fch is the font glyph number
408 * - uch is the unicode value
409 * - wch is the wchar_t value (may not be unicode, e.g. on BSD/solaris)
410 * - mbch is the native local-dependent multibyte representation
411 */
412
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100413/* Setup wchar glyph for one UCS-2 char */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200414static void convert_ucs(unsigned char fch, uint16_t uch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100415{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200416 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200417 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200418 char *puch, *pmbch;
419 size_t such, smbch;
420 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100421
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200422 puch = (char *) &uch;
423 pmbch = (char *) mbch;
424 such = sizeof(uch);
425 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100426
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200427 if (iconv(conv, &puch, &such, &pmbch, &smbch) == (size_t) -1) {
428 fprintf(stderr, "Could not convert 0x%04x "
429 "from UCS-2 to a multibyte character: %s\n",
430 uch, strerror(errno));
431 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100432 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200433
434 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200435 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200436 fprintf(stderr, "Could not convert 0x%04x "
437 "from a multibyte character to wchar_t: %s\n",
438 uch, strerror(errno));
439 return;
440 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200441
442 wch[1] = 0;
443 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100444}
445
446/* Setup wchar glyph for one font character */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200447static void convert_font(unsigned char fch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100448{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200449 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200450 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200451 char *pfch, *pmbch;
452 size_t sfch, smbch;
453 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100454
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200455 pfch = (char *) &fch;
456 pmbch = (char *) &mbch;
457 sfch = sizeof(fch);
458 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100459
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200460 if (iconv(conv, &pfch, &sfch, &pmbch, &smbch) == (size_t) -1) {
461 fprintf(stderr, "Could not convert font glyph 0x%02x "
462 "from %s to a multibyte character: %s\n",
463 fch, font_charset, strerror(errno));
464 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100465 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200466
467 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200468 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200469 fprintf(stderr, "Could not convert font glyph 0x%02x "
470 "from a multibyte character to wchar_t: %s\n",
471 fch, strerror(errno));
472 return;
473 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200474
475 wch[1] = 0;
476 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100477}
478
479/* Convert one wchar to UCS-2 */
480static uint16_t get_ucs(wchar_t wch, iconv_t conv)
481{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200482 char mbch[MB_LEN_MAX];
483 uint16_t uch;
484 char *pmbch, *puch;
485 size_t smbch, such;
486 mbstate_t ps;
487 int ret;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100488
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200489 memset(&ps, 0, sizeof(ps));
490 ret = wcrtomb(mbch, wch, &ps);
491 if (ret == -1) {
492 fprintf(stderr, "Could not convert 0x%04x "
493 "from wchar_t to a multibyte character: %s\n",
494 wch, strerror(errno));
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100495 return 0xFFFD;
496 }
497
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200498 pmbch = (char *) mbch;
499 puch = (char *) &uch;
500 smbch = ret;
501 such = sizeof(uch);
502
503 if (iconv(conv, &pmbch, &smbch, &puch, &such) == (size_t) -1) {
504 fprintf(stderr, "Could not convert 0x%04x "
505 "from a multibyte character to UCS-2 : %s\n",
506 wch, strerror(errno));
507 return 0xFFFD;
508 }
509
510 return uch;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100511}
512
513/*
514 * Setup mapping for vga to curses line graphics.
515 */
516static void font_setup(void)
517{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200518 iconv_t ucs2_to_nativecharset;
519 iconv_t nativecharset_to_ucs2;
520 iconv_t font_conv;
521 int i;
522
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100523 /*
524 * Control characters are normally non-printable, but VGA does have
525 * well-known glyphs for them.
526 */
527 static uint16_t control_characters[0x20] = {
528 0x0020,
529 0x263a,
530 0x263b,
531 0x2665,
532 0x2666,
533 0x2663,
534 0x2660,
535 0x2022,
536 0x25d8,
537 0x25cb,
538 0x25d9,
539 0x2642,
540 0x2640,
541 0x266a,
542 0x266b,
543 0x263c,
544 0x25ba,
545 0x25c4,
546 0x2195,
547 0x203c,
548 0x00b6,
549 0x00a7,
550 0x25ac,
551 0x21a8,
552 0x2191,
553 0x2193,
554 0x2192,
555 0x2190,
556 0x221f,
557 0x2194,
558 0x25b2,
559 0x25bc
560 };
561
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200562 ucs2_to_nativecharset = iconv_open(nl_langinfo(CODESET), "UCS-2");
563 if (ucs2_to_nativecharset == (iconv_t) -1) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100564 fprintf(stderr, "Could not convert font glyphs from UCS-2: '%s'\n",
565 strerror(errno));
566 exit(1);
567 }
568
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200569 nativecharset_to_ucs2 = iconv_open("UCS-2", nl_langinfo(CODESET));
570 if (nativecharset_to_ucs2 == (iconv_t) -1) {
571 iconv_close(ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100572 fprintf(stderr, "Could not convert font glyphs to UCS-2: '%s'\n",
573 strerror(errno));
574 exit(1);
575 }
576
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200577 font_conv = iconv_open(nl_langinfo(CODESET), font_charset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100578 if (font_conv == (iconv_t) -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200579 iconv_close(ucs2_to_nativecharset);
580 iconv_close(nativecharset_to_ucs2);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100581 fprintf(stderr, "Could not convert font glyphs from %s: '%s'\n",
582 font_charset, strerror(errno));
583 exit(1);
584 }
585
586 /* Control characters */
587 for (i = 0; i <= 0x1F; i++) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200588 convert_ucs(i, control_characters[i], ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100589 }
590
591 for (i = 0x20; i <= 0xFF; i++) {
592 convert_font(i, font_conv);
593 }
594
595 /* DEL */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200596 convert_ucs(0x7F, 0x2302, ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100597
598 if (strcmp(nl_langinfo(CODESET), "UTF-8")) {
599 /* Non-Unicode capable, use termcap equivalents for those available */
600 for (i = 0; i <= 0xFF; i++) {
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200601 wchar_t wch[CCHARW_MAX];
602 attr_t attr;
603 short color;
604 int ret;
605
606 ret = getcchar(&vga_to_curses[i], wch, &attr, &color, NULL);
607 if (ret == ERR)
608 continue;
609
610 switch (get_ucs(wch[0], nativecharset_to_ucs2)) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100611 case 0x00a3:
612 vga_to_curses[i] = *WACS_STERLING;
613 break;
614 case 0x2591:
615 vga_to_curses[i] = *WACS_BOARD;
616 break;
617 case 0x2592:
618 vga_to_curses[i] = *WACS_CKBOARD;
619 break;
620 case 0x2502:
621 vga_to_curses[i] = *WACS_VLINE;
622 break;
623 case 0x2524:
624 vga_to_curses[i] = *WACS_RTEE;
625 break;
626 case 0x2510:
627 vga_to_curses[i] = *WACS_URCORNER;
628 break;
629 case 0x2514:
630 vga_to_curses[i] = *WACS_LLCORNER;
631 break;
632 case 0x2534:
633 vga_to_curses[i] = *WACS_BTEE;
634 break;
635 case 0x252c:
636 vga_to_curses[i] = *WACS_TTEE;
637 break;
638 case 0x251c:
639 vga_to_curses[i] = *WACS_LTEE;
640 break;
641 case 0x2500:
642 vga_to_curses[i] = *WACS_HLINE;
643 break;
644 case 0x253c:
645 vga_to_curses[i] = *WACS_PLUS;
646 break;
647 case 0x256c:
648 vga_to_curses[i] = *WACS_LANTERN;
649 break;
650 case 0x256a:
651 vga_to_curses[i] = *WACS_NEQUAL;
652 break;
653 case 0x2518:
654 vga_to_curses[i] = *WACS_LRCORNER;
655 break;
656 case 0x250c:
657 vga_to_curses[i] = *WACS_ULCORNER;
658 break;
659 case 0x2588:
660 vga_to_curses[i] = *WACS_BLOCK;
661 break;
662 case 0x03c0:
663 vga_to_curses[i] = *WACS_PI;
664 break;
665 case 0x00b1:
666 vga_to_curses[i] = *WACS_PLMINUS;
667 break;
668 case 0x2265:
669 vga_to_curses[i] = *WACS_GEQUAL;
670 break;
671 case 0x2264:
672 vga_to_curses[i] = *WACS_LEQUAL;
673 break;
674 case 0x00b0:
675 vga_to_curses[i] = *WACS_DEGREE;
676 break;
677 case 0x25a0:
678 vga_to_curses[i] = *WACS_BULLET;
679 break;
680 case 0x2666:
681 vga_to_curses[i] = *WACS_DIAMOND;
682 break;
683 case 0x2192:
684 vga_to_curses[i] = *WACS_RARROW;
685 break;
686 case 0x2190:
687 vga_to_curses[i] = *WACS_LARROW;
688 break;
689 case 0x2191:
690 vga_to_curses[i] = *WACS_UARROW;
691 break;
692 case 0x2193:
693 vga_to_curses[i] = *WACS_DARROW;
694 break;
695 case 0x23ba:
696 vga_to_curses[i] = *WACS_S1;
697 break;
698 case 0x23bb:
699 vga_to_curses[i] = *WACS_S3;
700 break;
701 case 0x23bc:
702 vga_to_curses[i] = *WACS_S7;
703 break;
704 case 0x23bd:
705 vga_to_curses[i] = *WACS_S9;
706 break;
707 }
708 }
709 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200710 iconv_close(ucs2_to_nativecharset);
711 iconv_close(nativecharset_to_ucs2);
Samuel Thibaulta9fda242019-03-14 18:25:24 +0100712 iconv_close(font_conv);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100713}
714
balrog4d3b6f62008-02-10 16:33:14 +0000715static void curses_setup(void)
716{
717 int i, colour_default[8] = {
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900718 [QEMU_COLOR_BLACK] = COLOR_BLACK,
719 [QEMU_COLOR_BLUE] = COLOR_BLUE,
720 [QEMU_COLOR_GREEN] = COLOR_GREEN,
721 [QEMU_COLOR_CYAN] = COLOR_CYAN,
722 [QEMU_COLOR_RED] = COLOR_RED,
723 [QEMU_COLOR_MAGENTA] = COLOR_MAGENTA,
724 [QEMU_COLOR_YELLOW] = COLOR_YELLOW,
725 [QEMU_COLOR_WHITE] = COLOR_WHITE,
balrog4d3b6f62008-02-10 16:33:14 +0000726 };
727
728 /* input as raw as possible, let everything be interpreted
729 * by the guest system */
730 initscr(); noecho(); intrflush(stdscr, FALSE);
731 nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE);
732 start_color(); raw(); scrollok(stdscr, FALSE);
Samuel Thibault633786f2019-03-03 18:25:57 +0100733 set_escdelay(25);
balrog4d3b6f62008-02-10 16:33:14 +0000734
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900735 /* Make color pair to match color format (3bits bg:3bits fg) */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900736 for (i = 0; i < 64; i++) {
balrog4d3b6f62008-02-10 16:33:14 +0000737 init_pair(i, colour_default[i & 7], colour_default[i >> 3]);
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900738 }
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900739 /* Set default color for more than 64 for safety. */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900740 for (i = 64; i < COLOR_PAIRS; i++) {
741 init_pair(i, COLOR_WHITE, COLOR_BLACK);
742 }
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +0900743
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100744 font_setup();
balrog4d3b6f62008-02-10 16:33:14 +0000745}
746
747static void curses_keyboard_setup(void)
748{
balrog4d3b6f62008-02-10 16:33:14 +0000749#if defined(__APPLE__)
750 /* always use generic keymaps */
751 if (!keyboard_layout)
752 keyboard_layout = "en-us";
753#endif
754 if(keyboard_layout) {
Fei Liab4f9312018-10-17 10:26:50 +0200755 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
756 &error_fatal);
balrog4d3b6f62008-02-10 16:33:14 +0000757 }
balrog4d3b6f62008-02-10 16:33:14 +0000758}
759
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100760static const DisplayChangeListenerOps dcl_ops = {
761 .dpy_name = "curses",
762 .dpy_text_update = curses_update,
763 .dpy_text_resize = curses_resize,
764 .dpy_refresh = curses_refresh,
765 .dpy_text_cursor = curses_cursor_position,
766};
767
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100768static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
balrog4d3b6f62008-02-10 16:33:14 +0000769{
770#ifndef _WIN32
771 if (!isatty(1)) {
772 fprintf(stderr, "We need a terminal output\n");
773 exit(1);
774 }
775#endif
776
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100777 setlocale(LC_CTYPE, "");
778 if (opts->u.curses.charset) {
779 font_charset = opts->u.curses.charset;
780 }
balrog4d3b6f62008-02-10 16:33:14 +0000781 curses_setup();
782 curses_keyboard_setup();
Anthony Liguori28695482010-03-21 14:13:02 -0500783 atexit(curses_atexit);
balrog4d3b6f62008-02-10 16:33:14 +0000784
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100785 curses_winch_init();
balrog4d3b6f62008-02-10 16:33:14 +0000786
Markus Armbrusterfedf0d32015-11-03 17:12:03 +0100787 dcl = g_new0(DisplayChangeListener, 1);
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100788 dcl->ops = &dcl_ops;
Gerd Hoffmann52090892013-04-23 15:44:31 +0200789 register_displaychangelistener(dcl);
balrog4d3b6f62008-02-10 16:33:14 +0000790
791 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000792}
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100793
794static QemuDisplay qemu_display_curses = {
795 .type = DISPLAY_TYPE_CURSES,
796 .init = curses_display_init,
797};
798
799static void register_curses(void)
800{
801 qemu_display_register(&qemu_display_curses);
802}
803
804type_init(register_curses);