scrcpy.c 24.0 KB
Newer Older
R
Romain Vimont 已提交
1
#include "scrcpy.h"
R
Romain Vimont 已提交
2

R
Romain Vimont 已提交
3 4
#include <stdio.h>
#include <string.h>
R
Romain Vimont 已提交
5 6
#include <unistd.h>
#include <libavformat/avformat.h>
R
Romain Vimont 已提交
7
#include <sys/time.h>
R
Romain Vimont 已提交
8
#include <SDL2/SDL.h>
R
Romain Vimont 已提交
9
#include <SDL2/SDL_net.h>
R
Romain Vimont 已提交
10

R
Romain Vimont 已提交
11 12 13 14 15 16 17 18 19 20
#include "command.h"
#include "common.h"
#include "control.h"
#include "convert.h"
#include "decoder.h"
#include "events.h"
#include "frames.h"
#include "lockutil.h"
#include "netutil.h"
#include "server.h"
R
Romain Vimont 已提交
21
#include "tinyxpm.h"
R
Romain Vimont 已提交
22

R
Romain Vimont 已提交
23 24
#include "icon.xpm"

R
Romain Vimont 已提交
25 26 27 28 29 30 31 32 33 34 35
#define DEVICE_NAME_FIELD_LENGTH 64
#define DISPLAY_MARGINS 96

static struct frames frames;
static struct decoder decoder;
static struct controller controller;

static SDL_Window *window;
static SDL_Renderer *renderer;
static SDL_Texture *texture;
static struct size frame_size;
36 37
// used only in fullscreen mode to know the windowed window size
static struct size windowed_window_size;
R
Romain Vimont 已提交
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
static SDL_bool texture_empty = SDL_TRUE;
static SDL_bool fullscreen = SDL_FALSE;

static long timestamp_ms(void) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

static void count_frame(void) {
    static long ts = 0;
    static int nbframes = 0;
    long now = timestamp_ms();
    ++nbframes;
    if (now - ts > 1000) {
        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%d fps", nbframes);
        ts = now;
        nbframes = 0;
    }
}

static TCPsocket listen_on_port(Uint16 port) {
    IPaddress addr = {
        .host = INADDR_ANY,
        .port = SDL_SwapBE16(port),
    };
    return SDLNet_TCP_Open(&addr);
}

// name must be at least DEVICE_NAME_FIELD_LENGTH bytes
R
Romain Vimont 已提交
68
static SDL_bool read_initial_device_info(TCPsocket socket, char *device_name, struct size *size) {
R
Romain Vimont 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
    unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
    if (SDLNet_TCP_Recv(socket, buf, sizeof(buf)) <= 0) {
        return SDL_FALSE;
    }
    buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage
    // scrcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes
    // and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
    strcpy(device_name, (char *) buf);
    size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1];
    size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3];
    return SDL_TRUE;
}

#if SDL_VERSION_ATLEAST(2, 0, 5)
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
#else
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r))
#endif

// init the preferred display_bounds (i.e. the screen bounds with some margins)
static SDL_bool get_preferred_display_bounds(struct size *bounds) {
    SDL_Rect rect;
    if (GET_DISPLAY_BOUNDS(0, &rect)) {
        SDL_LogWarn(SDL_LOG_CATEGORY_SYSTEM, "Could not get display usable bounds: %s", SDL_GetError());
        return SDL_FALSE;
    }

    bounds->width = MAX(0, rect.w - DISPLAY_MARGINS);
    bounds->height = MAX(0, rect.h - DISPLAY_MARGINS);
    return SDL_TRUE;
}

static inline struct size get_window_size(SDL_Window *window) {
    int width;
    int height;
    SDL_GetWindowSize(window, &width, &height);

    struct size size;
    size.width = width;
    size.height = height;
    return size;
}

