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