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