scrcpy.c 12.8 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 9
#include <SDL2/SDL.h>

R
Romain Vimont 已提交
10 11
#include "command.h"
#include "common.h"
12
#include "compat.h"
R
Romain Vimont 已提交
13
#include "controller.h"
R
Romain Vimont 已提交
14
#include "decoder.h"
15
#include "device.h"
R
Romain Vimont 已提交
16
#include "events.h"
N
npes87184 已提交
17
#include "file_handler.h"
18 19
#include "fps_counter.h"
#include "input_manager.h"
R
Romain Vimont 已提交
20
#include "log.h"
21
#include "lock_util.h"
22
#include "net.h"
R
Romain Vimont 已提交
23
#include "recorder.h"
24
#include "screen.h"
R
Romain Vimont 已提交
25
#include "server.h"
R
Romain Vimont 已提交
26
#include "stream.h"
27
#include "tiny_xpm.h"
28
#include "video_buffer.h"
R
Romain Vimont 已提交
29

30
static struct server server = SERVER_INITIALIZER;
31
static struct screen screen = SCREEN_INITIALIZER;
32
static struct video_buffer video_buffer;
R
Romain Vimont 已提交
33
static struct stream stream;
R
Romain Vimont 已提交
34
static struct decoder decoder;
R
Romain Vimont 已提交
35
static struct recorder recorder;
R
Romain Vimont 已提交
36
static struct controller controller;
N
npes87184 已提交
37
static struct file_handler file_handler;
R
Romain Vimont 已提交
38

39 40
static struct input_manager input_manager = {
    .controller = &controller,
41
    .video_buffer = &video_buffer,
42 43 44
    .screen = &screen,
};

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
// init SDL and set appropriate hints
static bool
sdl_init_and_configure(bool display) {
    uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
    if (SDL_Init(flags)) {
        LOGC("Could not initialize SDL: %s", SDL_GetError());
        return false;
    }

    atexit(SDL_Quit);

    if (!display) {
        return true;
    }

    // Use the best available scale quality
    if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
        LOGW("Could not enable bilinear filtering");
    }

#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
    // Handle a click to gain focus as any other click
    if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
        LOGW("Could not enable mouse focus clickthrough");
    }
#endif

72 73 74 75 76 77 78
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
    // Disable compositor bypassing on X11
    if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
        LOGW("Could not disable X11 compositor bypass");
    }
#endif

R
Romain Vimont 已提交
79 80 81 82 83
    // Do not minimize on focus loss
    if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
        LOGW("Could not disable minimize on focus loss");
    }

84 85 86 87 88 89 90
    // Do not disable the screensaver when scrcpy is running
    SDL_EnableScreenSaver();

    return true;
}


91 92 93 94 95 96 97 98 99 100
#if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif

#ifdef CONTINUOUS_RESIZING_WORKAROUND
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
// not triggered. As a workaround, handle them in an event handler.
//
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178>
R
Romain Vimont 已提交
101 102 103 104
static int
event_watcher(void *data, SDL_Event *event) {
    if (event->type == SDL_WINDOWEVENT
            && event->window.event == SDL_WINDOWEVENT_RESIZED) {
105 106 107 108 109 110 111
        // called from another thread, not very safe, but it's a workaround!
        screen_render(&screen);
    }
    return 0;
}
#endif

112
static bool
R
Romain Vimont 已提交
113
is_apk(const char *file) {
114 115 116 117
    const char *ext = strrchr(file, '.');
    return ext && !strcmp(ext, ".apk");
}

118 119 120 121 122 123 124
enum event_result {
    EVENT_RESULT_CONTINUE,
    EVENT_RESULT_STOPPED_BY_USER,
    EVENT_RESULT_STOPPED_BY_EOS,
};

