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