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