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