1 #include "scrcpy.h"
2
3 #include <getopt.h>
4 #include <stdbool.h>
5 #include <stdint.h>
6 #include <unistd.h>
7 #include <libavformat/avformat.h>
8 #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
9 #include <SDL2/SDL.h>
10
11 #include "compat.h"
12 #include "config.h"
13 #include "log.h"
14 #include "recorder.h"
15
16 struct args {
17 const char *serial;
18 const char *crop;
19 const char *record_filename;
20 enum recorder_format record_format;
21 bool fullscreen;
22 bool no_control;
23 bool no_display;
24 bool help;
25 bool version;
26 bool show_touches;
27 uint16_t port;
28 uint16_t max_size;
29 uint32_t bit_rate;
30 bool always_on_top;
31 bool turn_screen_off;
32 bool render_expired_frames;
33 };
34
usage(const char * arg0)35 static void usage(const char *arg0) {
36 fprintf(stderr,
37 "Usage: %s [options]\n"
38 "\n"
39 "Options:\n"
40 "\n"
41 " -b, --bit-rate value\n"
42 " Encode the video at the given bit-rate, expressed in bits/s.\n"
43 " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
44 " Default is %d.\n"
45 "\n"
46 " -c, --crop width:height:x:y\n"
47 " Crop the device screen on the server.\n"
48 " The values are expressed in the device natural orientation\n"
49 " (typically, portrait for a phone, landscape for a tablet).\n"
50 " Any --max-size value is computed on the cropped size.\n"
51 "\n"
52 " -f, --fullscreen\n"
53 " Start in fullscreen.\n"
54 "\n"
55 " -F, --record-format\n"
56 " Force recording format (either mp4 or mkv).\n"
57 "\n"
58 " -h, --help\n"
59 " Print this help.\n"
60 "\n"
61 " -m, --max-size value\n"
62 " Limit both the width and height of the video to value. The\n"
63 " other dimension is computed so that the device aspect-ratio\n"
64 " is preserved.\n"
65 " Default is %d%s.\n"
66 "\n"
67 " -n, --no-control\n"
68 " Disable device control (mirror the device in read-only).\n"
69 "\n"
70 " -N, --no-display\n"
71 " Do not display device (only when screen recording is\n"
72 " enabled).\n"
73 "\n"
74 " -p, --port port\n"
75 " Set the TCP port the client listens on.\n"
76 " Default is %d.\n"
77 "\n"
78 " -r, --record file.mp4\n"
79 " Record screen to file.\n"
80 " The format is determined by the -F/--record-format option if\n"
81 " set, or by the file extension (.mp4 or .mkv).\n"
82 "\n"
83 " --render-expired-frames\n"
84 " By default, to minimize latency, scrcpy always renders the\n"
85 " last available decoded frame, and drops any previous ones.\n"
86 " This flag forces to render all frames, at a cost of a\n"
87 " possible increased latency.\n"
88 "\n"
89 " -s, --serial\n"
90 " The device serial number. Mandatory only if several devices\n"
91 " are connected to adb.\n"
92 "\n"
93 " -S, --turn-screen-off\n"
94 " Turn the device screen off immediately.\n"
95 "\n"
96 " -t, --show-touches\n"
97 " Enable \"show touches\" on start, disable on quit.\n"
98 " It only shows physical touches (not clicks from scrcpy).\n"
99 "\n"
100 " -T, --always-on-top\n"
101 " Make scrcpy window always on top (above other windows).\n"
102 "\n"
103 " -v, --version\n"
104 " Print the version of scrcpy.\n"
105 "\n"
106 "Shortcuts:\n"
107 "\n"
108 " Ctrl+f\n"
109 " switch fullscreen mode\n"
110 "\n"
111 " Ctrl+g\n"
112 " resize window to 1:1 (pixel-perfect)\n"
113 "\n"
114 " Ctrl+x\n"
115 " Double-click on black borders\n"
116 " resize window to remove black borders\n"
117 "\n"
118 " Ctrl+h\n"
119 " Middle-click\n"
120 " click on HOME\n"
121 "\n"
122 " Ctrl+b\n"
123 " Ctrl+Backspace\n"
124 " Right-click (when screen is on)\n"
125 " click on BACK\n"
126 "\n"
127 " Ctrl+s\n"
128 " click on APP_SWITCH\n"
129 "\n"
130 " Ctrl+m\n"
131 " click on MENU\n"
132 "\n"
133 " Ctrl+Up\n"
134 " click on VOLUME_UP\n"
135 "\n"
136 " Ctrl+Down\n"
137 " click on VOLUME_DOWN\n"
138 "\n"
139 " Ctrl+p\n"
140 " click on POWER (turn screen on/off)\n"
141 "\n"
142 " Right-click (when screen is off)\n"
143 " power on\n"
144 "\n"
145 " Ctrl+o\n"
146 " turn device screen off (keep mirroring)\n"
147 "\n"
148 " Ctrl+n\n"
149 " expand notification panel\n"
150 "\n"
151 " Ctrl+Shift+n\n"
152 " collapse notification panel\n"
153 "\n"
154 " Ctrl+c\n"
155 " copy device clipboard to computer\n"
156 "\n"
157 " Ctrl+v\n"
158 " paste computer clipboard to device\n"
159 "\n"
160 " Ctrl+Shift+v\n"
161 " copy computer clipboard to device\n"
162 "\n"
163 " Ctrl+i\n"
164 " enable/disable FPS counter (print frames/second in logs)\n"
165 "\n"
166 " Drag & drop APK file\n"
167 " install APK from computer\n"
168 "\n",
169 arg0,
170 DEFAULT_BIT_RATE,
171 DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
172 DEFAULT_LOCAL_PORT);
173 }
174
175 static void
print_version(void)176 print_version(void) {
177 fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
178
179 fprintf(stderr, "dependencies:\n");
180 fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
181 SDL_PATCHLEVEL);
182 fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR,
183 LIBAVCODEC_VERSION_MINOR,
184 LIBAVCODEC_VERSION_MICRO);
185 fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
186 LIBAVFORMAT_VERSION_MINOR,
187 LIBAVFORMAT_VERSION_MICRO);
188 fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
189 LIBAVUTIL_VERSION_MINOR,
190 LIBAVUTIL_VERSION_MICRO);
191 }
192
193 static bool
parse_bit_rate(char * optarg,uint32_t * bit_rate)194 parse_bit_rate(char *optarg, uint32_t *bit_rate) {
195 char *endptr;
196 if (*optarg == '\0') {
197 LOGE("Bit-rate parameter is empty");
198 return false;
199 }
200 long value = strtol(optarg, &endptr, 0);
201 int mul = 1;
202 if (*endptr != '\0') {
203 if (optarg == endptr) {
204 LOGE("Invalid bit-rate: %s", optarg);
205 return false;
206 }
207 if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') {
208 mul = 1000000;
209 } else if ((*endptr == 'K' || *endptr == 'k') && endptr[1] == '\0') {
210 mul = 1000;
211 } else {
212 LOGE("Invalid bit-rate unit: %s", optarg);
213 return false;
214 }
215 }
216 if (value < 0 || ((uint32_t) -1) / mul < value) {
217 LOGE("Bitrate must be positive and less than 2^32: %s", optarg);
218 return false;
219 }
220
221 *bit_rate = (uint32_t) value * mul;
222 return true;
223 }
224
225 static bool
parse_max_size(char * optarg,uint16_t * max_size)226 parse_max_size(char *optarg, uint16_t *max_size) {
227 char *endptr;
228 if (*optarg == '\0') {
229 LOGE("Max size parameter is empty");
230 return false;
231 }
232 long value = strtol(optarg, &endptr, 0);
233 if (*endptr != '\0') {
234 LOGE("Invalid max size: %s", optarg);
235 return false;
236 }
237 if (value & ~0xffff) {
238 LOGE("Max size must be between 0 and 65535: %ld", value);
239 return false;
240 }
241
242 *max_size = (uint16_t) value;
243 return true;
244 }
245
246 static bool
parse_port(char * optarg,uint16_t * port)247 parse_port(char *optarg, uint16_t *port) {
248 char *endptr;
249 if (*optarg == '\0') {
250 LOGE("Invalid port parameter is empty");
251 return false;
252 }
253 long value = strtol(optarg, &endptr, 0);
254 if (*endptr != '\0') {
255 LOGE("Invalid port: %s", optarg);
256 return false;
257 }
258 if (value & ~0xffff) {
259 LOGE("Port out of range: %ld", value);
260 return false;
261 }
262
263 *port = (uint16_t) value;
264 return true;
265 }
266
267 static bool
parse_record_format(const char * optarg,enum recorder_format * format)268 parse_record_format(const char *optarg, enum recorder_format *format) {
269 if (!strcmp(optarg, "mp4")) {
270 *format = RECORDER_FORMAT_MP4;
271 return true;
272 }
273 if (!strcmp(optarg, "mkv")) {
274 *format = RECORDER_FORMAT_MKV;
275 return true;
276 }
277 LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
278 return false;
279 }
280
281 static enum recorder_format
guess_record_format(const char * filename)282 guess_record_format(const char *filename) {
283 size_t len = strlen(filename);
284 if (len < 4) {
285 return 0;
286 }
287 const char *ext = &filename[len - 4];
288 if (!strcmp(ext, ".mp4")) {
289 return RECORDER_FORMAT_MP4;
290 }
291 if (!strcmp(ext, ".mkv")) {
292 return RECORDER_FORMAT_MKV;
293 }
294 return 0;
295 }
296
297 #define OPT_RENDER_EXPIRED_FRAMES 1000
298
299 static bool
parse_args(struct args * args,int argc,char * argv[])300 parse_args(struct args *args, int argc, char *argv[]) {
301 static const struct option long_options[] = {
302 {"always-on-top", no_argument, NULL, 'T'},
303 {"bit-rate", required_argument, NULL, 'b'},
304 {"crop", required_argument, NULL, 'c'},
305 {"fullscreen", no_argument, NULL, 'f'},
306 {"help", no_argument, NULL, 'h'},
307 {"max-size", required_argument, NULL, 'm'},
308 {"no-control", no_argument, NULL, 'n'},
309 {"no-display", no_argument, NULL, 'N'},
310 {"port", required_argument, NULL, 'p'},
311 {"record", required_argument, NULL, 'r'},
312 {"record-format", required_argument, NULL, 'f'},
313 {"render-expired-frames", no_argument, NULL,
314 OPT_RENDER_EXPIRED_FRAMES},
315 {"serial", required_argument, NULL, 's'},
316 {"show-touches", no_argument, NULL, 't'},
317 {"turn-screen-off", no_argument, NULL, 'S'},
318 {"version", no_argument, NULL, 'v'},
319 {NULL, 0, NULL, 0 },
320 };
321 int c;
322 while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
323 NULL)) != -1) {
324 switch (c) {
325 case 'b':
326 if (!parse_bit_rate(optarg, &args->bit_rate)) {
327 return false;
328 }
329 break;
330 case 'c':
331 args->crop = optarg;
332 break;
333 case 'f':
334 args->fullscreen = true;
335 break;
336 case 'F':
337 if (!parse_record_format(optarg, &args->record_format)) {
338 return false;
339 }
340 break;
341 case 'h':
342 args->help = true;
343 break;
344 case 'm':
345 if (!parse_max_size(optarg, &args->max_size)) {
346 return false;
347 }
348 break;
349 case 'n':
350 args->no_control = true;
351 break;
352 case 'N':
353 args->no_display = true;
354 break;
355 case 'p':
356 if (!parse_port(optarg, &args->port)) {
357 return false;
358 }
359 break;
360 case 'r':
361 args->record_filename = optarg;
362 break;
363 case 's':
364 args->serial = optarg;
365 break;
366 case 'S':
367 args->turn_screen_off = true;
368 break;
369 case 't':
370 args->show_touches = true;
371 break;
372 case 'T':
373 args->always_on_top = true;
374 break;
375 case 'v':
376 args->version = true;
377 break;
378 case OPT_RENDER_EXPIRED_FRAMES:
379 args->render_expired_frames = true;
380 break;
381 default:
382 // getopt prints the error message on stderr
383 return false;
384 }
385 }
386
387 if (args->no_display && !args->record_filename) {
388 LOGE("-N/--no-display requires screen recording (-r/--record)");
389 return false;
390 }
391
392 if (args->no_display && args->fullscreen) {
393 LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
394 return false;
395 }
396
397 int index = optind;
398 if (index < argc) {
399 LOGE("Unexpected additional argument: %s", argv[index]);
400 return false;
401 }
402
403 if (args->record_format && !args->record_filename) {
404 LOGE("Record format specified without recording");
405 return false;
406 }
407
408 if (args->record_filename && !args->record_format) {
409 args->record_format = guess_record_format(args->record_filename);
410 if (!args->record_format) {
411 LOGE("No format specified for \"%s\" (try with -F mkv)",
412 args->record_filename);
413 return false;
414 }
415 }
416
417 return true;
418 }
419
420 int
main(int argc,char * argv[])421 main(int argc, char *argv[]) {
422 #ifdef __WINDOWS__
423 // disable buffering, we want logs immediately
424 // even line buffering (setvbuf() with mode _IOLBF) is not sufficient
425 setbuf(stdout, NULL);
426 setbuf(stderr, NULL);
427 #endif
428 struct args args = {
429 .serial = NULL,
430 .crop = NULL,
431 .record_filename = NULL,
432 .record_format = 0,
433 .help = false,
434 .version = false,
435 .show_touches = false,
436 .port = DEFAULT_LOCAL_PORT,
437 .max_size = DEFAULT_MAX_SIZE,
438 .bit_rate = DEFAULT_BIT_RATE,
439 .always_on_top = false,
440 .no_control = false,
441 .no_display = false,
442 .turn_screen_off = false,
443 .render_expired_frames = false,
444 };
445 if (!parse_args(&args, argc, argv)) {
446 return 1;
447 }
448
449 if (args.help) {
450 usage(argv[0]);
451 return 0;
452 }
453
454 if (args.version) {
455 print_version();
456 return 0;
457 }
458
459 #ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
460 av_register_all();
461 #endif
462
463 if (avformat_network_init()) {
464 return 1;
465 }
466
467 #ifdef BUILD_DEBUG
468 SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
469 #endif
470
471 struct scrcpy_options options = {
472 .serial = args.serial,
473 .crop = args.crop,
474 .port = args.port,
475 .record_filename = args.record_filename,
476 .record_format = args.record_format,
477 .max_size = args.max_size,
478 .bit_rate = args.bit_rate,
479 .show_touches = args.show_touches,
480 .fullscreen = args.fullscreen,
481 .always_on_top = args.always_on_top,
482 .control = !args.no_control,
483 .display = !args.no_display,
484 .turn_screen_off = args.turn_screen_off,
485 .render_expired_frames = args.render_expired_frames,
486 };
487 int res = scrcpy(&options) ? 0 : 1;
488
489 avformat_network_deinit(); // ignore failure
490
491 #if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
492 if (res != 0) {
493 fprintf(stderr, "Press any key to continue...\n");
494 getchar();
495 }
496 #endif
497 return res;
498 }
499