112 113 114 115 116 117 118 119 120 121 122
static void set_window_size(SDL_Window *window, struct size new_size) {
    // setting the window size during fullscreen is implementation defined,
    // so apply the resize only after fullscreen is disabled
    if (fullscreen) {
        // SDL_SetWindowSize will be called when fullscreen will be disabled
        windowed_window_size = new_size;
    } else {
        SDL_SetWindowSize(window, new_size.width, new_size.height);
    }
}

R
Romain Vimont 已提交
123
static inline struct point get_mouse_point() {
R
Romain Vimont 已提交
124 125 126 127
    int x;
    int y;
    SDL_GetMouseState(&x, &y);
    SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
R
Romain Vimont 已提交
128
    return (struct point) {
R
Romain Vimont 已提交
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
        .x = (Uint16) x,
        .y = (Uint16) y,
    };
}

// return the optimal size of the window, with the following constraints:
//  - it attempts to keep at least one dimension of the current_size (i.e. it crops the black borders)
//  - it keeps the aspect ratio
//  - it scales down to make it fit in the display_size
static struct size get_optimal_size(struct size current_size, struct size frame_size) {
    struct size display_size;
    // 32 bits because we need to multiply two 16 bits values
    Uint32 w;
    Uint32 h;

    if (!get_preferred_display_bounds(&display_size)) {
        // cannot get display bounds, do not constraint the size
        w = current_size.width;
        h = current_size.height;
    } else {
        w = MIN(current_size.width, display_size.width);
        h = MIN(current_size.height, display_size.height);
    }

    SDL_bool keep_width = frame_size.width * h > frame_size.height * w;
    if (keep_width) {
        // remove black borders on top and bottom
        h = frame_size.height * w / frame_size.width;
    } else {
        // remove black borders on left and right (or none at all if it already fits)
        w = frame_size.width * h / frame_size.height;
    }

    // w and h must fit into 16 bits
    SDL_assert_release(w < 0x10000 && h < 0x10000);
    return (struct size) {w, h};
}

// initially, there is no current size, so use the frame size as current size
static inline struct size get_initial_optimal_size(struct size frame_size) {
    return get_optimal_size(frame_size, frame_size);
}

// same as get_optimal_size(), but read the current size from the window
static inline struct size get_optimal_window_size(SDL_Window *window, struct size frame_size) {
    struct size current_size = get_window_size(window);
    return get_optimal_size(current_size, frame_size);
}

178 179
static SDL_bool prepare_for_frame(SDL_Window *window, SDL_Renderer *renderer, SDL_Texture **texture,
                                  struct size old_frame_size, struct size frame_size) {
R
Romain Vimont 已提交
180 181 182 183
    if (old_frame_size.width != frame_size.width || old_frame_size.height != frame_size.height) {
        if (SDL_RenderSetLogicalSize(renderer, frame_size.width, frame_size.height)) {
            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Could not set renderer logical size: %s", SDL_GetError());
            return SDL_FALSE;
R
Romain Vimont 已提交
184
        }
R
Romain Vimont 已提交
185 186 187 188 189 190 191 192 193 194

        // frame dimension changed, destroy texture
        SDL_DestroyTexture(*texture);

        struct size current_size = get_window_size(window);
        struct size target_size = {
            (Uint32) current_size.width * frame_size.width / old_frame_size.width,
            (Uint32) current_size.height * frame_size.height / old_frame_size.height,
        };
        target_size = get_optimal_size(target_size, frame_size);
195
        set_window_size(window, target_size);
R
Romain Vimont 已提交
196 197 198 199 200 201

        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height);
        *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, frame_size.width, frame_size.height);
        if (!*texture) {
            SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Could not create texture: %s", SDL_GetError());
            return SDL_FALSE;
R
Romain Vimont 已提交
202 203 204
        }
    }

R
Romain Vimont 已提交
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
    return SDL_TRUE;
}

