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