1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25#include "qemu/osdep.h"
26
27#ifndef _WIN32
28#include <sys/ioctl.h>
29#include <termios.h>
30#endif
31#include <locale.h>
32#include <wchar.h>
33#include <iconv.h>
34
35#include "qapi/error.h"
36#include "qemu/module.h"
37#include "ui/console.h"
38#include "ui/input.h"
39#include "sysemu/sysemu.h"
40
41#if defined(__APPLE__) || defined(__OpenBSD__)
42#define _XOPEN_SOURCE_EXTENDED 1
43#endif
44
45
46#undef KEY_EVENT
47#include <curses.h>
48#undef KEY_EVENT
49
50#define FONT_HEIGHT 16
51#define FONT_WIDTH 8
52
53enum maybe_keycode {
54 CURSES_KEYCODE,
55 CURSES_CHAR,
56 CURSES_CHAR_OR_KEYCODE,
57};
58
59static DisplayChangeListener *dcl;
60static console_ch_t *screen;
61static WINDOW *screenpad = NULL;
62static int width, height, gwidth, gheight, invalidate;
63static int px, py, sminx, sminy, smaxx, smaxy;
64
65static const char *font_charset = "CP437";
66static cchar_t *vga_to_curses;
67
68static void curses_update(DisplayChangeListener *dcl,
69 int x, int y, int w, int h)
70{
71 console_ch_t *line;
72 g_autofree cchar_t *curses_line = g_new(cchar_t, width);
73 wchar_t wch[CCHARW_MAX];
74 attr_t attrs;
75 short colors;
76 int ret;
77
78 line = screen + y * width;
79 for (h += y; y < h; y ++, line += width) {
80 for (x = 0; x < width; x++) {
81 chtype ch = line[x] & A_CHARTEXT;
82 chtype at = line[x] & A_ATTRIBUTES;
83 short color_pair = PAIR_NUMBER(line[x]);
84
85 ret = getcchar(&vga_to_curses[ch], wch, &attrs, &colors, NULL);
86 if (ret == ERR || wch[0] == 0) {
87 wch[0] = ch;
88 wch[1] = 0;
89 }
90 setcchar(&curses_line[x], wch, at, color_pair, NULL);
91 }
92 mvwadd_wchnstr(screenpad, y, 0, curses_line, width);
93 }
94
95 pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1);
96 refresh();
97}
98
99static void curses_calc_pad(void)
100{
101 if (qemu_console_is_fixedsize(NULL)) {
102 width = gwidth;
103 height = gheight;
104 } else {
105 width = COLS;
106 height = LINES;
107 }
108
109 if (screenpad)
110 delwin(screenpad);
111
112 clear();
113 refresh();
114
115 screenpad = newpad(height, width);
116
117 if (width > COLS) {
118 px = (width - COLS) / 2;
119 sminx = 0;
120 smaxx = COLS;
121 } else {
122 px = 0;
123 sminx = (COLS - width) / 2;
124 smaxx = sminx + width;
125 }
126
127 if (height > LINES) {
128 py = (height - LINES) / 2;
129 sminy = 0;
130 smaxy = LINES;
131 } else {
132 py = 0;
133 sminy = (LINES - height) / 2;
134 smaxy = sminy + height;
135 }
136}
137
138static void curses_resize(DisplayChangeListener *dcl,
139 int width, int height)
140{
141 if (width == gwidth && height == gheight) {
142 return;
143 }
144
145 gwidth = width;
146 gheight = height;
147
148 curses_calc_pad();
149}
150
151#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE)
152static volatile sig_atomic_t got_sigwinch;
153static void curses_winch_check(void)
154{
155 struct winsize {
156 unsigned short ws_row;
157 unsigned short ws_col;
158 unsigned short ws_xpixel;
159 unsigned short ws_ypixel;
160 } ws;
161
162 if (!got_sigwinch) {
163 return;
164 }
165 got_sigwinch = false;
166
167 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
168 return;
169 }
170
171 resize_term(ws.ws_row, ws.ws_col);
172 invalidate = 1;
173}
174
175static void curses_winch_handler(int signum)
176{
177 got_sigwinch = true;
178}
179
180static void curses_winch_init(void)
181{
182 struct sigaction old, winch = {
183 .sa_handler = curses_winch_handler,
184 };
185 sigaction(SIGWINCH, &winch, &old);
186}
187#else
188static void curses_winch_check(void) {}
189static void curses_winch_init(void) {}
190#endif
191
192static void curses_cursor_position(DisplayChangeListener *dcl,
193 int x, int y)
194{
195 if (x >= 0) {
196 x = sminx + x - px;
197 y = sminy + y - py;
198
199 if (x >= 0 && y >= 0 && x < COLS && y < LINES) {
200 move(y, x);
201 curs_set(1);
202
203
204 if (!qemu_console_is_graphic(NULL)) {
205 curs_set(2);
206 }
207 return;
208 }
209 }
210
211 curs_set(0);
212}
213
214
215
216#include "curses_keys.h"
217
218static kbd_layout_t *kbd_layout = NULL;
219
220static wint_t console_getch(enum maybe_keycode *maybe_keycode)
221{
222 wint_t ret;
223 switch (get_wch(&ret)) {
224 case KEY_CODE_YES:
225 *maybe_keycode = CURSES_KEYCODE;
226 break;
227 case OK:
228 *maybe_keycode = CURSES_CHAR;
229 break;
230 case ERR:
231 ret = -1;
232 break;
233 default:
234 abort();
235 }
236 return ret;
237}
238
239static int curses2foo(const int _curses2foo[], const int _curseskey2foo[],
240 int chr, enum maybe_keycode maybe_keycode)
241{
242 int ret = -1;
243 if (maybe_keycode == CURSES_CHAR) {
244 if (chr < CURSES_CHARS) {
245 ret = _curses2foo[chr];
246 }
247 } else {
248 if (chr < CURSES_KEYS) {
249 ret = _curseskey2foo[chr];
250 }
251 if (ret == -1 && maybe_keycode == CURSES_CHAR_OR_KEYCODE &&
252 chr < CURSES_CHARS) {
253 ret = _curses2foo[chr];
254 }
255 }
256 return ret;
257}
258
259#define curses2keycode(chr, maybe_keycode) \
260 curses2foo(_curses2keycode, _curseskey2keycode, chr, maybe_keycode)
261#define curses2keysym(chr, maybe_keycode) \
262 curses2foo(_curses2keysym, _curseskey2keysym, chr, maybe_keycode)
263#define curses2qemu(chr, maybe_keycode) \
264 curses2foo(_curses2qemu, _curseskey2qemu, chr, maybe_keycode)
265
266static void curses_refresh(DisplayChangeListener *dcl)
267{
268 int chr, keysym, keycode, keycode_alt;
269 enum maybe_keycode maybe_keycode = CURSES_KEYCODE;
270
271 curses_winch_check();
272
273 if (invalidate) {
274 clear();
275 refresh();
276 curses_calc_pad();
277 graphic_hw_invalidate(NULL);
278 invalidate = 0;
279 }
280
281 graphic_hw_text_update(NULL, screen);
282
283 while (1) {
284
285 chr = console_getch(&maybe_keycode);
286
287 if (chr == -1)
288 break;
289
290#ifdef KEY_RESIZE
291
292 if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) {
293 clear();
294 refresh();
295 curses_calc_pad();
296 curses_update(dcl, 0, 0, width, height);
297 continue;
298 }
299#endif
300
301 keycode = curses2keycode(chr, maybe_keycode);
302 keycode_alt = 0;
303
304
305 if (keycode == 1) {
306 enum maybe_keycode next_maybe_keycode = CURSES_KEYCODE;
307 int nextchr = console_getch(&next_maybe_keycode);
308
309 if (nextchr != -1) {
310 chr = nextchr;
311 maybe_keycode = next_maybe_keycode;
312 keycode_alt = ALT;
313 keycode = curses2keycode(chr, maybe_keycode);
314
315 if (keycode != -1) {
316 keycode |= ALT;
317
318
319 if (keycode >= QEMU_KEY_CONSOLE0 &&
320 keycode < QEMU_KEY_CONSOLE0 + 9) {
321 erase();
322 wnoutrefresh(stdscr);
323 console_select(keycode - QEMU_KEY_CONSOLE0);
324
325 invalidate = 1;
326 continue;
327 }
328 }
329 }
330 }
331
332 if (kbd_layout) {
333 keysym = curses2keysym(chr, maybe_keycode);
334
335 if (keysym == -1) {
336 if (chr < ' ') {
337 keysym = chr + '@';
338 if (keysym >= 'A' && keysym <= 'Z')
339 keysym += 'a' - 'A';
340 keysym |= KEYSYM_CNTRL;
341 } else
342 keysym = chr;
343 }
344
345 keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK,
346 NULL, false);
347 if (keycode == 0)
348 continue;
349
350 keycode |= (keysym & ~KEYSYM_MASK) >> 16;
351 keycode |= keycode_alt;
352 }
353
354 if (keycode == -1)
355 continue;
356
357 if (qemu_console_is_graphic(NULL)) {
358
359
360 if (keycode & SHIFT) {
361 qemu_input_event_send_key_number(NULL, SHIFT_CODE, true);
362 qemu_input_event_send_key_delay(0);
363 }
364 if (keycode & CNTRL) {
365 qemu_input_event_send_key_number(NULL, CNTRL_CODE, true);
366 qemu_input_event_send_key_delay(0);
367 }
368 if (keycode & ALT) {
369 qemu_input_event_send_key_number(NULL, ALT_CODE, true);
370 qemu_input_event_send_key_delay(0);
371 }
372 if (keycode & ALTGR) {
373 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
374 qemu_input_event_send_key_delay(0);
375 }
376
377 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true);
378 qemu_input_event_send_key_delay(0);
379 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false);
380 qemu_input_event_send_key_delay(0);
381
382 if (keycode & ALTGR) {
383 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false);
384 qemu_input_event_send_key_delay(0);
385 }
386 if (keycode & ALT) {
387 qemu_input_event_send_key_number(NULL, ALT_CODE, false);
388 qemu_input_event_send_key_delay(0);
389 }
390 if (keycode & CNTRL) {
391 qemu_input_event_send_key_number(NULL, CNTRL_CODE, false);
392 qemu_input_event_send_key_delay(0);
393 }
394 if (keycode & SHIFT) {
395 qemu_input_event_send_key_number(NULL, SHIFT_CODE, false);
396 qemu_input_event_send_key_delay(0);
397 }
398 } else {
399 keysym = curses2qemu(chr, maybe_keycode);
400 if (keysym == -1)
401 keysym = chr;
402
403 kbd_put_keysym(keysym);
404 }
405 }
406}
407
408static void curses_atexit(void)
409{
410 endwin();
411 g_free(vga_to_curses);
412 g_free(screen);
413}
414
415
416
417
418
419
420
421
422
423
424static void convert_ucs(unsigned char fch, uint16_t uch, iconv_t conv)
425{
426 char mbch[MB_LEN_MAX];
427 wchar_t wch[2];
428 char *puch, *pmbch;
429 size_t such, smbch;
430 mbstate_t ps;
431
432 puch = (char *) &uch;
433 pmbch = (char *) mbch;
434 such = sizeof(uch);
435 smbch = sizeof(mbch);
436
437 if (iconv(conv, &puch, &such, &pmbch, &smbch) == (size_t) -1) {
438 fprintf(stderr, "Could not convert 0x%04x "
439 "from UCS-2 to a multibyte character: %s\n",
440 uch, strerror(errno));
441 return;
442 }
443
444 memset(&ps, 0, sizeof(ps));
445 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
446 fprintf(stderr, "Could not convert 0x%04x "
447 "from a multibyte character to wchar_t: %s\n",
448 uch, strerror(errno));
449 return;
450 }
451
452 wch[1] = 0;
453 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
454}
455
456
457static void convert_font(unsigned char fch, iconv_t conv)
458{
459 char mbch[MB_LEN_MAX];
460 wchar_t wch[2];
461 char *pfch, *pmbch;
462 size_t sfch, smbch;
463 mbstate_t ps;
464
465 pfch = (char *) &fch;
466 pmbch = (char *) &mbch;
467 sfch = sizeof(fch);
468 smbch = sizeof(mbch);
469
470 if (iconv(conv, &pfch, &sfch, &pmbch, &smbch) == (size_t) -1) {
471 fprintf(stderr, "Could not convert font glyph 0x%02x "
472 "from %s to a multibyte character: %s\n",
473 fch, font_charset, strerror(errno));
474 return;
475 }
476
477 memset(&ps, 0, sizeof(ps));
478 if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
479 fprintf(stderr, "Could not convert font glyph 0x%02x "
480 "from a multibyte character to wchar_t: %s\n",
481 fch, strerror(errno));
482 return;
483 }
484
485 wch[1] = 0;
486 setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
487}
488
489
490static uint16_t get_ucs(wchar_t wch, iconv_t conv)
491{
492 char mbch[MB_LEN_MAX];
493 uint16_t uch;
494 char *pmbch, *puch;
495 size_t smbch, such;
496 mbstate_t ps;
497 int ret;
498
499 memset(&ps, 0, sizeof(ps));
500 ret = wcrtomb(mbch, wch, &ps);
501 if (ret == -1) {
502 fprintf(stderr, "Could not convert 0x%04lx "
503 "from wchar_t to a multibyte character: %s\n",
504 (unsigned long)wch, strerror(errno));
505 return 0xFFFD;
506 }
507
508 pmbch = (char *) mbch;
509 puch = (char *) &uch;
510 smbch = ret;
511 such = sizeof(uch);
512
513 if (iconv(conv, &pmbch, &smbch, &puch, &such) == (size_t) -1) {
514 fprintf(stderr, "Could not convert 0x%04lx "
515 "from a multibyte character to UCS-2 : %s\n",
516 (unsigned long)wch, strerror(errno));
517 return 0xFFFD;
518 }
519
520 return uch;
521}
522
523
524
525
526static void font_setup(void)
527{
528 iconv_t ucs2_to_nativecharset;
529 iconv_t nativecharset_to_ucs2;
530 iconv_t font_conv;
531 int i;
532 g_autofree gchar *local_codeset = g_get_codeset();
533
534
535
536
537
538 static const uint16_t control_characters[0x20] = {
539 0x0020,
540 0x263a,
541 0x263b,
542 0x2665,
543 0x2666,
544 0x2663,
545 0x2660,
546 0x2022,
547 0x25d8,
548 0x25cb,
549 0x25d9,
550 0x2642,
551 0x2640,
552 0x266a,
553 0x266b,
554 0x263c,
555 0x25ba,
556 0x25c4,
557 0x2195,
558 0x203c,
559 0x00b6,
560 0x00a7,
561 0x25ac,
562 0x21a8,
563 0x2191,
564 0x2193,
565 0x2192,
566 0x2190,
567 0x221f,
568 0x2194,
569 0x25b2,
570 0x25bc
571 };
572
573 ucs2_to_nativecharset = iconv_open(local_codeset, "UCS-2");
574 if (ucs2_to_nativecharset == (iconv_t) -1) {
575 fprintf(stderr, "Could not convert font glyphs from UCS-2: '%s'\n",
576 strerror(errno));
577 exit(1);
578 }
579
580 nativecharset_to_ucs2 = iconv_open("UCS-2", local_codeset);
581 if (nativecharset_to_ucs2 == (iconv_t) -1) {
582 iconv_close(ucs2_to_nativecharset);
583 fprintf(stderr, "Could not convert font glyphs to UCS-2: '%s'\n",
584 strerror(errno));
585 exit(1);
586 }
587
588 font_conv = iconv_open(local_codeset, font_charset);
589 if (font_conv == (iconv_t) -1) {
590 iconv_close(ucs2_to_nativecharset);
591 iconv_close(nativecharset_to_ucs2);
592 fprintf(stderr, "Could not convert font glyphs from %s: '%s'\n",
593 font_charset, strerror(errno));
594 exit(1);
595 }
596
597
598 for (i = 0; i <= 0x1F; i++) {
599 convert_ucs(i, control_characters[i], ucs2_to_nativecharset);
600 }
601
602 for (i = 0x20; i <= 0xFF; i++) {
603 convert_font(i, font_conv);
604 }
605
606
607 convert_ucs(0x7F, 0x2302, ucs2_to_nativecharset);
608
609 if (strcmp(local_codeset, "UTF-8")) {
610
611 for (i = 0; i <= 0xFF; i++) {
612 wchar_t wch[CCHARW_MAX];
613 attr_t attr;
614 short color;
615 int ret;
616
617 ret = getcchar(&vga_to_curses[i], wch, &attr, &color, NULL);
618 if (ret == ERR)
619 continue;
620
621 switch (get_ucs(wch[0], nativecharset_to_ucs2)) {
622 case 0x00a3:
623 vga_to_curses[i] = *WACS_STERLING;
624 break;
625 case 0x2591:
626 vga_to_curses[i] = *WACS_BOARD;
627 break;
628 case 0x2592:
629 vga_to_curses[i] = *WACS_CKBOARD;
630 break;
631 case 0x2502:
632 vga_to_curses[i] = *WACS_VLINE;
633 break;
634 case 0x2524:
635 vga_to_curses[i] = *WACS_RTEE;
636 break;
637 case 0x2510:
638 vga_to_curses[i] = *WACS_URCORNER;
639 break;
640 case 0x2514:
641 vga_to_curses[i] = *WACS_LLCORNER;
642 break;
643 case 0x2534:
644 vga_to_curses[i] = *WACS_BTEE;
645 break;
646 case 0x252c:
647 vga_to_curses[i] = *WACS_TTEE;
648 break;
649 case 0x251c:
650 vga_to_curses[i] = *WACS_LTEE;
651 break;
652 case 0x2500:
653 vga_to_curses[i] = *WACS_HLINE;
654 break;
655 case 0x253c:
656 vga_to_curses[i] = *WACS_PLUS;
657 break;
658 case 0x256c:
659 vga_to_curses[i] = *WACS_LANTERN;
660 break;
661 case 0x256a:
662 vga_to_curses[i] = *WACS_NEQUAL;
663 break;
664 case 0x2518:
665 vga_to_curses[i] = *WACS_LRCORNER;
666 break;
667 case 0x250c:
668 vga_to_curses[i] = *WACS_ULCORNER;
669 break;
670 case 0x2588:
671 vga_to_curses[i] = *WACS_BLOCK;
672 break;
673 case 0x03c0:
674 vga_to_curses[i] = *WACS_PI;
675 break;
676 case 0x00b1:
677 vga_to_curses[i] = *WACS_PLMINUS;
678 break;
679 case 0x2265:
680 vga_to_curses[i] = *WACS_GEQUAL;
681 break;
682 case 0x2264:
683 vga_to_curses[i] = *WACS_LEQUAL;
684 break;
685 case 0x00b0:
686 vga_to_curses[i] = *WACS_DEGREE;
687 break;
688 case 0x25a0:
689 vga_to_curses[i] = *WACS_BULLET;
690 break;
691 case 0x2666:
692 vga_to_curses[i] = *WACS_DIAMOND;
693 break;
694 case 0x2192:
695 vga_to_curses[i] = *WACS_RARROW;
696 break;
697 case 0x2190:
698 vga_to_curses[i] = *WACS_LARROW;
699 break;
700 case 0x2191:
701 vga_to_curses[i] = *WACS_UARROW;
702 break;
703 case 0x2193:
704 vga_to_curses[i] = *WACS_DARROW;
705 break;
706 case 0x23ba:
707 vga_to_curses[i] = *WACS_S1;
708 break;
709 case 0x23bb:
710 vga_to_curses[i] = *WACS_S3;
711 break;
712 case 0x23bc:
713 vga_to_curses[i] = *WACS_S7;
714 break;
715 case 0x23bd:
716 vga_to_curses[i] = *WACS_S9;
717 break;
718 }
719 }
720 }
721 iconv_close(ucs2_to_nativecharset);
722 iconv_close(nativecharset_to_ucs2);
723 iconv_close(font_conv);
724}
725
726static void curses_setup(void)
727{
728 int i, colour_default[8] = {
729 [QEMU_COLOR_BLACK] = COLOR_BLACK,
730 [QEMU_COLOR_BLUE] = COLOR_BLUE,
731 [QEMU_COLOR_GREEN] = COLOR_GREEN,
732 [QEMU_COLOR_CYAN] = COLOR_CYAN,
733 [QEMU_COLOR_RED] = COLOR_RED,
734 [QEMU_COLOR_MAGENTA] = COLOR_MAGENTA,
735 [QEMU_COLOR_YELLOW] = COLOR_YELLOW,
736 [QEMU_COLOR_WHITE] = COLOR_WHITE,
737 };
738
739
740
741 initscr(); noecho(); intrflush(stdscr, FALSE);
742 nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE);
743 start_color(); raw(); scrollok(stdscr, FALSE);
744 set_escdelay(25);
745
746
747 for (i = 0; i < 64; i++) {
748 init_pair(i, colour_default[i & 7], colour_default[i >> 3]);
749 }
750
751 for (i = 64; i < COLOR_PAIRS; i++) {
752 init_pair(i, COLOR_WHITE, COLOR_BLACK);
753 }
754
755 font_setup();
756}
757
758static void curses_keyboard_setup(void)
759{
760#if defined(__APPLE__)
761
762 if (!keyboard_layout)
763 keyboard_layout = "en-us";
764#endif
765 if(keyboard_layout) {
766 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
767 &error_fatal);
768 }
769}
770
771static const DisplayChangeListenerOps dcl_ops = {
772 .dpy_name = "curses",
773 .dpy_text_update = curses_update,
774 .dpy_text_resize = curses_resize,
775 .dpy_refresh = curses_refresh,
776 .dpy_text_cursor = curses_cursor_position,
777};
778
779static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
780{
781#ifndef _WIN32
782 if (!isatty(1)) {
783 fprintf(stderr, "We need a terminal output\n");
784 exit(1);
785 }
786#endif
787
788 setlocale(LC_CTYPE, "");
789 if (opts->u.curses.charset) {
790 font_charset = opts->u.curses.charset;
791 }
792 screen = g_new0(console_ch_t, 160 * 100);
793 vga_to_curses = g_new0(cchar_t, 256);
794 curses_setup();
795 curses_keyboard_setup();
796 atexit(curses_atexit);
797
798 curses_winch_init();
799
800 dcl = g_new0(DisplayChangeListener, 1);
801 dcl->ops = &dcl_ops;
802 register_displaychangelistener(dcl);
803
804 invalidate = 1;
805}
806
807static QemuDisplay qemu_display_curses = {
808 .type = DISPLAY_TYPE_CURSES,
809 .init = curses_display_init,
810};
811
812static void register_curses(void)
813{
814 qemu_display_register(&qemu_display_curses);
815}
816
817type_init(register_curses);
818