The "screen control" handled user input, which happened to be only
used to control the screen.
The controller and screen were passed to every function. Instead, group
them in a struct input_manager.
The purpose is to add a new shortcut to enable/disable FPS counter. This
feature is not related to "screen control", and will require access to
the "frames" instance.
The device serial was provided as a positional argument:
scrcpy 0123456789abcdef
Instead, expose it as an optional argument, -s or --serial:
scrcpy -s 0123456789abcdef
This avoids inconsistency between platforms when the positional
argument is passed before the options (which is undefined).
The client was built with Meson, the server with Gradle, and were run by
a Makefile.
Add a Meson script for the server (which delegates to Gradle), and a
parent script to build and install both the client and the server to the
system, typically with:
meson --buildtype release build
cd build
ninja
sudo ninja install
In addition, use a separate Makefile to build a "portable" version of
the application (where the client expects the server to be in the
current directory). Typically:
make release-portable
cd dist/scrcpy
./scrcpy
This is especially useful for Windows builds, which are not "installed".
The current meson version is able to generate a config.h from a
configuration data object without any template.
However, older versions of meson require a template, so provide it for
compatibility.
The SDL clean up does not crash anymore on exit, probably since the
memory corruption caused by calling SDLNet_TCP_Close() too early has
been resolved.
On startup, the client has to:
1. listen on a port
2. push and start the server to the device
3. wait for the server to connect (accept)
4. read device name and size
5. initialize SDL
6. initialize the window and renderer
7. show the window
From the execution of the app_process command to start the server on the
device, to the execution of the java main method, it takes ~800ms. As a
consequence, step 3 also takes ~800ms on the client.
Once complete, the client initializes SDL, which takes ~500ms.
These two expensive actions are executed sequentially:
HOST DEVICE
listen on port | |
push/start the server |----------------->|| app_process loads the jar
accept the connection . ^ ||
. | ||
. | WASTE ||
. | OF ||
. | TIME ||
. | ||
. | ||
. v X execution of our java main
connection accepted |<-----------------| connect to the host
init SDL || |
|| ,----------------| send frames
|| |,---------------|
|| ||,--------------|
|| |||,-------------|
|| ||||,------------|
init window/renderer | |||||,-----------|
display frames |<++++++-----------|
(many frames skipped)
The rationale for step 3 occuring before step 5 is that initializing
SDL replaces the SIGTERM handler to receive the event in the event loop,
so pressing Ctrl+C during step 5 would not work (since it blocks the
event loop).
But this is not so important; let's parallelize the SDL initialization
with the app_process execution (we'll just add a timeout to the
connection):
HOST DEVICE
listen on port | |
push/start the server |----------------->||app_process loads the jar
init SDL || ||
|| ||
|| ||
|| ||
|| ||
|| ||
accept the connection . ||
. X execution of our java main
connection accepted |<-----------------| connect to the host
init window/renderer | |
display frames |<-----------------| send frames
|<-----------------|
In addition, show the window only once the first frame is available to
avoid flickering (opening a black window for 100~200ms).
Note: the window and renderer are initialized after the connection is
accepted because they use the device information received from the
device.
SDLNet_TCP_Close() not only closes, but also release the resources.
Therefore, we must not close the socket if another thread attempts to
read it.
For that purpose, move socket closing from server_stop() to
server_destroy().
Replace screen_update() by a higher-level screen_update_frame() handling
the whole frame updating, so that scrcpy.c just call it without managing
implementation details.
Do not wait 100ms anymore to let the server print any exception: we
justly want to ignore them.
Moreover, there is no nanosleep() on Windows, so this solve another
problem.
When the video stream socket is closed and read_packey() returns -1,
av_read_frame() still returns 0.
To detect EOF, check the flag eof_reached in the AVIOContext.
This avoids garbage errors on closing.
Expose frames_offer_decoded_frame() and frames_consume_rendered_frame()
so that callers are not exposed to frame swapping (between the decoding
and rendering frames) details.
The skip_frames flag was a non-configurable runtime flag. Since it is
not exposed to the user, there is no need for a (possible) runtime cost.
For testing purpose, we still want it to be configurable, so make it a
compilation flag.
We encounter some problems with SDL2_image on MSYS2 (Windows), so
implement our own XPM parsing which does not depend on SDL_image.
The input XPM is considered safe (it's in our source repo), so do not
check XPM format errors. This implies that read_xpm() is not safe to
call on any unsafe input.
Although less straightforward, use SDL_CreateRGBSurfaceFrom() instead of
SDL_CreateRGBSurfaceWithFormatFrom() because it is available with SDL
versions older than 2.0.5.
The purpose of handle_shortcut() was to group all actions together,
whether they are initiated from a text input event or a keycode event.
However, it did not handle the case where it was initiated from a mouse
event (a right-click must turn the screen on), since the action was
identified by the shortcut char.
Instead, expose one function per action, to be called directly from
where the event is handled.
The right-click is almost useless on Android, so use it to turn the
screen on.
Add a new control event type (command) to request the server to turn the
screen on.
The server is built as an APK to simplify the build, but in fact this is
a simple jar (it is not even signed).
In order to avoid confusion, rename it to .jar, so that users do not try
to "adb install" it.
Also rename it from "scrcpy" to "scrcpy-server" to distinguish from the
client-side.
The server path may be customized using SCRCPY_APK. If its basename is
different from "scrcpy.apk", it will be pushed with a different name,
so the execution would fail.
Therefore, force the push target filename.
On rotation, scrcpy resize the window to match the new rotation.
However, in fullscreen mode, setting the window size does not change the
windowed size on X11, so the behavior is incorrect.
To avoid the problem, apply the resize only after fullscreen is
disabled.
For readability, indent "case" in switch blocks.
Replace:
switch (x) {
case 1:
// ...
case 2:
// ...
case 3: { // a local scope block
int i = 42;
// ...
}
}
By:
switch (x) {
case 1:
// ...
case 2:
// ...
case 3: { // a local scope block
int i = 42;
// ...
}
}
Accept a parameter to limit the video size.
For instance, with "-m 960", the great side of the video will be scaled
down to 960 (if necessary), while the other side will be scaled down so
that the aspect ratio is preserved. Both dimensions must be a multiple
of 8, so black bands might be added, and the mouse positions must be
computed accordingly.
The video screen size on the client may differ from the real device
screen size (e.g. the video stream may be scaled down). As a
consequence, mouse events must be scaled to match the real device
coordinates.
For this purpose, make the client send the video screen size along with
the absolute pointer location, and the server scale the location to
match the real device size before injecting mouse events.
Ctrl, Alt, Shift and Meta should not be transmitted to the Android
device: they may generate unwanted events. For instance, resizing the
window using Alt+click will generate an Alt event which may open a menu
on the device.
All keycodes that generate a text input must also be excluded, to avoid
the text input to be written twice.
To control the device from the computer:
- retrieve mouse and keyboard SDL events;
- convert them to Android events;
- serialize them;
- send them on the same socket used by the video stream (but in the
opposite direction);
- deserialize the events on the Android side;
- inject them using the InputManager.
exit() should not be called from within a child process, since it would
call functions registered with atexit(), and flush stdio streams. Use
_exit() instead.