| /* | |
| * QEMU SDL display driver | |
| * | |
| * Copyright (c) 2003 Fabrice Bellard | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| * of this software and associated documentation files (the "Software"), to deal | |
| * in the Software without restriction, including without limitation the rights | |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| * copies of the Software, and to permit persons to whom the Software is | |
| * furnished to do so, subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included in | |
| * all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| * THE SOFTWARE. | |
| */ | |
| /* Ported SDL 1.2 code to 2.0 by Dave Airlie. */ | |
| #include "qemu/osdep.h" | |
| #include "qemu/module.h" | |
| #include "qemu/cutils.h" | |
| #include "ui/console.h" | |
| #include "ui/input.h" | |
| #include "ui/sdl2.h" | |
| #include "system/runstate.h" | |
| #include "system/runstate-action.h" | |
| #include "system/system.h" | |
| #include "qemu/log.h" | |
| #include "qemu-main.h" | |
| #ifdef CONFIG_X11 | |
| #include <X11/Xlib.h> | |
| #endif | |
| static int sdl2_num_outputs; | |
| static struct sdl2_console *sdl2_console; | |
| static SDL_Surface *guest_sprite_surface; | |
| static int gui_grab; /* if true, all keyboard/mouse events are grabbed */ | |
| static bool alt_grab; | |
| static bool ctrl_grab; | |
| static int gui_saved_grab; | |
| static int gui_fullscreen; | |
| static int gui_grab_code = KMOD_LALT | KMOD_LCTRL; | |
| static SDL_Cursor *sdl_cursor_normal; | |
| static SDL_Cursor *sdl_cursor_hidden; | |
| static int absolute_enabled; | |
| static bool guest_cursor; | |
| static int guest_x, guest_y; | |
| static SDL_Cursor *guest_sprite; | |
| static Notifier mouse_mode_notifier; | |
| #define SDL2_REFRESH_INTERVAL_BUSY 10 | |
| #define SDL2_MAX_IDLE_COUNT (2 * GUI_REFRESH_INTERVAL_DEFAULT \ | |
| / SDL2_REFRESH_INTERVAL_BUSY + 1) | |
| /* introduced in SDL 2.0.10 */ | |
| #ifndef SDL_HINT_RENDER_BATCHING | |
| #define SDL_HINT_RENDER_BATCHING "SDL_RENDER_BATCHING" | |
| #endif | |
| static void sdl_update_caption(struct sdl2_console *scon); | |
| static struct sdl2_console *get_scon_from_window(uint32_t window_id) | |
| { | |
| int i; | |
| for (i = 0; i < sdl2_num_outputs; i++) { | |
| if (sdl2_console[i].real_window == SDL_GetWindowFromID(window_id)) { | |
| return &sdl2_console[i]; | |
| } | |
| } | |
| return NULL; | |
| } | |
| void sdl2_window_create(struct sdl2_console *scon) | |
| { | |
| int flags = 0; | |
| if (!scon->surface) { | |
| return; | |
| } | |
| assert(!scon->real_window); | |
| if (gui_fullscreen) { | |
| flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; | |
| } else { | |
| flags |= SDL_WINDOW_RESIZABLE; | |
| } | |
| if (scon->hidden) { | |
| flags |= SDL_WINDOW_HIDDEN; | |
| } | |
| #ifdef CONFIG_OPENGL | |
| if (scon->opengl) { | |
| flags |= SDL_WINDOW_OPENGL; | |
| } | |
| #endif | |
| scon->real_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, | |
| SDL_WINDOWPOS_UNDEFINED, | |
| surface_width(scon->surface), | |
| surface_height(scon->surface), | |
| flags); | |
| if (scon->opengl) { | |
| const char *driver = "opengl"; | |
| if (scon->opts->gl == DISPLAY_GL_MODE_ES) { | |
| driver = "opengles2"; | |
| SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, | |
| SDL_GL_CONTEXT_PROFILE_ES); | |
| } | |
| SDL_SetHint(SDL_HINT_RENDER_DRIVER, driver); | |
| SDL_SetHint(SDL_HINT_RENDER_BATCHING, "1"); | |
| scon->winctx = SDL_GL_CreateContext(scon->real_window); | |
| SDL_GL_SetSwapInterval(0); | |
| #ifdef CONFIG_OPENGL | |
| qemu_egl_display = eglGetCurrentDisplay(); | |
| #endif | |
| } else { | |
| /* The SDL renderer is only used by sdl2-2D, when OpenGL is disabled */ | |
| scon->real_renderer = SDL_CreateRenderer(scon->real_window, -1, 0); | |
| } | |
| sdl_update_caption(scon); | |
| } | |
| void sdl2_window_destroy(struct sdl2_console *scon) | |
| { | |
| if (!scon->real_window) { | |
| return; | |
| } | |
| if (scon->winctx) { | |
| SDL_GL_DeleteContext(scon->winctx); | |
| scon->winctx = NULL; | |
| } | |
| if (scon->real_renderer) { | |
| SDL_DestroyRenderer(scon->real_renderer); | |
| scon->real_renderer = NULL; | |
| } | |
| SDL_DestroyWindow(scon->real_window); | |
| scon->real_window = NULL; | |
| } | |
| void sdl2_window_resize(struct sdl2_console *scon) | |
| { | |
| if (!scon->real_window) { | |
| return; | |
| } | |
| SDL_SetWindowSize(scon->real_window, | |
| surface_width(scon->surface), | |
| surface_height(scon->surface)); | |
| } | |
| static void sdl2_redraw(struct sdl2_console *scon) | |
| { | |
| if (scon->opengl) { | |
| #ifdef CONFIG_OPENGL | |
| sdl2_gl_redraw(scon); | |
| #endif | |
| } else { | |
| sdl2_2d_redraw(scon); | |
| } | |
| } | |
| static void sdl_update_caption(struct sdl2_console *scon) | |
| { | |
| char win_title[1024]; | |
| char icon_title[1024]; | |
| const char *status = ""; | |
| if (!runstate_is_running()) { | |
| status = " [Stopped]"; | |
| } else if (gui_grab) { | |
| if (alt_grab) { | |
| #ifdef CONFIG_DARWIN | |
| status = " - Press ⌃⌥⇧G to exit grab"; | |
| #else | |
| status = " - Press Ctrl-Alt-Shift-G to exit grab"; | |
| #endif | |
| } else if (ctrl_grab) { | |
| status = " - Press Right-Ctrl-G to exit grab"; | |
| } else { | |
| #ifdef CONFIG_DARWIN | |
| status = " - Press ⌃⌥G to exit grab"; | |
| #else | |
| status = " - Press Ctrl-Alt-G to exit grab"; | |
| #endif | |
| } | |
| } | |
| if (qemu_name) { | |
| snprintf(win_title, sizeof(win_title), "QEMU (%s-%d)%s", qemu_name, | |
| scon->idx, status); | |
| snprintf(icon_title, sizeof(icon_title), "QEMU (%s)", qemu_name); | |
| } else { | |
| snprintf(win_title, sizeof(win_title), "QEMU%s", status); | |
| snprintf(icon_title, sizeof(icon_title), "QEMU"); | |
| } | |
| if (scon->real_window) { | |
| SDL_SetWindowTitle(scon->real_window, win_title); | |
| } | |
| } | |
| static void sdl_hide_cursor(struct sdl2_console *scon) | |
| { | |
| if (scon->opts->has_show_cursor && scon->opts->show_cursor) { | |
| return; | |
| } | |
| SDL_ShowCursor(SDL_DISABLE); | |
| SDL_SetCursor(sdl_cursor_hidden); | |
| if (!qemu_input_is_absolute(scon->dcl.con)) { | |
| SDL_SetRelativeMouseMode(SDL_TRUE); | |
| } | |
| } | |
| static void sdl_show_cursor(struct sdl2_console *scon) | |
| { | |
| if (scon->opts->has_show_cursor && scon->opts->show_cursor) { | |
| return; | |
| } | |
| if (!qemu_input_is_absolute(scon->dcl.con)) { | |
| SDL_SetRelativeMouseMode(SDL_FALSE); | |
| } | |
| if (guest_cursor && | |
| (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled)) { | |
| SDL_SetCursor(guest_sprite); | |
| } else { | |
| SDL_SetCursor(sdl_cursor_normal); | |
| } | |
| SDL_ShowCursor(SDL_ENABLE); | |
| } | |
| static void sdl_grab_start(struct sdl2_console *scon) | |
| { | |
| QemuConsole *con = scon ? scon->dcl.con : NULL; | |
| if (!con || !qemu_console_is_graphic(con)) { | |
| return; | |
| } | |
| /* | |
| * If the application is not active, do not try to enter grab state. This | |
| * prevents 'SDL_WM_GrabInput(SDL_GRAB_ON)' from blocking all the | |
| * application (SDL bug). | |
| */ | |
| if (!(SDL_GetWindowFlags(scon->real_window) & SDL_WINDOW_INPUT_FOCUS)) { | |
| return; | |
| } | |
| if (guest_cursor) { | |
| SDL_SetCursor(guest_sprite); | |
| if (!qemu_input_is_absolute(scon->dcl.con) && !absolute_enabled) { | |
| SDL_WarpMouseInWindow(scon->real_window, guest_x, guest_y); | |
| } | |
| } else { | |
| sdl_hide_cursor(scon); | |
| } | |
| SDL_SetWindowGrab(scon->real_window, SDL_TRUE); | |
| gui_grab = 1; | |
| sdl_update_caption(scon); | |
| } | |
| static void sdl_grab_end(struct sdl2_console *scon) | |
| { | |
| SDL_SetWindowGrab(scon->real_window, SDL_FALSE); | |
| gui_grab = 0; | |
| sdl_show_cursor(scon); | |
| sdl_update_caption(scon); | |
| } | |
| static void absolute_mouse_grab(struct sdl2_console *scon) | |
| { | |
| int mouse_x, mouse_y; | |
| int scr_w, scr_h; | |
| SDL_GetMouseState(&mouse_x, &mouse_y); | |
| SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); | |
| if (mouse_x > 0 && mouse_x < scr_w - 1 && | |
| mouse_y > 0 && mouse_y < scr_h - 1) { | |
| sdl_grab_start(scon); | |
| } | |
| } | |
| static void sdl_mouse_mode_change(Notifier *notify, void *data) | |
| { | |
| if (qemu_input_is_absolute(sdl2_console[0].dcl.con)) { | |
| if (!absolute_enabled) { | |
| absolute_enabled = 1; | |
| SDL_SetRelativeMouseMode(SDL_FALSE); | |
| absolute_mouse_grab(&sdl2_console[0]); | |
| } | |
| } else if (absolute_enabled) { | |
| if (!gui_fullscreen) { | |
| sdl_grab_end(&sdl2_console[0]); | |
| } | |
| absolute_enabled = 0; | |
| } | |
| } | |
| static void sdl_send_mouse_event(struct sdl2_console *scon, int dx, int dy, | |
| int x, int y, int state) | |
| { | |
| static uint32_t bmap[INPUT_BUTTON__MAX] = { | |
| [INPUT_BUTTON_LEFT] = SDL_BUTTON(SDL_BUTTON_LEFT), | |
| [INPUT_BUTTON_MIDDLE] = SDL_BUTTON(SDL_BUTTON_MIDDLE), | |
| [INPUT_BUTTON_RIGHT] = SDL_BUTTON(SDL_BUTTON_RIGHT), | |
| [INPUT_BUTTON_SIDE] = SDL_BUTTON(SDL_BUTTON_X1), | |
| [INPUT_BUTTON_EXTRA] = SDL_BUTTON(SDL_BUTTON_X2) | |
| }; | |
| static uint32_t prev_state; | |
| if (prev_state != state) { | |
| qemu_input_update_buttons(scon->dcl.con, bmap, prev_state, state); | |
| prev_state = state; | |
| } | |
| if (qemu_input_is_absolute(scon->dcl.con)) { | |
| qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_X, | |
| x, 0, surface_width(scon->surface)); | |
| qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_Y, | |
| y, 0, surface_height(scon->surface)); | |
| } else { | |
| if (guest_cursor) { | |
| x -= guest_x; | |
| y -= guest_y; | |
| guest_x += x; | |
| guest_y += y; | |
| dx = x; | |
| dy = y; | |
| } | |
| qemu_input_queue_rel(scon->dcl.con, INPUT_AXIS_X, dx); | |
| qemu_input_queue_rel(scon->dcl.con, INPUT_AXIS_Y, dy); | |
| } | |
| qemu_input_event_sync(); | |
| } | |
| static void toggle_full_screen(struct sdl2_console *scon) | |
| { | |
| gui_fullscreen = !gui_fullscreen; | |
| if (gui_fullscreen) { | |
| SDL_SetWindowFullscreen(scon->real_window, | |
| SDL_WINDOW_FULLSCREEN_DESKTOP); | |
| gui_saved_grab = gui_grab; | |
| sdl_grab_start(scon); | |
| } else { | |
| if (!gui_saved_grab) { | |
| sdl_grab_end(scon); | |
| } | |
| SDL_SetWindowFullscreen(scon->real_window, 0); | |
| } | |
| sdl2_redraw(scon); | |
| } | |
| static int get_mod_state(void) | |
| { | |
| SDL_Keymod mod = SDL_GetModState(); | |
| if (alt_grab) { | |
| return (mod & (gui_grab_code | KMOD_LSHIFT)) == | |
| (gui_grab_code | KMOD_LSHIFT); | |
| } else if (ctrl_grab) { | |
| return (mod & KMOD_RCTRL) == KMOD_RCTRL; | |
| } else { | |
| return (mod & gui_grab_code) == gui_grab_code; | |
| } | |
| } | |
| static void handle_keydown(SDL_Event *ev) | |
| { | |
| int win; | |
| struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); | |
| int gui_key_modifier_pressed = get_mod_state(); | |
| if (!scon) { | |
| return; | |
| } | |
| scon->gui_keysym = false; | |
| if (!scon->ignore_hotkeys && gui_key_modifier_pressed && !ev->key.repeat) { | |
| switch (ev->key.keysym.scancode) { | |
| case SDL_SCANCODE_2: | |
| case SDL_SCANCODE_3: | |
| case SDL_SCANCODE_4: | |
| case SDL_SCANCODE_5: | |
| case SDL_SCANCODE_6: | |
| case SDL_SCANCODE_7: | |
| case SDL_SCANCODE_8: | |
| case SDL_SCANCODE_9: | |
| if (gui_grab) { | |
| sdl_grab_end(scon); | |
| } | |
| win = ev->key.keysym.scancode - SDL_SCANCODE_1; | |
| if (win < sdl2_num_outputs) { | |
| sdl2_console[win].hidden = !sdl2_console[win].hidden; | |
| if (sdl2_console[win].real_window) { | |
| if (sdl2_console[win].hidden) { | |
| SDL_HideWindow(sdl2_console[win].real_window); | |
| } else { | |
| SDL_ShowWindow(sdl2_console[win].real_window); | |
| } | |
| } | |
| sdl2_release_modifiers(scon); | |
| scon->gui_keysym = true; | |
| } | |
| break; | |
| case SDL_SCANCODE_F: | |
| toggle_full_screen(scon); | |
| scon->gui_keysym = true; | |
| break; | |
| case SDL_SCANCODE_G: | |
| scon->gui_keysym = true; | |
| if (!gui_grab) { | |
| sdl_grab_start(scon); | |
| } else if (!gui_fullscreen) { | |
| sdl_grab_end(scon); | |
| } | |
| break; | |
| case SDL_SCANCODE_0: | |
| sdl2_window_resize(scon); | |
| if (!scon->opengl) { | |
| /* re-create scon->texture */ | |
| sdl2_2d_switch(&scon->dcl, scon->surface); | |
| } | |
| scon->gui_keysym = true; | |
| break; | |
| #if 0 | |
| case SDL_SCANCODE_KP_PLUS: | |
| case SDL_SCANCODE_KP_MINUS: | |
| if (!gui_fullscreen) { | |
| int scr_w, scr_h; | |
| int width, height; | |
| SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); | |
| width = MAX(scr_w + (ev->key.keysym.scancode == | |
| SDL_SCANCODE_KP_PLUS ? 50 : -50), | |
| 160); | |
| height = (surface_height(scon->surface) * width) / | |
| surface_width(scon->surface); | |
| fprintf(stderr, "%s: scale to %dx%d\n", | |
| __func__, width, height); | |
| sdl_scale(scon, width, height); | |
| sdl2_redraw(scon); | |
| scon->gui_keysym = true; | |
| } | |
| #endif | |
| default: | |
| break; | |
| } | |
| } | |
| if (!scon->gui_keysym) { | |
| sdl2_process_key(scon, &ev->key); | |
| } | |
| } | |
| static void handle_keyup(SDL_Event *ev) | |
| { | |
| struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); | |
| if (!scon) { | |
| return; | |
| } | |
| scon->ignore_hotkeys = false; | |
| sdl2_process_key(scon, &ev->key); | |
| } | |
| static void handle_textinput(SDL_Event *ev) | |
| { | |
| struct sdl2_console *scon = get_scon_from_window(ev->text.windowID); | |
| QemuConsole *con = scon ? scon->dcl.con : NULL; | |
| if (!con) { | |
| return; | |
| } | |
| if (!scon->gui_keysym && QEMU_IS_TEXT_CONSOLE(con)) { | |
| qemu_text_console_put_string(QEMU_TEXT_CONSOLE(con), ev->text.text, strlen(ev->text.text)); | |
| } | |
| } | |
| static void handle_mousemotion(SDL_Event *ev) | |
| { | |
| int max_x, max_y; | |
| struct sdl2_console *scon = get_scon_from_window(ev->motion.windowID); | |
| int scr_w, scr_h, surf_w, surf_h, x, y, dx, dy; | |
| if (!scon || !qemu_console_is_graphic(scon->dcl.con)) { | |
| return; | |
| } | |
| SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); | |
| if (qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) { | |
| max_x = scr_w - 1; | |
| max_y = scr_h - 1; | |
| if (gui_grab && !gui_fullscreen | |
| && (ev->motion.x == 0 || ev->motion.y == 0 || | |
| ev->motion.x == max_x || ev->motion.y == max_y)) { | |
| sdl_grab_end(scon); | |
| } | |
| if (!gui_grab && | |
| (ev->motion.x > 0 && ev->motion.x < max_x && | |
| ev->motion.y > 0 && ev->motion.y < max_y)) { | |
| sdl_grab_start(scon); | |
| } | |
| } | |
| surf_w = surface_width(scon->surface); | |
| surf_h = surface_height(scon->surface); | |
| x = (int64_t)ev->motion.x * surf_w / scr_w; | |
| y = (int64_t)ev->motion.y * surf_h / scr_h; | |
| dx = (int64_t)ev->motion.xrel * surf_w / scr_w; | |
| dy = (int64_t)ev->motion.yrel * surf_h / scr_h; | |
| if (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) { | |
| sdl_send_mouse_event(scon, dx, dy, x, y, ev->motion.state); | |
| } | |
| } | |
| static void handle_mousebutton(SDL_Event *ev) | |
| { | |
| int buttonstate = SDL_GetMouseState(NULL, NULL); | |
| SDL_MouseButtonEvent *bev; | |
| struct sdl2_console *scon = get_scon_from_window(ev->button.windowID); | |
| int scr_w, scr_h, x, y; | |
| if (!scon || !qemu_console_is_graphic(scon->dcl.con)) { | |
| return; | |
| } | |
| bev = &ev->button; | |
| SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); | |
| x = (int64_t)bev->x * surface_width(scon->surface) / scr_w; | |
| y = (int64_t)bev->y * surface_height(scon->surface) / scr_h; | |
| if (!gui_grab && !qemu_input_is_absolute(scon->dcl.con)) { | |
| if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) { | |
| /* start grabbing all events */ | |
| sdl_grab_start(scon); | |
| } | |
| } else { | |
| if (ev->type == SDL_MOUSEBUTTONDOWN) { | |
| buttonstate |= SDL_BUTTON(bev->button); | |
| } else { | |
| buttonstate &= ~SDL_BUTTON(bev->button); | |
| } | |
| sdl_send_mouse_event(scon, 0, 0, x, y, buttonstate); | |
| } | |
| } | |
| static void handle_mousewheel(SDL_Event *ev) | |
| { | |
| struct sdl2_console *scon = get_scon_from_window(ev->wheel.windowID); | |
| SDL_MouseWheelEvent *wev = &ev->wheel; | |
| InputButton btn; | |
| if (!scon || !qemu_console_is_graphic(scon->dcl.con)) { | |
| return; | |
| } | |
| if (wev->y > 0) { | |
| btn = INPUT_BUTTON_WHEEL_UP; | |
| } else if (wev->y < 0) { | |
| btn = INPUT_BUTTON_WHEEL_DOWN; | |
| } else if (wev->x < 0) { | |
| btn = INPUT_BUTTON_WHEEL_RIGHT; | |
| } else if (wev->x > 0) { | |
| btn = INPUT_BUTTON_WHEEL_LEFT; | |
| } else { | |
| return; | |
| } | |
| qemu_input_queue_btn(scon->dcl.con, btn, true); | |
| qemu_input_event_sync(); | |
| qemu_input_queue_btn(scon->dcl.con, btn, false); | |
| qemu_input_event_sync(); | |
| } | |
| static void handle_windowevent(SDL_Event *ev) | |
| { | |
| struct sdl2_console *scon = get_scon_from_window(ev->window.windowID); | |
| bool allow_close = true; | |
| if (!scon) { | |
| return; | |
| } | |
| switch (ev->window.event) { | |
| case SDL_WINDOWEVENT_RESIZED: | |
| { | |
| QemuUIInfo info = { | |
| .width = ev->window.data1, | |
| .height = ev->window.data2, | |
| }; | |
| qemu_console_set_ui_info(scon->dcl.con, &info, true); | |
| } | |
| sdl2_redraw(scon); | |
| break; | |
| case SDL_WINDOWEVENT_EXPOSED: | |
| sdl2_redraw(scon); | |
| break; | |
| case SDL_WINDOWEVENT_FOCUS_GAINED: | |
| /* fall through */ | |
| case SDL_WINDOWEVENT_ENTER: | |
| if (!gui_grab && (qemu_input_is_absolute(scon->dcl.con) || absolute_enabled)) { | |
| absolute_mouse_grab(scon); | |
| } | |
| /* If a new console window opened using a hotkey receives the | |
| * focus, SDL sends another KEYDOWN event to the new window, | |
| * closing the console window immediately after. | |
| * | |
| * Work around this by ignoring further hotkey events until a | |
| * key is released. | |
| */ | |
| scon->ignore_hotkeys = get_mod_state(); | |
| break; | |
| case SDL_WINDOWEVENT_FOCUS_LOST: | |
| if (gui_grab && !gui_fullscreen) { | |
| sdl_grab_end(scon); | |
| } | |
| break; | |
| case SDL_WINDOWEVENT_RESTORED: | |
| qemu_console_listener_set_refresh(&scon->dcl, GUI_REFRESH_INTERVAL_DEFAULT); | |
| break; | |
| case SDL_WINDOWEVENT_MINIMIZED: | |
| qemu_console_listener_set_refresh(&scon->dcl, 500); | |
| break; | |
| case SDL_WINDOWEVENT_CLOSE: | |
| if (qemu_console_is_graphic(scon->dcl.con)) { | |
| if (scon->opts->has_window_close && !scon->opts->window_close) { | |
| allow_close = false; | |
| } | |
| if (allow_close) { | |
| shutdown_action = SHUTDOWN_ACTION_POWEROFF; | |
| qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); | |
| } | |
| } else { | |
| SDL_HideWindow(scon->real_window); | |
| scon->hidden = true; | |
| } | |
| break; | |
| case SDL_WINDOWEVENT_SHOWN: | |
| scon->hidden = false; | |
| break; | |
| case SDL_WINDOWEVENT_HIDDEN: | |
| scon->hidden = true; | |
| break; | |
| } | |
| } | |
| void sdl2_poll_events(struct sdl2_console *scon) | |
| { | |
| SDL_Event ev1, *ev = &ev1; | |
| bool allow_close = true; | |
| int idle = 1; | |
| if (scon->last_vm_running != runstate_is_running()) { | |
| scon->last_vm_running = runstate_is_running(); | |
| sdl_update_caption(scon); | |
| } | |
| while (SDL_PollEvent(ev)) { | |
| switch (ev->type) { | |
| case SDL_KEYDOWN: | |
| idle = 0; | |
| handle_keydown(ev); | |
| break; | |
| case SDL_KEYUP: | |
| idle = 0; | |
| handle_keyup(ev); | |
| break; | |
| case SDL_TEXTINPUT: | |
| idle = 0; | |
| handle_textinput(ev); | |
| break; | |
| case SDL_QUIT: | |
| if (scon->opts->has_window_close && !scon->opts->window_close) { | |
| allow_close = false; | |
| } | |
| if (allow_close) { | |
| shutdown_action = SHUTDOWN_ACTION_POWEROFF; | |
| qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); | |
| } | |
| break; | |
| case SDL_MOUSEMOTION: | |
| idle = 0; | |
| handle_mousemotion(ev); | |
| break; | |
| case SDL_MOUSEBUTTONDOWN: | |
| case SDL_MOUSEBUTTONUP: | |
| idle = 0; | |
| handle_mousebutton(ev); | |
| break; | |
| case SDL_MOUSEWHEEL: | |
| idle = 0; | |
| handle_mousewheel(ev); | |
| break; | |
| case SDL_WINDOWEVENT: | |
| handle_windowevent(ev); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| if (idle) { | |
| if (scon->idle_counter < SDL2_MAX_IDLE_COUNT) { | |
| scon->idle_counter++; | |
| if (scon->idle_counter >= SDL2_MAX_IDLE_COUNT) { | |
| scon->dcl.update_interval = GUI_REFRESH_INTERVAL_DEFAULT; | |
| } | |
| } | |
| } else { | |
| scon->idle_counter = 0; | |
| scon->dcl.update_interval = SDL2_REFRESH_INTERVAL_BUSY; | |
| } | |
| } | |
| static void sdl_mouse_warp(DisplayChangeListener *dcl, | |
| int x, int y, bool on) | |
| { | |
| struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl); | |
| if (!qemu_console_is_graphic(scon->dcl.con)) { | |
| return; | |
| } | |
| if (on) { | |
| if (!guest_cursor) { | |
| sdl_show_cursor(scon); | |
| } | |
| if (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) { | |
| SDL_SetCursor(guest_sprite); | |
| if (!qemu_input_is_absolute(scon->dcl.con) && !absolute_enabled) { | |
| SDL_WarpMouseInWindow(scon->real_window, x, y); | |
| } | |
| } | |
| } else if (gui_grab) { | |
| sdl_hide_cursor(scon); | |
| } | |
| guest_cursor = on; | |
| guest_x = x, guest_y = y; | |
| } | |
| static void sdl_mouse_define(DisplayChangeListener *dcl, | |
| QEMUCursor *c) | |
| { | |
| if (guest_sprite) { | |
| SDL_FreeCursor(guest_sprite); | |
| } | |
| if (guest_sprite_surface) { | |
| SDL_FreeSurface(guest_sprite_surface); | |
| } | |
| guest_sprite_surface = | |
| SDL_CreateRGBSurfaceFrom(c->data, c->width, c->height, 32, c->width * 4, | |
| 0xff0000, 0x00ff00, 0xff, 0xff000000); | |
| if (!guest_sprite_surface) { | |
| fprintf(stderr, "Failed to make rgb surface from %p\n", c); | |
| return; | |
| } | |
| guest_sprite = SDL_CreateColorCursor(guest_sprite_surface, | |
| c->hot_x, c->hot_y); | |
| if (!guest_sprite) { | |
| fprintf(stderr, "Failed to make color cursor from %p\n", c); | |
| return; | |
| } | |
| if (guest_cursor && | |
| (gui_grab || qemu_input_is_absolute(dcl->con) || absolute_enabled)) { | |
| SDL_SetCursor(guest_sprite); | |
| } | |
| } | |
| static void sdl_cleanup(void) | |
| { | |
| int i; | |
| if (!sdl2_console) { | |
| return; | |
| } | |
| qemu_remove_mouse_mode_change_notifier(&mouse_mode_notifier); | |
| for (i = 0; i < sdl2_num_outputs; i++) { | |
| qemu_console_unregister_listener(&sdl2_console[i].dcl); | |
| qkbd_state_free(sdl2_console[i].kbd); | |
| sdl2_window_destroy(&sdl2_console[i]); | |
| } | |
| g_clear_pointer(&sdl2_console, g_free); | |
| sdl2_num_outputs = 0; | |
| g_clear_pointer(&guest_sprite, SDL_FreeCursor); | |
| g_clear_pointer(&guest_sprite_surface, SDL_FreeSurface); | |
| g_clear_pointer(&sdl_cursor_hidden, SDL_FreeCursor); | |
| SDL_QuitSubSystem(SDL_INIT_VIDEO); | |
| } | |
| static const DisplayChangeListenerOps dcl_2d_ops = { | |
| .dpy_name = "sdl2-2d", | |
| .dpy_gfx_update = sdl2_2d_update, | |
| .dpy_gfx_switch = sdl2_2d_switch, | |
| .dpy_gfx_check_format = sdl2_2d_check_format, | |
| .dpy_refresh = sdl2_2d_refresh, | |
| .dpy_mouse_set = sdl_mouse_warp, | |
| .dpy_cursor_define = sdl_mouse_define, | |
| }; | |
| #ifdef CONFIG_OPENGL | |
| static const DisplayChangeListenerOps dcl_gl_ops = { | |
| .dpy_name = "sdl2-gl", | |
| .dpy_gfx_update = sdl2_gl_update, | |
| .dpy_gfx_switch = sdl2_gl_switch, | |
| .dpy_gfx_check_format = console_gl_check_format, | |
| .dpy_refresh = sdl2_gl_refresh, | |
| .dpy_mouse_set = sdl_mouse_warp, | |
| .dpy_cursor_define = sdl_mouse_define, | |
| .dpy_gl_scanout_disable = sdl2_gl_scanout_disable, | |
| .dpy_gl_scanout_texture = sdl2_gl_scanout_texture, | |
| .dpy_gl_update = sdl2_gl_scanout_flush, | |
| #ifdef CONFIG_GBM | |
| .dpy_gl_scanout_dmabuf = sdl2_gl_scanout_dmabuf, | |
| .dpy_gl_release_dmabuf = sdl2_gl_release_dmabuf, | |
| .dpy_has_dmabuf = sdl2_gl_has_dmabuf, | |
| #endif | |
| }; | |
| static bool | |
| sdl2_gl_is_compatible_dcl(DisplayGLCtx *dgc, | |
| DisplayChangeListener *dcl) | |
| { | |
| return dcl->ops == &dcl_gl_ops; | |
| } | |
| static const DisplayGLCtxOps gl_ctx_ops = { | |
| .dpy_gl_ctx_is_compatible_dcl = sdl2_gl_is_compatible_dcl, | |
| .dpy_gl_ctx_create = sdl2_gl_create_context, | |
| .dpy_gl_ctx_destroy = sdl2_gl_destroy_context, | |
| .dpy_gl_ctx_make_current = sdl2_gl_make_context_current, | |
| }; | |
| #endif | |
| static void sdl2_display_early_init(DisplayOptions *o) | |
| { | |
| assert(o->type == DISPLAY_TYPE_SDL); | |
| if (o->has_gl && o->gl) { | |
| #ifdef CONFIG_OPENGL | |
| display_opengl = 1; | |
| #endif | |
| } | |
| } | |
| static void sdl2_set_hint_x11_force_egl(void) | |
| { | |
| #if defined(SDL_HINT_VIDEO_X11_FORCE_EGL) && defined(CONFIG_OPENGL) && \ | |
| defined(CONFIG_X11) | |
| Display *x_disp = XOpenDisplay(NULL); | |
| EGLDisplay egl_display; | |
| if (!x_disp) { | |
| return; | |
| } | |
| /* Prefer EGL over GLX to get dma-buf support. */ | |
| egl_display = qemu_egl_get_display((EGLNativeDisplayType)x_disp, | |
| EGL_PLATFORM_X11_KHR); | |
| if (egl_display != EGL_NO_DISPLAY) { | |
| /* | |
| * Setting X11_FORCE_EGL hint doesn't make SDL to prefer X11 over | |
| * Wayland. SDL will use Wayland driver even if XWayland presents. | |
| * It's always safe to set the hint even if X11 is not used by SDL. | |
| * SDL will work regardless of the hint. | |
| */ | |
| SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1"); | |
| eglTerminate(egl_display); | |
| } | |
| XCloseDisplay(x_disp); | |
| #endif | |
| } | |
| static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) | |
| { | |
| uint8_t data = 0; | |
| int i; | |
| SDL_SysWMinfo info; | |
| SDL_Surface *icon = NULL; | |
| char *dir; | |
| assert(o->type == DISPLAY_TYPE_SDL); | |
| if (SDL_GetHintBoolean("QEMU_ENABLE_SDL_LOGGING", SDL_FALSE)) { | |
| SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); | |
| } | |
| if (SDL_Init(SDL_INIT_VIDEO)) { | |
| fprintf(stderr, "Could not initialize SDL(%s) - exiting\n", | |
| SDL_GetError()); | |
| exit(1); | |
| } | |
| #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* only available since SDL 2.0.8 */ | |
| SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); | |
| #endif | |
| SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); | |
| #ifdef SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED | |
| SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0"); | |
| #endif | |
| SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1"); | |
| sdl2_set_hint_x11_force_egl(); | |
| SDL_EnableScreenSaver(); | |
| memset(&info, 0, sizeof(info)); | |
| SDL_VERSION(&info.version); | |
| gui_fullscreen = o->has_full_screen && o->full_screen; | |
| if (o->u.sdl.has_grab_mod) { | |
| if (o->u.sdl.grab_mod == HOT_KEY_MOD_LSHIFT_LCTRL_LALT) { | |
| alt_grab = true; | |
| } else if (o->u.sdl.grab_mod == HOT_KEY_MOD_RCTRL) { | |
| ctrl_grab = true; | |
| } | |
| } | |
| for (i = 0;; i++) { | |
| QemuConsole *con = qemu_console_lookup_by_index(i); | |
| if (!con) { | |
| break; | |
| } | |
| } | |
| sdl2_num_outputs = i; | |
| if (sdl2_num_outputs == 0) { | |
| return; | |
| } | |
| sdl2_console = g_new0(struct sdl2_console, sdl2_num_outputs); | |
| for (i = 0; i < sdl2_num_outputs; i++) { | |
| QemuConsole *con = qemu_console_lookup_by_index(i); | |
| const DisplayChangeListenerOps *ops = &dcl_2d_ops; | |
| assert(con != NULL); | |
| if (!qemu_console_is_graphic(con) && | |
| qemu_console_get_index(con) != 0) { | |
| sdl2_console[i].hidden = true; | |
| } | |
| sdl2_console[i].idx = i; | |
| sdl2_console[i].opts = o; | |
| #ifdef CONFIG_OPENGL | |
| sdl2_console[i].opengl = display_opengl; | |
| sdl2_console[i].dgc.ops = display_opengl ? &gl_ctx_ops : NULL; | |
| ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops; | |
| #else | |
| sdl2_console[i].opengl = 0; | |
| #endif | |
| sdl2_console[i].kbd = qkbd_state_init(con); | |
| #ifdef CONFIG_OPENGL | |
| if (display_opengl) { | |
| qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dgc); | |
| sdl2_gl_console_init(&sdl2_console[i]); | |
| } | |
| #endif | |
| qemu_console_register_listener(con, &sdl2_console[i].dcl, ops); | |
| #if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11) | |
| if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) { | |
| #if defined(SDL_VIDEO_DRIVER_WINDOWS) | |
| qemu_console_set_window_id(con, (uintptr_t)info.info.win.window); | |
| #elif defined(SDL_VIDEO_DRIVER_X11) | |
| qemu_console_set_window_id(con, info.info.x11.window); | |
| #endif | |
| } | |
| #endif | |
| } | |
| #ifdef CONFIG_SDL_IMAGE | |
| dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/128x128/apps/qemu.png"); | |
| icon = IMG_Load(dir); | |
| #else | |
| /* Load a 32x32x4 image. White pixels are transparent. */ | |
| dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/32x32/apps/qemu.bmp"); | |
| icon = SDL_LoadBMP(dir); | |
| if (icon) { | |
| uint32_t colorkey = SDL_MapRGB(icon->format, 255, 255, 255); | |
| SDL_SetColorKey(icon, SDL_TRUE, colorkey); | |
| } | |
| #endif | |
| g_free(dir); | |
| if (icon) { | |
| SDL_SetWindowIcon(sdl2_console[0].real_window, icon); | |
| } | |
| mouse_mode_notifier.notify = sdl_mouse_mode_change; | |
| qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier); | |
| sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0); | |
| sdl_cursor_normal = SDL_GetCursor(); | |
| if (gui_fullscreen) { | |
| sdl_grab_start(&sdl2_console[0]); | |
| } | |
| /* SDL's event polling (in dpy_refresh) must happen on the main thread. */ | |
| qemu_main = NULL; | |
| } | |
| static QemuDisplay qemu_display_sdl2 = { | |
| .type = DISPLAY_TYPE_SDL, | |
| .early_init = sdl2_display_early_init, | |
| .init = sdl2_display_init, | |
| .cleanup = sdl_cleanup, | |
| }; | |
| static void register_sdl1(void) | |
| { | |
| qemu_display_register(&qemu_display_sdl2); | |
| } | |
| type_init(register_sdl1); | |
| #ifdef CONFIG_OPENGL | |
| module_dep("ui-opengl"); | |
| #endif |