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