static void update_texture(const AVFrame *frame, SDL_Texture *texture) {
    SDL_UpdateYUVTexture(texture, NULL,
            frame->data[0], frame->linesize[0],
            frame->data[1], frame->linesize[1],
            frame->data[2], frame->linesize[2]);
}

static void render(SDL_Renderer *renderer, SDL_Texture *texture) {
    SDL_RenderClear(renderer);
    if (texture) {
        SDL_RenderCopy(renderer, texture, NULL, NULL);
    }
    SDL_RenderPresent(renderer);
}

R
Romain Vimont 已提交
223
static void switch_fullscreen(void) {
224 225 226 227 228 229
    if (!fullscreen) {
        // going to fullscreen, store the current windowed window size
        windowed_window_size = get_window_size(window);
    }
    Uint32 new_mode = fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
    if (SDL_SetWindowFullscreen(window, new_mode)) {
R
Romain Vimont 已提交
230 231
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Could not switch fullscreen mode: %s", SDL_GetError());
        return;
232 233 234 235 236 237 238 239
    }

    fullscreen = !fullscreen;
    if (!fullscreen) {
        // fullscreen disabled, restore expected windowed window size
        SDL_SetWindowSize(window, windowed_window_size.width, windowed_window_size.height);
    }

R
Romain Vimont 已提交
240
    SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Switched to %s mode", fullscreen ? "fullscreen" : "windowed");
241 242 243
    render(renderer, texture_empty ? NULL : texture);
}

244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
static void resize_to_fit(void) {
    if (!fullscreen) {
        struct size optimal_size = get_optimal_window_size(window, frame_size);
        SDL_SetWindowSize(window, optimal_size.width, optimal_size.height);
        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Resized to optimal size");
    }
}

static void resize_to_pixel_perfect(void) {
    if (!fullscreen) {
        SDL_SetWindowSize(window, frame_size.width, frame_size.height);
        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Resized to pixel-perfect");
    }
}

R
Romain Vimont 已提交
259 260 261 262
static int wait_for_success(process_t proc, const char *name) {
    if (proc == PROCESS_NONE) {
        SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Could not execute \"%s\"", name);
        return -1;
R
Romain Vimont 已提交
263
    }
R
Romain Vimont 已提交
264 265 266 267 268 269 270
    exit_code_t exit_code;
    if (!cmd_simple_wait(proc, &exit_code)) {
        if (exit_code != NO_EXIT_CODE) {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "\"%s\" returned with value %" PRIexitcode, name, exit_code);
        } else {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "\"%s\" exited unexpectedly", name);
        }
R
Romain Vimont 已提交
271 272 273 274 275
        return -1;
    }
    return 0;
}

R
Romain Vimont 已提交
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
static void send_keycode(enum android_keycode keycode, const char *name) {
    // send DOWN event
    struct control_event control_event = {
        .type = CONTROL_EVENT_TYPE_KEYCODE,
        .keycode_event = {
            .action = AKEY_EVENT_ACTION_DOWN,
            .keycode = keycode,
            .metastate = 0,
        },
    };

    if (!controller_push_event(&controller, &control_event)) {
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send %s (DOWN)", name);
        return;
    }

    // send UP event
    control_event.keycode_event.action = AKEY_EVENT_ACTION_UP;
    if (!controller_push_event(&controller, &control_event)) {
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send %s (UP)", name);
    }
}

R
Romain Vimont 已提交
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
static inline void action_home(void) {
    send_keycode(AKEYCODE_HOME, "HOME");
}

static inline void action_back(void) {
    send_keycode(AKEYCODE_BACK, "BACK");
}

static inline void action_app_switch(void) {
    send_keycode(AKEYCODE_APP_SWITCH, "APP_SWITCH");
}

static inline void action_power(void) {
    send_keycode(AKEYCODE_POWER, "POWER");
}

static inline void action_volume_up(void) {
    send_keycode(AKEYCODE_VOLUME_UP, "VOLUME_UP");
}

