blob: 3a1b71451c93f026dd3b2af2d8977599f597c37b [file] [log] [blame]
balrog4d3b6f62008-02-10 16:33:14 +00001/*
2 * QEMU curses/ncurses display driver
3 *
4 * Copyright (c) 2005 Andrzej Zaborowski <balrog@zabor.org>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
Markus Armbruster0b8fa322019-05-23 16:35:07 +020024
Peter Maydelle16f4c82016-01-29 17:49:51 +000025#include "qemu/osdep.h"
balrog4d3b6f62008-02-10 16:33:14 +000026
27#ifndef _WIN32
balrog4d3b6f62008-02-10 16:33:14 +000028#include <sys/ioctl.h>
29#include <termios.h>
30#endif
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010031#include <locale.h>
32#include <wchar.h>
33#include <langinfo.h>
34#include <iconv.h>
balrog4d3b6f62008-02-10 16:33:14 +000035
Fei Liab4f9312018-10-17 10:26:50 +020036#include "qapi/error.h"
Markus Armbruster0b8fa322019-05-23 16:35:07 +020037#include "qemu/module.h"
Paolo Bonzini28ecbae2012-11-28 12:06:30 +010038#include "ui/console.h"
Gerd Hoffmanncd100322013-12-04 13:40:20 +010039#include "ui/input.h"
Paolo Bonzini9c17d612012-12-17 18:20:04 +010040#include "sysemu/sysemu.h"
blueswir1511d2b12009-03-07 15:32:56 +000041
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020042/* KEY_EVENT is defined in wincon.h and in curses.h. Avoid redefinition. */
43#undef KEY_EVENT
44#include <curses.h>
45#undef KEY_EVENT
46
balrog4d3b6f62008-02-10 16:33:14 +000047#define FONT_HEIGHT 16
48#define FONT_WIDTH 8
49
Samuel Thibault459a7072019-03-04 22:05:32 +010050enum maybe_keycode {
51 CURSES_KEYCODE,
52 CURSES_CHAR,
53 CURSES_CHAR_OR_KEYCODE,
54};
55
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010056static DisplayChangeListener *dcl;
Anthony Liguoric227f092009-10-01 16:12:16 -050057static console_ch_t screen[160 * 100];
balrog4d3b6f62008-02-10 16:33:14 +000058static WINDOW *screenpad = NULL;
59static int width, height, gwidth, gheight, invalidate;
60static int px, py, sminx, sminy, smaxx, smaxy;
61
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010062static const char *font_charset = "CP437";
63static cchar_t vga_to_curses[256];
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +090064
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010065static void curses_update(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010066 int x, int y, int w, int h)
balrog4d3b6f62008-02-10 16:33:14 +000067{
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020068 console_ch_t *line;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010069 cchar_t curses_line[width];
Samuel Thibault962cf8f2019-04-27 20:33:07 +020070 wchar_t wch[CCHARW_MAX];
71 attr_t attrs;
72 short colors;
73 int ret;
balrog4d3b6f62008-02-10 16:33:14 +000074
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020075 line = screen + y * width;
76 for (h += y; y < h; y ++, line += width) {
77 for (x = 0; x < width; x++) {
Matthew Kilgorecd54ea42019-10-03 23:53:37 -040078 chtype ch = line[x] & A_CHARTEXT;
79 chtype at = line[x] & A_ATTRIBUTES;
Matthew Kilgore30f5a9d2019-10-03 23:53:38 -040080 short color_pair = PAIR_NUMBER(line[x]);
81
Samuel Thibault962cf8f2019-04-27 20:33:07 +020082 ret = getcchar(&vga_to_curses[ch], wch, &attrs, &colors, NULL);
83 if (ret == ERR || wch[0] == 0) {
84 wch[0] = ch;
85 wch[1] = 0;
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020086 }
Matthew Kilgore30f5a9d2019-10-03 23:53:38 -040087 setcchar(&curses_line[x], wch, at, color_pair, NULL);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020088 }
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010089 mvwadd_wchnstr(screenpad, y, 0, curses_line, width);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020090 }
balrog4d3b6f62008-02-10 16:33:14 +000091
92 pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1);
93 refresh();
94}
95
96static void curses_calc_pad(void)
97{
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +010098 if (qemu_console_is_fixedsize(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +000099 width = gwidth;
100 height = gheight;
101 } else {
102 width = COLS;
103 height = LINES;
104 }
105
106 if (screenpad)
107 delwin(screenpad);
108
109 clear();
110 refresh();
111
112 screenpad = newpad(height, width);
113
114 if (width > COLS) {
115 px = (width - COLS) / 2;
116 sminx = 0;
117 smaxx = COLS;
118 } else {
119 px = 0;
120 sminx = (COLS - width) / 2;
121 smaxx = sminx + width;
122 }
123
124 if (height > LINES) {
125 py = (height - LINES) / 2;
126 sminy = 0;
127 smaxy = LINES;
128 } else {
129 py = 0;
130 sminy = (LINES - height) / 2;
131 smaxy = sminy + height;
132 }
133}
134
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100135static void curses_resize(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100136 int width, int height)
balrog4d3b6f62008-02-10 16:33:14 +0000137{
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200138 if (width == gwidth && height == gheight) {
balrog4d3b6f62008-02-10 16:33:14 +0000139 return;
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200140 }
balrog4d3b6f62008-02-10 16:33:14 +0000141
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200142 gwidth = width;
143 gheight = height;
balrog4d3b6f62008-02-10 16:33:14 +0000144
145 curses_calc_pad();
146}
147
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100148#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE)
149static volatile sig_atomic_t got_sigwinch;
150static void curses_winch_check(void)
balrog4d3b6f62008-02-10 16:33:14 +0000151{
152 struct winsize {
153 unsigned short ws_row;
154 unsigned short ws_col;
155 unsigned short ws_xpixel; /* unused */
156 unsigned short ws_ypixel; /* unused */
157 } ws;
158
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100159 if (!got_sigwinch) {
balrog4d3b6f62008-02-10 16:33:14 +0000160 return;
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100161 }
162 got_sigwinch = false;
163
164 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
165 return;
166 }
balrog4d3b6f62008-02-10 16:33:14 +0000167
168 resize_term(ws.ws_row, ws.ws_col);
balrog4d3b6f62008-02-10 16:33:14 +0000169 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000170}
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100171
172static void curses_winch_handler(int signum)
173{
174 got_sigwinch = true;
175}
176
177static void curses_winch_init(void)
178{
179 struct sigaction old, winch = {
180 .sa_handler = curses_winch_handler,
181 };
182 sigaction(SIGWINCH, &winch, &old);
183}
184#else
185static void curses_winch_check(void) {}
186static void curses_winch_init(void) {}
balrog4d3b6f62008-02-10 16:33:14 +0000187#endif
188
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100189static void curses_cursor_position(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100190 int x, int y)
balrog4d3b6f62008-02-10 16:33:14 +0000191{
192 if (x >= 0) {
193 x = sminx + x - px;
194 y = sminy + y - py;
195
196 if (x >= 0 && y >= 0 && x < COLS && y < LINES) {
197 move(y, x);
198 curs_set(1);
199 /* it seems that curs_set(1) must always be called before
200 * curs_set(2) for the latter to have effect */
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100201 if (!qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000202 curs_set(2);
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100203 }
balrog4d3b6f62008-02-10 16:33:14 +0000204 return;
205 }
206 }
207
208 curs_set(0);
209}
210
211/* generic keyboard conversion */
212
213#include "curses_keys.h"
balrog4d3b6f62008-02-10 16:33:14 +0000214
Anthony Liguoric227f092009-10-01 16:12:16 -0500215static kbd_layout_t *kbd_layout = NULL;
balrog4d3b6f62008-02-10 16:33:14 +0000216
Samuel Thibault459a7072019-03-04 22:05:32 +0100217static wint_t console_getch(enum maybe_keycode *maybe_keycode)
218{
219 wint_t ret;
220 switch (get_wch(&ret)) {
221 case KEY_CODE_YES:
222 *maybe_keycode = CURSES_KEYCODE;
223 break;
224 case OK:
225 *maybe_keycode = CURSES_CHAR;
226 break;
227 case ERR:
228 ret = -1;
229 break;
Paolo Bonzini68097ed2019-07-18 14:01:04 +0200230 default:
231 abort();
Samuel Thibault459a7072019-03-04 22:05:32 +0100232 }
233 return ret;
234}
235
236static int curses2foo(const int _curses2foo[], const int _curseskey2foo[],
237 int chr, enum maybe_keycode maybe_keycode)
238{
239 int ret = -1;
240 if (maybe_keycode == CURSES_CHAR) {
241 if (chr < CURSES_CHARS) {
242 ret = _curses2foo[chr];
243 }
244 } else {
245 if (chr < CURSES_KEYS) {
246 ret = _curseskey2foo[chr];
247 }
248 if (ret == -1 && maybe_keycode == CURSES_CHAR_OR_KEYCODE &&
249 chr < CURSES_CHARS) {
250 ret = _curses2foo[chr];
251 }
252 }
253 return ret;
254}
255
256#define curses2keycode(chr, maybe_keycode) \
257 curses2foo(_curses2keycode, _curseskey2keycode, chr, maybe_keycode)
258#define curses2keysym(chr, maybe_keycode) \
259 curses2foo(_curses2keysym, _curseskey2keysym, chr, maybe_keycode)
260#define curses2qemu(chr, maybe_keycode) \
261 curses2foo(_curses2qemu, _curseskey2qemu, chr, maybe_keycode)
262
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100263static void curses_refresh(DisplayChangeListener *dcl)
balrog4d3b6f62008-02-10 16:33:14 +0000264{
Peter Maydell99a9ef42016-08-11 15:23:27 +0100265 int chr, keysym, keycode, keycode_alt;
Samuel Thibault459a7072019-03-04 22:05:32 +0100266 enum maybe_keycode maybe_keycode;
balrog4d3b6f62008-02-10 16:33:14 +0000267
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100268 curses_winch_check();
269
balrog4d3b6f62008-02-10 16:33:14 +0000270 if (invalidate) {
271 clear();
272 refresh();
273 curses_calc_pad();
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100274 graphic_hw_invalidate(NULL);
balrog4d3b6f62008-02-10 16:33:14 +0000275 invalidate = 0;
276 }
277
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100278 graphic_hw_text_update(NULL, screen);
balrog4d3b6f62008-02-10 16:33:14 +0000279
balrog4d3b6f62008-02-10 16:33:14 +0000280 while (1) {
281 /* while there are any pending key strokes to process */
Samuel Thibault459a7072019-03-04 22:05:32 +0100282 chr = console_getch(&maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000283
Samuel Thibault459a7072019-03-04 22:05:32 +0100284 if (chr == -1)
balrog4d3b6f62008-02-10 16:33:14 +0000285 break;
286
balrogb1314cf2008-02-22 18:21:28 +0000287#ifdef KEY_RESIZE
balrog4d3b6f62008-02-10 16:33:14 +0000288 /* this shouldn't occur when we use a custom SIGWINCH handler */
Samuel Thibault459a7072019-03-04 22:05:32 +0100289 if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) {
balrog4d3b6f62008-02-10 16:33:14 +0000290 clear();
291 refresh();
292 curses_calc_pad();
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100293 curses_update(dcl, 0, 0, width, height);
balrog4d3b6f62008-02-10 16:33:14 +0000294 continue;
295 }
balrogb1314cf2008-02-22 18:21:28 +0000296#endif
balrog4d3b6f62008-02-10 16:33:14 +0000297
Samuel Thibault459a7072019-03-04 22:05:32 +0100298 keycode = curses2keycode(chr, maybe_keycode);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100299 keycode_alt = 0;
balrog4d3b6f62008-02-10 16:33:14 +0000300
Samuel Thibault633786f2019-03-03 18:25:57 +0100301 /* alt or esc key */
balrog4d3b6f62008-02-10 16:33:14 +0000302 if (keycode == 1) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100303 enum maybe_keycode next_maybe_keycode;
304 int nextchr = console_getch(&next_maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000305
Samuel Thibault459a7072019-03-04 22:05:32 +0100306 if (nextchr != -1) {
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100307 chr = nextchr;
Samuel Thibault459a7072019-03-04 22:05:32 +0100308 maybe_keycode = next_maybe_keycode;
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100309 keycode_alt = ALT;
Samuel Thibault459a7072019-03-04 22:05:32 +0100310 keycode = curses2keycode(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000311
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100312 if (keycode != -1) {
313 keycode |= ALT;
balrog4d3b6f62008-02-10 16:33:14 +0000314
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100315 /* process keys reserved for qemu */
316 if (keycode >= QEMU_KEY_CONSOLE0 &&
317 keycode < QEMU_KEY_CONSOLE0 + 9) {
318 erase();
319 wnoutrefresh(stdscr);
320 console_select(keycode - QEMU_KEY_CONSOLE0);
balrog4d3b6f62008-02-10 16:33:14 +0000321
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100322 invalidate = 1;
323 continue;
324 }
balrog4d3b6f62008-02-10 16:33:14 +0000325 }
326 }
327 }
328
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100329 if (kbd_layout) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100330 keysym = curses2keysym(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000331
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100332 if (keysym == -1) {
Samuel Thibaultd03703c2010-10-19 19:48:20 +0200333 if (chr < ' ') {
334 keysym = chr + '@';
335 if (keysym >= 'A' && keysym <= 'Z')
336 keysym += 'a' - 'A';
337 keysym |= KEYSYM_CNTRL;
338 } else
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100339 keysym = chr;
340 }
341
Gerd Hoffmannabb4f2c2018-02-22 08:05:13 +0100342 keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK,
Gerd Hoffmann19c1b9f2019-01-22 10:28:14 +0100343 NULL, false);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100344 if (keycode == 0)
345 continue;
346
347 keycode |= (keysym & ~KEYSYM_MASK) >> 16;
348 keycode |= keycode_alt;
balrog4d3b6f62008-02-10 16:33:14 +0000349 }
350
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100351 if (keycode == -1)
352 continue;
353
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100354 if (qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000355 /* since terminals don't know about key press and release
356 * events, we need to emit both for each key received */
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100357 if (keycode & SHIFT) {
358 qemu_input_event_send_key_number(NULL, SHIFT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200359 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100360 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100361 if (keycode & CNTRL) {
362 qemu_input_event_send_key_number(NULL, CNTRL_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200363 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100364 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100365 if (keycode & ALT) {
366 qemu_input_event_send_key_number(NULL, ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200367 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100368 }
369 if (keycode & ALTGR) {
370 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200371 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100372 }
373
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400374 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200375 qemu_input_event_send_key_delay(0);
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400376 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200377 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100378
379 if (keycode & ALTGR) {
380 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200381 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100382 }
383 if (keycode & ALT) {
384 qemu_input_event_send_key_number(NULL, ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200385 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100386 }
387 if (keycode & CNTRL) {
388 qemu_input_event_send_key_number(NULL, CNTRL_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200389 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100390 }
391 if (keycode & SHIFT) {
392 qemu_input_event_send_key_number(NULL, SHIFT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200393 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100394 }
balrog4d3b6f62008-02-10 16:33:14 +0000395 } else {
Samuel Thibault459a7072019-03-04 22:05:32 +0100396 keysym = curses2qemu(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000397 if (keysym == -1)
398 keysym = chr;
399
400 kbd_put_keysym(keysym);
401 }
402 }
403}
404
Blue Swirlaaf12c22010-03-21 19:44:06 +0000405static void curses_atexit(void)
balrog4d3b6f62008-02-10 16:33:14 +0000406{
407 endwin();
408}
409
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200410/*
411 * In the following:
412 * - fch is the font glyph number
413 * - uch is the unicode value
414 * - wch is the wchar_t value (may not be unicode, e.g. on BSD/solaris)
415 * - mbch is the native local-dependent multibyte representation
416 */
417
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100418/* Setup wchar glyph for one UCS-2 char */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200419static void convert_ucs(unsigned char fch, uint16_t uch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100420{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200421 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200422 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200423 char *puch, *pmbch;
424 size_t such, smbch;
425 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100426
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200427 puch = (char *) &uch;
428 pmbch = (char *) mbch;
429 such = sizeof(uch);
430 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100431
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200432 if (iconv(conv, &puch, &such, &pmbch, &smbch) == (size_t) -1) {
433 fprintf(stderr, "Could not convert 0x%04x "
434 "from UCS-2 to a multibyte character: %s\n",
435 uch, strerror(errno));
436 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100437 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200438
439 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200440 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200441 fprintf(stderr, "Could not convert 0x%04x "
442 "from a multibyte character to wchar_t: %s\n",
443 uch, strerror(errno));
444 return;
445 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200446
447 wch[1] = 0;
448 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100449}
450
451/* Setup wchar glyph for one font character */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200452static void convert_font(unsigned char fch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100453{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200454 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200455 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200456 char *pfch, *pmbch;
457 size_t sfch, smbch;
458 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100459
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200460 pfch = (char *) &fch;
461 pmbch = (char *) &mbch;
462 sfch = sizeof(fch);
463 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100464
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200465 if (iconv(conv, &pfch, &sfch, &pmbch, &smbch) == (size_t) -1) {
466 fprintf(stderr, "Could not convert font glyph 0x%02x "
467 "from %s to a multibyte character: %s\n",
468 fch, font_charset, strerror(errno));
469 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100470 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200471
472 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200473 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200474 fprintf(stderr, "Could not convert font glyph 0x%02x "
475 "from a multibyte character to wchar_t: %s\n",
476 fch, strerror(errno));
477 return;
478 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200479
480 wch[1] = 0;
481 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100482}
483
484/* Convert one wchar to UCS-2 */
485static uint16_t get_ucs(wchar_t wch, iconv_t conv)
486{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200487 char mbch[MB_LEN_MAX];
488 uint16_t uch;
489 char *pmbch, *puch;
490 size_t smbch, such;
491 mbstate_t ps;
492 int ret;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100493
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200494 memset(&ps, 0, sizeof(ps));
495 ret = wcrtomb(mbch, wch, &ps);
496 if (ret == -1) {
Max Reitzdc3c8712019-05-27 16:25:40 +0200497 fprintf(stderr, "Could not convert 0x%04lx "
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200498 "from wchar_t to a multibyte character: %s\n",
Max Reitzdc3c8712019-05-27 16:25:40 +0200499 (unsigned long)wch, strerror(errno));
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100500 return 0xFFFD;
501 }
502
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200503 pmbch = (char *) mbch;
504 puch = (char *) &uch;
505 smbch = ret;
506 such = sizeof(uch);
507
508 if (iconv(conv, &pmbch, &smbch, &puch, &such) == (size_t) -1) {
Max Reitzdc3c8712019-05-27 16:25:40 +0200509 fprintf(stderr, "Could not convert 0x%04lx "
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200510 "from a multibyte character to UCS-2 : %s\n",
Max Reitzdc3c8712019-05-27 16:25:40 +0200511 (unsigned long)wch, strerror(errno));
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200512 return 0xFFFD;
513 }
514
515 return uch;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100516}
517
518/*
519 * Setup mapping for vga to curses line graphics.
520 */
521static void font_setup(void)
522{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200523 iconv_t ucs2_to_nativecharset;
524 iconv_t nativecharset_to_ucs2;
525 iconv_t font_conv;
526 int i;
527
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100528 /*
529 * Control characters are normally non-printable, but VGA does have
530 * well-known glyphs for them.
531 */
532 static uint16_t control_characters[0x20] = {
533 0x0020,
534 0x263a,
535 0x263b,
536 0x2665,
537 0x2666,
538 0x2663,
539 0x2660,
540 0x2022,
541 0x25d8,
542 0x25cb,
543 0x25d9,
544 0x2642,
545 0x2640,
546 0x266a,
547 0x266b,
548 0x263c,
549 0x25ba,
550 0x25c4,
551 0x2195,
552 0x203c,
553 0x00b6,
554 0x00a7,
555 0x25ac,
556 0x21a8,
557 0x2191,
558 0x2193,
559 0x2192,
560 0x2190,
561 0x221f,
562 0x2194,
563 0x25b2,
564 0x25bc
565 };
566
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200567 ucs2_to_nativecharset = iconv_open(nl_langinfo(CODESET), "UCS-2");
568 if (ucs2_to_nativecharset == (iconv_t) -1) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100569 fprintf(stderr, "Could not convert font glyphs from UCS-2: '%s'\n",
570 strerror(errno));
571 exit(1);
572 }
573
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200574 nativecharset_to_ucs2 = iconv_open("UCS-2", nl_langinfo(CODESET));
575 if (nativecharset_to_ucs2 == (iconv_t) -1) {
576 iconv_close(ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100577 fprintf(stderr, "Could not convert font glyphs to UCS-2: '%s'\n",
578 strerror(errno));
579 exit(1);
580 }
581
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200582 font_conv = iconv_open(nl_langinfo(CODESET), font_charset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100583 if (font_conv == (iconv_t) -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200584 iconv_close(ucs2_to_nativecharset);
585 iconv_close(nativecharset_to_ucs2);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100586 fprintf(stderr, "Could not convert font glyphs from %s: '%s'\n",
587 font_charset, strerror(errno));
588 exit(1);
589 }
590
591 /* Control characters */
592 for (i = 0; i <= 0x1F; i++) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200593 convert_ucs(i, control_characters[i], ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100594 }
595
596 for (i = 0x20; i <= 0xFF; i++) {
597 convert_font(i, font_conv);
598 }
599
600 /* DEL */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200601 convert_ucs(0x7F, 0x2302, ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100602
603 if (strcmp(nl_langinfo(CODESET), "UTF-8")) {
604 /* Non-Unicode capable, use termcap equivalents for those available */
605 for (i = 0; i <= 0xFF; i++) {
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200606 wchar_t wch[CCHARW_MAX];
607 attr_t attr;
608 short color;
609 int ret;
610
611 ret = getcchar(&vga_to_curses[i], wch, &attr, &color, NULL);
612 if (ret == ERR)
613 continue;
614
615 switch (get_ucs(wch[0], nativecharset_to_ucs2)) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100616 case 0x00a3:
617 vga_to_curses[i] = *WACS_STERLING;
618 break;
619 case 0x2591:
620 vga_to_curses[i] = *WACS_BOARD;
621 break;
622 case 0x2592:
623 vga_to_curses[i] = *WACS_CKBOARD;
624 break;
625 case 0x2502:
626 vga_to_curses[i] = *WACS_VLINE;
627 break;
628 case 0x2524:
629 vga_to_curses[i] = *WACS_RTEE;
630 break;
631 case 0x2510:
632 vga_to_curses[i] = *WACS_URCORNER;
633 break;
634 case 0x2514:
635 vga_to_curses[i] = *WACS_LLCORNER;
636 break;
637 case 0x2534:
638 vga_to_curses[i] = *WACS_BTEE;
639 break;
640 case 0x252c:
641 vga_to_curses[i] = *WACS_TTEE;
642 break;
643 case 0x251c:
644 vga_to_curses[i] = *WACS_LTEE;
645 break;
646 case 0x2500:
647 vga_to_curses[i] = *WACS_HLINE;
648 break;
649 case 0x253c:
650 vga_to_curses[i] = *WACS_PLUS;
651 break;
652 case 0x256c:
653 vga_to_curses[i] = *WACS_LANTERN;
654 break;
655 case 0x256a:
656 vga_to_curses[i] = *WACS_NEQUAL;
657 break;
658 case 0x2518:
659 vga_to_curses[i] = *WACS_LRCORNER;
660 break;
661 case 0x250c:
662 vga_to_curses[i] = *WACS_ULCORNER;
663 break;
664 case 0x2588:
665 vga_to_curses[i] = *WACS_BLOCK;
666 break;
667 case 0x03c0:
668 vga_to_curses[i] = *WACS_PI;
669 break;
670 case 0x00b1:
671 vga_to_curses[i] = *WACS_PLMINUS;
672 break;
673 case 0x2265:
674 vga_to_curses[i] = *WACS_GEQUAL;
675 break;
676 case 0x2264:
677 vga_to_curses[i] = *WACS_LEQUAL;
678 break;
679 case 0x00b0:
680 vga_to_curses[i] = *WACS_DEGREE;
681 break;
682 case 0x25a0:
683 vga_to_curses[i] = *WACS_BULLET;
684 break;
685 case 0x2666:
686 vga_to_curses[i] = *WACS_DIAMOND;
687 break;
688 case 0x2192:
689 vga_to_curses[i] = *WACS_RARROW;
690 break;
691 case 0x2190:
692 vga_to_curses[i] = *WACS_LARROW;
693 break;
694 case 0x2191:
695 vga_to_curses[i] = *WACS_UARROW;
696 break;
697 case 0x2193:
698 vga_to_curses[i] = *WACS_DARROW;
699 break;
700 case 0x23ba:
701 vga_to_curses[i] = *WACS_S1;
702 break;
703 case 0x23bb:
704 vga_to_curses[i] = *WACS_S3;
705 break;
706 case 0x23bc:
707 vga_to_curses[i] = *WACS_S7;
708 break;
709 case 0x23bd:
710 vga_to_curses[i] = *WACS_S9;
711 break;
712 }
713 }
714 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200715 iconv_close(ucs2_to_nativecharset);
716 iconv_close(nativecharset_to_ucs2);
Samuel Thibaulta9fda242019-03-14 18:25:24 +0100717 iconv_close(font_conv);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100718}
719
balrog4d3b6f62008-02-10 16:33:14 +0000720static void curses_setup(void)
721{
722 int i, colour_default[8] = {
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900723 [QEMU_COLOR_BLACK] = COLOR_BLACK,
724 [QEMU_COLOR_BLUE] = COLOR_BLUE,
725 [QEMU_COLOR_GREEN] = COLOR_GREEN,
726 [QEMU_COLOR_CYAN] = COLOR_CYAN,
727 [QEMU_COLOR_RED] = COLOR_RED,
728 [QEMU_COLOR_MAGENTA] = COLOR_MAGENTA,
729 [QEMU_COLOR_YELLOW] = COLOR_YELLOW,
730 [QEMU_COLOR_WHITE] = COLOR_WHITE,
balrog4d3b6f62008-02-10 16:33:14 +0000731 };
732
733 /* input as raw as possible, let everything be interpreted
734 * by the guest system */
735 initscr(); noecho(); intrflush(stdscr, FALSE);
736 nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE);
737 start_color(); raw(); scrollok(stdscr, FALSE);
Samuel Thibault633786f2019-03-03 18:25:57 +0100738 set_escdelay(25);
balrog4d3b6f62008-02-10 16:33:14 +0000739
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900740 /* Make color pair to match color format (3bits bg:3bits fg) */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900741 for (i = 0; i < 64; i++) {
balrog4d3b6f62008-02-10 16:33:14 +0000742 init_pair(i, colour_default[i & 7], colour_default[i >> 3]);
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900743 }
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900744 /* Set default color for more than 64 for safety. */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900745 for (i = 64; i < COLOR_PAIRS; i++) {
746 init_pair(i, COLOR_WHITE, COLOR_BLACK);
747 }
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +0900748
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100749 font_setup();
balrog4d3b6f62008-02-10 16:33:14 +0000750}
751
752static void curses_keyboard_setup(void)
753{
balrog4d3b6f62008-02-10 16:33:14 +0000754#if defined(__APPLE__)
755 /* always use generic keymaps */
756 if (!keyboard_layout)
757 keyboard_layout = "en-us";
758#endif
759 if(keyboard_layout) {
Fei Liab4f9312018-10-17 10:26:50 +0200760 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
761 &error_fatal);
balrog4d3b6f62008-02-10 16:33:14 +0000762 }
balrog4d3b6f62008-02-10 16:33:14 +0000763}
764
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100765static const DisplayChangeListenerOps dcl_ops = {
766 .dpy_name = "curses",
767 .dpy_text_update = curses_update,
768 .dpy_text_resize = curses_resize,
769 .dpy_refresh = curses_refresh,
770 .dpy_text_cursor = curses_cursor_position,
771};
772
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100773static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
balrog4d3b6f62008-02-10 16:33:14 +0000774{
775#ifndef _WIN32
776 if (!isatty(1)) {
777 fprintf(stderr, "We need a terminal output\n");
778 exit(1);
779 }
780#endif
781
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100782 setlocale(LC_CTYPE, "");
783 if (opts->u.curses.charset) {
784 font_charset = opts->u.curses.charset;
785 }
balrog4d3b6f62008-02-10 16:33:14 +0000786 curses_setup();
787 curses_keyboard_setup();
Anthony Liguori28695482010-03-21 14:13:02 -0500788 atexit(curses_atexit);
balrog4d3b6f62008-02-10 16:33:14 +0000789
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100790 curses_winch_init();
balrog4d3b6f62008-02-10 16:33:14 +0000791
Markus Armbrusterfedf0d32015-11-03 17:12:03 +0100792 dcl = g_new0(DisplayChangeListener, 1);
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100793 dcl->ops = &dcl_ops;
Gerd Hoffmann52090892013-04-23 15:44:31 +0200794 register_displaychangelistener(dcl);
balrog4d3b6f62008-02-10 16:33:14 +0000795
796 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000797}
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100798
799static QemuDisplay qemu_display_curses = {
800 .type = DISPLAY_TYPE_CURSES,
801 .init = curses_display_init,
802};
803
804static void register_curses(void)
805{
806 qemu_display_register(&qemu_display_curses);
807}
808
809type_init(register_curses);