1 /* 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 */ 24 #include "qemu/osdep.h" 25 26 #ifndef _WIN32 27 #include <sys/ioctl.h> 28 #include <termios.h> 29 #endif 30 31 #include "qapi/error.h" 32 #include "qemu-common.h" 33 #include "ui/console.h" 34 #include "ui/input.h" 35 #include "sysemu/sysemu.h" 36 37 /* KEY_EVENT is defined in wincon.h and in curses.h. Avoid redefinition. */ 38 #undef KEY_EVENT 39 #include <curses.h> 40 #undef KEY_EVENT 41 42 #define FONT_HEIGHT 16 43 #define FONT_WIDTH 8 44 45 enum maybe_keycode { 46 CURSES_KEYCODE, 47 CURSES_CHAR, 48 CURSES_CHAR_OR_KEYCODE, 49 }; 50 51 static DisplayChangeListener *dcl; 52 static console_ch_t screen[160 * 100]; 53 static WINDOW *screenpad = NULL; 54 static int width, height, gwidth, gheight, invalidate; 55 static int px, py, sminx, sminy, smaxx, smaxy; 56 57 static chtype vga_to_curses[256]; 58 59 static void curses_update(DisplayChangeListener *dcl, 60 int x, int y, int w, int h) 61 { 62 console_ch_t *line; 63 chtype curses_line[width]; 64 65 line = screen + y * width; 66 for (h += y; y < h; y ++, line += width) { 67 for (x = 0; x < width; x++) { 68 chtype ch = line[x] & 0xff; 69 chtype at = line[x] & ~0xff; 70 if (vga_to_curses[ch]) { 71 ch = vga_to_curses[ch]; 72 } 73 curses_line[x] = ch | at; 74 } 75 mvwaddchnstr(screenpad, y, 0, curses_line, width); 76 } 77 78 pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1); 79 refresh(); 80 } 81 82 static void curses_calc_pad(void) 83 { 84 if (qemu_console_is_fixedsize(NULL)) { 85 width = gwidth; 86 height = gheight; 87 } else { 88 width = COLS; 89 height = LINES; 90 } 91 92 if (screenpad) 93 delwin(screenpad); 94 95 clear(); 96 refresh(); 97 98 screenpad = newpad(height, width); 99 100 if (width > COLS) { 101 px = (width - COLS) / 2; 102 sminx = 0; 103 smaxx = COLS; 104 } else { 105 px = 0; 106 sminx = (COLS - width) / 2; 107 smaxx = sminx + width; 108 } 109 110 if (height > LINES) { 111 py = (height - LINES) / 2; 112 sminy = 0; 113 smaxy = LINES; 114 } else { 115 py = 0; 116 sminy = (LINES - height) / 2; 117 smaxy = sminy + height; 118 } 119 } 120 121 static void curses_resize(DisplayChangeListener *dcl, 122 int width, int height) 123 { 124 if (width == gwidth && height == gheight) { 125 return; 126 } 127 128 gwidth = width; 129 gheight = height; 130 131 curses_calc_pad(); 132 } 133 134 #if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE) 135 static volatile sig_atomic_t got_sigwinch; 136 static void curses_winch_check(void) 137 { 138 struct winsize { 139 unsigned short ws_row; 140 unsigned short ws_col; 141 unsigned short ws_xpixel; /* unused */ 142 unsigned short ws_ypixel; /* unused */ 143 } ws; 144 145 if (!got_sigwinch) { 146 return; 147 } 148 got_sigwinch = false; 149 150 if (ioctl(1, TIOCGWINSZ, &ws) == -1) { 151 return; 152 } 153 154 resize_term(ws.ws_row, ws.ws_col); 155 invalidate = 1; 156 } 157 158 static void curses_winch_handler(int signum) 159 { 160 got_sigwinch = true; 161 } 162 163 static void curses_winch_init(void) 164 { 165 struct sigaction old, winch = { 166 .sa_handler = curses_winch_handler, 167 }; 168 sigaction(SIGWINCH, &winch, &old); 169 } 170 #else 171 static void curses_winch_check(void) {} 172 static void curses_winch_init(void) {} 173 #endif 174 175 static void curses_cursor_position(DisplayChangeListener *dcl, 176 int x, int y) 177 { 178 if (x >= 0) { 179 x = sminx + x - px; 180 y = sminy + y - py; 181 182 if (x >= 0 && y >= 0 && x < COLS && y < LINES) { 183 move(y, x); 184 curs_set(1); 185 /* it seems that curs_set(1) must always be called before 186 * curs_set(2) for the latter to have effect */ 187 if (!qemu_console_is_graphic(NULL)) { 188 curs_set(2); 189 } 190 return; 191 } 192 } 193 194 curs_set(0); 195 } 196 197 /* generic keyboard conversion */ 198 199 #include "curses_keys.h" 200 201 static kbd_layout_t *kbd_layout = NULL; 202 203 static wint_t console_getch(enum maybe_keycode *maybe_keycode) 204 { 205 wint_t ret; 206 switch (get_wch(&ret)) { 207 case KEY_CODE_YES: 208 *maybe_keycode = CURSES_KEYCODE; 209 break; 210 case OK: 211 *maybe_keycode = CURSES_CHAR; 212 break; 213 case ERR: 214 ret = -1; 215 break; 216 } 217 return ret; 218 } 219 220 static int curses2foo(const int _curses2foo[], const int _curseskey2foo[], 221 int chr, enum maybe_keycode maybe_keycode) 222 { 223 int ret = -1; 224 if (maybe_keycode == CURSES_CHAR) { 225 if (chr < CURSES_CHARS) { 226 ret = _curses2foo[chr]; 227 } 228 } else { 229 if (chr < CURSES_KEYS) { 230 ret = _curseskey2foo[chr]; 231 } 232 if (ret == -1 && maybe_keycode == CURSES_CHAR_OR_KEYCODE && 233 chr < CURSES_CHARS) { 234 ret = _curses2foo[chr]; 235 } 236 } 237 return ret; 238 } 239 240 #define curses2keycode(chr, maybe_keycode) \ 241 curses2foo(_curses2keycode, _curseskey2keycode, chr, maybe_keycode) 242 #define curses2keysym(chr, maybe_keycode) \ 243 curses2foo(_curses2keysym, _curseskey2keysym, chr, maybe_keycode) 244 #define curses2qemu(chr, maybe_keycode) \ 245 curses2foo(_curses2qemu, _curseskey2qemu, chr, maybe_keycode) 246 247 static void curses_refresh(DisplayChangeListener *dcl) 248 { 249 int chr, keysym, keycode, keycode_alt; 250 enum maybe_keycode maybe_keycode; 251 252 curses_winch_check(); 253 254 if (invalidate) { 255 clear(); 256 refresh(); 257 curses_calc_pad(); 258 graphic_hw_invalidate(NULL); 259 invalidate = 0; 260 } 261 262 graphic_hw_text_update(NULL, screen); 263 264 while (1) { 265 /* while there are any pending key strokes to process */ 266 chr = console_getch(&maybe_keycode); 267 268 if (chr == -1) 269 break; 270 271 #ifdef KEY_RESIZE 272 /* this shouldn't occur when we use a custom SIGWINCH handler */ 273 if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) { 274 clear(); 275 refresh(); 276 curses_calc_pad(); 277 curses_update(dcl, 0, 0, width, height); 278 continue; 279 } 280 #endif 281 282 keycode = curses2keycode(chr, maybe_keycode); 283 keycode_alt = 0; 284 285 /* alt or esc key */ 286 if (keycode == 1) { 287 enum maybe_keycode next_maybe_keycode; 288 int nextchr = console_getch(&next_maybe_keycode); 289 290 if (nextchr != -1) { 291 chr = nextchr; 292 maybe_keycode = next_maybe_keycode; 293 keycode_alt = ALT; 294 keycode = curses2keycode(chr, maybe_keycode); 295 296 if (keycode != -1) { 297 keycode |= ALT; 298 299 /* process keys reserved for qemu */ 300 if (keycode >= QEMU_KEY_CONSOLE0 && 301 keycode < QEMU_KEY_CONSOLE0 + 9) { 302 erase(); 303 wnoutrefresh(stdscr); 304 console_select(keycode - QEMU_KEY_CONSOLE0); 305 306 invalidate = 1; 307 continue; 308 } 309 } 310 } 311 } 312 313 if (kbd_layout) { 314 keysym = curses2keysym(chr, maybe_keycode); 315 316 if (keysym == -1) { 317 if (chr < ' ') { 318 keysym = chr + '@'; 319 if (keysym >= 'A' && keysym <= 'Z') 320 keysym += 'a' - 'A'; 321 keysym |= KEYSYM_CNTRL; 322 } else 323 keysym = chr; 324 } 325 326 keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK, 327 NULL, false); 328 if (keycode == 0) 329 continue; 330 331 keycode |= (keysym & ~KEYSYM_MASK) >> 16; 332 keycode |= keycode_alt; 333 } 334 335 if (keycode == -1) 336 continue; 337 338 if (qemu_console_is_graphic(NULL)) { 339 /* since terminals don't know about key press and release 340 * events, we need to emit both for each key received */ 341 if (keycode & SHIFT) { 342 qemu_input_event_send_key_number(NULL, SHIFT_CODE, true); 343 qemu_input_event_send_key_delay(0); 344 } 345 if (keycode & CNTRL) { 346 qemu_input_event_send_key_number(NULL, CNTRL_CODE, true); 347 qemu_input_event_send_key_delay(0); 348 } 349 if (keycode & ALT) { 350 qemu_input_event_send_key_number(NULL, ALT_CODE, true); 351 qemu_input_event_send_key_delay(0); 352 } 353 if (keycode & ALTGR) { 354 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true); 355 qemu_input_event_send_key_delay(0); 356 } 357 358 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true); 359 qemu_input_event_send_key_delay(0); 360 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false); 361 qemu_input_event_send_key_delay(0); 362 363 if (keycode & ALTGR) { 364 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false); 365 qemu_input_event_send_key_delay(0); 366 } 367 if (keycode & ALT) { 368 qemu_input_event_send_key_number(NULL, ALT_CODE, false); 369 qemu_input_event_send_key_delay(0); 370 } 371 if (keycode & CNTRL) { 372 qemu_input_event_send_key_number(NULL, CNTRL_CODE, false); 373 qemu_input_event_send_key_delay(0); 374 } 375 if (keycode & SHIFT) { 376 qemu_input_event_send_key_number(NULL, SHIFT_CODE, false); 377 qemu_input_event_send_key_delay(0); 378 } 379 } else { 380 keysym = curses2qemu(chr, maybe_keycode); 381 if (keysym == -1) 382 keysym = chr; 383 384 kbd_put_keysym(keysym); 385 } 386 } 387 } 388 389 static void curses_atexit(void) 390 { 391 endwin(); 392 } 393 394 static void curses_setup(void) 395 { 396 int i, colour_default[8] = { 397 [QEMU_COLOR_BLACK] = COLOR_BLACK, 398 [QEMU_COLOR_BLUE] = COLOR_BLUE, 399 [QEMU_COLOR_GREEN] = COLOR_GREEN, 400 [QEMU_COLOR_CYAN] = COLOR_CYAN, 401 [QEMU_COLOR_RED] = COLOR_RED, 402 [QEMU_COLOR_MAGENTA] = COLOR_MAGENTA, 403 [QEMU_COLOR_YELLOW] = COLOR_YELLOW, 404 [QEMU_COLOR_WHITE] = COLOR_WHITE, 405 }; 406 407 /* input as raw as possible, let everything be interpreted 408 * by the guest system */ 409 initscr(); noecho(); intrflush(stdscr, FALSE); 410 nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE); 411 start_color(); raw(); scrollok(stdscr, FALSE); 412 set_escdelay(25); 413 414 /* Make color pair to match color format (3bits bg:3bits fg) */ 415 for (i = 0; i < 64; i++) { 416 init_pair(i, colour_default[i & 7], colour_default[i >> 3]); 417 } 418 /* Set default color for more than 64 for safety. */ 419 for (i = 64; i < COLOR_PAIRS; i++) { 420 init_pair(i, COLOR_WHITE, COLOR_BLACK); 421 } 422 423 /* 424 * Setup mapping for vga to curses line graphics. 425 * FIXME: for better font, have to use ncursesw and setlocale() 426 */ 427 #if 0 428 /* FIXME: map from where? */ 429 ACS_S1; 430 ACS_S3; 431 ACS_S7; 432 ACS_S9; 433 #endif 434 /* ACS_* is not constant. So, we can't initialize statically. */ 435 vga_to_curses['\0'] = ' '; 436 vga_to_curses[0x04] = ACS_DIAMOND; 437 vga_to_curses[0x18] = ACS_UARROW; 438 vga_to_curses[0x19] = ACS_DARROW; 439 vga_to_curses[0x1a] = ACS_RARROW; 440 vga_to_curses[0x1b] = ACS_LARROW; 441 vga_to_curses[0x9c] = ACS_STERLING; 442 vga_to_curses[0xb0] = ACS_BOARD; 443 vga_to_curses[0xb1] = ACS_CKBOARD; 444 vga_to_curses[0xb3] = ACS_VLINE; 445 vga_to_curses[0xb4] = ACS_RTEE; 446 vga_to_curses[0xbf] = ACS_URCORNER; 447 vga_to_curses[0xc0] = ACS_LLCORNER; 448 vga_to_curses[0xc1] = ACS_BTEE; 449 vga_to_curses[0xc2] = ACS_TTEE; 450 vga_to_curses[0xc3] = ACS_LTEE; 451 vga_to_curses[0xc4] = ACS_HLINE; 452 vga_to_curses[0xc5] = ACS_PLUS; 453 vga_to_curses[0xce] = ACS_LANTERN; 454 vga_to_curses[0xd8] = ACS_NEQUAL; 455 vga_to_curses[0xd9] = ACS_LRCORNER; 456 vga_to_curses[0xda] = ACS_ULCORNER; 457 vga_to_curses[0xdb] = ACS_BLOCK; 458 vga_to_curses[0xe3] = ACS_PI; 459 vga_to_curses[0xf1] = ACS_PLMINUS; 460 vga_to_curses[0xf2] = ACS_GEQUAL; 461 vga_to_curses[0xf3] = ACS_LEQUAL; 462 vga_to_curses[0xf8] = ACS_DEGREE; 463 vga_to_curses[0xfe] = ACS_BULLET; 464 } 465 466 static void curses_keyboard_setup(void) 467 { 468 #if defined(__APPLE__) 469 /* always use generic keymaps */ 470 if (!keyboard_layout) 471 keyboard_layout = "en-us"; 472 #endif 473 if(keyboard_layout) { 474 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout, 475 &error_fatal); 476 } 477 } 478 479 static const DisplayChangeListenerOps dcl_ops = { 480 .dpy_name = "curses", 481 .dpy_text_update = curses_update, 482 .dpy_text_resize = curses_resize, 483 .dpy_refresh = curses_refresh, 484 .dpy_text_cursor = curses_cursor_position, 485 }; 486 487 static void curses_display_init(DisplayState *ds, DisplayOptions *opts) 488 { 489 #ifndef _WIN32 490 if (!isatty(1)) { 491 fprintf(stderr, "We need a terminal output\n"); 492 exit(1); 493 } 494 #endif 495 496 curses_setup(); 497 curses_keyboard_setup(); 498 atexit(curses_atexit); 499 500 curses_winch_init(); 501 502 dcl = g_new0(DisplayChangeListener, 1); 503 dcl->ops = &dcl_ops; 504 register_displaychangelistener(dcl); 505 506 invalidate = 1; 507 } 508 509 static QemuDisplay qemu_display_curses = { 510 .type = DISPLAY_TYPE_CURSES, 511 .init = curses_display_init, 512 }; 513 514 static void register_curses(void) 515 { 516 qemu_display_register(&qemu_display_curses); 517 } 518 519 type_init(register_curses); 520