diff --git a/app/src/device.c b/app/src/device.c index 8027ccbb..952b04ef 100644 --- a/app/src/device.c +++ b/app/src/device.c @@ -2,21 +2,23 @@ #include "log.h" bool -device_read_info(socket_t device_socket, char *device_name, struct size *size) { - unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; +device_read_info(socket_t device_socket, char *device_name, char *device_serial, struct size *size) { + unsigned char buf[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT + 4]; int r = net_recv_all(device_socket, buf, sizeof(buf)); - if (r < DEVICE_NAME_FIELD_LENGTH + 4) { + if (r < DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT + 4) { LOGE("Could not retrieve device information"); return false; } // in case the client sends garbage - buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; - // strcpy is safe here, since name contains at least - // DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH + for (int i=1; i<=DEVICE_INFO_FIELD_COUNT; i++) + buf[DEVICE_INFO_FIELD_LENGTH*i - 1] = '\0'; + // strcpy is safe here, since name/serial contains at least + // DEVICE_INFO_FIELD_LENGTH bytes and strlen(buf) < DEVICE_INFO_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]; + strcpy(device_serial, (char *) (buf+DEVICE_INFO_FIELD_LENGTH) ); + size->width = (buf[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT] << 8) + | buf[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT + 1]; + size->height = (buf[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT + 2] << 8) + | buf[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT + 3]; return true; } diff --git a/app/src/device.h b/app/src/device.h index f3449e5e..b7bdfce5 100644 --- a/app/src/device.h +++ b/app/src/device.h @@ -6,11 +6,12 @@ #include "common.h" #include "net.h" -#define DEVICE_NAME_FIELD_LENGTH 64 +#define DEVICE_INFO_FIELD_LENGTH 64 +#define DEVICE_INFO_FIELD_COUNT 2 #define DEVICE_SDCARD_PATH "/sdcard/" -// name must be at least DEVICE_NAME_FIELD_LENGTH bytes +// name and serial must be at least DEVICE_INFO_FIELD_LENGTH bytes bool -device_read_info(socket_t device_socket, char *device_name, struct size *size); +device_read_info(socket_t device_socket, char *device_name, char *device_serial, struct size *size); #endif diff --git a/app/src/main.c b/app/src/main.c index bf3b7a50..fe07efbd 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -17,6 +17,7 @@ struct args { const char *serial; const char *crop; const char *record_filename; + const char *window_title; enum recorder_format record_format; bool fullscreen; bool no_control; @@ -103,6 +104,10 @@ static void usage(const char *arg0) { " -v, --version\n" " Print the version of scrcpy.\n" "\n" + " --window-title text\n" + " Set the Window Title with some text.\n" + " Variables %%M and %%S can be used for replacement.\n" + "\n" "Shortcuts:\n" "\n" " Ctrl+f\n" @@ -295,6 +300,7 @@ guess_record_format(const char *filename) { } #define OPT_RENDER_EXPIRED_FRAMES 1000 +#define OPT_WINDOW_TITLE 1001 static bool parse_args(struct args *args, int argc, char *argv[]) { @@ -316,6 +322,8 @@ parse_args(struct args *args, int argc, char *argv[]) { {"show-touches", no_argument, NULL, 't'}, {"turn-screen-off", no_argument, NULL, 'S'}, {"version", no_argument, NULL, 'v'}, + {"window-title", required_argument, NULL, + OPT_WINDOW_TITLE}, {NULL, 0, NULL, 0 }, }; int c; @@ -378,6 +386,9 @@ parse_args(struct args *args, int argc, char *argv[]) { case OPT_RENDER_EXPIRED_FRAMES: args->render_expired_frames = true; break; + case OPT_WINDOW_TITLE: + args->window_title = optarg; + break; default: // getopt prints the error message on stderr return false; @@ -429,6 +440,7 @@ main(int argc, char *argv[]) { .serial = NULL, .crop = NULL, .record_filename = NULL, + .window_title = NULL, .record_format = 0, .help = false, .version = false, @@ -473,6 +485,7 @@ main(int argc, char *argv[]) { .crop = args.crop, .port = args.port, .record_filename = args.record_filename, + .window_title = args.window_title, .record_format = args.record_format, .max_size = args.max_size, .bit_rate = args.bit_rate, diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 761edb69..7bae8a1f 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -24,6 +24,7 @@ #include "screen.h" #include "server.h" #include "stream.h" +#include "str_util.h" #include "tiny_xpm.h" #include "video_buffer.h" @@ -310,13 +311,14 @@ scrcpy(const struct scrcpy_options *options) { goto end; } - char device_name[DEVICE_NAME_FIELD_LENGTH]; + char device_name[DEVICE_INFO_FIELD_LENGTH]; + char device_serial[DEVICE_INFO_FIELD_LENGTH]; struct size frame_size; // 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 (!device_read_info(server.video_socket, device_name, &frame_size)) { + if (!device_read_info(server.video_socket, device_name, device_serial, &frame_size)) { goto end; } @@ -380,7 +382,14 @@ scrcpy(const struct scrcpy_options *options) { controller_started = true; } - if (!screen_init_rendering(&screen, device_name, frame_size, + const char *window_title = device_name; + if (options->window_title) { + window_title = options->window_title; + window_title = strrep(window_title, "%M", device_name); + window_title = strrep(window_title, "%S", device_serial); + } + + if (!screen_init_rendering(&screen, window_title, frame_size, options->always_on_top)) { goto end; } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index d705d2db..fd86bad1 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -9,6 +9,7 @@ struct scrcpy_options { const char *serial; const char *crop; const char *record_filename; + const char *window_title; enum recorder_format record_format; uint16_t port; uint16_t max_size; diff --git a/app/src/screen.c b/app/src/screen.c index 67b268c5..aff1f1e3 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -134,7 +134,7 @@ create_texture(SDL_Renderer *renderer, struct size frame_size) { } bool -screen_init_rendering(struct screen *screen, const char *device_name, +screen_init_rendering(struct screen *screen, const char *window_title, struct size frame_size, bool always_on_top) { screen->frame_size = frame_size; @@ -152,7 +152,7 @@ screen_init_rendering(struct screen *screen, const char *device_name, #endif } - screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, + screen->window = SDL_CreateWindow(window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_size.width, window_size.height, window_flags); diff --git a/app/src/screen.h b/app/src/screen.h index 5734fdc2..63da6aa5 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -44,7 +44,7 @@ screen_init(struct screen *screen); // initialize screen, create window, renderer and texture (window is hidden) bool -screen_init_rendering(struct screen *screen, const char *device_name, +screen_init_rendering(struct screen *screen, const char *window_title, struct size frame_size, bool always_on_top); // show the window diff --git a/app/src/str_util.c b/app/src/str_util.c index 7d46a1a0..0f9bd133 100644 --- a/app/src/str_util.c +++ b/app/src/str_util.c @@ -44,6 +44,24 @@ truncated: return n; } +const char * +strrep(const char *src, const char *find, const char *rep) { + char *cmp = strstr(src, find); + if (!cmp) return src; + + size_t len_src = strlen(src); + size_t len_find = strlen(find); + size_t len_rep = strlen(rep); + char *tmp = SDL_malloc(len_src - len_find + len_rep + 1); + + size_t i = cmp - src; + strncpy(tmp, src, i); + tmp[i]='\0'; + strcat(tmp, rep); + strcat(tmp, cmp+len_find); + return tmp; +} + char * strquote(const char *src) { size_t len = strlen(src); diff --git a/app/src/str_util.h b/app/src/str_util.h index 0b7a571a..1d89527e 100644 --- a/app/src/str_util.h +++ b/app/src/str_util.h @@ -18,6 +18,11 @@ xstrncpy(char *dest, const char *src, size_t n); size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n); +// search for a string find within a string src and replace with rep +// returns a new string if a replacement is done +const char * +strrep(const char *src, const char *find, const char *rep); + // quote a string // returns the new allocated string, to be freed by the caller char * diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index a725d83d..fc0817ff 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -13,7 +13,8 @@ import java.nio.charset.StandardCharsets; public final class DesktopConnection implements Closeable { - private static final int DEVICE_NAME_FIELD_LENGTH = 64; + private static final int DEVICE_INFO_FIELD_LENGTH = 64; + private static final int DEVICE_INFO_FIELD_COUNT = 2; private static final String SOCKET_NAME = "scrcpy"; @@ -71,7 +72,7 @@ public final class DesktopConnection implements Closeable { DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket); Size videoSize = device.getScreenInfo().getVideoSize(); - connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); + connection.send(Device.getDeviceName(), Device.getDeviceSerial(), videoSize.getWidth(), videoSize.getHeight()); return connection; } @@ -85,18 +86,22 @@ public final class DesktopConnection implements Closeable { } @SuppressWarnings("checkstyle:MagicNumber") - private void send(String deviceName, int width, int height) throws IOException { - byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4]; + private void send(String deviceName, String deviceSerial, int width, int height) throws IOException { + byte[] buffer = new byte[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT + 4]; byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8); - int len = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_NAME_FIELD_LENGTH - 1); - System.arraycopy(deviceNameBytes, 0, buffer, 0, len); + int lenName = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_INFO_FIELD_LENGTH - 1); + System.arraycopy(deviceNameBytes, 0, buffer, 0, lenName); // byte[] are always 0-initialized in java, no need to set '\0' explicitly - buffer[DEVICE_NAME_FIELD_LENGTH] = (byte) (width >> 8); - buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width; - buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8); - buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height; + byte[] deviceSerialBytes = deviceSerial.getBytes(StandardCharsets.UTF_8); + int lenSerial = StringUtils.getUtf8TruncationIndex(deviceSerialBytes, DEVICE_INFO_FIELD_LENGTH - 1); + System.arraycopy(deviceSerialBytes, 0, buffer, DEVICE_INFO_FIELD_LENGTH, lenSerial); + + buffer[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT] = (byte) (width >> 8); + buffer[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT + 1] = (byte) width; + buffer[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT + 2] = (byte) (height >> 8); + buffer[DEVICE_INFO_FIELD_LENGTH*DEVICE_INFO_FIELD_COUNT + 3] = (byte) height; IO.writeFully(videoFd, buffer, 0, buffer.length); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 538135d4..31e5820f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -120,6 +120,9 @@ public final class Device { return Build.MODEL; } + public static String getDeviceSerial() { + return ( Build.VERSION.SDK_INT >= 26 ? Build.getSerial() : Build.SERIAL ); + } public boolean injectInputEvent(InputEvent inputEvent, int mode) { return serviceManager.getInputManager().injectInputEvent(inputEvent, mode); }