sdl2.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. /*
  2. * QEMU SDL display driver
  3. *
  4. * Copyright (c) 2003 Fabrice Bellard
  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. /* Ported SDL 1.2 code to 2.0 by Dave Airlie. */
  25. #include "qemu/osdep.h"
  26. #include "qemu/module.h"
  27. #include "qemu/cutils.h"
  28. #include "ui/console.h"
  29. #include "ui/input.h"
  30. #include "ui/sdl2.h"
  31. #include "sysemu/runstate.h"
  32. #include "sysemu/sysemu.h"
  33. #include "ui/win32-kbd-hook.h"
  34. static int sdl2_num_outputs;
  35. static struct sdl2_console *sdl2_console;
  36. static SDL_Surface *guest_sprite_surface;
  37. static int gui_grab; /* if true, all keyboard/mouse events are grabbed */
  38. static int gui_saved_grab;
  39. static int gui_fullscreen;
  40. static int gui_grab_code = KMOD_LALT | KMOD_LCTRL;
  41. static SDL_Cursor *sdl_cursor_normal;
  42. static SDL_Cursor *sdl_cursor_hidden;
  43. static int absolute_enabled;
  44. static int guest_cursor;
  45. static int guest_x, guest_y;
  46. static SDL_Cursor *guest_sprite;
  47. static Notifier mouse_mode_notifier;
  48. #define SDL2_REFRESH_INTERVAL_BUSY 10
  49. #define SDL2_MAX_IDLE_COUNT (2 * GUI_REFRESH_INTERVAL_DEFAULT \
  50. / SDL2_REFRESH_INTERVAL_BUSY + 1)
  51. static void sdl_update_caption(struct sdl2_console *scon);
  52. static struct sdl2_console *get_scon_from_window(uint32_t window_id)
  53. {
  54. int i;
  55. for (i = 0; i < sdl2_num_outputs; i++) {
  56. if (sdl2_console[i].real_window == SDL_GetWindowFromID(window_id)) {
  57. return &sdl2_console[i];
  58. }
  59. }
  60. return NULL;
  61. }
  62. void sdl2_window_create(struct sdl2_console *scon)
  63. {
  64. int flags = 0;
  65. if (!scon->surface) {
  66. return;
  67. }
  68. assert(!scon->real_window);
  69. if (gui_fullscreen) {
  70. flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
  71. } else {
  72. flags |= SDL_WINDOW_RESIZABLE;
  73. }
  74. if (scon->hidden) {
  75. flags |= SDL_WINDOW_HIDDEN;
  76. }
  77. #ifdef CONFIG_OPENGL
  78. if (scon->opengl) {
  79. flags |= SDL_WINDOW_OPENGL;
  80. }
  81. #endif
  82. scon->real_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED,
  83. SDL_WINDOWPOS_UNDEFINED,
  84. surface_width(scon->surface),
  85. surface_height(scon->surface),
  86. flags);
  87. scon->real_renderer = SDL_CreateRenderer(scon->real_window, -1, 0);
  88. if (scon->opengl) {
  89. scon->winctx = SDL_GL_GetCurrentContext();
  90. }
  91. sdl_update_caption(scon);
  92. }
  93. void sdl2_window_destroy(struct sdl2_console *scon)
  94. {
  95. if (!scon->real_window) {
  96. return;
  97. }
  98. SDL_DestroyRenderer(scon->real_renderer);
  99. scon->real_renderer = NULL;
  100. SDL_DestroyWindow(scon->real_window);
  101. scon->real_window = NULL;
  102. }
  103. void sdl2_window_resize(struct sdl2_console *scon)
  104. {
  105. if (!scon->real_window) {
  106. return;
  107. }
  108. SDL_SetWindowSize(scon->real_window,
  109. surface_width(scon->surface),
  110. surface_height(scon->surface));
  111. }
  112. static void sdl2_redraw(struct sdl2_console *scon)
  113. {
  114. if (scon->opengl) {
  115. #ifdef CONFIG_OPENGL
  116. sdl2_gl_redraw(scon);
  117. #endif
  118. } else {
  119. sdl2_2d_redraw(scon);
  120. }
  121. }
  122. static void sdl_update_caption(struct sdl2_console *scon)
  123. {
  124. char win_title[1024];
  125. char icon_title[1024];
  126. const char *status = "";
  127. if (!runstate_is_running()) {
  128. status = " [Stopped]";
  129. } else if (gui_grab) {
  130. if (alt_grab) {
  131. status = " - Press Ctrl-Alt-Shift-G to exit grab";
  132. } else if (ctrl_grab) {
  133. status = " - Press Right-Ctrl-G to exit grab";
  134. } else {
  135. status = " - Press Ctrl-Alt-G to exit grab";
  136. }
  137. }
  138. if (qemu_name) {
  139. snprintf(win_title, sizeof(win_title), "QEMU (%s-%d)%s", qemu_name,
  140. scon->idx, status);
  141. snprintf(icon_title, sizeof(icon_title), "QEMU (%s)", qemu_name);
  142. } else {
  143. snprintf(win_title, sizeof(win_title), "QEMU%s", status);
  144. snprintf(icon_title, sizeof(icon_title), "QEMU");
  145. }
  146. if (scon->real_window) {
  147. SDL_SetWindowTitle(scon->real_window, win_title);
  148. }
  149. }
  150. static void sdl_hide_cursor(struct sdl2_console *scon)
  151. {
  152. if (scon->opts->has_show_cursor && scon->opts->show_cursor) {
  153. return;
  154. }
  155. SDL_ShowCursor(SDL_DISABLE);
  156. SDL_SetCursor(sdl_cursor_hidden);
  157. if (!qemu_input_is_absolute()) {
  158. SDL_SetRelativeMouseMode(SDL_TRUE);
  159. }
  160. }
  161. static void sdl_show_cursor(struct sdl2_console *scon)
  162. {
  163. if (scon->opts->has_show_cursor && scon->opts->show_cursor) {
  164. return;
  165. }
  166. if (!qemu_input_is_absolute()) {
  167. SDL_SetRelativeMouseMode(SDL_FALSE);
  168. }
  169. if (guest_cursor &&
  170. (gui_grab || qemu_input_is_absolute() || absolute_enabled)) {
  171. SDL_SetCursor(guest_sprite);
  172. } else {
  173. SDL_SetCursor(sdl_cursor_normal);
  174. }
  175. SDL_ShowCursor(SDL_ENABLE);
  176. }
  177. static void sdl_grab_start(struct sdl2_console *scon)
  178. {
  179. QemuConsole *con = scon ? scon->dcl.con : NULL;
  180. if (!con || !qemu_console_is_graphic(con)) {
  181. return;
  182. }
  183. /*
  184. * If the application is not active, do not try to enter grab state. This
  185. * prevents 'SDL_WM_GrabInput(SDL_GRAB_ON)' from blocking all the
  186. * application (SDL bug).
  187. */
  188. if (!(SDL_GetWindowFlags(scon->real_window) & SDL_WINDOW_INPUT_FOCUS)) {
  189. return;
  190. }
  191. if (guest_cursor) {
  192. SDL_SetCursor(guest_sprite);
  193. if (!qemu_input_is_absolute() && !absolute_enabled) {
  194. SDL_WarpMouseInWindow(scon->real_window, guest_x, guest_y);
  195. }
  196. } else {
  197. sdl_hide_cursor(scon);
  198. }
  199. SDL_SetWindowGrab(scon->real_window, SDL_TRUE);
  200. gui_grab = 1;
  201. win32_kbd_set_grab(true);
  202. sdl_update_caption(scon);
  203. }
  204. static void sdl_grab_end(struct sdl2_console *scon)
  205. {
  206. SDL_SetWindowGrab(scon->real_window, SDL_FALSE);
  207. gui_grab = 0;
  208. win32_kbd_set_grab(false);
  209. sdl_show_cursor(scon);
  210. sdl_update_caption(scon);
  211. }
  212. static void absolute_mouse_grab(struct sdl2_console *scon)
  213. {
  214. int mouse_x, mouse_y;
  215. int scr_w, scr_h;
  216. SDL_GetMouseState(&mouse_x, &mouse_y);
  217. SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
  218. if (mouse_x > 0 && mouse_x < scr_w - 1 &&
  219. mouse_y > 0 && mouse_y < scr_h - 1) {
  220. sdl_grab_start(scon);
  221. }
  222. }
  223. static void sdl_mouse_mode_change(Notifier *notify, void *data)
  224. {
  225. if (qemu_input_is_absolute()) {
  226. if (!absolute_enabled) {
  227. absolute_enabled = 1;
  228. SDL_SetRelativeMouseMode(SDL_FALSE);
  229. absolute_mouse_grab(&sdl2_console[0]);
  230. }
  231. } else if (absolute_enabled) {
  232. if (!gui_fullscreen) {
  233. sdl_grab_end(&sdl2_console[0]);
  234. }
  235. absolute_enabled = 0;
  236. }
  237. }
  238. static void sdl_send_mouse_event(struct sdl2_console *scon, int dx, int dy,
  239. int x, int y, int state)
  240. {
  241. static uint32_t bmap[INPUT_BUTTON__MAX] = {
  242. [INPUT_BUTTON_LEFT] = SDL_BUTTON(SDL_BUTTON_LEFT),
  243. [INPUT_BUTTON_MIDDLE] = SDL_BUTTON(SDL_BUTTON_MIDDLE),
  244. [INPUT_BUTTON_RIGHT] = SDL_BUTTON(SDL_BUTTON_RIGHT),
  245. };
  246. static uint32_t prev_state;
  247. if (prev_state != state) {
  248. qemu_input_update_buttons(scon->dcl.con, bmap, prev_state, state);
  249. prev_state = state;
  250. }
  251. if (qemu_input_is_absolute()) {
  252. qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_X,
  253. x, 0, surface_width(scon->surface));
  254. qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_Y,
  255. y, 0, surface_height(scon->surface));
  256. } else {
  257. if (guest_cursor) {
  258. x -= guest_x;
  259. y -= guest_y;
  260. guest_x += x;
  261. guest_y += y;
  262. dx = x;
  263. dy = y;
  264. }
  265. qemu_input_queue_rel(scon->dcl.con, INPUT_AXIS_X, dx);
  266. qemu_input_queue_rel(scon->dcl.con, INPUT_AXIS_Y, dy);
  267. }
  268. qemu_input_event_sync();
  269. }
  270. static void toggle_full_screen(struct sdl2_console *scon)
  271. {
  272. gui_fullscreen = !gui_fullscreen;
  273. if (gui_fullscreen) {
  274. SDL_SetWindowFullscreen(scon->real_window,
  275. SDL_WINDOW_FULLSCREEN_DESKTOP);
  276. gui_saved_grab = gui_grab;
  277. sdl_grab_start(scon);
  278. } else {
  279. if (!gui_saved_grab) {
  280. sdl_grab_end(scon);
  281. }
  282. SDL_SetWindowFullscreen(scon->real_window, 0);
  283. }
  284. sdl2_redraw(scon);
  285. }
  286. static int get_mod_state(void)
  287. {
  288. SDL_Keymod mod = SDL_GetModState();
  289. if (alt_grab) {
  290. return (mod & (gui_grab_code | KMOD_LSHIFT)) ==
  291. (gui_grab_code | KMOD_LSHIFT);
  292. } else if (ctrl_grab) {
  293. return (mod & KMOD_RCTRL) == KMOD_RCTRL;
  294. } else {
  295. return (mod & gui_grab_code) == gui_grab_code;
  296. }
  297. }
  298. static void *sdl2_win32_get_hwnd(struct sdl2_console *scon)
  299. {
  300. #ifdef CONFIG_WIN32
  301. SDL_SysWMinfo info;
  302. SDL_VERSION(&info.version);
  303. if (SDL_GetWindowWMInfo(scon->real_window, &info)) {
  304. return info.info.win.window;
  305. }
  306. #endif
  307. return NULL;
  308. }
  309. static void handle_keydown(SDL_Event *ev)
  310. {
  311. int win;
  312. struct sdl2_console *scon = get_scon_from_window(ev->key.windowID);
  313. int gui_key_modifier_pressed = get_mod_state();
  314. int gui_keysym = 0;
  315. if (!scon) {
  316. return;
  317. }
  318. if (!scon->ignore_hotkeys && gui_key_modifier_pressed && !ev->key.repeat) {
  319. switch (ev->key.keysym.scancode) {
  320. case SDL_SCANCODE_2:
  321. case SDL_SCANCODE_3:
  322. case SDL_SCANCODE_4:
  323. case SDL_SCANCODE_5:
  324. case SDL_SCANCODE_6:
  325. case SDL_SCANCODE_7:
  326. case SDL_SCANCODE_8:
  327. case SDL_SCANCODE_9:
  328. if (gui_grab) {
  329. sdl_grab_end(scon);
  330. }
  331. win = ev->key.keysym.scancode - SDL_SCANCODE_1;
  332. if (win < sdl2_num_outputs) {
  333. sdl2_console[win].hidden = !sdl2_console[win].hidden;
  334. if (sdl2_console[win].real_window) {
  335. if (sdl2_console[win].hidden) {
  336. SDL_HideWindow(sdl2_console[win].real_window);
  337. } else {
  338. SDL_ShowWindow(sdl2_console[win].real_window);
  339. }
  340. }
  341. gui_keysym = 1;
  342. }
  343. break;
  344. case SDL_SCANCODE_F:
  345. toggle_full_screen(scon);
  346. gui_keysym = 1;
  347. break;
  348. case SDL_SCANCODE_G:
  349. gui_keysym = 1;
  350. if (!gui_grab) {
  351. sdl_grab_start(scon);
  352. } else if (!gui_fullscreen) {
  353. sdl_grab_end(scon);
  354. }
  355. break;
  356. case SDL_SCANCODE_U:
  357. sdl2_window_resize(scon);
  358. if (!scon->opengl) {
  359. /* re-create scon->texture */
  360. sdl2_2d_switch(&scon->dcl, scon->surface);
  361. }
  362. gui_keysym = 1;
  363. break;
  364. #if 0
  365. case SDL_SCANCODE_KP_PLUS:
  366. case SDL_SCANCODE_KP_MINUS:
  367. if (!gui_fullscreen) {
  368. int scr_w, scr_h;
  369. int width, height;
  370. SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
  371. width = MAX(scr_w + (ev->key.keysym.scancode ==
  372. SDL_SCANCODE_KP_PLUS ? 50 : -50),
  373. 160);
  374. height = (surface_height(scon->surface) * width) /
  375. surface_width(scon->surface);
  376. fprintf(stderr, "%s: scale to %dx%d\n",
  377. __func__, width, height);
  378. sdl_scale(scon, width, height);
  379. sdl2_redraw(scon);
  380. gui_keysym = 1;
  381. }
  382. #endif
  383. default:
  384. break;
  385. }
  386. }
  387. if (!gui_keysym) {
  388. sdl2_process_key(scon, &ev->key);
  389. }
  390. }
  391. static void handle_keyup(SDL_Event *ev)
  392. {
  393. struct sdl2_console *scon = get_scon_from_window(ev->key.windowID);
  394. if (!scon) {
  395. return;
  396. }
  397. scon->ignore_hotkeys = false;
  398. sdl2_process_key(scon, &ev->key);
  399. }
  400. static void handle_textinput(SDL_Event *ev)
  401. {
  402. struct sdl2_console *scon = get_scon_from_window(ev->text.windowID);
  403. QemuConsole *con = scon ? scon->dcl.con : NULL;
  404. if (!con) {
  405. return;
  406. }
  407. if (qemu_console_is_graphic(con)) {
  408. return;
  409. }
  410. kbd_put_string_console(con, ev->text.text, strlen(ev->text.text));
  411. }
  412. static void handle_mousemotion(SDL_Event *ev)
  413. {
  414. int max_x, max_y;
  415. struct sdl2_console *scon = get_scon_from_window(ev->motion.windowID);
  416. if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
  417. return;
  418. }
  419. if (qemu_input_is_absolute() || absolute_enabled) {
  420. int scr_w, scr_h;
  421. SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
  422. max_x = scr_w - 1;
  423. max_y = scr_h - 1;
  424. if (gui_grab && !gui_fullscreen
  425. && (ev->motion.x == 0 || ev->motion.y == 0 ||
  426. ev->motion.x == max_x || ev->motion.y == max_y)) {
  427. sdl_grab_end(scon);
  428. }
  429. if (!gui_grab &&
  430. (ev->motion.x > 0 && ev->motion.x < max_x &&
  431. ev->motion.y > 0 && ev->motion.y < max_y)) {
  432. sdl_grab_start(scon);
  433. }
  434. }
  435. if (gui_grab || qemu_input_is_absolute() || absolute_enabled) {
  436. sdl_send_mouse_event(scon, ev->motion.xrel, ev->motion.yrel,
  437. ev->motion.x, ev->motion.y, ev->motion.state);
  438. }
  439. }
  440. static void handle_mousebutton(SDL_Event *ev)
  441. {
  442. int buttonstate = SDL_GetMouseState(NULL, NULL);
  443. SDL_MouseButtonEvent *bev;
  444. struct sdl2_console *scon = get_scon_from_window(ev->button.windowID);
  445. if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
  446. return;
  447. }
  448. bev = &ev->button;
  449. if (!gui_grab && !qemu_input_is_absolute()) {
  450. if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) {
  451. /* start grabbing all events */
  452. sdl_grab_start(scon);
  453. }
  454. } else {
  455. if (ev->type == SDL_MOUSEBUTTONDOWN) {
  456. buttonstate |= SDL_BUTTON(bev->button);
  457. } else {
  458. buttonstate &= ~SDL_BUTTON(bev->button);
  459. }
  460. sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate);
  461. }
  462. }
  463. static void handle_mousewheel(SDL_Event *ev)
  464. {
  465. struct sdl2_console *scon = get_scon_from_window(ev->wheel.windowID);
  466. SDL_MouseWheelEvent *wev = &ev->wheel;
  467. InputButton btn;
  468. if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
  469. return;
  470. }
  471. if (wev->y > 0) {
  472. btn = INPUT_BUTTON_WHEEL_UP;
  473. } else if (wev->y < 0) {
  474. btn = INPUT_BUTTON_WHEEL_DOWN;
  475. } else {
  476. return;
  477. }
  478. qemu_input_queue_btn(scon->dcl.con, btn, true);
  479. qemu_input_event_sync();
  480. qemu_input_queue_btn(scon->dcl.con, btn, false);
  481. qemu_input_event_sync();
  482. }
  483. static void handle_windowevent(SDL_Event *ev)
  484. {
  485. struct sdl2_console *scon = get_scon_from_window(ev->window.windowID);
  486. bool allow_close = true;
  487. if (!scon) {
  488. return;
  489. }
  490. switch (ev->window.event) {
  491. case SDL_WINDOWEVENT_RESIZED:
  492. {
  493. QemuUIInfo info;
  494. memset(&info, 0, sizeof(info));
  495. info.width = ev->window.data1;
  496. info.height = ev->window.data2;
  497. dpy_set_ui_info(scon->dcl.con, &info);
  498. }
  499. sdl2_redraw(scon);
  500. break;
  501. case SDL_WINDOWEVENT_EXPOSED:
  502. sdl2_redraw(scon);
  503. break;
  504. case SDL_WINDOWEVENT_FOCUS_GAINED:
  505. win32_kbd_set_grab(gui_grab);
  506. if (qemu_console_is_graphic(scon->dcl.con)) {
  507. win32_kbd_set_window(sdl2_win32_get_hwnd(scon));
  508. }
  509. /* fall through */
  510. case SDL_WINDOWEVENT_ENTER:
  511. if (!gui_grab && (qemu_input_is_absolute() || absolute_enabled)) {
  512. absolute_mouse_grab(scon);
  513. }
  514. /* If a new console window opened using a hotkey receives the
  515. * focus, SDL sends another KEYDOWN event to the new window,
  516. * closing the console window immediately after.
  517. *
  518. * Work around this by ignoring further hotkey events until a
  519. * key is released.
  520. */
  521. scon->ignore_hotkeys = get_mod_state();
  522. break;
  523. case SDL_WINDOWEVENT_FOCUS_LOST:
  524. if (qemu_console_is_graphic(scon->dcl.con)) {
  525. win32_kbd_set_window(NULL);
  526. }
  527. if (gui_grab && !gui_fullscreen) {
  528. sdl_grab_end(scon);
  529. }
  530. break;
  531. case SDL_WINDOWEVENT_RESTORED:
  532. update_displaychangelistener(&scon->dcl, GUI_REFRESH_INTERVAL_DEFAULT);
  533. break;
  534. case SDL_WINDOWEVENT_MINIMIZED:
  535. update_displaychangelistener(&scon->dcl, 500);
  536. break;
  537. case SDL_WINDOWEVENT_CLOSE:
  538. if (qemu_console_is_graphic(scon->dcl.con)) {
  539. if (scon->opts->has_window_close && !scon->opts->window_close) {
  540. allow_close = false;
  541. }
  542. if (allow_close) {
  543. no_shutdown = 0;
  544. qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
  545. }
  546. } else {
  547. SDL_HideWindow(scon->real_window);
  548. scon->hidden = true;
  549. }
  550. break;
  551. case SDL_WINDOWEVENT_SHOWN:
  552. scon->hidden = false;
  553. break;
  554. case SDL_WINDOWEVENT_HIDDEN:
  555. scon->hidden = true;
  556. break;
  557. }
  558. }
  559. void sdl2_poll_events(struct sdl2_console *scon)
  560. {
  561. SDL_Event ev1, *ev = &ev1;
  562. bool allow_close = true;
  563. int idle = 1;
  564. if (scon->last_vm_running != runstate_is_running()) {
  565. scon->last_vm_running = runstate_is_running();
  566. sdl_update_caption(scon);
  567. }
  568. while (SDL_PollEvent(ev)) {
  569. switch (ev->type) {
  570. case SDL_KEYDOWN:
  571. idle = 0;
  572. handle_keydown(ev);
  573. break;
  574. case SDL_KEYUP:
  575. idle = 0;
  576. handle_keyup(ev);
  577. break;
  578. case SDL_TEXTINPUT:
  579. idle = 0;
  580. handle_textinput(ev);
  581. break;
  582. case SDL_QUIT:
  583. if (scon->opts->has_window_close && !scon->opts->window_close) {
  584. allow_close = false;
  585. }
  586. if (allow_close) {
  587. no_shutdown = 0;
  588. qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
  589. }
  590. break;
  591. case SDL_MOUSEMOTION:
  592. idle = 0;
  593. handle_mousemotion(ev);
  594. break;
  595. case SDL_MOUSEBUTTONDOWN:
  596. case SDL_MOUSEBUTTONUP:
  597. idle = 0;
  598. handle_mousebutton(ev);
  599. break;
  600. case SDL_MOUSEWHEEL:
  601. idle = 0;
  602. handle_mousewheel(ev);
  603. break;
  604. case SDL_WINDOWEVENT:
  605. handle_windowevent(ev);
  606. break;
  607. default:
  608. break;
  609. }
  610. }
  611. if (idle) {
  612. if (scon->idle_counter < SDL2_MAX_IDLE_COUNT) {
  613. scon->idle_counter++;
  614. if (scon->idle_counter >= SDL2_MAX_IDLE_COUNT) {
  615. scon->dcl.update_interval = GUI_REFRESH_INTERVAL_DEFAULT;
  616. }
  617. }
  618. } else {
  619. scon->idle_counter = 0;
  620. scon->dcl.update_interval = SDL2_REFRESH_INTERVAL_BUSY;
  621. }
  622. }
  623. static void sdl_mouse_warp(DisplayChangeListener *dcl,
  624. int x, int y, int on)
  625. {
  626. struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
  627. if (!qemu_console_is_graphic(scon->dcl.con)) {
  628. return;
  629. }
  630. if (on) {
  631. if (!guest_cursor) {
  632. sdl_show_cursor(scon);
  633. }
  634. if (gui_grab || qemu_input_is_absolute() || absolute_enabled) {
  635. SDL_SetCursor(guest_sprite);
  636. if (!qemu_input_is_absolute() && !absolute_enabled) {
  637. SDL_WarpMouseInWindow(scon->real_window, x, y);
  638. }
  639. }
  640. } else if (gui_grab) {
  641. sdl_hide_cursor(scon);
  642. }
  643. guest_cursor = on;
  644. guest_x = x, guest_y = y;
  645. }
  646. static void sdl_mouse_define(DisplayChangeListener *dcl,
  647. QEMUCursor *c)
  648. {
  649. if (guest_sprite) {
  650. SDL_FreeCursor(guest_sprite);
  651. }
  652. if (guest_sprite_surface) {
  653. SDL_FreeSurface(guest_sprite_surface);
  654. }
  655. guest_sprite_surface =
  656. SDL_CreateRGBSurfaceFrom(c->data, c->width, c->height, 32, c->width * 4,
  657. 0xff0000, 0x00ff00, 0xff, 0xff000000);
  658. if (!guest_sprite_surface) {
  659. fprintf(stderr, "Failed to make rgb surface from %p\n", c);
  660. return;
  661. }
  662. guest_sprite = SDL_CreateColorCursor(guest_sprite_surface,
  663. c->hot_x, c->hot_y);
  664. if (!guest_sprite) {
  665. fprintf(stderr, "Failed to make color cursor from %p\n", c);
  666. return;
  667. }
  668. if (guest_cursor &&
  669. (gui_grab || qemu_input_is_absolute() || absolute_enabled)) {
  670. SDL_SetCursor(guest_sprite);
  671. }
  672. }
  673. static void sdl_cleanup(void)
  674. {
  675. if (guest_sprite) {
  676. SDL_FreeCursor(guest_sprite);
  677. }
  678. SDL_QuitSubSystem(SDL_INIT_VIDEO);
  679. }
  680. static const DisplayChangeListenerOps dcl_2d_ops = {
  681. .dpy_name = "sdl2-2d",
  682. .dpy_gfx_update = sdl2_2d_update,
  683. .dpy_gfx_switch = sdl2_2d_switch,
  684. .dpy_gfx_check_format = sdl2_2d_check_format,
  685. .dpy_refresh = sdl2_2d_refresh,
  686. .dpy_mouse_set = sdl_mouse_warp,
  687. .dpy_cursor_define = sdl_mouse_define,
  688. };
  689. #ifdef CONFIG_OPENGL
  690. static const DisplayChangeListenerOps dcl_gl_ops = {
  691. .dpy_name = "sdl2-gl",
  692. .dpy_gfx_update = sdl2_gl_update,
  693. .dpy_gfx_switch = sdl2_gl_switch,
  694. .dpy_gfx_check_format = console_gl_check_format,
  695. .dpy_refresh = sdl2_gl_refresh,
  696. .dpy_mouse_set = sdl_mouse_warp,
  697. .dpy_cursor_define = sdl_mouse_define,
  698. .dpy_gl_ctx_create = sdl2_gl_create_context,
  699. .dpy_gl_ctx_destroy = sdl2_gl_destroy_context,
  700. .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
  701. .dpy_gl_ctx_get_current = sdl2_gl_get_current_context,
  702. .dpy_gl_scanout_disable = sdl2_gl_scanout_disable,
  703. .dpy_gl_scanout_texture = sdl2_gl_scanout_texture,
  704. .dpy_gl_update = sdl2_gl_scanout_flush,
  705. };
  706. #endif
  707. static void sdl2_display_early_init(DisplayOptions *o)
  708. {
  709. assert(o->type == DISPLAY_TYPE_SDL);
  710. if (o->has_gl && o->gl) {
  711. #ifdef CONFIG_OPENGL
  712. display_opengl = 1;
  713. #endif
  714. }
  715. }
  716. static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
  717. {
  718. uint8_t data = 0;
  719. int i;
  720. SDL_SysWMinfo info;
  721. SDL_Surface *icon = NULL;
  722. char *dir;
  723. assert(o->type == DISPLAY_TYPE_SDL);
  724. #ifdef __linux__
  725. /* on Linux, SDL may use fbcon|directfb|svgalib when run without
  726. * accessible $DISPLAY to open X11 window. This is often the case
  727. * when qemu is run using sudo. But in this case, and when actually
  728. * run in X11 environment, SDL fights with X11 for the video card,
  729. * making current display unavailable, often until reboot.
  730. * So make x11 the default SDL video driver if this variable is unset.
  731. * This is a bit hackish but saves us from bigger problem.
  732. * Maybe it's a good idea to fix this in SDL instead.
  733. */
  734. g_setenv("SDL_VIDEODRIVER", "x11", 0);
  735. #endif
  736. if (SDL_Init(SDL_INIT_VIDEO)) {
  737. fprintf(stderr, "Could not initialize SDL(%s) - exiting\n",
  738. SDL_GetError());
  739. exit(1);
  740. }
  741. #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* only available since SDL 2.0.8 */
  742. SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
  743. #endif
  744. SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
  745. memset(&info, 0, sizeof(info));
  746. SDL_VERSION(&info.version);
  747. gui_fullscreen = o->has_full_screen && o->full_screen;
  748. for (i = 0;; i++) {
  749. QemuConsole *con = qemu_console_lookup_by_index(i);
  750. if (!con) {
  751. break;
  752. }
  753. }
  754. sdl2_num_outputs = i;
  755. if (sdl2_num_outputs == 0) {
  756. return;
  757. }
  758. sdl2_console = g_new0(struct sdl2_console, sdl2_num_outputs);
  759. for (i = 0; i < sdl2_num_outputs; i++) {
  760. QemuConsole *con = qemu_console_lookup_by_index(i);
  761. assert(con != NULL);
  762. if (!qemu_console_is_graphic(con) &&
  763. qemu_console_get_index(con) != 0) {
  764. sdl2_console[i].hidden = true;
  765. }
  766. sdl2_console[i].idx = i;
  767. sdl2_console[i].opts = o;
  768. #ifdef CONFIG_OPENGL
  769. sdl2_console[i].opengl = display_opengl;
  770. sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops;
  771. #else
  772. sdl2_console[i].opengl = 0;
  773. sdl2_console[i].dcl.ops = &dcl_2d_ops;
  774. #endif
  775. sdl2_console[i].dcl.con = con;
  776. sdl2_console[i].kbd = qkbd_state_init(con);
  777. register_displaychangelistener(&sdl2_console[i].dcl);
  778. #if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
  779. if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
  780. #if defined(SDL_VIDEO_DRIVER_WINDOWS)
  781. qemu_console_set_window_id(con, (uintptr_t)info.info.win.window);
  782. #elif defined(SDL_VIDEO_DRIVER_X11)
  783. qemu_console_set_window_id(con, info.info.x11.window);
  784. #endif
  785. }
  786. #endif
  787. }
  788. #ifdef CONFIG_SDL_IMAGE
  789. dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/128x128/apps/qemu.png");
  790. icon = IMG_Load(dir);
  791. #else
  792. /* Load a 32x32x4 image. White pixels are transparent. */
  793. dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/32x32/apps/qemu.bmp");
  794. icon = SDL_LoadBMP(dir);
  795. if (icon) {
  796. uint32_t colorkey = SDL_MapRGB(icon->format, 255, 255, 255);
  797. SDL_SetColorKey(icon, SDL_TRUE, colorkey);
  798. }
  799. #endif
  800. g_free(dir);
  801. if (icon) {
  802. SDL_SetWindowIcon(sdl2_console[0].real_window, icon);
  803. }
  804. mouse_mode_notifier.notify = sdl_mouse_mode_change;
  805. qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier);
  806. sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0);
  807. sdl_cursor_normal = SDL_GetCursor();
  808. if (gui_fullscreen) {
  809. sdl_grab_start(&sdl2_console[0]);
  810. }
  811. atexit(sdl_cleanup);
  812. }
  813. static QemuDisplay qemu_display_sdl2 = {
  814. .type = DISPLAY_TYPE_SDL,
  815. .early_init = sdl2_display_early_init,
  816. .init = sdl2_display_init,
  817. };
  818. static void register_sdl1(void)
  819. {
  820. qemu_display_register(&qemu_display_sdl2);
  821. }
  822. type_init(register_sdl1);