2
0

curses.c 12 KB

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