xref: /openbmc/qemu/ui/sdl2.c (revision 56c39a41adadfc567e1ac22089670bfde6b35365)
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_console_is_graphic(con)) {
487          return;
488      }
489      kbd_put_string_console(con, ev->text.text, strlen(ev->text.text));
490  }
491  
492  static void handle_mousemotion(SDL_Event *ev)
493  {
494      int max_x, max_y;
495      struct sdl2_console *scon = get_scon_from_window(ev->motion.windowID);
496  
497      if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
498          return;
499      }
500  
501      if (qemu_input_is_absolute() || absolute_enabled) {
502          int scr_w, scr_h;
503          SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
504          max_x = scr_w - 1;
505          max_y = scr_h - 1;
506          if (gui_grab && !gui_fullscreen
507              && (ev->motion.x == 0 || ev->motion.y == 0 ||
508                  ev->motion.x == max_x || ev->motion.y == max_y)) {
509              sdl_grab_end(scon);
510          }
511          if (!gui_grab &&
512              (ev->motion.x > 0 && ev->motion.x < max_x &&
513               ev->motion.y > 0 && ev->motion.y < max_y)) {
514              sdl_grab_start(scon);
515          }
516      }
517      if (gui_grab || qemu_input_is_absolute() || absolute_enabled) {
518          sdl_send_mouse_event(scon, ev->motion.xrel, ev->motion.yrel,
519                               ev->motion.x, ev->motion.y, ev->motion.state);
520      }
521  }
522  
523  static void handle_mousebutton(SDL_Event *ev)
524  {
525      int buttonstate = SDL_GetMouseState(NULL, NULL);
526      SDL_MouseButtonEvent *bev;
527      struct sdl2_console *scon = get_scon_from_window(ev->button.windowID);
528  
529      if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
530          return;
531      }
532  
533      bev = &ev->button;
534      if (!gui_grab && !qemu_input_is_absolute()) {
535          if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) {
536              /* start grabbing all events */
537              sdl_grab_start(scon);
538          }
539      } else {
540          if (ev->type == SDL_MOUSEBUTTONDOWN) {
541              buttonstate |= SDL_BUTTON(bev->button);
542          } else {
543              buttonstate &= ~SDL_BUTTON(bev->button);
544          }
545          sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate);
546      }
547  }
548  
549  static void handle_mousewheel(SDL_Event *ev)
550  {
551      struct sdl2_console *scon = get_scon_from_window(ev->wheel.windowID);
552      SDL_MouseWheelEvent *wev = &ev->wheel;
553      InputButton btn;
554  
555      if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
556          return;
557      }
558  
559      if (wev->y > 0) {
560          btn = INPUT_BUTTON_WHEEL_UP;
561      } else if (wev->y < 0) {
562          btn = INPUT_BUTTON_WHEEL_DOWN;
563      } else if (wev->x < 0) {
564          btn = INPUT_BUTTON_WHEEL_RIGHT;
565      } else if (wev->x > 0) {
566          btn = INPUT_BUTTON_WHEEL_LEFT;
567      } else {
568          return;
569      }
570  
571      qemu_input_queue_btn(scon->dcl.con, btn, true);
572      qemu_input_event_sync();
573      qemu_input_queue_btn(scon->dcl.con, btn, false);
574      qemu_input_event_sync();
575  }
576  
577  static void handle_windowevent(SDL_Event *ev)
578  {
579      struct sdl2_console *scon = get_scon_from_window(ev->window.windowID);
580      bool allow_close = true;
581  
582      if (!scon) {
583          return;
584      }
585  
586      switch (ev->window.event) {
587      case SDL_WINDOWEVENT_RESIZED:
588          {
589              QemuUIInfo info;
590              memset(&info, 0, sizeof(info));
591              info.width = ev->window.data1;
592              info.height = ev->window.data2;
593              dpy_set_ui_info(scon->dcl.con, &info, true);
594          }
595          sdl2_redraw(scon);
596          break;
597      case SDL_WINDOWEVENT_EXPOSED:
598          sdl2_redraw(scon);
599          break;
600      case SDL_WINDOWEVENT_FOCUS_GAINED:
601          win32_kbd_set_grab(gui_grab);
602          if (qemu_console_is_graphic(scon->dcl.con)) {
603              win32_kbd_set_window(sdl2_win32_get_hwnd(scon));
604          }
605          /* fall through */
606      case SDL_WINDOWEVENT_ENTER:
607          if (!gui_grab && (qemu_input_is_absolute() || absolute_enabled)) {
608              absolute_mouse_grab(scon);
609          }
610          /* If a new console window opened using a hotkey receives the
611           * focus, SDL sends another KEYDOWN event to the new window,
612           * closing the console window immediately after.
613           *
614           * Work around this by ignoring further hotkey events until a
615           * key is released.
616           */
617          scon->ignore_hotkeys = get_mod_state();
618          break;
619      case SDL_WINDOWEVENT_FOCUS_LOST:
620          if (qemu_console_is_graphic(scon->dcl.con)) {
621              win32_kbd_set_window(NULL);
622          }
623          if (gui_grab && !gui_fullscreen) {
624              sdl_grab_end(scon);
625          }
626          break;
627      case SDL_WINDOWEVENT_RESTORED:
628          update_displaychangelistener(&scon->dcl, GUI_REFRESH_INTERVAL_DEFAULT);
629          break;
630      case SDL_WINDOWEVENT_MINIMIZED:
631          update_displaychangelistener(&scon->dcl, 500);
632          break;
633      case SDL_WINDOWEVENT_CLOSE:
634          if (qemu_console_is_graphic(scon->dcl.con)) {
635              if (scon->opts->has_window_close && !scon->opts->window_close) {
636                  allow_close = false;
637              }
638              if (allow_close) {
639                  shutdown_action = SHUTDOWN_ACTION_POWEROFF;
640                  qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
641              }
642          } else {
643              SDL_HideWindow(scon->real_window);
644              scon->hidden = true;
645          }
646          break;
647      case SDL_WINDOWEVENT_SHOWN:
648          scon->hidden = false;
649          break;
650      case SDL_WINDOWEVENT_HIDDEN:
651          scon->hidden = true;
652          break;
653      }
654  }
655  
656  void sdl2_poll_events(struct sdl2_console *scon)
657  {
658      SDL_Event ev1, *ev = &ev1;
659      bool allow_close = true;
660      int idle = 1;
661  
662      if (scon->last_vm_running != runstate_is_running()) {
663          scon->last_vm_running = runstate_is_running();
664          sdl_update_caption(scon);
665      }
666  
667      while (SDL_PollEvent(ev)) {
668          switch (ev->type) {
669          case SDL_KEYDOWN:
670              idle = 0;
671              handle_keydown(ev);
672              break;
673          case SDL_KEYUP:
674              idle = 0;
675              handle_keyup(ev);
676              break;
677          case SDL_TEXTINPUT:
678              idle = 0;
679              handle_textinput(ev);
680              break;
681          case SDL_QUIT:
682              if (scon->opts->has_window_close && !scon->opts->window_close) {
683                  allow_close = false;
684              }
685              if (allow_close) {
686                  shutdown_action = SHUTDOWN_ACTION_POWEROFF;
687                  qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
688              }
689              break;
690          case SDL_MOUSEMOTION:
691              idle = 0;
692              handle_mousemotion(ev);
693              break;
694          case SDL_MOUSEBUTTONDOWN:
695          case SDL_MOUSEBUTTONUP:
696              idle = 0;
697              handle_mousebutton(ev);
698              break;
699          case SDL_MOUSEWHEEL:
700              idle = 0;
701              handle_mousewheel(ev);
702              break;
703          case SDL_WINDOWEVENT:
704              handle_windowevent(ev);
705              break;
706          default:
707              break;
708          }
709      }
710  
711      if (idle) {
712          if (scon->idle_counter < SDL2_MAX_IDLE_COUNT) {
713              scon->idle_counter++;
714              if (scon->idle_counter >= SDL2_MAX_IDLE_COUNT) {
715                  scon->dcl.update_interval = GUI_REFRESH_INTERVAL_DEFAULT;
716              }
717          }
718      } else {
719          scon->idle_counter = 0;
720          scon->dcl.update_interval = SDL2_REFRESH_INTERVAL_BUSY;
721      }
722  }
723  
724  static void sdl_mouse_warp(DisplayChangeListener *dcl,
725                             int x, int y, int on)
726  {
727      struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
728  
729      if (!qemu_console_is_graphic(scon->dcl.con)) {
730          return;
731      }
732  
733      if (on) {
734          if (!guest_cursor) {
735              sdl_show_cursor(scon);
736          }
737          if (gui_grab || qemu_input_is_absolute() || absolute_enabled) {
738              SDL_SetCursor(guest_sprite);
739              if (!qemu_input_is_absolute() && !absolute_enabled) {
740                  SDL_WarpMouseInWindow(scon->real_window, x, y);
741              }
742          }
743      } else if (gui_grab) {
744          sdl_hide_cursor(scon);
745      }
746      guest_cursor = on;
747      guest_x = x, guest_y = y;
748  }
749  
750  static void sdl_mouse_define(DisplayChangeListener *dcl,
751                               QEMUCursor *c)
752  {
753  
754      if (guest_sprite) {
755          SDL_FreeCursor(guest_sprite);
756      }
757  
758      if (guest_sprite_surface) {
759          SDL_FreeSurface(guest_sprite_surface);
760      }
761  
762      guest_sprite_surface =
763          SDL_CreateRGBSurfaceFrom(c->data, c->width, c->height, 32, c->width * 4,
764                                   0xff0000, 0x00ff00, 0xff, 0xff000000);
765  
766      if (!guest_sprite_surface) {
767          fprintf(stderr, "Failed to make rgb surface from %p\n", c);
768          return;
769      }
770      guest_sprite = SDL_CreateColorCursor(guest_sprite_surface,
771                                           c->hot_x, c->hot_y);
772      if (!guest_sprite) {
773          fprintf(stderr, "Failed to make color cursor from %p\n", c);
774          return;
775      }
776      if (guest_cursor &&
777          (gui_grab || qemu_input_is_absolute() || absolute_enabled)) {
778          SDL_SetCursor(guest_sprite);
779      }
780  }
781  
782  static void sdl_cleanup(void)
783  {
784      if (guest_sprite) {
785          SDL_FreeCursor(guest_sprite);
786      }
787      SDL_QuitSubSystem(SDL_INIT_VIDEO);
788  }
789  
790  static const DisplayChangeListenerOps dcl_2d_ops = {
791      .dpy_name             = "sdl2-2d",
792      .dpy_gfx_update       = sdl2_2d_update,
793      .dpy_gfx_switch       = sdl2_2d_switch,
794      .dpy_gfx_check_format = sdl2_2d_check_format,
795      .dpy_refresh          = sdl2_2d_refresh,
796      .dpy_mouse_set        = sdl_mouse_warp,
797      .dpy_cursor_define    = sdl_mouse_define,
798  };
799  
800  #ifdef CONFIG_OPENGL
801  static const DisplayChangeListenerOps dcl_gl_ops = {
802      .dpy_name                = "sdl2-gl",
803      .dpy_gfx_update          = sdl2_gl_update,
804      .dpy_gfx_switch          = sdl2_gl_switch,
805      .dpy_gfx_check_format    = console_gl_check_format,
806      .dpy_refresh             = sdl2_gl_refresh,
807      .dpy_mouse_set           = sdl_mouse_warp,
808      .dpy_cursor_define       = sdl_mouse_define,
809  
810      .dpy_gl_scanout_disable  = sdl2_gl_scanout_disable,
811      .dpy_gl_scanout_texture  = sdl2_gl_scanout_texture,
812      .dpy_gl_update           = sdl2_gl_scanout_flush,
813  };
814  
815  static bool
816  sdl2_gl_is_compatible_dcl(DisplayGLCtx *dgc,
817                            DisplayChangeListener *dcl)
818  {
819      return dcl->ops == &dcl_gl_ops;
820  }
821  
822  static const DisplayGLCtxOps gl_ctx_ops = {
823      .dpy_gl_ctx_is_compatible_dcl = sdl2_gl_is_compatible_dcl,
824      .dpy_gl_ctx_create       = sdl2_gl_create_context,
825      .dpy_gl_ctx_destroy      = sdl2_gl_destroy_context,
826      .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
827  };
828  #endif
829  
830  static void sdl2_display_early_init(DisplayOptions *o)
831  {
832      assert(o->type == DISPLAY_TYPE_SDL);
833      if (o->has_gl && o->gl) {
834  #ifdef CONFIG_OPENGL
835          display_opengl = 1;
836  #endif
837      }
838  }
839  
840  static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
841  {
842      uint8_t data = 0;
843      int i;
844      SDL_SysWMinfo info;
845      SDL_Surface *icon = NULL;
846      char *dir;
847  
848      assert(o->type == DISPLAY_TYPE_SDL);
849  
850      if (SDL_GetHintBoolean("QEMU_ENABLE_SDL_LOGGING", SDL_FALSE)) {
851          SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
852      }
853  
854      if (SDL_Init(SDL_INIT_VIDEO)) {
855          fprintf(stderr, "Could not initialize SDL(%s) - exiting\n",
856                  SDL_GetError());
857          exit(1);
858      }
859  #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* only available since SDL 2.0.8 */
860      SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
861  #endif
862  #ifndef CONFIG_WIN32
863      /* QEMU uses its own low level keyboard hook procecure on Windows */
864      SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
865  #endif
866  #ifdef SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED
867      SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0");
868  #endif
869      SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1");
870      memset(&info, 0, sizeof(info));
871      SDL_VERSION(&info.version);
872  
873      gui_fullscreen = o->has_full_screen && o->full_screen;
874  
875      if (o->u.sdl.has_grab_mod) {
876          if (o->u.sdl.grab_mod == HOT_KEY_MOD_LSHIFT_LCTRL_LALT) {
877              alt_grab = true;
878          } else if (o->u.sdl.grab_mod == HOT_KEY_MOD_RCTRL) {
879              ctrl_grab = true;
880          }
881      }
882  
883      for (i = 0;; i++) {
884          QemuConsole *con = qemu_console_lookup_by_index(i);
885          if (!con) {
886              break;
887          }
888      }
889      sdl2_num_outputs = i;
890      if (sdl2_num_outputs == 0) {
891          return;
892      }
893      sdl2_console = g_new0(struct sdl2_console, sdl2_num_outputs);
894      for (i = 0; i < sdl2_num_outputs; i++) {
895          QemuConsole *con = qemu_console_lookup_by_index(i);
896          assert(con != NULL);
897          if (!qemu_console_is_graphic(con) &&
898              qemu_console_get_index(con) != 0) {
899              sdl2_console[i].hidden = true;
900          }
901          sdl2_console[i].idx = i;
902          sdl2_console[i].opts = o;
903  #ifdef CONFIG_OPENGL
904          sdl2_console[i].opengl = display_opengl;
905          sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops;
906          sdl2_console[i].dgc.ops = display_opengl ? &gl_ctx_ops : NULL;
907  #else
908          sdl2_console[i].opengl = 0;
909          sdl2_console[i].dcl.ops = &dcl_2d_ops;
910  #endif
911          sdl2_console[i].dcl.con = con;
912          sdl2_console[i].kbd = qkbd_state_init(con);
913          if (display_opengl) {
914              qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dgc);
915          }
916          register_displaychangelistener(&sdl2_console[i].dcl);
917  
918  #if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
919          if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
920  #if defined(SDL_VIDEO_DRIVER_WINDOWS)
921              qemu_console_set_window_id(con, (uintptr_t)info.info.win.window);
922  #elif defined(SDL_VIDEO_DRIVER_X11)
923              qemu_console_set_window_id(con, info.info.x11.window);
924  #endif
925          }
926  #endif
927      }
928  
929  #ifdef CONFIG_SDL_IMAGE
930      dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/128x128/apps/qemu.png");
931      icon = IMG_Load(dir);
932  #else
933      /* Load a 32x32x4 image. White pixels are transparent. */
934      dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/32x32/apps/qemu.bmp");
935      icon = SDL_LoadBMP(dir);
936      if (icon) {
937          uint32_t colorkey = SDL_MapRGB(icon->format, 255, 255, 255);
938          SDL_SetColorKey(icon, SDL_TRUE, colorkey);
939      }
940  #endif
941      g_free(dir);
942      if (icon) {
943          SDL_SetWindowIcon(sdl2_console[0].real_window, icon);
944      }
945  
946      mouse_mode_notifier.notify = sdl_mouse_mode_change;
947      qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier);
948  
949      sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0);
950      sdl_cursor_normal = SDL_GetCursor();
951  
952      if (gui_fullscreen) {
953          sdl_grab_start(&sdl2_console[0]);
954      }
955  
956      atexit(sdl_cleanup);
957  }
958  
959  static QemuDisplay qemu_display_sdl2 = {
960      .type       = DISPLAY_TYPE_SDL,
961      .early_init = sdl2_display_early_init,
962      .init       = sdl2_display_init,
963  };
964  
965  static void register_sdl1(void)
966  {
967      qemu_display_register(&qemu_display_sdl2);
968  }
969  
970  type_init(register_sdl1);
971  
972  #ifdef CONFIG_OPENGL
973  module_dep("ui-opengl");
974  #endif
975