xref: /openbmc/qemu/ui/sdl2.c (revision 42fe7499)
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         kbd_put_string_console(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