blob: ec281125acbdf28230d1808f68f2feaa24892aad [file] [log] [blame]
balrog4d3b6f62008-02-10 16:33:14 +00001/*
2 * QEMU curses/ncurses display driver
3 *
4 * Copyright (c) 2005 Andrzej Zaborowski <balrog@zabor.org>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
Markus Armbruster0b8fa322019-05-23 16:35:07 +020024
Peter Maydelle16f4c82016-01-29 17:49:51 +000025#include "qemu/osdep.h"
balrog4d3b6f62008-02-10 16:33:14 +000026
27#ifndef _WIN32
balrog4d3b6f62008-02-10 16:33:14 +000028#include <sys/ioctl.h>
29#include <termios.h>
30#endif
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010031#include <locale.h>
32#include <wchar.h>
33#include <langinfo.h>
34#include <iconv.h>
balrog4d3b6f62008-02-10 16:33:14 +000035
Fei Liab4f9312018-10-17 10:26:50 +020036#include "qapi/error.h"
Markus Armbruster0b8fa322019-05-23 16:35:07 +020037#include "qemu/module.h"
Paolo Bonzini28ecbae2012-11-28 12:06:30 +010038#include "ui/console.h"
Gerd Hoffmanncd100322013-12-04 13:40:20 +010039#include "ui/input.h"
Paolo Bonzini9c17d612012-12-17 18:20:04 +010040#include "sysemu/sysemu.h"
blueswir1511d2b12009-03-07 15:32:56 +000041
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020042/* KEY_EVENT is defined in wincon.h and in curses.h. Avoid redefinition. */
43#undef KEY_EVENT
44#include <curses.h>
45#undef KEY_EVENT
46
balrog4d3b6f62008-02-10 16:33:14 +000047#define FONT_HEIGHT 16
48#define FONT_WIDTH 8
49
Samuel Thibault459a7072019-03-04 22:05:32 +010050enum maybe_keycode {
51 CURSES_KEYCODE,
52 CURSES_CHAR,
53 CURSES_CHAR_OR_KEYCODE,
54};
55
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010056static DisplayChangeListener *dcl;
Anthony Liguoric227f092009-10-01 16:12:16 -050057static console_ch_t screen[160 * 100];
balrog4d3b6f62008-02-10 16:33:14 +000058static WINDOW *screenpad = NULL;
59static int width, height, gwidth, gheight, invalidate;
60static int px, py, sminx, sminy, smaxx, smaxy;
61
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010062static const char *font_charset = "CP437";
63static cchar_t vga_to_curses[256];
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +090064
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010065static void curses_update(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +010066 int x, int y, int w, int h)
balrog4d3b6f62008-02-10 16:33:14 +000067{
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020068 console_ch_t *line;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010069 cchar_t curses_line[width];
Samuel Thibault962cf8f2019-04-27 20:33:07 +020070 wchar_t wch[CCHARW_MAX];
71 attr_t attrs;
72 short colors;
73 int ret;
balrog4d3b6f62008-02-10 16:33:14 +000074
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020075 line = screen + y * width;
76 for (h += y; y < h; y ++, line += width) {
77 for (x = 0; x < width; x++) {
78 chtype ch = line[x] & 0xff;
79 chtype at = line[x] & ~0xff;
Samuel Thibault962cf8f2019-04-27 20:33:07 +020080 ret = getcchar(&vga_to_curses[ch], wch, &attrs, &colors, NULL);
81 if (ret == ERR || wch[0] == 0) {
82 wch[0] = ch;
83 wch[1] = 0;
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020084 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +020085 setcchar(&curses_line[x], wch, at, 0, NULL);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020086 }
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +010087 mvwadd_wchnstr(screenpad, y, 0, curses_line, width);
Gerd Hoffmanne2f82e92017-09-27 12:38:11 +020088 }
balrog4d3b6f62008-02-10 16:33:14 +000089
90 pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1);
91 refresh();
92}
93
94static void curses_calc_pad(void)
95{
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +010096 if (qemu_console_is_fixedsize(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +000097 width = gwidth;
98 height = gheight;
99 } else {
100 width = COLS;
101 height = LINES;
102 }
103
104 if (screenpad)
105 delwin(screenpad);
106
107 clear();
108 refresh();
109
110 screenpad = newpad(height, width);
111
112 if (width > COLS) {
113 px = (width - COLS) / 2;
114 sminx = 0;
115 smaxx = COLS;
116 } else {
117 px = 0;
118 sminx = (COLS - width) / 2;
119 smaxx = sminx + width;
120 }
121
122 if (height > LINES) {
123 py = (height - LINES) / 2;
124 sminy = 0;
125 smaxy = LINES;
126 } else {
127 py = 0;
128 sminy = (LINES - height) / 2;
129 smaxy = sminy + height;
130 }
131}
132
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100133static void curses_resize(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100134 int width, int height)
balrog4d3b6f62008-02-10 16:33:14 +0000135{
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200136 if (width == gwidth && height == gheight) {
balrog4d3b6f62008-02-10 16:33:14 +0000137 return;
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200138 }
balrog4d3b6f62008-02-10 16:33:14 +0000139
Gerd Hoffmanna93a4a22012-09-28 15:02:08 +0200140 gwidth = width;
141 gheight = height;
balrog4d3b6f62008-02-10 16:33:14 +0000142
143 curses_calc_pad();
144}
145
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100146#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE)
147static volatile sig_atomic_t got_sigwinch;
148static void curses_winch_check(void)
balrog4d3b6f62008-02-10 16:33:14 +0000149{
150 struct winsize {
151 unsigned short ws_row;
152 unsigned short ws_col;
153 unsigned short ws_xpixel; /* unused */
154 unsigned short ws_ypixel; /* unused */
155 } ws;
156
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100157 if (!got_sigwinch) {
balrog4d3b6f62008-02-10 16:33:14 +0000158 return;
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100159 }
160 got_sigwinch = false;
161
162 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
163 return;
164 }
balrog4d3b6f62008-02-10 16:33:14 +0000165
166 resize_term(ws.ws_row, ws.ws_col);
balrog4d3b6f62008-02-10 16:33:14 +0000167 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000168}
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100169
170static void curses_winch_handler(int signum)
171{
172 got_sigwinch = true;
173}
174
175static void curses_winch_init(void)
176{
177 struct sigaction old, winch = {
178 .sa_handler = curses_winch_handler,
179 };
180 sigaction(SIGWINCH, &winch, &old);
181}
182#else
183static void curses_winch_check(void) {}
184static void curses_winch_init(void) {}
balrog4d3b6f62008-02-10 16:33:14 +0000185#endif
186
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100187static void curses_cursor_position(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100188 int x, int y)
balrog4d3b6f62008-02-10 16:33:14 +0000189{
190 if (x >= 0) {
191 x = sminx + x - px;
192 y = sminy + y - py;
193
194 if (x >= 0 && y >= 0 && x < COLS && y < LINES) {
195 move(y, x);
196 curs_set(1);
197 /* it seems that curs_set(1) must always be called before
198 * curs_set(2) for the latter to have effect */
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100199 if (!qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000200 curs_set(2);
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100201 }
balrog4d3b6f62008-02-10 16:33:14 +0000202 return;
203 }
204 }
205
206 curs_set(0);
207}
208
209/* generic keyboard conversion */
210
211#include "curses_keys.h"
balrog4d3b6f62008-02-10 16:33:14 +0000212
Anthony Liguoric227f092009-10-01 16:12:16 -0500213static kbd_layout_t *kbd_layout = NULL;
balrog4d3b6f62008-02-10 16:33:14 +0000214
Samuel Thibault459a7072019-03-04 22:05:32 +0100215static wint_t console_getch(enum maybe_keycode *maybe_keycode)
216{
217 wint_t ret;
218 switch (get_wch(&ret)) {
219 case KEY_CODE_YES:
220 *maybe_keycode = CURSES_KEYCODE;
221 break;
222 case OK:
223 *maybe_keycode = CURSES_CHAR;
224 break;
225 case ERR:
226 ret = -1;
227 break;
Paolo Bonzini68097ed2019-07-18 14:01:04 +0200228 default:
229 abort();
Samuel Thibault459a7072019-03-04 22:05:32 +0100230 }
231 return ret;
232}
233
234static int curses2foo(const int _curses2foo[], const int _curseskey2foo[],
235 int chr, enum maybe_keycode maybe_keycode)
236{
237 int ret = -1;
238 if (maybe_keycode == CURSES_CHAR) {
239 if (chr < CURSES_CHARS) {
240 ret = _curses2foo[chr];
241 }
242 } else {
243 if (chr < CURSES_KEYS) {
244 ret = _curseskey2foo[chr];
245 }
246 if (ret == -1 && maybe_keycode == CURSES_CHAR_OR_KEYCODE &&
247 chr < CURSES_CHARS) {
248 ret = _curses2foo[chr];
249 }
250 }
251 return ret;
252}
253
254#define curses2keycode(chr, maybe_keycode) \
255 curses2foo(_curses2keycode, _curseskey2keycode, chr, maybe_keycode)
256#define curses2keysym(chr, maybe_keycode) \
257 curses2foo(_curses2keysym, _curseskey2keysym, chr, maybe_keycode)
258#define curses2qemu(chr, maybe_keycode) \
259 curses2foo(_curses2qemu, _curseskey2qemu, chr, maybe_keycode)
260
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100261static void curses_refresh(DisplayChangeListener *dcl)
balrog4d3b6f62008-02-10 16:33:14 +0000262{
Peter Maydell99a9ef42016-08-11 15:23:27 +0100263 int chr, keysym, keycode, keycode_alt;
Samuel Thibault459a7072019-03-04 22:05:32 +0100264 enum maybe_keycode maybe_keycode;
balrog4d3b6f62008-02-10 16:33:14 +0000265
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100266 curses_winch_check();
267
balrog4d3b6f62008-02-10 16:33:14 +0000268 if (invalidate) {
269 clear();
270 refresh();
271 curses_calc_pad();
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100272 graphic_hw_invalidate(NULL);
balrog4d3b6f62008-02-10 16:33:14 +0000273 invalidate = 0;
274 }
275
Gerd Hoffmann1dbfa002013-03-12 13:44:38 +0100276 graphic_hw_text_update(NULL, screen);
balrog4d3b6f62008-02-10 16:33:14 +0000277
balrog4d3b6f62008-02-10 16:33:14 +0000278 while (1) {
279 /* while there are any pending key strokes to process */
Samuel Thibault459a7072019-03-04 22:05:32 +0100280 chr = console_getch(&maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000281
Samuel Thibault459a7072019-03-04 22:05:32 +0100282 if (chr == -1)
balrog4d3b6f62008-02-10 16:33:14 +0000283 break;
284
balrogb1314cf2008-02-22 18:21:28 +0000285#ifdef KEY_RESIZE
balrog4d3b6f62008-02-10 16:33:14 +0000286 /* this shouldn't occur when we use a custom SIGWINCH handler */
Samuel Thibault459a7072019-03-04 22:05:32 +0100287 if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) {
balrog4d3b6f62008-02-10 16:33:14 +0000288 clear();
289 refresh();
290 curses_calc_pad();
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100291 curses_update(dcl, 0, 0, width, height);
balrog4d3b6f62008-02-10 16:33:14 +0000292 continue;
293 }
balrogb1314cf2008-02-22 18:21:28 +0000294#endif
balrog4d3b6f62008-02-10 16:33:14 +0000295
Samuel Thibault459a7072019-03-04 22:05:32 +0100296 keycode = curses2keycode(chr, maybe_keycode);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100297 keycode_alt = 0;
balrog4d3b6f62008-02-10 16:33:14 +0000298
Samuel Thibault633786f2019-03-03 18:25:57 +0100299 /* alt or esc key */
balrog4d3b6f62008-02-10 16:33:14 +0000300 if (keycode == 1) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100301 enum maybe_keycode next_maybe_keycode;
302 int nextchr = console_getch(&next_maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000303
Samuel Thibault459a7072019-03-04 22:05:32 +0100304 if (nextchr != -1) {
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100305 chr = nextchr;
Samuel Thibault459a7072019-03-04 22:05:32 +0100306 maybe_keycode = next_maybe_keycode;
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100307 keycode_alt = ALT;
Samuel Thibault459a7072019-03-04 22:05:32 +0100308 keycode = curses2keycode(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000309
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100310 if (keycode != -1) {
311 keycode |= ALT;
balrog4d3b6f62008-02-10 16:33:14 +0000312
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100313 /* process keys reserved for qemu */
314 if (keycode >= QEMU_KEY_CONSOLE0 &&
315 keycode < QEMU_KEY_CONSOLE0 + 9) {
316 erase();
317 wnoutrefresh(stdscr);
318 console_select(keycode - QEMU_KEY_CONSOLE0);
balrog4d3b6f62008-02-10 16:33:14 +0000319
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100320 invalidate = 1;
321 continue;
322 }
balrog4d3b6f62008-02-10 16:33:14 +0000323 }
324 }
325 }
326
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100327 if (kbd_layout) {
Samuel Thibault459a7072019-03-04 22:05:32 +0100328 keysym = curses2keysym(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000329
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100330 if (keysym == -1) {
Samuel Thibaultd03703c2010-10-19 19:48:20 +0200331 if (chr < ' ') {
332 keysym = chr + '@';
333 if (keysym >= 'A' && keysym <= 'Z')
334 keysym += 'a' - 'A';
335 keysym |= KEYSYM_CNTRL;
336 } else
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100337 keysym = chr;
338 }
339
Gerd Hoffmannabb4f2c2018-02-22 08:05:13 +0100340 keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK,
Gerd Hoffmann19c1b9f2019-01-22 10:28:14 +0100341 NULL, false);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100342 if (keycode == 0)
343 continue;
344
345 keycode |= (keysym & ~KEYSYM_MASK) >> 16;
346 keycode |= keycode_alt;
balrog4d3b6f62008-02-10 16:33:14 +0000347 }
348
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100349 if (keycode == -1)
350 continue;
351
Gerd Hoffmann81c0d5a2013-03-14 14:27:08 +0100352 if (qemu_console_is_graphic(NULL)) {
balrog4d3b6f62008-02-10 16:33:14 +0000353 /* since terminals don't know about key press and release
354 * events, we need to emit both for each key received */
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100355 if (keycode & SHIFT) {
356 qemu_input_event_send_key_number(NULL, SHIFT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200357 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100358 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100359 if (keycode & CNTRL) {
360 qemu_input_event_send_key_number(NULL, CNTRL_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200361 qemu_input_event_send_key_delay(0);
Samuel Thibault44bb61c2010-02-28 21:03:00 +0100362 }
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100363 if (keycode & ALT) {
364 qemu_input_event_send_key_number(NULL, ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200365 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100366 }
367 if (keycode & ALTGR) {
368 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200369 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100370 }
371
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400372 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200373 qemu_input_event_send_key_delay(0);
Andrew Oatesf5c0ab12014-05-23 20:16:09 -0400374 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200375 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100376
377 if (keycode & ALTGR) {
378 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200379 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100380 }
381 if (keycode & ALT) {
382 qemu_input_event_send_key_number(NULL, ALT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200383 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100384 }
385 if (keycode & CNTRL) {
386 qemu_input_event_send_key_number(NULL, CNTRL_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200387 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100388 }
389 if (keycode & SHIFT) {
390 qemu_input_event_send_key_number(NULL, SHIFT_CODE, false);
Gerd Hoffmann5a165662014-05-28 13:05:04 +0200391 qemu_input_event_send_key_delay(0);
Gerd Hoffmanncd100322013-12-04 13:40:20 +0100392 }
balrog4d3b6f62008-02-10 16:33:14 +0000393 } else {
Samuel Thibault459a7072019-03-04 22:05:32 +0100394 keysym = curses2qemu(chr, maybe_keycode);
balrog4d3b6f62008-02-10 16:33:14 +0000395 if (keysym == -1)
396 keysym = chr;
397
398 kbd_put_keysym(keysym);
399 }
400 }
401}
402
Blue Swirlaaf12c22010-03-21 19:44:06 +0000403static void curses_atexit(void)
balrog4d3b6f62008-02-10 16:33:14 +0000404{
405 endwin();
406}
407
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200408/*
409 * In the following:
410 * - fch is the font glyph number
411 * - uch is the unicode value
412 * - wch is the wchar_t value (may not be unicode, e.g. on BSD/solaris)
413 * - mbch is the native local-dependent multibyte representation
414 */
415
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100416/* Setup wchar glyph for one UCS-2 char */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200417static void convert_ucs(unsigned char fch, uint16_t uch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100418{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200419 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200420 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200421 char *puch, *pmbch;
422 size_t such, smbch;
423 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100424
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200425 puch = (char *) &uch;
426 pmbch = (char *) mbch;
427 such = sizeof(uch);
428 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100429
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200430 if (iconv(conv, &puch, &such, &pmbch, &smbch) == (size_t) -1) {
431 fprintf(stderr, "Could not convert 0x%04x "
432 "from UCS-2 to a multibyte character: %s\n",
433 uch, strerror(errno));
434 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100435 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200436
437 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200438 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200439 fprintf(stderr, "Could not convert 0x%04x "
440 "from a multibyte character to wchar_t: %s\n",
441 uch, strerror(errno));
442 return;
443 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200444
445 wch[1] = 0;
446 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100447}
448
449/* Setup wchar glyph for one font character */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200450static void convert_font(unsigned char fch, iconv_t conv)
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100451{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200452 char mbch[MB_LEN_MAX];
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200453 wchar_t wch[2];
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200454 char *pfch, *pmbch;
455 size_t sfch, smbch;
456 mbstate_t ps;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100457
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200458 pfch = (char *) &fch;
459 pmbch = (char *) &mbch;
460 sfch = sizeof(fch);
461 smbch = sizeof(mbch);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100462
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200463 if (iconv(conv, &pfch, &sfch, &pmbch, &smbch) == (size_t) -1) {
464 fprintf(stderr, "Could not convert font glyph 0x%02x "
465 "from %s to a multibyte character: %s\n",
466 fch, font_charset, strerror(errno));
467 return;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100468 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200469
470 memset(&ps, 0, sizeof(ps));
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200471 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200472 fprintf(stderr, "Could not convert font glyph 0x%02x "
473 "from a multibyte character to wchar_t: %s\n",
474 fch, strerror(errno));
475 return;
476 }
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200477
478 wch[1] = 0;
479 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100480}
481
482/* Convert one wchar to UCS-2 */
483static uint16_t get_ucs(wchar_t wch, iconv_t conv)
484{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200485 char mbch[MB_LEN_MAX];
486 uint16_t uch;
487 char *pmbch, *puch;
488 size_t smbch, such;
489 mbstate_t ps;
490 int ret;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100491
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200492 memset(&ps, 0, sizeof(ps));
493 ret = wcrtomb(mbch, wch, &ps);
494 if (ret == -1) {
Max Reitzdc3c8712019-05-27 16:25:40 +0200495 fprintf(stderr, "Could not convert 0x%04lx "
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200496 "from wchar_t to a multibyte character: %s\n",
Max Reitzdc3c8712019-05-27 16:25:40 +0200497 (unsigned long)wch, strerror(errno));
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100498 return 0xFFFD;
499 }
500
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200501 pmbch = (char *) mbch;
502 puch = (char *) &uch;
503 smbch = ret;
504 such = sizeof(uch);
505
506 if (iconv(conv, &pmbch, &smbch, &puch, &such) == (size_t) -1) {
Max Reitzdc3c8712019-05-27 16:25:40 +0200507 fprintf(stderr, "Could not convert 0x%04lx "
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200508 "from a multibyte character to UCS-2 : %s\n",
Max Reitzdc3c8712019-05-27 16:25:40 +0200509 (unsigned long)wch, strerror(errno));
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200510 return 0xFFFD;
511 }
512
513 return uch;
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100514}
515
516/*
517 * Setup mapping for vga to curses line graphics.
518 */
519static void font_setup(void)
520{
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200521 iconv_t ucs2_to_nativecharset;
522 iconv_t nativecharset_to_ucs2;
523 iconv_t font_conv;
524 int i;
525
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100526 /*
527 * Control characters are normally non-printable, but VGA does have
528 * well-known glyphs for them.
529 */
530 static uint16_t control_characters[0x20] = {
531 0x0020,
532 0x263a,
533 0x263b,
534 0x2665,
535 0x2666,
536 0x2663,
537 0x2660,
538 0x2022,
539 0x25d8,
540 0x25cb,
541 0x25d9,
542 0x2642,
543 0x2640,
544 0x266a,
545 0x266b,
546 0x263c,
547 0x25ba,
548 0x25c4,
549 0x2195,
550 0x203c,
551 0x00b6,
552 0x00a7,
553 0x25ac,
554 0x21a8,
555 0x2191,
556 0x2193,
557 0x2192,
558 0x2190,
559 0x221f,
560 0x2194,
561 0x25b2,
562 0x25bc
563 };
564
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200565 ucs2_to_nativecharset = iconv_open(nl_langinfo(CODESET), "UCS-2");
566 if (ucs2_to_nativecharset == (iconv_t) -1) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100567 fprintf(stderr, "Could not convert font glyphs from UCS-2: '%s'\n",
568 strerror(errno));
569 exit(1);
570 }
571
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200572 nativecharset_to_ucs2 = iconv_open("UCS-2", nl_langinfo(CODESET));
573 if (nativecharset_to_ucs2 == (iconv_t) -1) {
574 iconv_close(ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100575 fprintf(stderr, "Could not convert font glyphs to UCS-2: '%s'\n",
576 strerror(errno));
577 exit(1);
578 }
579
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200580 font_conv = iconv_open(nl_langinfo(CODESET), font_charset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100581 if (font_conv == (iconv_t) -1) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200582 iconv_close(ucs2_to_nativecharset);
583 iconv_close(nativecharset_to_ucs2);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100584 fprintf(stderr, "Could not convert font glyphs from %s: '%s'\n",
585 font_charset, strerror(errno));
586 exit(1);
587 }
588
589 /* Control characters */
590 for (i = 0; i <= 0x1F; i++) {
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200591 convert_ucs(i, control_characters[i], ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100592 }
593
594 for (i = 0x20; i <= 0xFF; i++) {
595 convert_font(i, font_conv);
596 }
597
598 /* DEL */
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200599 convert_ucs(0x7F, 0x2302, ucs2_to_nativecharset);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100600
601 if (strcmp(nl_langinfo(CODESET), "UTF-8")) {
602 /* Non-Unicode capable, use termcap equivalents for those available */
603 for (i = 0; i <= 0xFF; i++) {
Samuel Thibault962cf8f2019-04-27 20:33:07 +0200604 wchar_t wch[CCHARW_MAX];
605 attr_t attr;
606 short color;
607 int ret;
608
609 ret = getcchar(&vga_to_curses[i], wch, &attr, &color, NULL);
610 if (ret == ERR)
611 continue;
612
613 switch (get_ucs(wch[0], nativecharset_to_ucs2)) {
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100614 case 0x00a3:
615 vga_to_curses[i] = *WACS_STERLING;
616 break;
617 case 0x2591:
618 vga_to_curses[i] = *WACS_BOARD;
619 break;
620 case 0x2592:
621 vga_to_curses[i] = *WACS_CKBOARD;
622 break;
623 case 0x2502:
624 vga_to_curses[i] = *WACS_VLINE;
625 break;
626 case 0x2524:
627 vga_to_curses[i] = *WACS_RTEE;
628 break;
629 case 0x2510:
630 vga_to_curses[i] = *WACS_URCORNER;
631 break;
632 case 0x2514:
633 vga_to_curses[i] = *WACS_LLCORNER;
634 break;
635 case 0x2534:
636 vga_to_curses[i] = *WACS_BTEE;
637 break;
638 case 0x252c:
639 vga_to_curses[i] = *WACS_TTEE;
640 break;
641 case 0x251c:
642 vga_to_curses[i] = *WACS_LTEE;
643 break;
644 case 0x2500:
645 vga_to_curses[i] = *WACS_HLINE;
646 break;
647 case 0x253c:
648 vga_to_curses[i] = *WACS_PLUS;
649 break;
650 case 0x256c:
651 vga_to_curses[i] = *WACS_LANTERN;
652 break;
653 case 0x256a:
654 vga_to_curses[i] = *WACS_NEQUAL;
655 break;
656 case 0x2518:
657 vga_to_curses[i] = *WACS_LRCORNER;
658 break;
659 case 0x250c:
660 vga_to_curses[i] = *WACS_ULCORNER;
661 break;
662 case 0x2588:
663 vga_to_curses[i] = *WACS_BLOCK;
664 break;
665 case 0x03c0:
666 vga_to_curses[i] = *WACS_PI;
667 break;
668 case 0x00b1:
669 vga_to_curses[i] = *WACS_PLMINUS;
670 break;
671 case 0x2265:
672 vga_to_curses[i] = *WACS_GEQUAL;
673 break;
674 case 0x2264:
675 vga_to_curses[i] = *WACS_LEQUAL;
676 break;
677 case 0x00b0:
678 vga_to_curses[i] = *WACS_DEGREE;
679 break;
680 case 0x25a0:
681 vga_to_curses[i] = *WACS_BULLET;
682 break;
683 case 0x2666:
684 vga_to_curses[i] = *WACS_DIAMOND;
685 break;
686 case 0x2192:
687 vga_to_curses[i] = *WACS_RARROW;
688 break;
689 case 0x2190:
690 vga_to_curses[i] = *WACS_LARROW;
691 break;
692 case 0x2191:
693 vga_to_curses[i] = *WACS_UARROW;
694 break;
695 case 0x2193:
696 vga_to_curses[i] = *WACS_DARROW;
697 break;
698 case 0x23ba:
699 vga_to_curses[i] = *WACS_S1;
700 break;
701 case 0x23bb:
702 vga_to_curses[i] = *WACS_S3;
703 break;
704 case 0x23bc:
705 vga_to_curses[i] = *WACS_S7;
706 break;
707 case 0x23bd:
708 vga_to_curses[i] = *WACS_S9;
709 break;
710 }
711 }
712 }
Samuel Thibaultb7b664a2019-04-27 20:33:06 +0200713 iconv_close(ucs2_to_nativecharset);
714 iconv_close(nativecharset_to_ucs2);
Samuel Thibaulta9fda242019-03-14 18:25:24 +0100715 iconv_close(font_conv);
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100716}
717
balrog4d3b6f62008-02-10 16:33:14 +0000718static void curses_setup(void)
719{
720 int i, colour_default[8] = {
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900721 [QEMU_COLOR_BLACK] = COLOR_BLACK,
722 [QEMU_COLOR_BLUE] = COLOR_BLUE,
723 [QEMU_COLOR_GREEN] = COLOR_GREEN,
724 [QEMU_COLOR_CYAN] = COLOR_CYAN,
725 [QEMU_COLOR_RED] = COLOR_RED,
726 [QEMU_COLOR_MAGENTA] = COLOR_MAGENTA,
727 [QEMU_COLOR_YELLOW] = COLOR_YELLOW,
728 [QEMU_COLOR_WHITE] = COLOR_WHITE,
balrog4d3b6f62008-02-10 16:33:14 +0000729 };
730
731 /* input as raw as possible, let everything be interpreted
732 * by the guest system */
733 initscr(); noecho(); intrflush(stdscr, FALSE);
734 nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE);
735 start_color(); raw(); scrollok(stdscr, FALSE);
Samuel Thibault633786f2019-03-03 18:25:57 +0100736 set_escdelay(25);
balrog4d3b6f62008-02-10 16:33:14 +0000737
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900738 /* Make color pair to match color format (3bits bg:3bits fg) */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900739 for (i = 0; i < 64; i++) {
balrog4d3b6f62008-02-10 16:33:14 +0000740 init_pair(i, colour_default[i & 7], colour_default[i >> 3]);
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900741 }
OGAWA Hirofumi40837332015-11-29 22:28:24 +0900742 /* Set default color for more than 64 for safety. */
OGAWA Hirofumi615220d2015-10-19 21:23:10 +0900743 for (i = 64; i < COLOR_PAIRS; i++) {
744 init_pair(i, COLOR_WHITE, COLOR_BLACK);
745 }
OGAWA Hirofumie2368dc2015-10-19 21:23:46 +0900746
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100747 font_setup();
balrog4d3b6f62008-02-10 16:33:14 +0000748}
749
750static void curses_keyboard_setup(void)
751{
balrog4d3b6f62008-02-10 16:33:14 +0000752#if defined(__APPLE__)
753 /* always use generic keymaps */
754 if (!keyboard_layout)
755 keyboard_layout = "en-us";
756#endif
757 if(keyboard_layout) {
Fei Liab4f9312018-10-17 10:26:50 +0200758 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
759 &error_fatal);
balrog4d3b6f62008-02-10 16:33:14 +0000760 }
balrog4d3b6f62008-02-10 16:33:14 +0000761}
762
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100763static const DisplayChangeListenerOps dcl_ops = {
764 .dpy_name = "curses",
765 .dpy_text_update = curses_update,
766 .dpy_text_resize = curses_resize,
767 .dpy_refresh = curses_refresh,
768 .dpy_text_cursor = curses_cursor_position,
769};
770
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100771static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
balrog4d3b6f62008-02-10 16:33:14 +0000772{
773#ifndef _WIN32
774 if (!isatty(1)) {
775 fprintf(stderr, "We need a terminal output\n");
776 exit(1);
777 }
778#endif
779
Samuel Thibault2f8b7cd2019-03-11 14:51:27 +0100780 setlocale(LC_CTYPE, "");
781 if (opts->u.curses.charset) {
782 font_charset = opts->u.curses.charset;
783 }
balrog4d3b6f62008-02-10 16:33:14 +0000784 curses_setup();
785 curses_keyboard_setup();
Anthony Liguori28695482010-03-21 14:13:02 -0500786 atexit(curses_atexit);
balrog4d3b6f62008-02-10 16:33:14 +0000787
Gerd Hoffmann032ac6f2013-11-22 15:35:03 +0100788 curses_winch_init();
balrog4d3b6f62008-02-10 16:33:14 +0000789
Markus Armbrusterfedf0d32015-11-03 17:12:03 +0100790 dcl = g_new0(DisplayChangeListener, 1);
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +0100791 dcl->ops = &dcl_ops;
Gerd Hoffmann52090892013-04-23 15:44:31 +0200792 register_displaychangelistener(dcl);
balrog4d3b6f62008-02-10 16:33:14 +0000793
794 invalidate = 1;
balrog4d3b6f62008-02-10 16:33:14 +0000795}
Gerd Hoffmannb0766612018-03-01 11:05:38 +0100796
797static QemuDisplay qemu_display_curses = {
798 .type = DISPLAY_TYPE_CURSES,
799 .init = curses_display_init,
800};
801
802static void register_curses(void)
803{
804 qemu_display_register(&qemu_display_curses);
805}
806
807type_init(register_curses);