static inline void action_volume_down(void) {
    send_keycode(AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
}

R
Romain Vimont 已提交
323 324 325 326 327 328 329 330 331 332 333 334
static void turn_screen_on(void) {
    struct control_event control_event = {
        .type = CONTROL_EVENT_TYPE_COMMAND,
        .command_event = {
            .action = CONTROL_EVENT_COMMAND_SCREEN_ON,
        },
    };
    if (!controller_push_event(&controller, &control_event)) {
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot turn screen on");
    }
}

R
Romain Vimont 已提交
335 336 337 338
static SDL_bool handle_new_frame(void) {
    mutex_lock(frames.mutex);
    AVFrame *frame = frames.rendering_frame;
    frames.rendering_frame_consumed = SDL_TRUE;
339 340 341 342 343
#ifndef SKIP_FRAMES
    // if SKIP_FRAMES is disabled, then notify the decoder the current frame is
    // consumed, so that it may push a new one
    cond_signal(frames.rendering_frame_consumed_cond);
#endif
R
Romain Vimont 已提交
344

R
Romain Vimont 已提交
345 346 347
    struct size current_frame_size = {frame->width, frame->height};
    if (!prepare_for_frame(window, renderer, &texture, frame_size, current_frame_size)) {
        return SDL_FALSE;
R
Romain Vimont 已提交
348 349
    }

R
Romain Vimont 已提交
350
    frame_size = current_frame_size;
R
Romain Vimont 已提交
351

R
Romain Vimont 已提交
352 353 354 355 356 357 358
    update_texture(frame, texture);
    mutex_unlock(frames.mutex);

    render(renderer, texture);
    return SDL_TRUE;
}

R
Romain Vimont 已提交
359 360 361 362 363
static SDL_bool is_ctrl_down(void) {
    const Uint8 *state = SDL_GetKeyboardState(NULL);
    return state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL];
}

R
Romain Vimont 已提交
364
static void handle_text_input(const SDL_TextInputEvent *event) {
R
Romain Vimont 已提交
365
    if (is_ctrl_down()) {
R
Romain Vimont 已提交
366 367 368 369 370 371 372 373
        switch (event->text[0]) {
            case '+':
                action_volume_up();
                break;
            case '-':
                action_volume_down();
                break;
        }
R
Romain Vimont 已提交
374 375 376
        return;
    }

R
Romain Vimont 已提交
377 378 379 380 381 382
    struct control_event control_event;
    control_event.type = CONTROL_EVENT_TYPE_TEXT;
    strncpy(control_event.text_event.text, event->text, TEXT_MAX_LENGTH);
    control_event.text_event.text[TEXT_MAX_LENGTH] = '\0';
    if (!controller_push_event(&controller, &control_event)) {
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send text event");
R
Romain Vimont 已提交
383
    }
R
Romain Vimont 已提交
384
}
R
Romain Vimont 已提交
385

R
Romain Vimont 已提交
386 387 388 389 390
static void handle_key(const SDL_KeyboardEvent *event) {
    SDL_Keycode keycode = event->keysym.sym;
    SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
    SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
    SDL_bool repeat = event->repeat;
R
Romain Vimont 已提交
391

R
Romain Vimont 已提交
392 393 394 395 396 397 398
    // capture all Ctrl events
    if (ctrl) {
        // only consider keydown events, and ignore repeated events
        if (repeat || event->type != SDL_KEYDOWN) {
            return;
        }

399 400
        if (shift) {
            // currently, there is no shortcut implying SHIFT
R
Romain Vimont 已提交
401 402 403
            return;
        }

404
        switch (keycode) {
405
            case SDLK_h:
R
Romain Vimont 已提交
406
                action_home();
407
                return;
408 409
            case SDLK_b: // fall-through
            case SDLK_BACKSPACE:
R
Romain Vimont 已提交
410
                action_back();
411 412
                return;
            case SDLK_m:
R
Romain Vimont 已提交
413
                action_app_switch();
414 415
                return;
            case SDLK_p:
R
Romain Vimont 已提交
416 417 418 419 420 421 422 423 424 425
                action_power();
                return;
            case SDLK_f:
                switch_fullscreen();
                return;
            case SDLK_x:
                resize_to_fit();
                return;
            case SDLK_g:
                resize_to_pixel_perfect();
426
                return;
R
Romain Vimont 已提交
427 428
        }

R
Romain Vimont 已提交
429 430 431 432 433 434 435 436 437 438
        return;
    }

    struct control_event control_event;
    if (input_key_from_sdl_to_android(event, &control_event)) {
        if (!controller_push_event(&controller, &control_event)) {
            SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send control event");
        }
    }
}
R
Romain Vimont 已提交
439