static enum event_result
125
handle_event(SDL_Event *event, bool control) {
126 127 128 129 130 131 132 133 134
    switch (event->type) {
        case EVENT_STREAM_STOPPED:
            LOGD("Video stream stopped");
            return EVENT_RESULT_STOPPED_BY_EOS;
        case SDL_QUIT:
            LOGD("User requested to quit");
            return EVENT_RESULT_STOPPED_BY_USER;
        case EVENT_NEW_FRAME:
            if (!screen.has_frame) {
135
                screen.has_frame = true;
136 137 138 139
                // this is the very first frame, show the window
                screen_show_window(&screen);
            }
            if (!screen_update_frame(&screen, &video_buffer)) {
140
                return EVENT_RESULT_CONTINUE;
141 142 143 144 145 146 147 148 149 150 151
            }
            break;
        case SDL_WINDOWEVENT:
            switch (event->window.event) {
                case SDL_WINDOWEVENT_EXPOSED:
                case SDL_WINDOWEVENT_SIZE_CHANGED:
                    screen_render(&screen);
                    break;
            }
            break;
        case SDL_TEXTINPUT:
152 153 154
            if (!control) {
                break;
            }
155 156 157 158
            input_manager_process_text_input(&input_manager, &event->text);
            break;
        case SDL_KEYDOWN:
        case SDL_KEYUP:
159 160
            // some key events do not interact with the device, so process the
            // event even if control is disabled
161
            input_manager_process_key(&input_manager, &event->key, control);
162 163
            break;
        case SDL_MOUSEMOTION:
164 165 166
            if (!control) {
                break;
            }
167 168 169
            input_manager_process_mouse_motion(&input_manager, &event->motion);
            break;
        case SDL_MOUSEWHEEL:
170 171 172
            if (!control) {
                break;
            }
173 174 175 176
            input_manager_process_mouse_wheel(&input_manager, &event->wheel);
            break;
        case SDL_MOUSEBUTTONDOWN:
        case SDL_MOUSEBUTTONUP:
177 178
            // some mouse events do not interact with the device, so process
            // the event even if control is disabled
179 180
            input_manager_process_mouse_button(&input_manager, &event->button,
                                               control);
181 182
            break;
        case SDL_DROPFILE: {
183 184 185
            if (!control) {
                break;
            }
186 187 188 189 190 191 192 193 194 195 196 197 198
            file_handler_action_t action;
            if (is_apk(event->drop.file)) {
                action = ACTION_INSTALL_APK;
            } else {
                action = ACTION_PUSH_FILE;
            }
            file_handler_request(&file_handler, action, event->drop.file);
            break;
        }
    }
    return EVENT_RESULT_CONTINUE;
}

199 200
static bool
event_loop(bool display, bool control) {
201
#ifdef CONTINUOUS_RESIZING_WORKAROUND
202 203 204
    if (display) {
        SDL_AddEventWatch(event_watcher, NULL);
    }
205
#endif
R
Romain Vimont 已提交
206 207
    SDL_Event event;
    while (SDL_WaitEvent(&event)) {
208
        enum event_result result = handle_event(&event, control);
209 210
        switch (result) {
            case EVENT_RESULT_STOPPED_BY_USER:
211
                return true;
212
            case EVENT_RESULT_STOPPED_BY_EOS:
213
                return false;
214
            case EVENT_RESULT_CONTINUE:
215
                break;
R
Romain Vimont 已提交
216 217
        }
    }
218
    return false;
R
Romain Vimont 已提交
219 220
}

R
Romain Vimont 已提交
221
static process_t
222
set_show_touches_enabled(const char *serial, bool enabled) {
223 224 225 226
    const char *value = enabled ? "1" : "0";
    const char *const adb_cmd[] = {
        "shell", "settings", "put", "system", "show_touches", value
    };
227 228 229
    return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}

R
Romain Vimont 已提交
230 231
static void
wait_show_touches(process_t process) {
232 233
    // reap the process, ignore the result
    process_check_success(process, "show_touches");
234 235
}

R
Romain Vimont 已提交
236 237
static SDL_LogPriority
sdl_priority_from_av_level(int level) {
R
Romain Vimont 已提交
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
    switch (level) {
        case AV_LOG_PANIC:
        case AV_LOG_FATAL:
            return SDL_LOG_PRIORITY_CRITICAL;
        case AV_LOG_ERROR:
            return SDL_LOG_PRIORITY_ERROR;
        case AV_LOG_WARNING:
            return SDL_LOG_PRIORITY_WARN;
        case AV_LOG_INFO:
            return SDL_LOG_PRIORITY_INFO;
    }
    // do not forward others, which are too verbose
    return 0;
}

