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