R
Romain Vimont 已提交
440
static void handle_mouse_motion(const SDL_MouseMotionEvent *event, struct size screen_size) {
441 442 443 444
    if (!event->state) {
        // do not send motion events when no button is pressed
        return;
    }
R
Romain Vimont 已提交
445 446 447 448 449 450 451 452 453
    struct control_event control_event;
    if (mouse_motion_from_sdl_to_android(event, screen_size, &control_event)) {
        if (!controller_push_event(&controller, &control_event)) {
            SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send mouse motion event");
        }
    }
}

static void handle_mouse_button(const SDL_MouseButtonEvent *event, struct size screen_size) {
R
Romain Vimont 已提交
454 455 456 457
    if (event->button == SDL_BUTTON_RIGHT) {
        turn_screen_on();
        return;
    };
R
Romain Vimont 已提交
458 459 460 461 462 463 464 465
    struct control_event control_event;
    if (mouse_button_from_sdl_to_android(event, screen_size, &control_event)) {
        if (!controller_push_event(&controller, &control_event)) {
            SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send mouse button event");
        }
    }
}

R
Romain Vimont 已提交
466
static void handle_mouse_wheel(const SDL_MouseWheelEvent *event, struct position position) {
R
Romain Vimont 已提交
467
    struct control_event control_event;
R
Romain Vimont 已提交
468
    if (mouse_wheel_from_sdl_to_android(event, position, &control_event)) {
R
Romain Vimont 已提交
469 470 471 472 473 474
        if (!controller_push_event(&controller, &control_event)) {
            SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send wheel button event");
        }
    }
}

R
Romain Vimont 已提交
475
static void event_loop(void) {
R
Romain Vimont 已提交
476 477 478
    SDL_Event event;
    while (SDL_WaitEvent(&event)) {
        switch (event.type) {
R
Romain Vimont 已提交
479 480
            case EVENT_DECODER_STOPPED:
                SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Video decoder stopped");
R
Romain Vimont 已提交
481
                return;
R
Romain Vimont 已提交
482
            case SDL_QUIT:
R
Romain Vimont 已提交
483
                SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "User requested to quit");
R
Romain Vimont 已提交
484
                return;
R
Romain Vimont 已提交
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
            case EVENT_NEW_FRAME:
                if (!handle_new_frame()) {
                    return;
                }
                texture_empty = SDL_FALSE;
                count_frame(); // display fps for debug
                break;
            case SDL_WINDOWEVENT:
                switch (event.window.event) {
                case SDL_WINDOWEVENT_EXPOSED:
                case SDL_WINDOWEVENT_SIZE_CHANGED:
                    render(renderer, texture_empty ? NULL : texture);
                    break;
                }
                break;
            case SDL_TEXTINPUT: {
                handle_text_input(&event.text);
                break;
R
Romain Vimont 已提交
503
            }
R
Romain Vimont 已提交
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
            case SDL_KEYDOWN:
            case SDL_KEYUP:
                handle_key(&event.key);
                break;
            case SDL_MOUSEMOTION:
                handle_mouse_motion(&event.motion, frame_size);
                break;
            case SDL_MOUSEWHEEL: {
                struct position position = {
                    .screen_size = frame_size,
                    .point = get_mouse_point(),
                };
                handle_mouse_wheel(&event.wheel, position);
                break;
            }
            case SDL_MOUSEBUTTONDOWN:
            case SDL_MOUSEBUTTONUP: {
                handle_mouse_button(&event.button, frame_size);
R
Romain Vimont 已提交
522 523 524 525 526 527
                break;
            }
        }
    }
}

