]> git.proxmox.com Git - mirror_qemu.git/blame - ui/sdl2.c
spice: Update QXLInterface for spice >= 0.15.0
[mirror_qemu.git] / ui / sdl2.c
CommitLineData
47c03744
DA
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
e16f4c87 26#include "qemu/osdep.h"
0b8fa32f 27#include "qemu/module.h"
77d910fb 28#include "qemu/cutils.h"
47c03744
DA
29#include "ui/console.h"
30#include "ui/input.h"
5d0fe650 31#include "ui/sdl2.h"
54d31236 32#include "sysemu/runstate.h"
e6dba048 33#include "sysemu/runstate-action.h"
47c03744 34#include "sysemu/sysemu.h"
83047345 35#include "ui/win32-kbd-hook.h"
ed80f503 36#include "qemu/log.h"
47c03744 37
47c03744 38static int sdl2_num_outputs;
5d0fe650 39static struct sdl2_console *sdl2_console;
47c03744
DA
40
41static SDL_Surface *guest_sprite_surface;
42static int gui_grab; /* if true, all keyboard/mouse events are grabbed */
43
47c03744
DA
44static int gui_saved_grab;
45static int gui_fullscreen;
47c03744 46static int gui_grab_code = KMOD_LALT | KMOD_LCTRL;
47c03744
DA
47static SDL_Cursor *sdl_cursor_normal;
48static SDL_Cursor *sdl_cursor_hidden;
49static int absolute_enabled;
50static int guest_cursor;
51static int guest_x, guest_y;
52static SDL_Cursor *guest_sprite;
47c03744
DA
53static Notifier mouse_mode_notifier;
54
56bdd4b6
JM
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
5d0fe650 59static void sdl_update_caption(struct sdl2_console *scon);
47c03744 60
5d0fe650 61static struct sdl2_console *get_scon_from_window(uint32_t window_id)
47c03744
DA
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
2c3056f1 72void sdl2_window_create(struct sdl2_console *scon)
47c03744 73{
46522a82 74 int flags = 0;
47c03744 75
46522a82
GH
76 if (!scon->surface) {
77 return;
78 }
79 assert(!scon->real_window);
80
81 if (gui_fullscreen) {
82 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
47c03744 83 } else {
46522a82
GH
84 flags |= SDL_WINDOW_RESIZABLE;
85 }
86 if (scon->hidden) {
87 flags |= SDL_WINDOW_HIDDEN;
88 }
67c6f1db
JHW
89#ifdef CONFIG_OPENGL
90 if (scon->opengl) {
91 flags |= SDL_WINDOW_OPENGL;
92 }
93#endif
46522a82
GH
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);
0b71a5d5
GH
101 if (scon->opengl) {
102 scon->winctx = SDL_GL_GetCurrentContext();
103 }
46522a82
GH
104 sdl_update_caption(scon);
105}
47c03744 106
2c3056f1 107void sdl2_window_destroy(struct sdl2_console *scon)
46522a82
GH
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
2c3056f1 119void sdl2_window_resize(struct sdl2_console *scon)
46522a82
GH
120{
121 if (!scon->real_window) {
122 return;
47c03744 123 }
46522a82
GH
124
125 SDL_SetWindowSize(scon->real_window,
126 surface_width(scon->surface),
127 surface_height(scon->surface));
47c03744
DA
128}
129
0b71a5d5
GH
130static 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
5d0fe650 141static void sdl_update_caption(struct sdl2_console *scon)
47c03744
DA
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) {
f8d2c936 151 status = " - Press Ctrl-Alt-Shift-G to exit grab";
47c03744 152 } else if (ctrl_grab) {
f8d2c936 153 status = " - Press Right-Ctrl-G to exit grab";
47c03744 154 } else {
f8d2c936 155 status = " - Press Ctrl-Alt-G to exit grab";
47c03744
DA
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
86a088e6 173static void sdl_hide_cursor(struct sdl2_console *scon)
47c03744 174{
86a088e6 175 if (scon->opts->has_show_cursor && scon->opts->show_cursor) {
47c03744
DA
176 return;
177 }
178
253347e1
JM
179 SDL_ShowCursor(SDL_DISABLE);
180 SDL_SetCursor(sdl_cursor_hidden);
181
182 if (!qemu_input_is_absolute()) {
2d968ffb 183 SDL_SetRelativeMouseMode(SDL_TRUE);
47c03744
DA
184 }
185}
186
86a088e6 187static void sdl_show_cursor(struct sdl2_console *scon)
47c03744 188{
86a088e6 189 if (scon->opts->has_show_cursor && scon->opts->show_cursor) {
47c03744
DA
190 return;
191 }
192
193 if (!qemu_input_is_absolute()) {
2d968ffb 194 SDL_SetRelativeMouseMode(SDL_FALSE);
47c03744 195 }
253347e1
JM
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);
47c03744
DA
205}
206
5d0fe650 207static void sdl_grab_start(struct sdl2_console *scon)
47c03744 208{
f2335791
GH
209 QemuConsole *con = scon ? scon->dcl.con : NULL;
210
211 if (!con || !qemu_console_is_graphic(con)) {
212 return;
213 }
47c03744
DA
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 {
86a088e6 228 sdl_hide_cursor(scon);
47c03744
DA
229 }
230 SDL_SetWindowGrab(scon->real_window, SDL_TRUE);
231 gui_grab = 1;
83047345 232 win32_kbd_set_grab(true);
47c03744
DA
233 sdl_update_caption(scon);
234}
235
5d0fe650 236static void sdl_grab_end(struct sdl2_console *scon)
47c03744
DA
237{
238 SDL_SetWindowGrab(scon->real_window, SDL_FALSE);
239 gui_grab = 0;
83047345 240 win32_kbd_set_grab(false);
86a088e6 241 sdl_show_cursor(scon);
47c03744
DA
242 sdl_update_caption(scon);
243}
244
5d0fe650 245static void absolute_mouse_grab(struct sdl2_console *scon)
47c03744
DA
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
257static void sdl_mouse_mode_change(Notifier *notify, void *data)
258{
259 if (qemu_input_is_absolute()) {
260 if (!absolute_enabled) {
261 absolute_enabled = 1;
8dfa3061 262 SDL_SetRelativeMouseMode(SDL_FALSE);
47c03744
DA
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
5d0fe650 273static void sdl_send_mouse_event(struct sdl2_console *scon, int dx, int dy,
3f2fde2a 274 int x, int y, int state)
47c03744 275{
7fb1cf16 276 static uint32_t bmap[INPUT_BUTTON__MAX] = {
47c03744
DA
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),
29511061
DW
280 [INPUT_BUTTON_SIDE] = SDL_BUTTON(SDL_BUTTON_X1),
281 [INPUT_BUTTON_EXTRA] = SDL_BUTTON(SDL_BUTTON_X2)
47c03744
DA
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()) {
d9f06262
JM
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));
afbc0dd6
CR
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);
47c03744
DA
306 }
307 qemu_input_event_sync();
308}
309
5d0fe650 310static void toggle_full_screen(struct sdl2_console *scon)
47c03744 311{
47c03744
DA
312 gui_fullscreen = !gui_fullscreen;
313 if (gui_fullscreen) {
46522a82
GH
314 SDL_SetWindowFullscreen(scon->real_window,
315 SDL_WINDOW_FULLSCREEN_DESKTOP);
47c03744
DA
316 gui_saved_grab = gui_grab;
317 sdl_grab_start(scon);
318 } else {
47c03744
DA
319 if (!gui_saved_grab) {
320 sdl_grab_end(scon);
321 }
46522a82 322 SDL_SetWindowFullscreen(scon->real_window, 0);
47c03744 323 }
0b71a5d5 324 sdl2_redraw(scon);
47c03744
DA
325}
326
849bbe60 327static int get_mod_state(void)
47c03744 328{
849bbe60 329 SDL_Keymod mod = SDL_GetModState();
47c03744
DA
330
331 if (alt_grab) {
849bbe60 332 return (mod & (gui_grab_code | KMOD_LSHIFT)) ==
47c03744
DA
333 (gui_grab_code | KMOD_LSHIFT);
334 } else if (ctrl_grab) {
849bbe60 335 return (mod & KMOD_RCTRL) == KMOD_RCTRL;
47c03744 336 } else {
849bbe60 337 return (mod & gui_grab_code) == gui_grab_code;
47c03744 338 }
849bbe60 339}
47c03744 340
83047345
VR
341static 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
849bbe60
JM
354static void handle_keydown(SDL_Event *ev)
355{
356 int win;
357 struct sdl2_console *scon = get_scon_from_window(ev->key.windowID);
afb92eb9 358 int gui_key_modifier_pressed = get_mod_state();
07333e1c 359 int gui_keysym = 0;
849bbe60 360
32ec9839
CD
361 if (!scon) {
362 return;
363 }
364
849bbe60 365 if (!scon->ignore_hotkeys && gui_key_modifier_pressed && !ev->key.repeat) {
47c03744 366 switch (ev->key.keysym.scancode) {
363f59d9
GH
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:
56f289f3
CR
375 if (gui_grab) {
376 sdl_grab_end(scon);
377 }
378
363f59d9
GH
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;
47c03744
DA
392 case SDL_SCANCODE_F:
393 toggle_full_screen(scon);
394 gui_keysym = 1;
395 break;
f8d2c936
GH
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;
47c03744 404 case SDL_SCANCODE_U:
64bf97e5 405 sdl2_window_resize(scon);
0b71a5d5
GH
406 if (!scon->opengl) {
407 /* re-create scon->texture */
408 sdl2_2d_switch(&scon->dcl, scon->surface);
409 }
47c03744
DA
410 gui_keysym = 1;
411 break;
46522a82 412#if 0
47c03744
DA
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);
46522a82
GH
425 fprintf(stderr, "%s: scale to %dx%d\n",
426 __func__, width, height);
47c03744 427 sdl_scale(scon, width, height);
0b71a5d5 428 sdl2_redraw(scon);
47c03744
DA
429 gui_keysym = 1;
430 }
46522a82 431#endif
47c03744
DA
432 default:
433 break;
434 }
435 }
436 if (!gui_keysym) {
8fc1a3f5 437 sdl2_process_key(scon, &ev->key);
47c03744
DA
438 }
439}
440
441static void handle_keyup(SDL_Event *ev)
442{
5d0fe650 443 struct sdl2_console *scon = get_scon_from_window(ev->key.windowID);
47c03744 444
32ec9839
CD
445 if (!scon) {
446 return;
447 }
448
849bbe60 449 scon->ignore_hotkeys = false;
07333e1c 450 sdl2_process_key(scon, &ev->key);
47c03744
DA
451}
452
f2335791
GH
453static void handle_textinput(SDL_Event *ev)
454{
48db08cf 455 struct sdl2_console *scon = get_scon_from_window(ev->text.windowID);
f2335791
GH
456 QemuConsole *con = scon ? scon->dcl.con : NULL;
457
32ec9839
CD
458 if (!con) {
459 return;
460 }
461
f2335791
GH
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
47c03744
DA
468static void handle_mousemotion(SDL_Event *ev)
469{
470 int max_x, max_y;
48db08cf 471 struct sdl2_console *scon = get_scon_from_window(ev->motion.windowID);
47c03744 472
49213b72 473 if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
28216716
JM
474 return;
475 }
476
47c03744
DA
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;
24952847
JM
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)) {
47c03744
DA
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) {
3f2fde2a 494 sdl_send_mouse_event(scon, ev->motion.xrel, ev->motion.yrel,
47c03744
DA
495 ev->motion.x, ev->motion.y, ev->motion.state);
496 }
497}
498
499static void handle_mousebutton(SDL_Event *ev)
500{
501 int buttonstate = SDL_GetMouseState(NULL, NULL);
502 SDL_MouseButtonEvent *bev;
48db08cf 503 struct sdl2_console *scon = get_scon_from_window(ev->button.windowID);
47c03744 504
49213b72 505 if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
28216716
JM
506 return;
507 }
508
47c03744
DA
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 {
47c03744
DA
516 if (ev->type == SDL_MOUSEBUTTONDOWN) {
517 buttonstate |= SDL_BUTTON(bev->button);
518 } else {
519 buttonstate &= ~SDL_BUTTON(bev->button);
520 }
3f2fde2a
CR
521 sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate);
522 }
523}
524
525static void handle_mousewheel(SDL_Event *ev)
526{
48db08cf 527 struct sdl2_console *scon = get_scon_from_window(ev->wheel.windowID);
3f2fde2a
CR
528 SDL_MouseWheelEvent *wev = &ev->wheel;
529 InputButton btn;
530
49213b72 531 if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
28216716
JM
532 return;
533 }
534
3f2fde2a 535 if (wev->y > 0) {
f22d0af0 536 btn = INPUT_BUTTON_WHEEL_UP;
3f2fde2a 537 } else if (wev->y < 0) {
f22d0af0 538 btn = INPUT_BUTTON_WHEEL_DOWN;
ed80f503
DP
539 } else if (wev->x < 0) {
540 btn = INPUT_BUTTON_WHEEL_RIGHT;
541 } else if (wev->x > 0) {
542 btn = INPUT_BUTTON_WHEEL_LEFT;
3f2fde2a
CR
543 } else {
544 return;
47c03744 545 }
3f2fde2a
CR
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();
47c03744
DA
551}
552
1dfc5c88 553static void handle_windowevent(SDL_Event *ev)
47c03744 554{
1dfc5c88 555 struct sdl2_console *scon = get_scon_from_window(ev->window.windowID);
fe91f36a 556 bool allow_close = true;
1dfc5c88 557
08d49df0
AG
558 if (!scon) {
559 return;
560 }
561
47c03744
DA
562 switch (ev->window.event) {
563 case SDL_WINDOWEVENT_RESIZED:
8b15d9f1
DA
564 {
565 QemuUIInfo info;
566 memset(&info, 0, sizeof(info));
567 info.width = ev->window.data1;
568 info.height = ev->window.data2;
ca19ef52 569 dpy_set_ui_info(scon->dcl.con, &info, true);
8b15d9f1 570 }
0b71a5d5 571 sdl2_redraw(scon);
47c03744
DA
572 break;
573 case SDL_WINDOWEVENT_EXPOSED:
0b71a5d5 574 sdl2_redraw(scon);
47c03744
DA
575 break;
576 case SDL_WINDOWEVENT_FOCUS_GAINED:
83047345
VR
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 */
47c03744
DA
582 case SDL_WINDOWEVENT_ENTER:
583 if (!gui_grab && (qemu_input_is_absolute() || absolute_enabled)) {
584 absolute_mouse_grab(scon);
585 }
849bbe60
JM
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();
47c03744
DA
594 break;
595 case SDL_WINDOWEVENT_FOCUS_LOST:
83047345
VR
596 if (qemu_console_is_graphic(scon->dcl.con)) {
597 win32_kbd_set_window(NULL);
598 }
47c03744
DA
599 if (gui_grab && !gui_fullscreen) {
600 sdl_grab_end(scon);
601 }
602 break;
603 case SDL_WINDOWEVENT_RESTORED:
63ed4907 604 update_displaychangelistener(&scon->dcl, GUI_REFRESH_INTERVAL_DEFAULT);
47c03744
DA
605 break;
606 case SDL_WINDOWEVENT_MINIMIZED:
63ed4907 607 update_displaychangelistener(&scon->dcl, 500);
47c03744
DA
608 break;
609 case SDL_WINDOWEVENT_CLOSE:
fc49e727 610 if (qemu_console_is_graphic(scon->dcl.con)) {
844fd50d 611 if (scon->opts->has_window_close && !scon->opts->window_close) {
fe91f36a
GH
612 allow_close = false;
613 }
614 if (allow_close) {
e6dba048 615 shutdown_action = SHUTDOWN_ACTION_POWEROFF;
fc49e727
JM
616 qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
617 }
618 } else {
619 SDL_HideWindow(scon->real_window);
620 scon->hidden = true;
47c03744
DA
621 }
622 break;
d3f3a0f4 623 case SDL_WINDOWEVENT_SHOWN:
bcf43cdc 624 scon->hidden = false;
d3f3a0f4
HR
625 break;
626 case SDL_WINDOWEVENT_HIDDEN:
bcf43cdc 627 scon->hidden = true;
d3f3a0f4 628 break;
47c03744
DA
629 }
630}
631
63ed4907 632void sdl2_poll_events(struct sdl2_console *scon)
47c03744 633{
47c03744 634 SDL_Event ev1, *ev = &ev1;
fe91f36a 635 bool allow_close = true;
56bdd4b6 636 int idle = 1;
47c03744
DA
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
47c03744
DA
643 while (SDL_PollEvent(ev)) {
644 switch (ev->type) {
645 case SDL_KEYDOWN:
56bdd4b6 646 idle = 0;
47c03744
DA
647 handle_keydown(ev);
648 break;
649 case SDL_KEYUP:
56bdd4b6 650 idle = 0;
47c03744
DA
651 handle_keyup(ev);
652 break;
f2335791 653 case SDL_TEXTINPUT:
56bdd4b6 654 idle = 0;
f2335791
GH
655 handle_textinput(ev);
656 break;
47c03744 657 case SDL_QUIT:
844fd50d 658 if (scon->opts->has_window_close && !scon->opts->window_close) {
fe91f36a
GH
659 allow_close = false;
660 }
661 if (allow_close) {
e6dba048 662 shutdown_action = SHUTDOWN_ACTION_POWEROFF;
cf83f140 663 qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
47c03744
DA
664 }
665 break;
666 case SDL_MOUSEMOTION:
56bdd4b6 667 idle = 0;
47c03744
DA
668 handle_mousemotion(ev);
669 break;
670 case SDL_MOUSEBUTTONDOWN:
671 case SDL_MOUSEBUTTONUP:
56bdd4b6 672 idle = 0;
47c03744
DA
673 handle_mousebutton(ev);
674 break;
3f2fde2a 675 case SDL_MOUSEWHEEL:
56bdd4b6 676 idle = 0;
3f2fde2a
CR
677 handle_mousewheel(ev);
678 break;
47c03744 679 case SDL_WINDOWEVENT:
1dfc5c88 680 handle_windowevent(ev);
47c03744
DA
681 break;
682 default:
683 break;
684 }
685 }
56bdd4b6
JM
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 }
47c03744
DA
698}
699
700static void sdl_mouse_warp(DisplayChangeListener *dcl,
701 int x, int y, int on)
702{
5d0fe650 703 struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
28216716
JM
704
705 if (!qemu_console_is_graphic(scon->dcl.con)) {
706 return;
707 }
708
47c03744
DA
709 if (on) {
710 if (!guest_cursor) {
86a088e6 711 sdl_show_cursor(scon);
47c03744
DA
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) {
86a088e6 720 sdl_hide_cursor(scon);
47c03744
DA
721 }
722 guest_cursor = on;
723 guest_x = x, guest_y = y;
724}
725
726static 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
758static void sdl_cleanup(void)
759{
760 if (guest_sprite) {
761 SDL_FreeCursor(guest_sprite);
762 }
763 SDL_QuitSubSystem(SDL_INIT_VIDEO);
764}
765
f1ddebd8 766static const DisplayChangeListenerOps dcl_2d_ops = {
877417d9
GH
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,
47c03744
DA
774};
775
0b71a5d5
GH
776#ifdef CONFIG_OPENGL
777static 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,
cb47dc9a 785
db6cdfbe 786 .dpy_gl_scanout_disable = sdl2_gl_scanout_disable,
f4c36bda 787 .dpy_gl_scanout_texture = sdl2_gl_scanout_texture,
cb47dc9a 788 .dpy_gl_update = sdl2_gl_scanout_flush,
0b71a5d5 789};
5e79d516
MAL
790
791static const DisplayGLCtxOps gl_ctx_ops = {
792 .compatible_dcl = &dcl_gl_ops,
793 .dpy_gl_ctx_create = sdl2_gl_create_context,
794 .dpy_gl_ctx_destroy = sdl2_gl_destroy_context,
795 .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
796};
0b71a5d5
GH
797#endif
798
5ee1718f 799static void sdl2_display_early_init(DisplayOptions *o)
0b71a5d5 800{
fe91f36a
GH
801 assert(o->type == DISPLAY_TYPE_SDL);
802 if (o->has_gl && o->gl) {
0b71a5d5
GH
803#ifdef CONFIG_OPENGL
804 display_opengl = 1;
805#endif
0b71a5d5
GH
806 }
807}
808
5ee1718f 809static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
47c03744 810{
47c03744 811 uint8_t data = 0;
47c03744 812 int i;
d8253671 813 SDL_SysWMinfo info;
a442fe2f 814 SDL_Surface *icon = NULL;
77d910fb 815 char *dir;
47c03744 816
fe91f36a 817 assert(o->type == DISPLAY_TYPE_SDL);
fe91f36a 818
47c03744
DA
819#ifdef __linux__
820 /* on Linux, SDL may use fbcon|directfb|svgalib when run without
821 * accessible $DISPLAY to open X11 window. This is often the case
822 * when qemu is run using sudo. But in this case, and when actually
823 * run in X11 environment, SDL fights with X11 for the video card,
824 * making current display unavailable, often until reboot.
825 * So make x11 the default SDL video driver if this variable is unset.
826 * This is a bit hackish but saves us from bigger problem.
827 * Maybe it's a good idea to fix this in SDL instead.
828 */
6ff5b5d6
PM
829 if (!g_setenv("SDL_VIDEODRIVER", "x11", 0)) {
830 fprintf(stderr, "Could not set SDL_VIDEODRIVER environment variable\n");
831 exit(1);
832 }
47c03744
DA
833#endif
834
2313e482 835 if (SDL_Init(SDL_INIT_VIDEO)) {
47c03744
DA
836 fprintf(stderr, "Could not initialize SDL(%s) - exiting\n",
837 SDL_GetError());
838 exit(1);
839 }
8c2b816f
SK
840#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* only available since SDL 2.0.8 */
841 SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
842#endif
44f017d0 843 SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
d8253671
ST
844 memset(&info, 0, sizeof(info));
845 SDL_VERSION(&info.version);
47c03744 846
6fb34ffc
TH
847 gui_fullscreen = o->has_full_screen && o->full_screen;
848
47c03744
DA
849 for (i = 0;; i++) {
850 QemuConsole *con = qemu_console_lookup_by_index(i);
f2335791 851 if (!con) {
47c03744
DA
852 break;
853 }
854 }
855 sdl2_num_outputs = i;
8efa5f29
GH
856 if (sdl2_num_outputs == 0) {
857 return;
858 }
5d0fe650 859 sdl2_console = g_new0(struct sdl2_console, sdl2_num_outputs);
47c03744
DA
860 for (i = 0; i < sdl2_num_outputs; i++) {
861 QemuConsole *con = qemu_console_lookup_by_index(i);
85970a62 862 assert(con != NULL);
6624c38d
GH
863 if (!qemu_console_is_graphic(con) &&
864 qemu_console_get_index(con) != 0) {
f2335791
GH
865 sdl2_console[i].hidden = true;
866 }
0b71a5d5 867 sdl2_console[i].idx = i;
f88e5c57 868 sdl2_console[i].opts = o;
0b71a5d5
GH
869#ifdef CONFIG_OPENGL
870 sdl2_console[i].opengl = display_opengl;
871 sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops;
5e79d516 872 sdl2_console[i].dgc.ops = display_opengl ? &gl_ctx_ops : NULL;
0b71a5d5
GH
873#else
874 sdl2_console[i].opengl = 0;
f1ddebd8 875 sdl2_console[i].dcl.ops = &dcl_2d_ops;
0b71a5d5 876#endif
47c03744 877 sdl2_console[i].dcl.con = con;
07333e1c 878 sdl2_console[i].kbd = qkbd_state_init(con);
ac32b2ff 879 if (display_opengl) {
5e79d516 880 qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dgc);
ac32b2ff 881 }
47c03744 882 register_displaychangelistener(&sdl2_console[i].dcl);
d8253671 883
8417cd00 884#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
d8253671 885 if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
8417cd00
GH
886#if defined(SDL_VIDEO_DRIVER_WINDOWS)
887 qemu_console_set_window_id(con, (uintptr_t)info.info.win.window);
888#elif defined(SDL_VIDEO_DRIVER_X11)
d8253671 889 qemu_console_set_window_id(con, info.info.x11.window);
8417cd00 890#endif
d8253671 891 }
8417cd00 892#endif
47c03744
DA
893 }
894
a442fe2f 895#ifdef CONFIG_SDL_IMAGE
77d910fb
PB
896 dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/128x128/apps/qemu.png");
897 icon = IMG_Load(dir);
a442fe2f 898#else
47c03744 899 /* Load a 32x32x4 image. White pixels are transparent. */
77d910fb
PB
900 dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/32x32/apps/qemu.bmp");
901 icon = SDL_LoadBMP(dir);
a442fe2f
DB
902 if (icon) {
903 uint32_t colorkey = SDL_MapRGB(icon->format, 255, 255, 255);
904 SDL_SetColorKey(icon, SDL_TRUE, colorkey);
905 }
906#endif
77d910fb 907 g_free(dir);
a442fe2f
DB
908 if (icon) {
909 SDL_SetWindowIcon(sdl2_console[0].real_window, icon);
47c03744
DA
910 }
911
47c03744
DA
912 mouse_mode_notifier.notify = sdl_mouse_mode_change;
913 qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier);
914
47c03744
DA
915 sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0);
916 sdl_cursor_normal = SDL_GetCursor();
917
7dafc679
VR
918 if (gui_fullscreen) {
919 sdl_grab_start(&sdl2_console[0]);
920 }
921
47c03744
DA
922 atexit(sdl_cleanup);
923}
5ee1718f
GH
924
925static QemuDisplay qemu_display_sdl2 = {
926 .type = DISPLAY_TYPE_SDL,
927 .early_init = sdl2_display_early_init,
928 .init = sdl2_display_init,
929};
930
931static void register_sdl1(void)
932{
933 qemu_display_register(&qemu_display_sdl2);
934}
935
936type_init(register_sdl1);
b36ae1c1
GH
937
938#ifdef CONFIG_OPENGL
939module_dep("ui-opengl");
940#endif