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 <curses.h> 25 26 #ifndef _WIN32 27 #include <sys/ioctl.h> 28 #include <termios.h> 29 #endif 30 31 #include "qemu-common.h" 32 #include "ui/console.h" 33 #include "ui/input.h" 34 #include "sysemu/sysemu.h" 35 36 #define FONT_HEIGHT 16 37 #define FONT_WIDTH 8 38 39 static DisplayChangeListener *dcl; 40 static console_ch_t screen[160 * 100]; 41 static WINDOW *screenpad = NULL; 42 static int width, height, gwidth, gheight, invalidate; 43 static int px, py, sminx, sminy, smaxx, smaxy; 44 45 static void curses_update(DisplayChangeListener *dcl, 46 int x, int y, int w, int h) 47 { 48 chtype *line; 49 50 line = ((chtype *) screen) + y * width; 51 for (h += y; y < h; y ++, line += width) 52 mvwaddchnstr(screenpad, y, 0, line, width); 53 54 pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1); 55 refresh(); 56 } 57 58 static void curses_calc_pad(void) 59 { 60 if (qemu_console_is_fixedsize(NULL)) { 61 width = gwidth; 62 height = gheight; 63 } else { 64 width = COLS; 65 height = LINES; 66 } 67 68 if (screenpad) 69 delwin(screenpad); 70 71 clear(); 72 refresh(); 73 74 screenpad = newpad(height, width); 75 76 if (width > COLS) { 77 px = (width - COLS) / 2; 78 sminx = 0; 79 smaxx = COLS; 80 } else { 81 px = 0; 82 sminx = (COLS - width) / 2; 83 smaxx = sminx + width; 84 } 85 86 if (height > LINES) { 87 py = (height - LINES) / 2; 88 sminy = 0; 89 smaxy = LINES; 90 } else { 91 py = 0; 92 sminy = (LINES - height) / 2; 93 smaxy = sminy + height; 94 } 95 } 96 97 static void curses_resize(DisplayChangeListener *dcl, 98 int width, int height) 99 { 100 if (width == gwidth && height == gheight) { 101 return; 102 } 103 104 gwidth = width; 105 gheight = height; 106 107 curses_calc_pad(); 108 } 109 110 #if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE) 111 static volatile sig_atomic_t got_sigwinch; 112 static void curses_winch_check(void) 113 { 114 struct winsize { 115 unsigned short ws_row; 116 unsigned short ws_col; 117 unsigned short ws_xpixel; /* unused */ 118 unsigned short ws_ypixel; /* unused */ 119 } ws; 120 121 if (!got_sigwinch) { 122 return; 123 } 124 got_sigwinch = false; 125 126 if (ioctl(1, TIOCGWINSZ, &ws) == -1) { 127 return; 128 } 129 130 resize_term(ws.ws_row, ws.ws_col); 131 invalidate = 1; 132 } 133 134 static void curses_winch_handler(int signum) 135 { 136 got_sigwinch = true; 137 } 138 139 static void curses_winch_init(void) 140 { 141 struct sigaction old, winch = { 142 .sa_handler = curses_winch_handler, 143 }; 144 sigaction(SIGWINCH, &winch, &old); 145 } 146 #else 147 static void curses_winch_check(void) {} 148 static void curses_winch_init(void) {} 149 #endif 150 151 static void curses_cursor_position(DisplayChangeListener *dcl, 152 int x, int y) 153 { 154 if (x >= 0) { 155 x = sminx + x - px; 156 y = sminy + y - py; 157 158 if (x >= 0 && y >= 0 && x < COLS && y < LINES) { 159 move(y, x); 160 curs_set(1); 161 /* it seems that curs_set(1) must always be called before 162 * curs_set(2) for the latter to have effect */ 163 if (!qemu_console_is_graphic(NULL)) { 164 curs_set(2); 165 } 166 return; 167 } 168 } 169 170 curs_set(0); 171 } 172 173 /* generic keyboard conversion */ 174 175 #include "curses_keys.h" 176 177 static kbd_layout_t *kbd_layout = NULL; 178 179 static void curses_refresh(DisplayChangeListener *dcl) 180 { 181 int chr, nextchr, keysym, keycode, keycode_alt; 182 183 curses_winch_check(); 184 185 if (invalidate) { 186 clear(); 187 refresh(); 188 curses_calc_pad(); 189 graphic_hw_invalidate(NULL); 190 invalidate = 0; 191 } 192 193 graphic_hw_text_update(NULL, screen); 194 195 nextchr = ERR; 196 while (1) { 197 /* while there are any pending key strokes to process */ 198 if (nextchr == ERR) 199 chr = getch(); 200 else { 201 chr = nextchr; 202 nextchr = ERR; 203 } 204 205 if (chr == ERR) 206 break; 207 208 #ifdef KEY_RESIZE 209 /* this shouldn't occur when we use a custom SIGWINCH handler */ 210 if (chr == KEY_RESIZE) { 211 clear(); 212 refresh(); 213 curses_calc_pad(); 214 curses_update(dcl, 0, 0, width, height); 215 continue; 216 } 217 #endif 218 219 keycode = curses2keycode[chr]; 220 keycode_alt = 0; 221 222 /* alt key */ 223 if (keycode == 1) { 224 nextchr = getch(); 225 226 if (nextchr != ERR) { 227 chr = nextchr; 228 keycode_alt = ALT; 229 keycode = curses2keycode[nextchr]; 230 nextchr = ERR; 231 232 if (keycode != -1) { 233 keycode |= ALT; 234 235 /* process keys reserved for qemu */ 236 if (keycode >= QEMU_KEY_CONSOLE0 && 237 keycode < QEMU_KEY_CONSOLE0 + 9) { 238 erase(); 239 wnoutrefresh(stdscr); 240 console_select(keycode - QEMU_KEY_CONSOLE0); 241 242 invalidate = 1; 243 continue; 244 } 245 } 246 } 247 } 248 249 if (kbd_layout) { 250 keysym = -1; 251 if (chr < CURSES_KEYS) 252 keysym = curses2keysym[chr]; 253 254 if (keysym == -1) { 255 if (chr < ' ') { 256 keysym = chr + '@'; 257 if (keysym >= 'A' && keysym <= 'Z') 258 keysym += 'a' - 'A'; 259 keysym |= KEYSYM_CNTRL; 260 } else 261 keysym = chr; 262 } 263 264 keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK); 265 if (keycode == 0) 266 continue; 267 268 keycode |= (keysym & ~KEYSYM_MASK) >> 16; 269 keycode |= keycode_alt; 270 } 271 272 if (keycode == -1) 273 continue; 274 275 if (qemu_console_is_graphic(NULL)) { 276 /* since terminals don't know about key press and release 277 * events, we need to emit both for each key received */ 278 if (keycode & SHIFT) { 279 qemu_input_event_send_key_number(NULL, SHIFT_CODE, true); 280 } 281 if (keycode & CNTRL) { 282 qemu_input_event_send_key_number(NULL, CNTRL_CODE, true); 283 } 284 if (keycode & ALT) { 285 qemu_input_event_send_key_number(NULL, ALT_CODE, true); 286 } 287 if (keycode & ALTGR) { 288 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true); 289 } 290 291 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true); 292 qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false); 293 294 if (keycode & ALTGR) { 295 qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false); 296 } 297 if (keycode & ALT) { 298 qemu_input_event_send_key_number(NULL, ALT_CODE, false); 299 } 300 if (keycode & CNTRL) { 301 qemu_input_event_send_key_number(NULL, CNTRL_CODE, false); 302 } 303 if (keycode & SHIFT) { 304 qemu_input_event_send_key_number(NULL, SHIFT_CODE, false); 305 } 306 } else { 307 keysym = curses2qemu[chr]; 308 if (keysym == -1) 309 keysym = chr; 310 311 kbd_put_keysym(keysym); 312 } 313 } 314 } 315 316 static void curses_atexit(void) 317 { 318 endwin(); 319 } 320 321 static void curses_setup(void) 322 { 323 int i, colour_default[8] = { 324 COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, 325 COLOR_RED, COLOR_MAGENTA, COLOR_YELLOW, COLOR_WHITE, 326 }; 327 328 /* input as raw as possible, let everything be interpreted 329 * by the guest system */ 330 initscr(); noecho(); intrflush(stdscr, FALSE); 331 nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE); 332 start_color(); raw(); scrollok(stdscr, FALSE); 333 334 for (i = 0; i < 64; i ++) 335 init_pair(i, colour_default[i & 7], colour_default[i >> 3]); 336 } 337 338 static void curses_keyboard_setup(void) 339 { 340 #if defined(__APPLE__) 341 /* always use generic keymaps */ 342 if (!keyboard_layout) 343 keyboard_layout = "en-us"; 344 #endif 345 if(keyboard_layout) { 346 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout); 347 if (!kbd_layout) 348 exit(1); 349 } 350 } 351 352 static const DisplayChangeListenerOps dcl_ops = { 353 .dpy_name = "curses", 354 .dpy_text_update = curses_update, 355 .dpy_text_resize = curses_resize, 356 .dpy_refresh = curses_refresh, 357 .dpy_text_cursor = curses_cursor_position, 358 }; 359 360 void curses_display_init(DisplayState *ds, int full_screen) 361 { 362 #ifndef _WIN32 363 if (!isatty(1)) { 364 fprintf(stderr, "We need a terminal output\n"); 365 exit(1); 366 } 367 #endif 368 369 curses_setup(); 370 curses_keyboard_setup(); 371 atexit(curses_atexit); 372 373 curses_winch_init(); 374 375 dcl = (DisplayChangeListener *) g_malloc0(sizeof(DisplayChangeListener)); 376 dcl->ops = &dcl_ops; 377 register_displaychangelistener(dcl); 378 379 invalidate = 1; 380 } 381