R
Romain Vimont 已提交
528
SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate) {
R
Romain Vimont 已提交
529
    SDL_bool ret = SDL_TRUE;
R
Romain Vimont 已提交
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547

    process_t push_proc = push_server(serial);
    if (wait_for_success(push_proc, "adb push")) {
        return SDL_FALSE;
    }

    process_t reverse_tunnel_proc = enable_tunnel(serial, local_port);
    if (wait_for_success(reverse_tunnel_proc, "adb reverse")) {
        return SDL_FALSE;
    }

    TCPsocket server_socket = listen_on_port(local_port);
    if (!server_socket) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not open video socket");
        goto screen_finally_adb_reverse_remove;
    }

    // server will connect to our socket
R
Romain Vimont 已提交
548
    process_t server = start_server(serial, max_size, bit_rate);
R
Romain Vimont 已提交
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
    if (server == PROCESS_NONE) {
        ret = SDL_FALSE;
        SDLNet_TCP_Close(server_socket);
        goto screen_finally_adb_reverse_remove;
    }

    // to reduce startup time, we could be tempted to init other stuff before blocking here
    // but we should not block after SDL_Init since it handles the signals (Ctrl+C) in its
    // event loop: blocking could lead to deadlock
    TCPsocket device_socket = blocking_accept(server_socket);
    // we don't need the server socket anymore
    SDLNet_TCP_Close(server_socket);
    if (!device_socket) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not accept video socket: %s", SDL_GetError());
        ret = SDL_FALSE;
        stop_server(server);
        goto screen_finally_adb_reverse_remove;
    }

    char device_name[DEVICE_NAME_FIELD_LENGTH];

    // screenrecord does not send frames when the screen content does not change
    // therefore, we transmit the screen size before the video stream, to be able
    // to init the window immediately
    if (!read_initial_device_info(device_socket, device_name, &frame_size)) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not retrieve initial screen size");
        ret = SDL_FALSE;
        SDLNet_TCP_Close(device_socket);
        stop_server(server);
        goto screen_finally_adb_reverse_remove;
    }

    if (!frames_init(&frames)) {
        ret = SDL_FALSE;
        SDLNet_TCP_Close(device_socket);
        stop_server(server);
        goto screen_finally_adb_reverse_remove;
    }

    decoder.frames = &frames;
    decoder.video_socket = device_socket;

    // now we consumed the header values, the socket receives the video stream
    // start the decoder
    if (!decoder_start(&decoder)) {
        ret = SDL_FALSE;
        SDLNet_TCP_Close(device_socket);
        stop_server(server);
        goto screen_finally_destroy_frames;
    }

    if (!controller_init(&controller, device_socket)) {
        ret = SDL_FALSE;
        SDLNet_TCP_Close(device_socket);
        stop_server(server);
        goto screen_finally_stop_decoder;
    }

    if (!controller_start(&controller)) {
        ret = SDL_FALSE;
        SDLNet_TCP_Close(device_socket);
        stop_server(server);
        goto screen_finally_destroy_controller;
    }

    if (SDL_Init(SDL_INIT_VIDEO)) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Could not initialize SDL: %s", SDL_GetError());
        ret = SDL_FALSE;
        goto screen_finally_stop_and_join_controller;
    }
    // FIXME it may crash in SDL_Quit in i965_dri.so
    // As a workaround, do not call SDL_Quit() (we are exiting anyway).
    // atexit(SDL_Quit);

    // Bilinear resizing
    if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
        SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "Could not enable bilinear filtering");
    }

