From 9396ea6d422d6d8f35f2c898bcf621c4895cd9b9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 9 Mar 2018 21:47:14 +0100 Subject: [PATCH 01/12] Fix text input event segfault The text input control_event was initially designed for mapping SDL_TextInputEvent, limited to 32 characters. For simplicity, the copy/paste feature was implemented using the same control_event: it just sends the text to paste. However, the pasted text might have a length breaking some assumptions: - on the client, the event max-size was smaller than the text max-length, - on the server, the raw buffer storing the events was smaller than the max event size. Fix these inconsistencies, and encode the length on 2 bytes, to accept more than 256 characters. Fixes . --- app/src/controlevent.c | 6 ++-- app/src/controlevent.h | 4 +-- app/tests/test_control_event_serialize.c | 25 +++++++++++++-- .../genymobile/scrcpy/ControlEventReader.java | 6 ++-- .../scrcpy/ControlEventReaderTest.java | 31 ++++++++++++++++--- 5 files changed, 57 insertions(+), 15 deletions(-) diff --git a/app/src/controlevent.c b/app/src/controlevent.c index d288cee4..ba52e717 100644 --- a/app/src/controlevent.c +++ b/app/src/controlevent.c @@ -39,9 +39,9 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu // injecting a text takes time, so limit the text length len = TEXT_MAX_LENGTH; } - buf[1] = (Uint8) len; - memcpy(&buf[2], event->text_event.text, len); - return 2 + len; + write16(&buf[1], (Uint16) len); + memcpy(&buf[3], event->text_event.text, len); + return 3 + len; } case CONTROL_EVENT_TYPE_MOUSE: buf[1] = event->mouse_event.action; diff --git a/app/src/controlevent.h b/app/src/controlevent.h index 7c52d6e0..65f965e4 100644 --- a/app/src/controlevent.h +++ b/app/src/controlevent.h @@ -9,8 +9,8 @@ #include "common.h" #define CONTROL_EVENT_QUEUE_SIZE 64 -#define SERIALIZED_EVENT_MAX_SIZE 33 -#define TEXT_MAX_LENGTH 256 +#define TEXT_MAX_LENGTH 300 +#define SERIALIZED_EVENT_MAX_SIZE 3 + TEXT_MAX_LENGTH enum control_event_type { CONTROL_EVENT_TYPE_KEYCODE, diff --git a/app/tests/test_control_event_serialize.c b/app/tests/test_control_event_serialize.c index b983fea9..112ea8b8 100644 --- a/app/tests/test_control_event_serialize.c +++ b/app/tests/test_control_event_serialize.c @@ -35,16 +35,36 @@ static void test_serialize_text_event() { unsigned char buf[SERIALIZED_EVENT_MAX_SIZE]; int size = control_event_serialize(&event, buf); - assert(size == 15); + assert(size == 16); const unsigned char expected[] = { 0x01, // CONTROL_EVENT_TYPE_KEYCODE - 0x0d, // text length + 0x00, 0x0d, // text length 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text }; assert(!memcmp(buf, expected, sizeof(expected))); } +static void test_serialize_long_text_event() { + struct control_event event; + event.type = CONTROL_EVENT_TYPE_TEXT; + char text[TEXT_MAX_LENGTH]; + memset(text, 'a', sizeof(text)); + event.text_event.text = text; + + unsigned char buf[SERIALIZED_EVENT_MAX_SIZE]; + int size = control_event_serialize(&event, buf); + assert(size == 3 + sizeof(text)); + + unsigned char expected[3 + TEXT_MAX_LENGTH]; + expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE + expected[1] = 0x01; + expected[2] = 0x2c; // text length (16 bits) + memset(&expected[3], 'a', TEXT_MAX_LENGTH); + + assert(!memcmp(buf, expected, sizeof(expected))); +} + static void test_serialize_mouse_event() { struct control_event event = { .type = CONTROL_EVENT_TYPE_MOUSE, @@ -114,6 +134,7 @@ static void test_serialize_scroll_event() { int main() { test_serialize_keycode_event(); test_serialize_text_event(); + test_serialize_long_text_event(); test_serialize_mouse_event(); test_serialize_scroll_event(); return 0; diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java index c1250896..83088b10 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java @@ -13,8 +13,8 @@ public class ControlEventReader { private static final int SCROLL_PAYLOAD_LENGTH = 16; private static final int COMMAND_PAYLOAD_LENGTH = 1; - private static final int TEXT_MAX_LENGTH = 256; - private static final int RAW_BUFFER_SIZE = 128; + public static final int TEXT_MAX_LENGTH = 300; + private static final int RAW_BUFFER_SIZE = 1024; private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE]; private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); @@ -94,7 +94,7 @@ public class ControlEventReader { if (buffer.remaining() < 1) { return null; } - int len = toUnsigned(buffer.get()); + int len = toUnsigned(buffer.getShort()); if (buffer.remaining() < len) { return null; } diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlEventReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlEventReaderTest.java index 4d9952d0..d2b10ccb 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlEventReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlEventReaderTest.java @@ -3,14 +3,15 @@ package com.genymobile.scrcpy; import android.view.KeyEvent; import android.view.MotionEvent; -import org.junit.Assert; -import org.junit.Test; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Test; public class ControlEventReaderTest { @@ -43,8 +44,8 @@ public class ControlEventReaderTest { DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(ControlEvent.TYPE_TEXT); byte[] text = "testé".getBytes(StandardCharsets.UTF_8); - dos.writeByte(text.length); - dos.write("testé".getBytes(StandardCharsets.UTF_8)); + dos.writeShort(text.length); + dos.write(text); byte[] packet = bos.toByteArray(); reader.readFrom(new ByteArrayInputStream(packet)); @@ -54,6 +55,26 @@ public class ControlEventReaderTest { Assert.assertEquals("testé", event.getText()); } + @Test + public void testParseLongTextEvent() throws IOException { + ControlEventReader reader = new ControlEventReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlEvent.TYPE_TEXT); + byte[] text = new byte[ControlEventReader.TEXT_MAX_LENGTH]; + Arrays.fill(text, (byte) 'a'); + dos.writeShort(text.length); + dos.write(text); + byte[] packet = bos.toByteArray(); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlEvent event = reader.next(); + + Assert.assertEquals(ControlEvent.TYPE_TEXT, event.getType()); + Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText()); + } + @Test public void testParseMouseEvent() throws IOException { ControlEventReader reader = new ControlEventReader(); From 675704c71c0d105af2242d9bbeee83127097fa0d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 10 Mar 2018 00:11:52 +0100 Subject: [PATCH 02/12] Map right-click to BACK if screen is on Right-click was used to turn the screen on. It did nothing when the screen was already on. Instead, in that case, press BACK (like Vysor). Suggested by: --- README.md | 4 +++- app/src/controlevent.h | 2 +- app/src/inputmanager.c | 7 ++++--- .../main/java/com/genymobile/scrcpy/ControlEvent.java | 2 +- .../main/java/com/genymobile/scrcpy/EventController.java | 9 +++++++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8452f302..9c034ae2 100644 --- a/README.md +++ b/README.md @@ -199,10 +199,12 @@ To run without installing: | click on `VOLUME_UP` | `Ctrl`+`+` | | click on `VOLUME_DOWN` | `Ctrl`+`-` | | click on `POWER` | `Ctrl`+`p` | - | turn screen on | _Right-click_ | + | turn screen on or press BACK¹ | _Right-click_ | | paste computer clipboard to device | `Ctrl`+`v` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` | +_¹ Press BACK if the screen is already on._ + ## Why _scrcpy_? diff --git a/app/src/controlevent.h b/app/src/controlevent.h index 65f965e4..471a9a9b 100644 --- a/app/src/controlevent.h +++ b/app/src/controlevent.h @@ -20,7 +20,7 @@ enum control_event_type { CONTROL_EVENT_TYPE_COMMAND, }; -#define CONTROL_EVENT_COMMAND_SCREEN_ON 0 +#define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0 struct control_event { enum control_event_type type; diff --git a/app/src/inputmanager.c b/app/src/inputmanager.c index 81fa9619..7b10a91d 100644 --- a/app/src/inputmanager.c +++ b/app/src/inputmanager.c @@ -78,10 +78,11 @@ static inline void action_volume_down(struct controller *controller) { send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN"); } -static void turn_screen_on(struct controller *controller) { +// turn the screen on if it was off, press BACK otherwise +static void press_back_or_turn_screen_on(struct controller *controller) { struct control_event control_event; control_event.type = CONTROL_EVENT_TYPE_COMMAND; - control_event.command_event.action = CONTROL_EVENT_COMMAND_SCREEN_ON; + control_event.command_event.action = CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON; if (!controller_push_event(controller, &control_event)) { LOGW("Cannot turn screen on"); @@ -226,7 +227,7 @@ void input_manager_process_mouse_motion(struct input_manager *input_manager, void input_manager_process_mouse_button(struct input_manager *input_manager, const SDL_MouseButtonEvent *event) { if (event->button == SDL_BUTTON_RIGHT && event->type == SDL_MOUSEBUTTONDOWN) { - turn_screen_on(input_manager->controller); + press_back_or_turn_screen_on(input_manager->controller); return; }; struct control_event control_event; diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java b/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java index 43b8f930..3c9cbda1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java @@ -11,7 +11,7 @@ public final class ControlEvent { public static final int TYPE_SCROLL = 3; public static final int TYPE_COMMAND = 4; - public static final int COMMAND_SCREEN_ON = 0; + public static final int COMMAND_BACK_OR_SCREEN_ON = 0; private int type; private String text; diff --git a/server/src/main/java/com/genymobile/scrcpy/EventController.java b/server/src/main/java/com/genymobile/scrcpy/EventController.java index a7733f96..4a0ce331 100644 --- a/server/src/main/java/com/genymobile/scrcpy/EventController.java +++ b/server/src/main/java/com/genymobile/scrcpy/EventController.java @@ -167,10 +167,15 @@ public class EventController { return device.isScreenOn() || injectKeycode(KeyEvent.KEYCODE_POWER); } + private boolean pressBackOrTurnScreenOn() { + int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; + return injectKeycode(keycode); + } + private boolean executeCommand(int action) { switch (action) { - case ControlEvent.COMMAND_SCREEN_ON: - return turnScreenOn(); + case ControlEvent.COMMAND_BACK_OR_SCREEN_ON: + return pressBackOrTurnScreenOn(); default: Ln.w("Unsupported command: " + action); } From c87d94ee2779838cf4576f6505a918bc485620d5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 10 Mar 2018 00:40:55 +0100 Subject: [PATCH 03/12] Map middle-click to HOME Middle-click is useless in practice. Use it for HOME. --- README.md | 30 +++++++++++++++--------------- app/src/inputmanager.c | 12 +++++++++--- app/src/main.c | 4 +++- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 9c034ae2..337f12e1 100644 --- a/README.md +++ b/README.md @@ -188,22 +188,22 @@ To run without installing: ## Shortcuts - | Action | Shortcut | - | ------------------------------------- | -------------:| - | switch fullscreen mode | `Ctrl`+`f` | - | resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | - | resize window to remove black borders | `Ctrl`+`x` | - | click on `HOME` | `Ctrl`+`h` | - | click on `BACK` | `Ctrl`+`b` | - | click on `APP_SWITCH` | `Ctrl`+`m` | - | click on `VOLUME_UP` | `Ctrl`+`+` | - | click on `VOLUME_DOWN` | `Ctrl`+`-` | - | click on `POWER` | `Ctrl`+`p` | - | turn screen on or press BACK¹ | _Right-click_ | - | paste computer clipboard to device | `Ctrl`+`v` | - | enable/disable FPS counter (on stdout) | `Ctrl`+`i` | + | Action | Shortcut | + | -------------------------------------- |:---------------------------- | + | switch fullscreen mode | `Ctrl`+`f` | + | resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | + | resize window to remove black borders | `Ctrl`+`x` | + | click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | + | click on `BACK` | `Ctrl`+`b` \| _Right-click¹_ | + | click on `APP_SWITCH` | `Ctrl`+`m` | + | click on `VOLUME_UP` | `Ctrl`+`+` | + | click on `VOLUME_DOWN` | `Ctrl`+`-` | + | click on `POWER` | `Ctrl`+`p` | + | turn screen on | _Right-click¹_ | + | paste computer clipboard to device | `Ctrl`+`v` | + | enable/disable FPS counter (on stdout) | `Ctrl`+`i` | -_¹ Press BACK if the screen is already on._ +_¹Right-click turns the screen on if it was off, presses BACK otherwise._ ## Why _scrcpy_? diff --git a/app/src/inputmanager.c b/app/src/inputmanager.c index 7b10a91d..6a9243e2 100644 --- a/app/src/inputmanager.c +++ b/app/src/inputmanager.c @@ -226,9 +226,15 @@ void input_manager_process_mouse_motion(struct input_manager *input_manager, void input_manager_process_mouse_button(struct input_manager *input_manager, const SDL_MouseButtonEvent *event) { - if (event->button == SDL_BUTTON_RIGHT && event->type == SDL_MOUSEBUTTONDOWN) { - press_back_or_turn_screen_on(input_manager->controller); - return; + if (event->type == SDL_MOUSEBUTTONDOWN) { + if (event->button == SDL_BUTTON_RIGHT) { + press_back_or_turn_screen_on(input_manager->controller); + return; + } + if (event->button == SDL_BUTTON_MIDDLE) { + action_home(input_manager->controller); + return; + } }; struct control_event control_event; if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) { diff --git a/app/src/main.c b/app/src/main.c index 1f2164b6..287c9d7d 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -61,10 +61,12 @@ static void usage(const char *arg0) { "\n" " Ctrl+h\n" " Home\n" + " Middle-click\n" " click on HOME\n" "\n" " Ctrl+b\n" " Ctrl+Backspace\n" + " Right-click (when screen is on)\n" " click on BACK\n" "\n" " Ctrl+m\n" @@ -79,7 +81,7 @@ static void usage(const char *arg0) { " Ctrl+p\n" " click on POWER (turn screen on/off)\n" "\n" - " Right-click\n" + " Right-click (when screen is off)\n" " turn screen on\n" "\n" " Ctrl+v\n" From dac7196bd61e6a4714e68b10f53f4ed5082b5b95 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Mar 2018 13:16:42 +0100 Subject: [PATCH 04/12] Support screens with dimensions not divisible by 8 The codec only supports dimensions which are multiple of 8. Thus, when --max-size is specified, the value is always rounded down to the nearest multiple of 8. However, it was wrongly assumed that the physical size is always a multiple of 8. To support such devices, also round down the physical screen dimensions. Fixes . --- server/src/main/java/com/genymobile/scrcpy/Device.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index d78ea1b3..07c15e2b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -50,8 +50,8 @@ public final class Device { DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(); boolean rotated = (displayInfo.getRotation() & 1) != 0; Size deviceSize = displayInfo.getSize(); - int w = deviceSize.getWidth(); - int h = deviceSize.getHeight(); + int w = deviceSize.getWidth() & ~7; // in case it's not a multiple of 8 + int h = deviceSize.getHeight() & ~7; if (maxSize > 0) { if (BuildConfig.DEBUG && maxSize % 8 != 0) { throw new AssertionError("Max size must be a multiple of 8"); From c075ad0a1e9e496bd2b0adf353d539933aa8c7ee Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Mar 2018 14:11:43 +0100 Subject: [PATCH 05/12] Fix mouse clicks on LG devices Use default values (0) for some fields of PointerCoords so that mouse clicks work correctly on LG devices. Fixes . --- .../src/main/java/com/genymobile/scrcpy/EventController.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/EventController.java b/server/src/main/java/com/genymobile/scrcpy/EventController.java index 4a0ce331..547e20ca 100644 --- a/server/src/main/java/com/genymobile/scrcpy/EventController.java +++ b/server/src/main/java/com/genymobile/scrcpy/EventController.java @@ -39,10 +39,6 @@ public class EventController { coords.orientation = 0; coords.pressure = 1; coords.size = 1; - coords.toolMajor = 1; - coords.toolMinor = 1; - coords.touchMajor = 1; - coords.touchMinor = 1; } private void setPointerCoords(Point point) { From 9e328ef98b0fc730036aa389f33649a31737d0e3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 12 Mar 2018 14:07:47 +0100 Subject: [PATCH 06/12] Always use the best render scale quality available Because why not. See . --- app/src/screen.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index c6838946..6587d659 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -18,8 +18,8 @@ SDL_bool sdl_init_and_configure(void) { atexit(SDL_Quit); - // Bilinear resizing - if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) { + // Use the best available scale quality + if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) { LOGW("Could not enable bilinear filtering"); } From 2b3ed5bcdba63d30da427f324a7ab77c9a4b7937 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 12 Mar 2018 10:19:12 +0100 Subject: [PATCH 07/12] Store serial in server instance The serial is needed for many server actions, but this is an implementation detail, so the caller should not have to provide it on every call. Instead, store the serial in the server instance on server_start(). This paves the way to implement the "adb forward" fallback properly. --- app/src/scrcpy.c | 12 ++++++------ app/src/server.c | 17 +++++++++++------ app/src/server.h | 6 ++++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 8c549d67..66badb06 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -103,9 +103,9 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b // managed by the event loop. This blocking call blocks the event loop, so // timeout the connection not to block indefinitely in case of SIGTERM. #define SERVER_CONNECT_TIMEOUT_MS 2000 - socket_t device_socket = server_connect_to(&server, serial, SERVER_CONNECT_TIMEOUT_MS); + socket_t device_socket = server_connect_to(&server, SERVER_CONNECT_TIMEOUT_MS); if (device_socket == INVALID_SOCKET) { - server_stop(&server, serial); + server_stop(&server); ret = SDL_FALSE; goto finally_destroy_server; } @@ -117,13 +117,13 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b // therefore, we transmit the screen size before the video stream, to be able // to init the window immediately if (!device_read_info(device_socket, device_name, &frame_size)) { - server_stop(&server, serial); + server_stop(&server); ret = SDL_FALSE; goto finally_destroy_server; } if (!frames_init(&frames)) { - server_stop(&server, serial); + server_stop(&server); ret = SDL_FALSE; goto finally_destroy_server; } @@ -134,7 +134,7 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b // start the decoder if (!decoder_start(&decoder)) { ret = SDL_FALSE; - server_stop(&server, serial); + server_stop(&server); goto finally_destroy_frames; } @@ -165,7 +165,7 @@ finally_destroy_controller: finally_stop_decoder: decoder_stop(&decoder); // stop the server before decoder_join() to wake up the decoder - server_stop(&server, serial); + server_stop(&server); decoder_join(&decoder); finally_destroy_frames: frames_destroy(&frames); diff --git a/app/src/server.c b/app/src/server.c index d5d8b673..1e1c95bd 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -85,6 +85,10 @@ void server_init(struct server *server) { SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate) { + if (serial) { + server->serial = SDL_strdup(serial); + } + if (!push_server(serial)) { return SDL_FALSE; } @@ -121,7 +125,7 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po return SDL_TRUE; } -socket_t server_connect_to(struct server *server, const char *serial, Uint32 timeout_ms) { +socket_t server_connect_to(struct server *server, Uint32 timeout_ms) { server->device_socket = net_accept(server->server_socket); if (server->device_socket == INVALID_SOCKET) { return INVALID_SOCKET; @@ -131,17 +135,17 @@ socket_t server_connect_to(struct server *server, const char *serial, Uint32 tim close_socket(&server->server_socket); // the server is started, we can clean up the jar from the temporary folder - remove_server(serial); // ignore failure + remove_server(server->serial); // ignore failure server->server_copied_to_device = SDL_FALSE; // we don't need the adb tunnel anymore - disable_tunnel(serial); // ignore failure + disable_tunnel(server->serial); // ignore failure server->adb_reverse_enabled = SDL_FALSE; return server->device_socket; } -void server_stop(struct server *server, const char *serial) { +void server_stop(struct server *server) { SDL_assert(server->process != PROCESS_NONE); if (!cmd_terminate(server->process)) { @@ -153,11 +157,11 @@ void server_stop(struct server *server, const char *serial) { if (server->adb_reverse_enabled) { // ignore failure - disable_tunnel(serial); + disable_tunnel(server->serial); } if (server->server_copied_to_device) { - remove_server(serial); // ignore failure + remove_server(server->serial); // ignore failure } } @@ -168,4 +172,5 @@ void server_destroy(struct server *server) { if (server->device_socket != INVALID_SOCKET) { close_socket(&server->device_socket); } + SDL_free((void *) server->serial); } diff --git a/app/src/server.h b/app/src/server.h index c2fe1125..63547343 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -5,6 +5,7 @@ #include "net.h" struct server { + const char *serial; process_t process; socket_t server_socket; socket_t device_socket; @@ -13,6 +14,7 @@ struct server { }; #define SERVER_INITIALIZER { \ + .serial = NULL, \ .process = PROCESS_NONE, \ .server_socket = INVALID_SOCKET, \ .device_socket = INVALID_SOCKET, \ @@ -28,10 +30,10 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po Uint16 max_size, Uint32 bit_rate); // block until the communication with the server is established -socket_t server_connect_to(struct server *server, const char *serial, Uint32 timeout_ms); +socket_t server_connect_to(struct server *server, Uint32 timeout_ms); // disconnect and kill the server process -void server_stop(struct server *server, const char *serial); +void server_stop(struct server *server); // close and release sockets void server_destroy(struct server *server); From 1038bad3850f18717a048a4d5c0f8110e54ee172 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 12 Mar 2018 08:35:51 +0100 Subject: [PATCH 08/12] Make it work over tcpip "adb reverse" currently does not work over tcpip (i.e. on a device connected by "adb connect"): To work around the problem, if the call to "adb reverse" fails, then fallback to "adb forward", and reverse the client/server roles. Keep the "adb reverse" mode as the default because it does not involve connection retries: when using "adb forward", the client must try to connect successively until the server listens. Due to the tunnel, every connect() will succeed, so the client must attempt to read() to detect a connection failure. For this purpose, when using the "adb forward" mode, the server initially writes a dummy byte, read by the client. Fixes . --- app/src/command.c | 7 + app/src/command.h | 1 + app/src/net.c | 20 +++ app/src/net.h | 1 + app/src/server.c | 131 ++++++++++++++---- app/src/server.h | 10 +- .../genymobile/scrcpy/DesktopConnection.java | 17 ++- .../java/com/genymobile/scrcpy/Options.java | 9 ++ .../java/com/genymobile/scrcpy/Server.java | 10 +- 9 files changed, 174 insertions(+), 32 deletions(-) diff --git a/app/src/command.c b/app/src/command.c index 6a5a38e4..246d1515 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -45,6 +45,13 @@ process_t adb_forward(const char *serial, uint16_t local_port, const char *devic return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } +process_t adb_forward_remove(const char *serial, uint16_t local_port) { + char local[4 + 5 + 1]; // tcp:PORT + sprintf(local, "tcp:%" PRIu16, local_port); + const char *const adb_cmd[] = {"forward", "--remove", local}; + return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); +} + process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME diff --git a/app/src/command.h b/app/src/command.h index 5308aa6a..ed7d5faf 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -39,6 +39,7 @@ SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code); process_t adb_execute(const char *serial, const char *const adb_cmd[], int len); process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name); +process_t adb_forward_remove(const char *serial, uint16_t local_port); process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port); process_t adb_reverse_remove(const char *serial, const char *device_socket_name); process_t adb_push(const char *serial, const char *local, const char *remote); diff --git a/app/src/net.c b/app/src/net.c index 60fb3988..1d68f068 100644 --- a/app/src/net.c +++ b/app/src/net.c @@ -18,6 +18,26 @@ typedef struct in_addr IN_ADDR; #endif +socket_t net_connect(Uint32 addr, Uint16 port) { + socket_t sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + perror("socket"); + return INVALID_SOCKET; + } + + SOCKADDR_IN sin; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(addr); + sin.sin_port = htons(port); + + if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { + perror("connect"); + return INVALID_SOCKET; + } + + return sock; +} + socket_t net_listen(Uint32 addr, Uint16 port, int backlog) { socket_t sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { diff --git a/app/src/net.h b/app/src/net.h index 03ad8c39..52a1e9a4 100644 --- a/app/src/net.h +++ b/app/src/net.h @@ -21,6 +21,7 @@ SDL_bool net_init(void); void net_cleanup(void); +socket_t net_connect(Uint32 addr, Uint16 port); socket_t net_listen(Uint32 addr, Uint16 port, int backlog); socket_t net_accept(socket_t server_socket); diff --git a/app/src/server.c b/app/src/server.c index 1e1c95bd..33f25b55 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "config.h" #include "log.h" @@ -37,17 +38,45 @@ static SDL_bool remove_server(const char *serial) { return process_check_success(process, "adb shell rm"); } -static SDL_bool enable_tunnel(const char *serial, Uint16 local_port) { +static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) { process_t process = adb_reverse(serial, SOCKET_NAME, local_port); return process_check_success(process, "adb reverse"); } -static SDL_bool disable_tunnel(const char *serial) { +static SDL_bool disable_tunnel_reverse(const char *serial) { process_t process = adb_reverse_remove(serial, SOCKET_NAME); return process_check_success(process, "adb reverse --remove"); } -static process_t execute_server(const char *serial, Uint16 max_size, Uint32 bit_rate) { +static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) { + process_t process = adb_forward(serial, local_port, SOCKET_NAME); + return process_check_success(process, "adb forward"); +} + +static SDL_bool disable_tunnel_forward(const char *serial, Uint16 local_port) { + process_t process = adb_forward_remove(serial, local_port); + return process_check_success(process, "adb forward --remove"); +} + +static SDL_bool enable_tunnel(struct server *server) { + if (enable_tunnel_reverse(server->serial, server->local_port)) { + return SDL_TRUE; + } + + LOGW("'adb reverse' failed, fallback to 'adb forward'"); + server->tunnel_forward = SDL_TRUE; + return enable_tunnel_forward(server->serial, server->local_port); +} + +static SDL_bool disable_tunnel(struct server *server) { + if (server->tunnel_forward) { + return disable_tunnel_forward(server->serial, server->local_port); + } + return disable_tunnel_reverse(server->serial); +} + +static process_t execute_server(const char *serial, + Uint16 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) { char max_size_string[6]; char bit_rate_string[11]; sprintf(max_size_string, "%"PRIu16, max_size); @@ -60,15 +89,48 @@ static process_t execute_server(const char *serial, Uint16 max_size, Uint32 bit_ "com.genymobile.scrcpy.Server", max_size_string, bit_rate_string, + tunnel_forward ? "true" : "false", }; return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0])); } -static socket_t listen_on_port(Uint16 port) { #define IPV4_LOCALHOST 0x7F000001 + +static socket_t listen_on_port(Uint16 port) { return net_listen(IPV4_LOCALHOST, port, 1); } +static socket_t connect_and_read_byte(Uint16 port) { + socket_t socket = net_connect(IPV4_LOCALHOST, port); + if (socket == INVALID_SOCKET) { + return INVALID_SOCKET; + } + + char byte; + // the connection may succeed even if the server behind the "adb tunnel" + // is not listening, so read one byte to detect a working connection + if (net_recv_all(socket, &byte, 1) != 1) { + // the server is not listening yet behind the adb tunnel + return INVALID_SOCKET; + } + return socket; +} + +static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) { + do { + LOGD("Remaining connection attempts: %d", (int) attempts); + socket_t socket = connect_and_read_byte(port); + if (socket != INVALID_SOCKET) { + // it worked! + return socket; + } + if (attempts) { + SDL_Delay(delay); + } + } while (--attempts > 0); + return INVALID_SOCKET; +} + static void close_socket(socket_t *socket) { SDL_assert(*socket != INVALID_SOCKET); net_shutdown(*socket, SHUT_RDWR); @@ -85,6 +147,8 @@ void server_init(struct server *server) { SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate) { + server->local_port = local_port; + if (serial) { server->serial = SDL_strdup(serial); } @@ -95,52 +159,67 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po server->server_copied_to_device = SDL_TRUE; - if (!enable_tunnel(serial, local_port)) { + if (!enable_tunnel(server)) { return SDL_FALSE; } - // At the application level, the device part is "the server" because it - // serves video stream and control. However, at the network level, the - // client listens and the server connects to the client. That way, the - // client can listen before starting the server app, so there is no need to - // try to connect until the server socket is listening on the device. + // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to + // "adb forward", so the app socket is the client + if (!server->tunnel_forward) { + // At the application level, the device part is "the server" because it + // serves video stream and control. However, at the network level, the + // client listens and the server connects to the client. That way, the + // client can listen before starting the server app, so there is no need to + // try to connect until the server socket is listening on the device. - server->server_socket = listen_on_port(local_port); - if (server->server_socket == INVALID_SOCKET) { - LOGE("Could not listen on port %" PRIu16, local_port); - disable_tunnel(serial); - return SDL_FALSE; + server->server_socket = listen_on_port(local_port); + if (server->server_socket == INVALID_SOCKET) { + LOGE("Could not listen on port %" PRIu16, local_port); + disable_tunnel(server); + return SDL_FALSE; + } } // server will connect to our server socket - server->process = execute_server(serial, max_size, bit_rate); + server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward); if (server->process == PROCESS_NONE) { - close_socket(&server->server_socket); - disable_tunnel(serial); + if (!server->tunnel_forward) { + close_socket(&server->server_socket); + } + disable_tunnel(server); return SDL_FALSE; } - server->adb_reverse_enabled = SDL_TRUE; + server->tunnel_enabled = SDL_TRUE; return SDL_TRUE; } socket_t server_connect_to(struct server *server, Uint32 timeout_ms) { - server->device_socket = net_accept(server->server_socket); + if (!server->tunnel_forward) { + server->device_socket = net_accept(server->server_socket); + } else { + Uint32 attempts = 10; + Uint32 delay = 100; // ms + server->device_socket = connect_to_server(server->local_port, attempts, delay); + } + if (server->device_socket == INVALID_SOCKET) { return INVALID_SOCKET; } - // we don't need the server socket anymore - close_socket(&server->server_socket); + if (!server->tunnel_forward) { + // we don't need the server socket anymore + close_socket(&server->server_socket); + } // the server is started, we can clean up the jar from the temporary folder remove_server(server->serial); // ignore failure server->server_copied_to_device = SDL_FALSE; // we don't need the adb tunnel anymore - disable_tunnel(server->serial); // ignore failure - server->adb_reverse_enabled = SDL_FALSE; + disable_tunnel(server); // ignore failure + server->tunnel_enabled = SDL_FALSE; return server->device_socket; } @@ -155,9 +234,9 @@ void server_stop(struct server *server) { cmd_simple_wait(server->process, NULL); // ignore exit code LOGD("Server terminated"); - if (server->adb_reverse_enabled) { + if (server->tunnel_enabled) { // ignore failure - disable_tunnel(server->serial); + disable_tunnel(server); } if (server->server_copied_to_device) { diff --git a/app/src/server.h b/app/src/server.h index 63547343..fcd44b8d 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -7,9 +7,11 @@ struct server { const char *serial; process_t process; - socket_t server_socket; + socket_t server_socket; // only used if !tunnel_forward socket_t device_socket; - SDL_bool adb_reverse_enabled; + Uint16 local_port; + SDL_bool tunnel_enabled; + SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse" SDL_bool server_copied_to_device; }; @@ -18,7 +20,9 @@ struct server { .process = PROCESS_NONE, \ .server_socket = INVALID_SOCKET, \ .device_socket = INVALID_SOCKET, \ - .adb_reverse_enabled = SDL_FALSE, \ + .local_port = 0, \ + .tunnel_enabled = SDL_FALSE, \ + .tunnel_forward = SDL_FALSE, \ .server_copied_to_device = SDL_FALSE, \ } diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 203ecc9b..24735f78 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy; +import android.net.LocalServerSocket; import android.net.LocalSocket; import android.net.LocalSocketAddress; @@ -33,8 +34,20 @@ public final class DesktopConnection implements Closeable { return localSocket; } - public static DesktopConnection open(Device device) throws IOException { - LocalSocket socket = connect(SOCKET_NAME); + private static LocalSocket listenAndAccept(String abstractName) throws IOException { + LocalServerSocket localServerSocket = new LocalServerSocket(abstractName); + return localServerSocket.accept(); + } + + public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException { + LocalSocket socket; + if (tunnelForward) { + socket = listenAndAccept(SOCKET_NAME); + // send one byte so the client may read() to detect a connection error + socket.getOutputStream().write(0); + } else { + socket = connect(SOCKET_NAME); + } DesktopConnection connection = new DesktopConnection(socket); Size videoSize = device.getScreenInfo().getVideoSize(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 6ab2f694..332463e6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -3,6 +3,7 @@ package com.genymobile.scrcpy; public class Options { private int maxSize; private int bitRate; + private boolean tunnelForward; public int getMaxSize() { return maxSize; @@ -19,4 +20,12 @@ public class Options { public void setBitRate(int bitRate) { this.bitRate = bitRate; } + + public boolean isTunnelForward() { + return tunnelForward; + } + + public void setTunnelForward(boolean tunnelForward) { + this.tunnelForward = tunnelForward; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 5df436d1..d7294ae0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -10,7 +10,8 @@ public final class Server { private static void scrcpy(Options options) throws IOException { final Device device = new Device(options); - try (DesktopConnection connection = DesktopConnection.open(device)) { + boolean tunnelForward = options.isTunnelForward(); + try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate()); // asynchronous @@ -55,6 +56,13 @@ public final class Server { int bitRate = Integer.parseInt(args[1]); options.setBitRate(bitRate); + if (args.length < 3) { + return options; + } + // use "adb forward" instead of "adb tunnel"? (so the server must listen) + boolean tunnelForward = Boolean.parseBoolean(args[2]); + options.setTunnelForward(tunnelForward); + return options; } From b858204786bbcf9700e0bed2dc5b4ce7f07b2e6b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 13 Mar 2018 08:32:48 +0100 Subject: [PATCH 09/12] Remove black borders on double-click Resize the window to fit the device screen on click on black borders (same as Ctrl+x). Suggested-by: Guillaume Roche --- README.md | 31 ++++++++++++++++--------------- app/src/inputmanager.c | 10 ++++++++++ app/src/main.c | 1 + 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 337f12e1..195193d3 100644 --- a/README.md +++ b/README.md @@ -188,22 +188,23 @@ To run without installing: ## Shortcuts - | Action | Shortcut | - | -------------------------------------- |:---------------------------- | - | switch fullscreen mode | `Ctrl`+`f` | - | resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | - | resize window to remove black borders | `Ctrl`+`x` | - | click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | - | click on `BACK` | `Ctrl`+`b` \| _Right-click¹_ | - | click on `APP_SWITCH` | `Ctrl`+`m` | - | click on `VOLUME_UP` | `Ctrl`+`+` | - | click on `VOLUME_DOWN` | `Ctrl`+`-` | - | click on `POWER` | `Ctrl`+`p` | - | turn screen on | _Right-click¹_ | - | paste computer clipboard to device | `Ctrl`+`v` | - | enable/disable FPS counter (on stdout) | `Ctrl`+`i` | + | Action | Shortcut | + | -------------------------------------- |:---------------------------- | + | switch fullscreen mode | `Ctrl`+`f` | + | resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | + | resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | + | click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | + | click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | + | click on `APP_SWITCH` | `Ctrl`+`m` | + | click on `VOLUME_UP` | `Ctrl`+`+` | + | click on `VOLUME_DOWN` | `Ctrl`+`-` | + | click on `POWER` | `Ctrl`+`p` | + | turn screen on | _Right-click¹_ | + | paste computer clipboard to device | `Ctrl`+`v` | + | enable/disable FPS counter (on stdout) | `Ctrl`+`i` | -_¹Right-click turns the screen on if it was off, presses BACK otherwise._ +_¹Double-click on black borders to remove them._ +_²Right-click turns the screen on if it was off, presses BACK otherwise._ ## Why _scrcpy_? diff --git a/app/src/inputmanager.c b/app/src/inputmanager.c index 6a9243e2..4a1140d4 100644 --- a/app/src/inputmanager.c +++ b/app/src/inputmanager.c @@ -235,6 +235,16 @@ void input_manager_process_mouse_button(struct input_manager *input_manager, action_home(input_manager->controller); return; } + // double-click on black borders resize to fit the device screen + if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { + SDL_bool outside_device_screen = + event->x < 0 || event->x >= input_manager->screen->frame_size.width || + event->y < 0 || event->y >= input_manager->screen->frame_size.height; + if (outside_device_screen) { + screen_resize_to_fit(input_manager->screen); + } + return; + } }; struct control_event control_event; if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) { diff --git a/app/src/main.c b/app/src/main.c index 287c9d7d..b56b78a6 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -57,6 +57,7 @@ static void usage(const char *arg0) { " resize window to 1:1 (pixel-perfect)\n" "\n" " Ctrl+x\n" + " Double-click on black borders\n" " resize window to remove black borders\n" "\n" " Ctrl+h\n" From 0b1e59186f1fb75d74f2929f636128958b067ab4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 13 Mar 2018 21:26:05 +0100 Subject: [PATCH 10/12] Workaround continuous resizing on Windows/MacOS On Windows and MacOS, resizing blocks the event loop, so resizing events are not triggered: - - As a workaround, register an event watcher to render the screen from another thread. Since the whole event loop is blocked during resizing, the screen content is not refreshed (on Windows and MacOS) until resizing ends. --- app/src/scrcpy.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 66badb06..cc4528df 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -35,7 +35,29 @@ static struct input_manager input_manager = { .screen = &screen, }; +#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. +// +// +// +static int event_watcher(void* data, SDL_Event* event) { + if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) { + // called from another thread, not very safe, but it's a workaround! + screen_render(&screen); + } + return 0; +} +#endif + static void event_loop(void) { +#ifdef CONTINUOUS_RESIZING_WORKAROUND + SDL_AddEventWatch(event_watcher, screen.window); +#endif SDL_Event event; while (SDL_WaitEvent(&event)) { switch (event.type) { From c530d95881988d6d48d431e335c50286a53181d9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 14 Mar 2018 09:28:25 +0100 Subject: [PATCH 11/12] Immediately close the server socket on the device In "adb forward" mode, close the server socket as soon as the client is connected. Even if unlikely to be useful, it allows to run several instances of scrcpy also in "adb forward" mode. --- .../main/java/com/genymobile/scrcpy/DesktopConnection.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 24735f78..d5740c15 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -36,7 +36,11 @@ public final class DesktopConnection implements Closeable { private static LocalSocket listenAndAccept(String abstractName) throws IOException { LocalServerSocket localServerSocket = new LocalServerSocket(abstractName); - return localServerSocket.accept(); + try { + return localServerSocket.accept(); + } finally { + localServerSocket.close(); + } } public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException { From 8a3c6a3ae718e53aafc98f97014980a06307328d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 14 Mar 2018 09:31:16 +0100 Subject: [PATCH 12/12] Remove useless argument Do not pass any data to the event watcher, it is unused. --- app/src/scrcpy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index cc4528df..4dd480b6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -45,7 +45,7 @@ static struct input_manager input_manager = { // // // -static int event_watcher(void* data, SDL_Event* event) { +static int event_watcher(void *data, SDL_Event *event) { if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) { // called from another thread, not very safe, but it's a workaround! screen_render(&screen); @@ -56,7 +56,7 @@ static int event_watcher(void* data, SDL_Event* event) { static void event_loop(void) { #ifdef CONTINUOUS_RESIZING_WORKAROUND - SDL_AddEventWatch(event_watcher, screen.window); + SDL_AddEventWatch(event_watcher, NULL); #endif SDL_Event event; while (SDL_WaitEvent(&event)) {