static void
av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
    SDL_LogPriority priority = sdl_priority_from_av_level(level);
    if (priority == 0) {
        return;
    }
    char *local_fmt = SDL_malloc(strlen(fmt) + 10);
    if (!local_fmt) {
        LOGC("Cannot allocate string");
        return;
    }
    // strcpy is safe here, the destination is large enough
    strcpy(local_fmt, "[FFmpeg] ");
    strcpy(local_fmt + 9, fmt);
    SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
    SDL_free(local_fmt);
}

271
bool
R
Romain Vimont 已提交
272
scrcpy(const struct scrcpy_options *options) {
273
    bool record = !!options->record_filename;
R
Romain Vimont 已提交
274 275 276 277 278 279
    struct server_params params = {
        .crop = options->crop,
        .local_port = options->port,
        .max_size = options->max_size,
        .bit_rate = options->bit_rate,
        .send_frame_meta = record,
280
        .control = options->control,
R
Romain Vimont 已提交
281 282
    };
    if (!server_start(&server, options->serial, &params)) {
283
        return false;
R
Romain Vimont 已提交
284 285
    }

R
Romain Vimont 已提交
286
    process_t proc_show_touches = PROCESS_NONE;
287
    bool show_touches_waited;
288 289
    if (options->show_touches) {
        LOGI("Enable show_touches");
290 291
        proc_show_touches = set_show_touches_enabled(options->serial, true);
        show_touches_waited = false;
292 293
    }

R
Romain Vimont 已提交
294 295 296 297 298 299 300 301
    bool ret = false;

    bool video_buffer_initialized = false;
    bool file_handler_initialized = false;
    bool recorder_initialized = false;
    bool stream_started = false;
    bool controller_initialized = false;
    bool controller_started = false;
302

303
    if (!sdl_init_and_configure(options->display)) {
R
Romain Vimont 已提交
304
        goto end;
R
Romain Vimont 已提交
305 306
    }

307
    if (!server_connect_to(&server)) {
R
Romain Vimont 已提交
308
        goto end;
R
Romain Vimont 已提交
309 310 311
    }

    char device_name[DEVICE_NAME_FIELD_LENGTH];
312
    struct size frame_size;
R
Romain Vimont 已提交
313

R
Romain Vimont 已提交
314 315 316
    // 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
317
    if (!device_read_info(server.video_socket, device_name, &frame_size)) {
R
Romain Vimont 已提交
318
        goto end;
R
Romain Vimont 已提交
319 320
    }

R
Romain Vimont 已提交
321
    struct decoder *dec = NULL;
322
    if (options->display) {
323
        if (!video_buffer_init(&video_buffer, options->render_expired_frames)) {
R
Romain Vimont 已提交
324
            goto end;
R
Romain Vimont 已提交
325
        }
R
Romain Vimont 已提交
326
        video_buffer_initialized = true;
R
Romain Vimont 已提交
327

328
        if (options->control) {
R
Romain Vimont 已提交
329 330 331 332
            if (!file_handler_init(&file_handler, server.serial)) {
                goto end;
            }
            file_handler_initialized = true;
R
Romain Vimont 已提交
333
        }
334

R
Romain Vimont 已提交
335 336 337
        decoder_init(&decoder, &video_buffer);
        dec = &decoder;
    }
R
Romain Vimont 已提交
338

R
Romain Vimont 已提交
339
    struct recorder *rec = NULL;
340
    if (record) {
R
Romain Vimont 已提交
341 342 343 344
        if (!recorder_init(&recorder,
                           options->record_filename,
                           options->record_format,
                           frame_size)) {
R
Romain Vimont 已提交
345
            goto end;
R
Romain Vimont 已提交
346 347
        }
        rec = &recorder;
R
Romain Vimont 已提交
348
        recorder_initialized = true;
R
Romain Vimont 已提交
349 350
    }

R
Romain Vimont 已提交
351 352
    av_log_set_callback(av_log_callback);

353
    stream_init(&stream, server.video_socket, dec, rec);
R
Romain Vimont 已提交
354 355

    // now we consumed the header values, the socket receives the video stream
R
Romain Vimont 已提交
356 357
    // start the stream
    if (!stream_start(&stream)) {
R
Romain Vimont 已提交
358
        goto end;
R
Romain Vimont 已提交
359
    }
R
Romain Vimont 已提交
360
    stream_started = true;
R
Romain Vimont 已提交
361

362 363
    if (options->display) {
        if (options->control) {
364
            if (!controller_init(&controller, server.control_socket)) {
R
Romain Vimont 已提交
365
                goto end;
366
            }
R
Romain Vimont 已提交
367

368
            if (!controller_start(&controller)) {
R
Romain Vimont 已提交
369
                goto end;
370
            }
R
Romain Vimont 已提交
371
        }
R
Romain Vimont 已提交
372

R
Romain Vimont 已提交
373 374
        if (!screen_init_rendering(&screen, device_name, frame_size,
                                   options->always_on_top)) {
R
Romain Vimont 已提交
375
            goto end;
R
Romain Vimont 已提交
376 377
        }

378 379 380 381 382 383 384 385 386 387
        if (options->turn_screen_off) {
            struct control_msg msg;
            msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
            msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;

            if (!controller_push_msg(&controller, &msg)) {
                LOGW("Cannot request 'set screen power mode'");
            }
        }

R
Romain Vimont 已提交
388 389 390
        if (options->fullscreen) {
            screen_switch_fullscreen(&screen);
        }
R
Romain Vimont 已提交
391
    }
R
Romain Vimont 已提交
392

393
    if (options->show_touches) {
394
        wait_show_touches(proc_show_touches);
395
        show_touches_waited = true;
396
    }
R
Romain Vimont 已提交
397

398
    ret = event_loop(options->display, options->control);
R
Romain Vimont 已提交
399
    LOGD("quit...");
400

401 402
    screen_destroy(&screen);

R
Romain Vimont 已提交
403 404 405 406 407 408 409
end:
    // stop stream and controller so that they don't continue once their socket
    // is shutdown
    if (stream_started) {
        stream_stop(&stream);
    }
    if (controller_started) {
R
Romain Vimont 已提交
410
        controller_stop(&controller);
R
Romain Vimont 已提交
411 412 413 414 415 416 417 418 419 420 421 422 423 424
    }
    if (file_handler_initialized) {
        file_handler_stop(&file_handler);
    }

    // shutdown the sockets and kill the server
    server_stop(&server);

    // now that the sockets are shutdown, the stream and controller are
    // interrupted, we can join them
    if (stream_started) {
        stream_join(&stream);
    }
    if (controller_started) {
R
Romain Vimont 已提交
425 426
        controller_join(&controller);
    }
R
Romain Vimont 已提交
427
    if (controller_initialized) {
R
Romain Vimont 已提交
428 429
        controller_destroy(&controller);
    }
R
Romain Vimont 已提交
430 431

    if (recorder_initialized) {
R
Romain Vimont 已提交
432 433
        recorder_destroy(&recorder);
    }
R
Romain Vimont 已提交
434 435

    if (file_handler_initialized) {
R
Romain Vimont 已提交
436 437 438
        file_handler_join(&file_handler);
        file_handler_destroy(&file_handler);
    }
R
Romain Vimont 已提交
439 440

    if (video_buffer_initialized) {
R
Romain Vimont 已提交
441 442
        video_buffer_destroy(&video_buffer);
    }
R
Romain Vimont 已提交
443

444 445 446 447 448 449
    if (options->show_touches) {
        if (!show_touches_waited) {
            // wait the process which enabled "show touches"
            wait_show_touches(proc_show_touches);
        }
        LOGI("Disable show_touches");
R
Romain Vimont 已提交
450
        proc_show_touches = set_show_touches_enabled(options->serial, false);
451 452 453
        wait_show_touches(proc_show_touches);
    }

454
    server_destroy(&server);
R
Romain Vimont 已提交
455

R
Romain Vimont 已提交
456
    return ret;
R
Romain Vimont 已提交
457
}