628
#if SDL_VERSION_ATLEAST(2, 0, 5)
R
Romain Vimont 已提交
629 630 631 632
    // Handle a click to gain focus as any other click
    if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
        SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "Could not enable mouse focus clickthrough");
    }
633
#endif
R
Romain Vimont 已提交
634

R
Romain Vimont 已提交
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
    struct size window_size = get_initial_optimal_size(frame_size);
    window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                                          window_size.width, window_size.height, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
    if (!window) {
        SDL_LogCritical(SDL_LOG_CATEGORY_SYSTEM, "Could not create window: %s", SDL_GetError());
        ret = SDL_FALSE;
        goto screen_finally_stop_decoder;
    }

    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if (!renderer) {
        SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Could not create renderer: %s", SDL_GetError());
        ret = SDL_FALSE;
        goto screen_finally_destroy_window;
    }

    if (SDL_RenderSetLogicalSize(renderer, frame_size.width, frame_size.height)) {
        SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Could not set renderer logical size: %s", SDL_GetError());
        ret = SDL_FALSE;
        goto screen_finally_destroy_renderer;
    }

R
Romain Vimont 已提交
657
    SDL_Surface *icon = read_xpm(icon_xpm);
R
Romain Vimont 已提交
658 659 660 661 662 663 664 665
    if (!icon) {
        SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Could not load icon: %s", SDL_GetError());
        ret = SDL_FALSE;
        goto screen_finally_destroy_renderer;
    }
    SDL_SetWindowIcon(window, icon);
    SDL_FreeSurface(icon);

R
Romain Vimont 已提交
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height);
    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, frame_size.width, frame_size.height);
    if (!texture) {
        SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Could not create texture: %s", SDL_GetError());
        ret = SDL_FALSE;
        goto screen_finally_destroy_renderer;
    }

    SDL_RenderClear(renderer);
    SDL_RenderPresent(renderer);

    event_loop();

    SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "quit...");
    SDL_DestroyTexture(texture);
screen_finally_destroy_renderer:
    // FIXME it may crash at exit if we destroy the renderer or the window,
    // with the exact same stack trace as <https://bugs.launchpad.net/mir/+bug/1466535>.
    // As a workaround, leak the renderer and the window (we are exiting anyway).
    //SDL_DestroyRenderer(renderer);
screen_finally_destroy_window:
    //SDL_DestroyWindow(window);
R
Romain Vimont 已提交
688 689
    // at least we hide it
    SDL_HideWindow(window);
R
Romain Vimont 已提交
690 691 692 693 694 695 696
screen_finally_stop_and_join_controller:
    controller_stop(&controller);
    controller_join(&controller);
screen_finally_destroy_controller:
    controller_destroy(&controller);
screen_finally_stop_decoder:
    SDLNet_TCP_Close(device_socket);
R
Romain Vimont 已提交
697 698 699 700 701 702 703 704

    // let the server some time to print any exception trace before killing it
    struct timespec timespec = {
        .tv_sec = 0,
        .tv_nsec = 100000000, // 100ms
    };
    nanosleep(&timespec, NULL); // ignore error

R
Romain Vimont 已提交
705 706 707 708 709 710 711 712 713 714 715 716 717
    // kill the server before decoder_join() to wake up the decoder
    stop_server(server);
    decoder_join(&decoder);
screen_finally_destroy_frames:
    frames_destroy(&frames);
screen_finally_adb_reverse_remove:
    {
        process_t remove = disable_tunnel(serial);
        if (remove != PROCESS_NONE) {
            // ignore failure
            cmd_simple_wait(remove, NULL);
        }
    }
R
Romain Vimont 已提交
718

R
Romain Vimont 已提交
719
    return ret;
R
Romain Vimont 已提交
720
}