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