blob: e4f9588c3e8ad001d74f9704ba3bf55299d98925 [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>
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010033#include <iconv.h>
balrog4d3b6f62008-02-10 16:33:14 +000034
Fei Liab4f9312018-10-17 10:26:50 +020035#include "qapi/error.h"
Markus Armbruster0b8fa322019-05-23 16:35:07 +020036#include "qemu/module.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;
Philippe Mathieu-Daudé76c51fc2020-03-05 13:45:24 +010056static console_ch_t *screen;
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";
Philippe Mathieu-Daudé76c51fc2020-03-05 13:45:24 +010062static cchar_t *vga_to_curses;
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++) {
Matthew Kilgorecd54ea42019-10-03 23:53:37 -040077 chtype ch = line[x] & A_CHARTEXT;
78 chtype at = line[x] & A_ATTRIBUTES;
Matthew Kilgore30f5a9d2019-10-03 23:53:38 -040079 short color_pair = PAIR_NUMBER(line[x]);
80
Samuel Thibault962cf8f2019-04-27 20:33:07 +020081 ret = getcchar(&vga_to_curses[ch], wch, &attrs, &colors, NULL);
82 if (ret == ERR || wch[0] == 0) {
83 wch[0] = ch;
84 wch[1] = 0;
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020085 }
Matthew Kilgore30f5a9d2019-10-03 23:53:38 -040086 setcchar(&curses_line[x], wch, at, color_pair, NULL);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020087 }
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010088 mvwadd_wchnstr(screenpad, y, 0, curses_line, width);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020089 }
balrog4d3b6f62008-02-10 16:33:14 +000090
91 pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1);
92 refresh();
93}
94
95static void curses_calc_pad(void)
96{
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +010097 if (qemu_console_is_fixedsize(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +000098 width = gwidth;
99 height = gheight;
100 } else {
101 width = COLS;
102 height = LINES;
103 }
104
105 if (screenpad)
106 delwin(screenpad);
107
108 clear();
109 refresh();
110
111 screenpad = newpad(height, width);
112
113 if (width > COLS) {
114 px = (width - COLS) / 2;
115 sminx = 0;
116 smaxx = COLS;
117 } else {
118 px = 0;
119 sminx = (COLS - width) / 2;
120 smaxx = sminx + width;
121 }
122
123 if (height > LINES) {
124 py = (height - LINES) / 2;
125 sminy = 0;
126 smaxy = LINES;
127 } else {
128 py = 0;
129 sminy = (LINES - height) / 2;
130 smaxy = sminy + height;
131 }
132}
133
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100134static void curses_resize(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100135 int width, int height)
balrog4d3b6f62008-02-10 16:33:14 +0000136{
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200137 if (width == gwidth && height == gheight) {
balrog4d3b6f62008-02-10 16:33:14 +0000138 return;
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200139 }
balrog4d3b6f62008-02-10 16:33:14 +0000140
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200141 gwidth = width;
142 gheight = height;
balrog4d3b6f62008-02-10 16:33:14 +0000143
144 curses_calc_pad();
145}
146
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100147#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE)
148static volatile sig_atomic_t got_sigwinch;
149static void curses_winch_check(void)
balrog4d3b6f62008-02-10 16:33:14 +0000150{
151 struct winsize {
152 unsigned short ws_row;
153 unsigned short ws_col;
154 unsigned short ws_xpixel; /* unused */
155 unsigned short ws_ypixel; /* unused */
156 } ws;
157
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100158 if (!got_sigwinch) {
balrog4d3b6f62008-02-10 16:33:14 +0000159 return;
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100160 }
161 got_sigwinch = false;
162
163 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
164 return;
165 }
balrog4d3b6f62008-02-10 16:33:14 +0000166
167 resize_term(ws.ws_row, ws.ws_col);
balrog4d3b6f62008-02-10 16:33:14 +0000168 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000169}
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100170
171static void curses_winch_handler(int signum)
172{
173 got_sigwinch = true;
174}
175
176static void curses_winch_init(void)
177{
178 struct sigaction old, winch = {
179 .sa_handler = curses_winch_handler,
180 };
181 sigaction(SIGWINCH, &winch, &old);
182}
183#else
184static void curses_winch_check(void) {}
185static void curses_winch_init(void) {}
balrog4d3b6f62008-02-10 16:33:14 +0000186#endif
187
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100188static void curses_cursor_position(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100189 int x, int y)
balrog4d3b6f62008-02-10 16:33:14 +0000190{
191 if (x >= 0) {
192 x = sminx + x - px;
193 y = sminy + y - py;
194
195 if (x >= 0 && y >= 0 && x < COLS && y < LINES) {
196 move(y, x);
197 curs_set(1);
198 /* it seems that curs_set(1) must always be called before
199 * curs_set(2) for the latter to have effect */
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100200 if (!qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000201 curs_set(2);
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100202 }
balrog4d3b6f62008-02-10 16:33:14 +0000203 return;
204 }
205 }
206
207 curs_set(0);
208}
209
210/* generic keyboard conversion */
211
212#include "curses_keys.h"
balrog4d3b6f62008-02-10 16:33:14 +0000213
Anthony Liguoric227f092009-10-01 16:12:16 -0500214static kbd_layout_t *kbd_layout = NULL;
balrog4d3b6f62008-02-10 16:33:14 +0000215
Samuel Thibault459a7072019-03-04 22:05:32 +0100216static wint_t console_getch(enum maybe_keycode *maybe_keycode)
217{
218 wint_t ret;
219 switch (get_wch(&ret)) {
220 case KEY_CODE_YES:
221 *maybe_keycode = CURSES_KEYCODE;
222 break;
223 case OK:
224 *maybe_keycode = CURSES_CHAR;
225 break;
226 case ERR:
227 ret = -1;
228 break;
Paolo Bonzini68097ed2019-07-18 14:01:04 +0200229 default:
230 abort();
Samuel Thibault459a7072019-03-04 22:05:32 +0100231 }
232 return ret;
233}
234
235static int curses2foo(const int _curses2foo[], const int _curseskey2foo[],
236 int chr, enum maybe_keycode maybe_keycode)
237{
238 int ret = -1;
239 if (maybe_keycode == CURSES_CHAR) {
240 if (chr < CURSES_CHARS) {
241 ret = _curses2foo[chr];
242 }
243 } else {
244 if (chr < CURSES_KEYS) {
245 ret = _curseskey2foo[chr];
246 }
247 if (ret == -1 && maybe_keycode == CURSES_CHAR_OR_KEYCODE &&
248 chr < CURSES_CHARS) {
249 ret = _curses2foo[chr];
250 }
251 }
252 return ret;
253}
254
255#define curses2keycode(chr, maybe_keycode) \
256 curses2foo(_curses2keycode, _curseskey2keycode, chr, maybe_keycode)
257#define curses2keysym(chr, maybe_keycode) \
258 curses2foo(_curses2keysym, _curseskey2keysym, chr, maybe_keycode)
259#define curses2qemu(chr, maybe_keycode) \
260 curses2foo(_curses2qemu, _curseskey2qemu, chr, maybe_keycode)
261
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100262static void curses_refresh(DisplayChangeListener *dcl)
balrog4d3b6f62008-02-10 16:33:14 +0000263{
Peter Maydell99a9ef42016-08-11 15:23:27 +0100264 int chr, keysym, keycode, keycode_alt;
Yonggang Luo65f52792020-10-13 07:43:46 +0800265 enum maybe_keycode maybe_keycode = CURSES_KEYCODE;
balrog4d3b6f62008-02-10 16:33:14 +0000266
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100267 curses_winch_check();
268
balrog4d3b6f62008-02-10 16:33:14 +0000269 if (invalidate) {
270 clear();
271 refresh();
272 curses_calc_pad();
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100273 graphic_hw_invalidate(NULL);
balrog4d3b6f62008-02-10 16:33:14 +0000274 invalidate = 0;
275 }
276
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100277 graphic_hw_text_update(NULL, screen);
balrog4d3b6f62008-02-10 16:33:14 +0000278
balrog4d3b6f62008-02-10 16:33:14 +0000279 while (1) {
280 /* while there are any pending key strokes to process */
Samuel Thibault459a7072019-03-04 22:05:32 +0100281 chr = console_getch(&maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000282
Samuel Thibault459a7072019-03-04 22:05:32 +0100283 if (chr == -1)
balrog4d3b6f62008-02-10 16:33:14 +0000284 break;
285
balrogb1314cf2008-02-22 18:21:28 +0000286#ifdef KEY_RESIZE
balrog4d3b6f62008-02-10 16:33:14 +0000287 /* this shouldn't occur when we use a custom SIGWINCH handler */
Samuel Thibault459a7072019-03-04 22:05:32 +0100288 if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) {
balrog4d3b6f62008-02-10 16:33:14 +0000289 clear();
290 refresh();
291 curses_calc_pad();
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100292 curses_update(dcl, 0, 0, width, height);
balrog4d3b6f62008-02-10 16:33:14 +0000293 continue;
294 }
balrogb1314cf2008-02-22 18:21:28 +0000295#endif
balrog4d3b6f62008-02-10 16:33:14 +0000296
Samuel Thibault459a7072019-03-04 22:05:32 +0100297 keycode = curses2keycode(chr, maybe_keycode);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100298 keycode_alt = 0;
balrog4d3b6f62008-02-10 16:33:14 +0000299
Samuel Thibault633786f2019-03-03 18:25:57 +0100300 /* alt or esc key */
balrog4d3b6f62008-02-10 16:33:14 +0000301 if (keycode == 1) {
Yonggang Luo65f52792020-10-13 07:43:46 +0800302 enum maybe_keycode next_maybe_keycode = CURSES_KEYCODE;
Samuel Thibault459a7072019-03-04 22:05:32 +0100303 int nextchr = console_getch(&next_maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000304
Samuel Thibault459a7072019-03-04 22:05:32 +0100305 if (nextchr != -1) {
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100306 chr = nextchr;
Samuel Thibault459a7072019-03-04 22:05:32 +0100307 maybe_keycode = next_maybe_keycode;
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100308 keycode_alt = ALT;
Samuel Thibault459a7072019-03-04 22:05:32 +0100309 keycode = curses2keycode(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000310
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100311 if (keycode != -1) {
312 keycode |= ALT;
balrog4d3b6f62008-02-10 16:33:14 +0000313
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100314 /* process keys reserved for qemu */
315 if (keycode >= QEMU_KEY_CONSOLE0 &&
316 keycode < QEMU_KEY_CONSOLE0 + 9) {
317 erase();
318 wnoutrefresh(stdscr);
319 console_select(keycode - QEMU_KEY_CONSOLE0);
balrog4d3b6f62008-02-10 16:33:14 +0000320
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100321 invalidate = 1;
322 continue;
323 }
balrog4d3b6f62008-02-10 16:33:14 +0000324 }
325 }
326 }
327
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100328 if (kbd_layout) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100329 keysym = curses2keysym(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000330
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100331 if (keysym == -1) {
Samuel Thibaultd03703c2010-10-19 19:48:20 +0200332 if (chr < ' ') {
333 keysym = chr + '@';
334 if (keysym >= 'A' && keysym <= 'Z')
335 keysym += 'a' - 'A';
336 keysym |= KEYSYM_CNTRL;
337 } else
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100338 keysym = chr;
339 }
340
Gerd Hoffmannabb4f2c2018-02-22 08:05:13 +0100341 keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK,
Gerd Hoffmann19c1b9f2019-01-22 10:28:14 +0100342 NULL, false);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100343 if (keycode == 0)
344 continue;
345
346 keycode |= (keysym & ~KEYSYM_MASK) >> 16;
347 keycode |= keycode_alt;
balrog4d3b6f62008-02-10 16:33:14 +0000348 }
349
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100350 if (keycode == -1)
351 continue;
352
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100353 if (qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000354 /* since terminals don't know about key press and release
355 * events, we need to emit both for each key received */
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100356 if (keycode & SHIFT) {
357 qemu_input_event_send_key_number(NULL, SHIFT_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 & CNTRL) {
361 qemu_input_event_send_key_number(NULL, CNTRL_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200362 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100363 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100364 if (keycode & ALT) {
365 qemu_input_event_send_key_number(NULL, 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 if (keycode & ALTGR) {
369 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200370 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100371 }
372
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400373 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200374 qemu_input_event_send_key_delay(0);
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400375 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, 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 & ALTGR) {
379 qemu_input_event_send_key_number(NULL, GREY | 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 & ALT) {
383 qemu_input_event_send_key_number(NULL, ALT_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 & CNTRL) {
387 qemu_input_event_send_key_number(NULL, CNTRL_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 }
390 if (keycode & SHIFT) {
391 qemu_input_event_send_key_number(NULL, SHIFT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200392 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100393 }
balrog4d3b6f62008-02-10 16:33:14 +0000394 } else {
Samuel Thibault459a7072019-03-04 22:05:32 +0100395 keysym = curses2qemu(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000396 if (keysym == -1)
397 keysym = chr;
398
399 kbd_put_keysym(keysym);
400 }
401 }
402}
403
Blue Swirlaaf12c22010-03-21 19:44:06 +0000404static void curses_atexit(void)
balrog4d3b6f62008-02-10 16:33:14 +0000405{
406 endwin();
Philippe Mathieu-Daudé76c51fc2020-03-05 13:45:24 +0100407 g_free(vga_to_curses);
408 g_free(screen);
balrog4d3b6f62008-02-10 16:33:14 +0000409}
410
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200411/*
412 * In the following:
413 * - fch is the font glyph number
414 * - uch is the unicode value
415 * - wch is the wchar_t value (may not be unicode, e.g. on BSD/solaris)
416 * - mbch is the native local-dependent multibyte representation
417 */
418
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100419/* Setup wchar glyph for one UCS-2 char */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200420static void convert_ucs(unsigned char fch, uint16_t uch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100421{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200422 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200423 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200424 char *puch, *pmbch;
425 size_t such, smbch;
426 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100427
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200428 puch = (char *) &uch;
429 pmbch = (char *) mbch;
430 such = sizeof(uch);
431 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100432
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200433 if (iconv(conv, &puch, &such, &pmbch, &smbch) == (size_t) -1) {
434 fprintf(stderr, "Could not convert 0x%04x "
435 "from UCS-2 to a multibyte character: %s\n",
436 uch, strerror(errno));
437 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100438 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200439
440 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200441 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200442 fprintf(stderr, "Could not convert 0x%04x "
443 "from a multibyte character to wchar_t: %s\n",
444 uch, strerror(errno));
445 return;
446 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200447
448 wch[1] = 0;
449 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100450}
451
452/* Setup wchar glyph for one font character */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200453static void convert_font(unsigned char fch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100454{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200455 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200456 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200457 char *pfch, *pmbch;
458 size_t sfch, smbch;
459 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100460
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200461 pfch = (char *) &fch;
462 pmbch = (char *) &mbch;
463 sfch = sizeof(fch);
464 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100465
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200466 if (iconv(conv, &pfch, &sfch, &pmbch, &smbch) == (size_t) -1) {
467 fprintf(stderr, "Could not convert font glyph 0x%02x "
468 "from %s to a multibyte character: %s\n",
469 fch, font_charset, strerror(errno));
470 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100471 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200472
473 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200474 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200475 fprintf(stderr, "Could not convert font glyph 0x%02x "
476 "from a multibyte character to wchar_t: %s\n",
477 fch, strerror(errno));
478 return;
479 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200480
481 wch[1] = 0;
482 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100483}
484
485/* Convert one wchar to UCS-2 */
486static uint16_t get_ucs(wchar_t wch, iconv_t conv)
487{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200488 char mbch[MB_LEN_MAX];
489 uint16_t uch;
490 char *pmbch, *puch;
491 size_t smbch, such;
492 mbstate_t ps;
493 int ret;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100494
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200495 memset(&ps, 0, sizeof(ps));
496 ret = wcrtomb(mbch, wch, &ps);
497 if (ret == -1) {
Max Reitzdc3c8712019-05-27 16:25:40 +0200498 fprintf(stderr, "Could not convert 0x%04lx "
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200499 "from wchar_t to a multibyte character: %s\n",
Max Reitzdc3c8712019-05-27 16:25:40 +0200500 (unsigned long)wch, strerror(errno));
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100501 return 0xFFFD;
502 }
503
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200504 pmbch = (char *) mbch;
505 puch = (char *) &uch;
506 smbch = ret;
507 such = sizeof(uch);
508
509 if (iconv(conv, &pmbch, &smbch, &puch, &such) == (size_t) -1) {
Max Reitzdc3c8712019-05-27 16:25:40 +0200510 fprintf(stderr, "Could not convert 0x%04lx "
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200511 "from a multibyte character to UCS-2 : %s\n",
Max Reitzdc3c8712019-05-27 16:25:40 +0200512 (unsigned long)wch, strerror(errno));
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200513 return 0xFFFD;
514 }
515
516 return uch;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100517}
518
519/*
520 * Setup mapping for vga to curses line graphics.
521 */
522static void font_setup(void)
523{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200524 iconv_t ucs2_to_nativecharset;
525 iconv_t nativecharset_to_ucs2;
526 iconv_t font_conv;
527 int i;
Yonggang Luo80d3ab62020-10-13 07:43:45 +0800528 g_autofree gchar *local_codeset = g_get_codeset();
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200529
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100530 /*
531 * Control characters are normally non-printable, but VGA does have
532 * well-known glyphs for them.
533 */
Philippe Mathieu-Daudé80e8c2e2020-03-05 13:45:23 +0100534 static const uint16_t control_characters[0x20] = {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100535 0x0020,
536 0x263a,
537 0x263b,
538 0x2665,
539 0x2666,
540 0x2663,
541 0x2660,
542 0x2022,
543 0x25d8,
544 0x25cb,
545 0x25d9,
546 0x2642,
547 0x2640,
548 0x266a,
549 0x266b,
550 0x263c,
551 0x25ba,
552 0x25c4,
553 0x2195,
554 0x203c,
555 0x00b6,
556 0x00a7,
557 0x25ac,
558 0x21a8,
559 0x2191,
560 0x2193,
561 0x2192,
562 0x2190,
563 0x221f,
564 0x2194,
565 0x25b2,
566 0x25bc
567 };
568
Yonggang Luo80d3ab62020-10-13 07:43:45 +0800569 ucs2_to_nativecharset = iconv_open(local_codeset, "UCS-2");
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200570 if (ucs2_to_nativecharset == (iconv_t) -1) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100571 fprintf(stderr, "Could not convert font glyphs from UCS-2: '%s'\n",
572 strerror(errno));
573 exit(1);
574 }
575
Yonggang Luo80d3ab62020-10-13 07:43:45 +0800576 nativecharset_to_ucs2 = iconv_open("UCS-2", local_codeset);
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200577 if (nativecharset_to_ucs2 == (iconv_t) -1) {
578 iconv_close(ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100579 fprintf(stderr, "Could not convert font glyphs to UCS-2: '%s'\n",
580 strerror(errno));
581 exit(1);
582 }
583
Yonggang Luo80d3ab62020-10-13 07:43:45 +0800584 font_conv = iconv_open(local_codeset, font_charset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100585 if (font_conv == (iconv_t) -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200586 iconv_close(ucs2_to_nativecharset);
587 iconv_close(nativecharset_to_ucs2);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100588 fprintf(stderr, "Could not convert font glyphs from %s: '%s'\n",
589 font_charset, strerror(errno));
590 exit(1);
591 }
592
593 /* Control characters */
594 for (i = 0; i <= 0x1F; i++) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200595 convert_ucs(i, control_characters[i], ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100596 }
597
598 for (i = 0x20; i <= 0xFF; i++) {
599 convert_font(i, font_conv);
600 }
601
602 /* DEL */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200603 convert_ucs(0x7F, 0x2302, ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100604
Yonggang Luo80d3ab62020-10-13 07:43:45 +0800605 if (strcmp(local_codeset, "UTF-8")) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100606 /* Non-Unicode capable, use termcap equivalents for those available */
607 for (i = 0; i <= 0xFF; i++) {
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200608 wchar_t wch[CCHARW_MAX];
609 attr_t attr;
610 short color;
611 int ret;
612
613 ret = getcchar(&vga_to_curses[i], wch, &attr, &color, NULL);
614 if (ret == ERR)
615 continue;
616
617 switch (get_ucs(wch[0], nativecharset_to_ucs2)) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100618 case 0x00a3:
619 vga_to_curses[i] = *WACS_STERLING;
620 break;
621 case 0x2591:
622 vga_to_curses[i] = *WACS_BOARD;
623 break;
624 case 0x2592:
625 vga_to_curses[i] = *WACS_CKBOARD;
626 break;
627 case 0x2502:
628 vga_to_curses[i] = *WACS_VLINE;
629 break;
630 case 0x2524:
631 vga_to_curses[i] = *WACS_RTEE;
632 break;
633 case 0x2510:
634 vga_to_curses[i] = *WACS_URCORNER;
635 break;
636 case 0x2514:
637 vga_to_curses[i] = *WACS_LLCORNER;
638 break;
639 case 0x2534:
640 vga_to_curses[i] = *WACS_BTEE;
641 break;
642 case 0x252c:
643 vga_to_curses[i] = *WACS_TTEE;
644 break;
645 case 0x251c:
646 vga_to_curses[i] = *WACS_LTEE;
647 break;
648 case 0x2500:
649 vga_to_curses[i] = *WACS_HLINE;
650 break;
651 case 0x253c:
652 vga_to_curses[i] = *WACS_PLUS;
653 break;
654 case 0x256c:
655 vga_to_curses[i] = *WACS_LANTERN;
656 break;
657 case 0x256a:
658 vga_to_curses[i] = *WACS_NEQUAL;
659 break;
660 case 0x2518:
661 vga_to_curses[i] = *WACS_LRCORNER;
662 break;
663 case 0x250c:
664 vga_to_curses[i] = *WACS_ULCORNER;
665 break;
666 case 0x2588:
667 vga_to_curses[i] = *WACS_BLOCK;
668 break;
669 case 0x03c0:
670 vga_to_curses[i] = *WACS_PI;
671 break;
672 case 0x00b1:
673 vga_to_curses[i] = *WACS_PLMINUS;
674 break;
675 case 0x2265:
676 vga_to_curses[i] = *WACS_GEQUAL;
677 break;
678 case 0x2264:
679 vga_to_curses[i] = *WACS_LEQUAL;
680 break;
681 case 0x00b0:
682 vga_to_curses[i] = *WACS_DEGREE;
683 break;
684 case 0x25a0:
685 vga_to_curses[i] = *WACS_BULLET;
686 break;
687 case 0x2666:
688 vga_to_curses[i] = *WACS_DIAMOND;
689 break;
690 case 0x2192:
691 vga_to_curses[i] = *WACS_RARROW;
692 break;
693 case 0x2190:
694 vga_to_curses[i] = *WACS_LARROW;
695 break;
696 case 0x2191:
697 vga_to_curses[i] = *WACS_UARROW;
698 break;
699 case 0x2193:
700 vga_to_curses[i] = *WACS_DARROW;
701 break;
702 case 0x23ba:
703 vga_to_curses[i] = *WACS_S1;
704 break;
705 case 0x23bb:
706 vga_to_curses[i] = *WACS_S3;
707 break;
708 case 0x23bc:
709 vga_to_curses[i] = *WACS_S7;
710 break;
711 case 0x23bd:
712 vga_to_curses[i] = *WACS_S9;
713 break;
714 }
715 }
716 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200717 iconv_close(ucs2_to_nativecharset);
718 iconv_close(nativecharset_to_ucs2);
Samuel Thibaulta9fda242019-03-14 18:25:24 +0100719 iconv_close(font_conv);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100720}
721
balrog4d3b6f62008-02-10 16:33:14 +0000722static void curses_setup(void)
723{
724 int i, colour_default[8] = {
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900725 [QEMU_COLOR_BLACK] = COLOR_BLACK,
726 [QEMU_COLOR_BLUE] = COLOR_BLUE,
727 [QEMU_COLOR_GREEN] = COLOR_GREEN,
728 [QEMU_COLOR_CYAN] = COLOR_CYAN,
729 [QEMU_COLOR_RED] = COLOR_RED,
730 [QEMU_COLOR_MAGENTA] = COLOR_MAGENTA,
731 [QEMU_COLOR_YELLOW] = COLOR_YELLOW,
732 [QEMU_COLOR_WHITE] = COLOR_WHITE,
balrog4d3b6f62008-02-10 16:33:14 +0000733 };
734
735 /* input as raw as possible, let everything be interpreted
736 * by the guest system */
737 initscr(); noecho(); intrflush(stdscr, FALSE);
738 nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE);
739 start_color(); raw(); scrollok(stdscr, FALSE);
Samuel Thibault633786f2019-03-03 18:25:57 +0100740 set_escdelay(25);
balrog4d3b6f62008-02-10 16:33:14 +0000741
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900742 /* Make color pair to match color format (3bits bg:3bits fg) */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900743 for (i = 0; i < 64; i++) {
balrog4d3b6f62008-02-10 16:33:14 +0000744 init_pair(i, colour_default[i & 7], colour_default[i >> 3]);
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900745 }
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900746 /* Set default color for more than 64 for safety. */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900747 for (i = 64; i < COLOR_PAIRS; i++) {
748 init_pair(i, COLOR_WHITE, COLOR_BLACK);
749 }
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +0900750
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100751 font_setup();
balrog4d3b6f62008-02-10 16:33:14 +0000752}
753
754static void curses_keyboard_setup(void)
755{
balrog4d3b6f62008-02-10 16:33:14 +0000756#if defined(__APPLE__)
757 /* always use generic keymaps */
758 if (!keyboard_layout)
759 keyboard_layout = "en-us";
760#endif
761 if(keyboard_layout) {
Fei Liab4f9312018-10-17 10:26:50 +0200762 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
763 &error_fatal);
balrog4d3b6f62008-02-10 16:33:14 +0000764 }
balrog4d3b6f62008-02-10 16:33:14 +0000765}
766
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100767static const DisplayChangeListenerOps dcl_ops = {
768 .dpy_name = "curses",
769 .dpy_text_update = curses_update,
770 .dpy_text_resize = curses_resize,
771 .dpy_refresh = curses_refresh,
772 .dpy_text_cursor = curses_cursor_position,
773};
774
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100775static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
balrog4d3b6f62008-02-10 16:33:14 +0000776{
777#ifndef _WIN32
778 if (!isatty(1)) {
779 fprintf(stderr, "We need a terminal output\n");
780 exit(1);
781 }
782#endif
783
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100784 setlocale(LC_CTYPE, "");
785 if (opts->u.curses.charset) {
786 font_charset = opts->u.curses.charset;
787 }
Philippe Mathieu-Daudé76c51fc2020-03-05 13:45:24 +0100788 screen = g_new0(console_ch_t, 160 * 100);
789 vga_to_curses = g_new0(cchar_t, 256);
balrog4d3b6f62008-02-10 16:33:14 +0000790 curses_setup();
791 curses_keyboard_setup();
Anthony Liguori28695482010-03-21 14:13:02 -0500792 atexit(curses_atexit);
balrog4d3b6f62008-02-10 16:33:14 +0000793
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100794 curses_winch_init();
balrog4d3b6f62008-02-10 16:33:14 +0000795
Markus Armbrusterfedf0d32015-11-03 17:12:03 +0100796 dcl = g_new0(DisplayChangeListener, 1);
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100797 dcl->ops = &dcl_ops;
Gerd Hoffmann52090892013-04-23 15:44:31 +0200798 register_displaychangelistener(dcl);
balrog4d3b6f62008-02-10 16:33:14 +0000799
800 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000801}
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100802
803static QemuDisplay qemu_display_curses = {
804 .type = DISPLAY_TYPE_CURSES,
805 .init = curses_display_init,
806};
807
808static void register_curses(void)
809{
810 qemu_display_register(&qemu_display_curses);
811}
812
813type_init(register_curses);