Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
d113b6ef61 buffered_reader
Not merged: it has not visible impact on CPU usage.
2019-07-31 15:56:31 +02:00
72 changed files with 503 additions and 1045 deletions

View File

@@ -43,7 +43,7 @@ Install the required packages from your package manager.
sudo apt install ffmpeg libsdl2-2.0-0 sudo apt install ffmpeg libsdl2-2.0-0
# client build dependencies # client build dependencies
sudo apt install gcc git pkg-config meson ninja-build \ sudo apt install make gcc git pkg-config meson ninja-build \
libavcodec-dev libavformat-dev libavutil-dev \ libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev libsdl2-dev
@@ -234,10 +234,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server ## Prebuilt server
- [`scrcpy-server-v1.10.jar`][direct-scrcpy-server] - [`scrcpy-server-v1.9.jar`][direct-scrcpy-server]
_(SHA-256: cbeb1a4e046f1392c1dc73c3ccffd7f86dec4636b505556ea20929687a119390)_ _(SHA-256: ad7e539f100e48259b646f26982bc63e0a60a81ac87ae135e242855bef69bd1a)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.10/scrcpy-server-v1.10.jar [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.9/scrcpy-server-v1.9.jar
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:

44
FAQ.md
View File

@@ -1,5 +1,10 @@
# Frequently Asked Questions # Frequently Asked Questions
## Common issues
The application is very young, it is not unlikely that you encounter problems
with it.
Here are the common reported problems and their status. Here are the common reported problems and their status.
@@ -15,13 +20,9 @@ Windows may need some [drivers] to detect your device.
[drivers]: https://developer.android.com/studio/run/oem-usb.html [drivers]: https://developer.android.com/studio/run/oem-usb.html
### I can only mirror, I cannot interact with the device ### Mouse clicks do not work
On some devices, you may need to enable an option to allow [simulating input]. On some devices, you may need to enable an option to allow [simulating input].
In developer options, enable:
> **USB debugging (Security settings)**
> _Allow granting permissions and simulating input via USB debugging_
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 [simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
@@ -42,16 +43,6 @@ meson x --buildtype release -Dhidpi_support=false
However, the video will be displayed at lower resolution. However, the video will be displayed at lower resolution.
### The quality is low on HiDPI display
On Windows, you may need to configure the [scaling behavior].
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### KWin compositor crashes ### KWin compositor crashes
On Plasma Desktop, compositor is disabled while _scrcpy_ is running. On Plasma Desktop, compositor is disabled while _scrcpy_ is running.
@@ -59,26 +50,3 @@ On Plasma Desktop, compositor is disabled while _scrcpy_ is running.
As a workaround, [disable "Block compositing"][kwin]. As a workaround, [disable "Block compositing"][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613 [kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
### I get an error "Could not open video stream"
There may be many reasons. One common cause is that the hardware encoder of your
device is not able to encode at the given definition:
```
ERROR: Exception on thread Thread[main,5,main]
android.media.MediaCodec$CodecException: Error 0xfffffc0e
...
Exit due to uncaughtException in main thread:
ERROR: Could not open video stream
INFO: Initial texture: 1080x2336
```
Just try with a lower definition:
```
scrcpy -m 1920
scrcpy -m 1024
scrcpy -m 800
```

View File

@@ -100,38 +100,37 @@ dist-win32: build-server build-win32 build-win32-noconsole
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe" cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.1.4-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.3-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1.4-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.3-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1.4-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.3-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1.4-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.3-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1.4-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.10/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/SDL2-2.0.8/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64 build-win64-noconsole dist-win64: build-server build-win64 build-win64-noconsole
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe" cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.1.4-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1.4-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1.4-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1.4-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1.4-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.10/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/SDL2-2.0.8/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32 zip-win32: dist-win32
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ cd "$(DIST)"; \
zip -r "../$(WIN32_TARGET)" . zip -r "$(WIN32_TARGET)" "$(WIN32_TARGET_DIR)"
zip-win64: dist-win64 zip-win64: dist-win64
cd "$(DIST)/$(WIN64_TARGET_DIR)"; \ cd "$(DIST)"; \
zip -r "../$(WIN64_TARGET)" . zip -r "$(WIN64_TARGET)" "$(WIN64_TARGET_DIR)"
sums: sums:
cd "$(DIST)"; \ cd "$(DIST)"; \

View File

@@ -1,4 +1,4 @@
# scrcpy (v1.10) # scrcpy (v1.9)
This application provides display and control of Android devices connected on This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access. USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
@@ -6,17 +6,6 @@ It works on _GNU/Linux_, _Windows_ and _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
It focuses on:
- **lightness** (native, displays only the device screen)
- **performance** (30~60fps)
- **quality** (1920×1080 or above)
- **low latency** ([35~70ms][lowlatency])
- **low startup time** (~1 second to display the first image)
- **non-intrusiveness** (nothing is left installed on the device)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## Requirements ## Requirements
@@ -62,13 +51,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
For Windows, for simplicity, prebuilt archives with all the dependencies For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available: (including `adb`) are available:
- [`scrcpy-win32-v1.10.zip`][direct-win32] - [`scrcpy-win32-v1.9.zip`][direct-win32]
_(SHA-256: f98b400b3764404b33b212e9762dd6f1593ddb766c1480fc2609c94768e4a8e1)_ _(SHA-256: 3234f7fbcc26b9e399f50b5ca9ed085708954c87fda1b0dd32719d6e7dd861ef)_
- [`scrcpy-win64-v1.10.zip`][direct-win64] - [`scrcpy-win64-v1.9.zip`][direct-win64]
_(SHA-256: 95de34575d873c7e95dfcfb5e74d0f6af4f70b2a5bc6fde0f48d1a05480e3a44)_ _(SHA-256: 0088eca1811ea7c7ac350d636c8465b266e6c830bb268770ff88fddbb493077e)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.10/scrcpy-win32-v1.10.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.9/scrcpy-win32-v1.9.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.10/scrcpy-win64-v1.10.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.9/scrcpy-win64-v1.9.zip
You can also [build the app manually][BUILD]. You can also [build the app manually][BUILD].
@@ -326,26 +315,26 @@ Also see [issue #14].
## Shortcuts ## Shortcuts
| Action | Shortcut | Shortcut (macOS) | Action | Shortcut |
| -------------------------------------- |:----------------------------- |:----------------------------- | -------------------------------------- |:---------------------------- |
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f` | Switch fullscreen mode | `Ctrl`+`f` |
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g` | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` |
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_ | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ |
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ |
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_ | Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ |
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` | Click on `APP_SWITCH` | `Ctrl`+`s` |
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m` | Click on `MENU` | `Ctrl`+`m` |
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_ | Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ (`Cmd`+`↑` on macOS) |
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_ | Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on macOS) |
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p` | Click on `POWER` | `Ctrl`+`p` |
| Power on | _Right-click²_ | _Right-click²_ | Power on | _Right-click²_ |
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o` | Turn device screen off (keep mirroring)| `Ctrl`+`o` |
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n` | Expand notification panel | `Ctrl`+`n` |
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` | Collapse notification panel | `Ctrl`+`Shift`+`n` |
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c` | Copy device clipboard to computer | `Ctrl`+`c` |
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v` | Paste computer clipboard to device | `Ctrl`+`v` |
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` | Copy computer clipboard to device | `Ctrl`+`Shift`+`v` |
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i` | Enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
_¹Double-click on black borders to remove them._ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._

View File

@@ -1,12 +1,13 @@
src = [ src = [
'src/main.c', 'src/main.c',
'src/buffered_reader.c',
'src/command.c', 'src/command.c',
'src/control_msg.c', 'src/control_msg.c',
'src/controller.c', 'src/controller.c',
'src/convert.c',
'src/decoder.c', 'src/decoder.c',
'src/device.c', 'src/device.c',
'src/device_msg.c', 'src/device_msg.c',
'src/event_converter.c',
'src/file_handler.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/input_manager.c', 'src/input_manager.c',
@@ -150,9 +151,6 @@ tests = [
'tests/test_device_msg_deserialize.c', 'tests/test_device_msg_deserialize.c',
'src/device_msg.c' 'src/device_msg.c'
]], ]],
['test_queue', [
'tests/test_queue.c',
]],
['test_strutil', [ ['test_strutil', [
'tests/test_strutil.c', 'tests/test_strutil.c',
'src/str_util.c' 'src/str_util.c'

View File

@@ -4,8 +4,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
static inline void static inline void
buffer_write16be(uint8_t *buf, uint16_t value) { buffer_write16be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8; buf[0] = value >> 8;
@@ -20,12 +18,6 @@ buffer_write32be(uint8_t *buf, uint32_t value) {
buf[3] = value; buf[3] = value;
} }
static inline void
buffer_write64be(uint8_t *buf, uint64_t value) {
buffer_write32be(buf, value >> 32);
buffer_write32be(&buf[4], (uint32_t) value);
}
static inline uint16_t static inline uint16_t
buffer_read16be(const uint8_t *buf) { buffer_read16be(const uint8_t *buf) {
return (buf[0] << 8) | buf[1]; return (buf[0] << 8) | buf[1];

72
app/src/buffered_reader.c Normal file
View File

@@ -0,0 +1,72 @@
#include "buffered_reader.h"
#include <SDL2/SDL_assert.h>
#include "log.h"
bool
buffered_reader_init(struct buffered_reader *reader, socket_t socket,
size_t bufsize) {
reader->buf = SDL_malloc(bufsize);
if (!reader->buf) {
LOGC("Could not allocate buffer");
return false;
}
reader->socket = socket;
reader->bufsize = bufsize;
reader->offset = 0;
reader->len = 0;
return true;
}
void
buffered_reader_destroy(struct buffered_reader *reader) {
SDL_free(reader->buf);
}
static ssize_t
buffered_reader_fill(struct buffered_reader *reader) {
SDL_assert(!reader->len);
ssize_t r = net_recv(reader->socket, reader->buf, reader->bufsize);
if (r > 0) {
reader->offset = 0;
reader->len = r;
}
return r;
}
ssize_t
buffered_reader_recv(struct buffered_reader *reader, void *buf, size_t count) {
if (!reader->len) {
// read from the socket
ssize_t r = buffered_reader_fill(reader);
if (r <= 0) {
return r;
}
}
size_t r = count < reader->len ? count : reader->len;
memcpy(buf, reader->buf + reader->offset, r);
reader->offset += r;
reader->len -= r;
return r;
}
ssize_t
buffered_reader_recv_all(struct buffered_reader *reader, void *buf,
size_t count) {
size_t done = 0;
while (done < count) {
ssize_t r = buffered_reader_recv(reader, buf, count - done);
if (r <= 0) {
// if there was some data, return them immediately
return done ? done : r;
}
done += r;
buf += r;
}
return done;
}

29
app/src/buffered_reader.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef BUFFERED_READER_H
#define BUFFERED_READER_H
#include "common.h"
#include "net.h"
struct buffered_reader {
socket_t socket;
void *buf;
size_t bufsize;
size_t offset;
size_t len;
};
bool
buffered_reader_init(struct buffered_reader *reader, socket_t socket,
size_t bufsize);
void
buffered_reader_destroy(struct buffered_reader *reader);
ssize_t
buffered_reader_recv(struct buffered_reader *reader, void *buf, size_t count);
ssize_t
buffered_reader_recv_all(struct buffered_reader *reader, void *buf,
size_t count);
#endif

View File

@@ -5,10 +5,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
// To define a circular buffer type of 20 ints: // To define a circular buffer type of 20 ints:
// struct cbuf_int CBUF(int, 20); // typedef CBUF(int, 20) my_cbuf_t;
// //
// data has length CAP + 1 to distinguish empty vs full. // data has length CAP + 1 to distinguish empty vs full.
#define CBUF(TYPE, CAP) { \ #define CBUF(TYPE, CAP) { \
@@ -37,7 +35,7 @@
(PCBUF)->head = ((PCBUF)->head + 1) % cbuf_size_(PCBUF); \ (PCBUF)->head = ((PCBUF)->head + 1) % cbuf_size_(PCBUF); \
} \ } \
ok; \ ok; \
}) }) \
#define cbuf_take(PCBUF, PITEM) \ #define cbuf_take(PCBUF, PITEM) \
({ \ ({ \

View File

@@ -5,7 +5,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "config.h"
#include "common.h" #include "common.h"
#include "log.h" #include "log.h"
#include "str_util.h" #include "str_util.h"

View File

@@ -33,8 +33,6 @@
#endif #endif
#include "config.h"
# define NO_EXIT_CODE -1 # define NO_EXIT_CODE -1
enum process_result { enum process_result {

View File

@@ -3,8 +3,6 @@
#include <stdint.h> #include <stdint.h>
#include "config.h"
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)

View File

@@ -1,9 +1,7 @@
#include "control_msg.h" #include "control_msg.h"
#include <string.h> #include <string.h>
#include <SDL2/SDL_assert.h>
#include "config.h"
#include "buffer_util.h" #include "buffer_util.h"
#include "log.h" #include "log.h"
#include "str_util.h" #include "str_util.h"
@@ -25,16 +23,6 @@ write_string(const char *utf8, size_t max_len, unsigned char *buf) {
return 2 + len; return 2 + len;
} }
static uint16_t
to_fixed_point_16(float f) {
SDL_assert(f >= 0.0f && f <= 1.0f);
uint32_t u = f * 0x1p16f; // 2^16
if (u >= 0xffff) {
u = 0xffff;
}
return (uint16_t) u;
}
size_t size_t
control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buf[0] = msg->type; buf[0] = msg->type;
@@ -54,14 +42,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buffer_write32be(&buf[2], msg->inject_mouse_event.buttons); buffer_write32be(&buf[2], msg->inject_mouse_event.buttons);
write_position(&buf[6], &msg->inject_mouse_event.position); write_position(&buf[6], &msg->inject_mouse_event.position);
return 18; return 18;
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
buf[1] = msg->inject_touch_event.action;
buffer_write64be(&buf[2], msg->inject_touch_event.finger_id);
write_position(&buf[10], &msg->inject_touch_event.position);
uint16_t pressure =
to_fixed_point_16(msg->inject_touch_event.pressure);
buffer_write16be(&buf[22], pressure);
return 24;
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
write_position(&buf[1], &msg->inject_scroll_event.position); write_position(&buf[1], &msg->inject_scroll_event.position);
buffer_write32be(&buf[13], buffer_write32be(&buf[13],

View File

@@ -5,7 +5,6 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
#include "android/input.h" #include "android/input.h"
#include "android/keycodes.h" #include "android/keycodes.h"
#include "common.h" #include "common.h"
@@ -19,7 +18,6 @@ enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE, CONTROL_MSG_TYPE_INJECT_KEYCODE,
CONTROL_MSG_TYPE_INJECT_TEXT, CONTROL_MSG_TYPE_INJECT_TEXT,
CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT, CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
@@ -51,12 +49,6 @@ struct control_msg {
enum android_motionevent_buttons buttons; enum android_motionevent_buttons buttons;
struct position position; struct position position;
} inject_mouse_event; } inject_mouse_event;
struct {
enum android_motionevent_action action;
uint64_t finger_id;
struct position position;
float pressure;
} inject_touch_event;
struct { struct {
struct position position; struct position position;
int32_t hscroll; int32_t hscroll;

View File

@@ -5,7 +5,6 @@
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "config.h"
#include "cbuf.h" #include "cbuf.h"
#include "control_msg.h" #include "control_msg.h"
#include "net.h" #include "net.h"

View File

@@ -1,6 +1,4 @@
#include "event_converter.h" #include "convert.h"
#include "config.h"
#define MAP(FROM, TO) case FROM: *to = TO; return true #define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false #define FAIL default: return false
@@ -33,6 +31,7 @@ autocomplete_metastate(enum android_metastate metastate) {
return metastate; return metastate;
} }
static enum android_metastate static enum android_metastate
convert_meta_state(SDL_Keymod mod) { convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0; enum android_metastate metastate = 0;
@@ -159,7 +158,8 @@ convert_mouse_buttons(uint32_t state) {
} }
bool bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to) { input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
@@ -177,8 +177,9 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to) {
} }
bool bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size, mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct control_msg *to) { struct size screen_size,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT;
if (!convert_mouse_action(from->type, &to->inject_mouse_event.action)) { if (!convert_mouse_action(from->type, &to->inject_mouse_event.action)) {
@@ -195,8 +196,9 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
} }
bool bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size, mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct control_msg *to) { struct size screen_size,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT;
to->inject_mouse_event.action = AMOTION_EVENT_ACTION_MOVE; to->inject_mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_mouse_event.buttons = convert_mouse_buttons(from->state); to->inject_mouse_event.buttons = convert_mouse_buttons(from->state);
@@ -207,37 +209,10 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size,
return true; return true;
} }
static bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
FAIL;
}
}
bool bool
convert_touch(const SDL_TouchFingerEvent *from, struct size screen_size, mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
struct control_msg *to) { struct position position,
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; struct control_msg *to) {
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.finger_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen_size;
// SDL touch event coordinates are normalized in the range [0; 1]
to->inject_touch_event.position.point.x = from->x * screen_size.width;
to->inject_touch_event.position.point.y = from->y * screen_size.height;
to->inject_touch_event.pressure = from->pressure;
return true;
}
bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct position position,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
to->inject_scroll_event.position = position; to->inject_scroll_event.position = position;

41
app/src/convert.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef CONVERT_H
#define CONVERT_H
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "control_msg.h"
struct complete_mouse_motion_event {
SDL_MouseMotionEvent *mouse_motion_event;
struct size screen_size;
};
struct complete_mouse_wheel_event {
SDL_MouseWheelEvent *mouse_wheel_event;
struct point position;
};
bool
input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
struct control_msg *to);
bool
mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct size screen_size,
struct control_msg *to);
// the video size may be different from the real device size, so we need the
// size to which the absolute position apply, to scale it accordingly
bool
mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct size screen_size,
struct control_msg *to);
// on Android, a scroll event requires the current mouse position
bool
mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
struct position position,
struct control_msg *to);
#endif

View File

@@ -8,8 +8,8 @@
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#include "compat.h" #include "compat.h"
#include "config.h"
#include "buffer_util.h" #include "buffer_util.h"
#include "events.h" #include "events.h"
#include "lock_util.h" #include "lock_util.h"

View File

@@ -4,8 +4,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "config.h"
struct video_buffer; struct video_buffer;
struct decoder { struct decoder {

View File

@@ -1,6 +1,4 @@
#include "device.h" #include "device.h"
#include "config.h"
#include "log.h" #include "log.h"
bool bool

View File

@@ -3,7 +3,6 @@
#include <stdbool.h> #include <stdbool.h>
#include "config.h"
#include "common.h" #include "common.h"
#include "net.h" #include "net.h"

View File

@@ -3,7 +3,6 @@
#include <string.h> #include <string.h>
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include "config.h"
#include "buffer_util.h" #include "buffer_util.h"
#include "log.h" #include "log.h"

View File

@@ -5,8 +5,6 @@
#include <stdint.h> #include <stdint.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093 #define DEVICE_MSG_TEXT_MAX_LENGTH 4093
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH) #define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)

View File

@@ -1,42 +0,0 @@
#ifndef CONVERT_H
#define CONVERT_H
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "config.h"
#include "control_msg.h"
struct complete_mouse_motion_event {
SDL_MouseMotionEvent *mouse_motion_event;
struct size screen_size;
};
struct complete_mouse_wheel_event {
SDL_MouseWheelEvent *mouse_wheel_event;
struct point position;
};
bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to);
bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
struct control_msg *to);
// the video size may be different from the real device size, so we need the
// size to which the absolute position apply, to scale it accordingly
bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size,
struct control_msg *to);
bool
convert_touch(const SDL_TouchFingerEvent *from, struct size screen_size,
struct control_msg *to);
// on Android, a scroll event requires the current mouse position
bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct position position,
struct control_msg *to);
#endif

View File

@@ -5,7 +5,6 @@
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "config.h"
#include "cbuf.h" #include "cbuf.h"
#include "command.h" #include "command.h"

View File

@@ -3,7 +3,6 @@
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include "config.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"

View File

@@ -7,8 +7,6 @@
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "config.h"
struct fps_counter { struct fps_counter {
SDL_Thread *thread; SDL_Thread *thread;
SDL_mutex *mutex; SDL_mutex *mutex;

View File

@@ -1,9 +1,7 @@
#include "input_manager.h" #include "input_manager.h"
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include "convert.h"
#include "config.h"
#include "event_converter.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
@@ -104,7 +102,7 @@ press_back_or_turn_screen_on(struct controller *controller) {
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'"); LOGW("Could not request 'turn screen on'");
} }
} }
@@ -244,27 +242,16 @@ input_manager_process_key(struct input_manager *input_manager,
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT); bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI); bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
// use Cmd on macOS, Ctrl on other platforms
#ifdef __APPLE__
bool cmd = !ctrl && meta;
#else
if (meta) {
// no shortcuts involve Meta on platforms other than macOS, and it must
// not be forwarded to the device
return;
}
bool cmd = ctrl; // && !meta, already guaranteed
#endif
if (alt) { if (alt) {
// no shortcuts involve Alt, and it must not be forwarded to the device // no shortcut involves Alt or Meta, and they should not be forwarded
// to the device
return; return;
} }
struct controller *controller = input_manager->controller; struct controller *controller = input_manager->controller;
// capture all Ctrl events // capture all Ctrl events
if (ctrl || cmd) { if (ctrl | meta) {
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN; bool down = event->type == SDL_KEYDOWN;
int action = down ? ACTION_DOWN : ACTION_UP; int action = down ? ACTION_DOWN : ACTION_UP;
@@ -272,59 +259,63 @@ input_manager_process_key(struct input_manager *input_manager,
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT); bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
switch (keycode) { switch (keycode) {
case SDLK_h: case SDLK_h:
// Ctrl+h on all platform, since Cmd+h is already captured by
// the system on macOS to hide the window
if (control && ctrl && !meta && !shift && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_home(controller, action); action_home(controller, action);
} }
return; return;
case SDLK_b: // fall-through case SDLK_b: // fall-through
case SDLK_BACKSPACE: case SDLK_BACKSPACE:
if (control && cmd && !shift && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_back(controller, action); action_back(controller, action);
} }
return; return;
case SDLK_s: case SDLK_s:
if (control && cmd && !shift && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_app_switch(controller, action); action_app_switch(controller, action);
} }
return; return;
case SDLK_m: case SDLK_m:
// Ctrl+m on all platform, since Cmd+m is already captured by
// the system on macOS to minimize the window
if (control && ctrl && !meta && !shift && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_menu(controller, action); action_menu(controller, action);
} }
return; return;
case SDLK_p: case SDLK_p:
if (control && cmd && !shift && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_power(controller, action); action_power(controller, action);
} }
return; return;
case SDLK_o: case SDLK_o:
if (control && cmd && !shift && down) { if (control && ctrl && !shift && !meta && down) {
set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF); set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF);
} }
return; return;
case SDLK_DOWN: case SDLK_DOWN:
if (control && cmd && !shift) { #ifdef __APPLE__
if (control && !ctrl && meta && !shift) {
#else
if (control && ctrl && !meta && !shift) {
#endif
// forward repeated events // forward repeated events
action_volume_down(controller, action); action_volume_down(controller, action);
} }
return; return;
case SDLK_UP: case SDLK_UP:
if (control && cmd && !shift) { #ifdef __APPLE__
if (control && !ctrl && meta && !shift) {
#else
if (control && ctrl && !meta && !shift) {
#endif
// forward repeated events // forward repeated events
action_volume_up(controller, action); action_volume_up(controller, action);
} }
return; return;
case SDLK_c: case SDLK_c:
if (control && cmd && !shift && !repeat && down) { if (control && ctrl && !meta && !shift && !repeat && down) {
request_device_clipboard(controller); request_device_clipboard(controller);
} }
return; return;
case SDLK_v: case SDLK_v:
if (control && cmd && !repeat && down) { if (control && ctrl && !meta && !repeat && down) {
if (shift) { if (shift) {
// store the text in the device clipboard // store the text in the device clipboard
set_device_clipboard(controller); set_device_clipboard(controller);
@@ -335,29 +326,29 @@ input_manager_process_key(struct input_manager *input_manager,
} }
return; return;
case SDLK_f: case SDLK_f:
if (!shift && cmd && !repeat && down) { if (ctrl && !meta && !shift && !repeat && down) {
screen_switch_fullscreen(input_manager->screen); screen_switch_fullscreen(input_manager->screen);
} }
return; return;
case SDLK_x: case SDLK_x:
if (!shift && cmd && !repeat && down) { if (ctrl && !meta && !shift && !repeat && down) {
screen_resize_to_fit(input_manager->screen); screen_resize_to_fit(input_manager->screen);
} }
return; return;
case SDLK_g: case SDLK_g:
if (!shift && cmd && !repeat && down) { if (ctrl && !meta && !shift && !repeat && down) {
screen_resize_to_pixel_perfect(input_manager->screen); screen_resize_to_pixel_perfect(input_manager->screen);
} }
return; return;
case SDLK_i: case SDLK_i:
if (!shift && cmd && !repeat && down) { if (ctrl && !meta && !shift && !repeat && down) {
struct fps_counter *fps_counter = struct fps_counter *fps_counter =
input_manager->video_buffer->fps_counter; input_manager->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter); switch_fps_counter_state(fps_counter);
} }
return; return;
case SDLK_n: case SDLK_n:
if (control && cmd && !repeat && down) { if (control && ctrl && !meta && !repeat && down) {
if (shift) { if (shift) {
collapse_notification_panel(controller); collapse_notification_panel(controller);
} else { } else {
@@ -375,7 +366,7 @@ input_manager_process_key(struct input_manager *input_manager,
} }
struct control_msg msg; struct control_msg msg;
if (convert_input_key(event, &msg)) { if (input_key_from_sdl_to_android(event, &msg)) {
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject keycode'"); LOGW("Could not request 'inject keycode'");
} }
@@ -389,29 +380,16 @@ input_manager_process_mouse_motion(struct input_manager *input_manager,
// do not send motion events when no button is pressed // do not send motion events when no button is pressed
return; return;
} }
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
return;
}
struct control_msg msg; struct control_msg msg;
if (convert_mouse_motion(event, input_manager->screen->frame_size, &msg)) { if (mouse_motion_from_sdl_to_android(event,
input_manager->screen->frame_size,
&msg)) {
if (!controller_push_msg(input_manager->controller, &msg)) { if (!controller_push_msg(input_manager->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'"); LOGW("Could not request 'inject mouse motion event'");
} }
} }
} }
void
input_manager_process_touch(struct input_manager *input_manager,
const SDL_TouchFingerEvent *event) {
struct control_msg msg;
if (convert_touch(event, input_manager->screen->frame_size, &msg)) {
if (!controller_push_msg(input_manager->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
}
static bool static bool
is_outside_device_screen(struct input_manager *input_manager, int x, int y) is_outside_device_screen(struct input_manager *input_manager, int x, int y)
{ {
@@ -449,7 +427,9 @@ input_manager_process_mouse_button(struct input_manager *input_manager,
} }
struct control_msg msg; struct control_msg msg;
if (convert_mouse_button(event, input_manager->screen->frame_size, &msg)) { if (mouse_button_from_sdl_to_android(event,
input_manager->screen->frame_size,
&msg)) {
if (!controller_push_msg(input_manager->controller, &msg)) { if (!controller_push_msg(input_manager->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'"); LOGW("Could not request 'inject mouse button event'");
} }
@@ -464,7 +444,7 @@ input_manager_process_mouse_wheel(struct input_manager *input_manager,
.point = get_mouse_point(input_manager->screen), .point = get_mouse_point(input_manager->screen),
}; };
struct control_msg msg; struct control_msg msg;
if (convert_mouse_wheel(event, position, &msg)) { if (mouse_wheel_from_sdl_to_android(event, position, &msg)) {
if (!controller_push_msg(input_manager->controller, &msg)) { if (!controller_push_msg(input_manager->controller, &msg)) {
LOGW("Could not request 'inject mouse wheel event'"); LOGW("Could not request 'inject mouse wheel event'");
} }

View File

@@ -3,7 +3,6 @@
#include <stdbool.h> #include <stdbool.h>
#include "config.h"
#include "common.h" #include "common.h"
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
@@ -29,10 +28,6 @@ void
input_manager_process_mouse_motion(struct input_manager *input_manager, input_manager_process_mouse_motion(struct input_manager *input_manager,
const SDL_MouseMotionEvent *event); const SDL_MouseMotionEvent *event);
void
input_manager_process_touch(struct input_manager *input_manager,
const SDL_TouchFingerEvent *event);
void void
input_manager_process_mouse_button(struct input_manager *input_manager, input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event, const SDL_MouseButtonEvent *event,

View File

@@ -4,7 +4,6 @@
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include "config.h"
#include "log.h" #include "log.h"
static inline void static inline void

View File

@@ -8,8 +8,8 @@
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
#include "compat.h" #include "compat.h"
#include "config.h"
#include "log.h" #include "log.h"
#include "recorder.h" #include "recorder.h"
@@ -35,11 +35,6 @@ struct args {
}; };
static void usage(const char *arg0) { static void usage(const char *arg0) {
#ifdef __APPLE__
# define CTRL_OR_CMD "Cmd"
#else
# define CTRL_OR_CMD "Ctrl"
#endif
fprintf(stderr, fprintf(stderr,
"Usage: %s [options]\n" "Usage: %s [options]\n"
"\n" "\n"
@@ -120,13 +115,13 @@ static void usage(const char *arg0) {
"\n" "\n"
"Shortcuts:\n" "Shortcuts:\n"
"\n" "\n"
" " CTRL_OR_CMD "+f\n" " Ctrl+f\n"
" switch fullscreen mode\n" " switch fullscreen mode\n"
"\n" "\n"
" " CTRL_OR_CMD "+g\n" " Ctrl+g\n"
" resize window to 1:1 (pixel-perfect)\n" " resize window to 1:1 (pixel-perfect)\n"
"\n" "\n"
" " CTRL_OR_CMD "+x\n" " Ctrl+x\n"
" Double-click on black borders\n" " Double-click on black borders\n"
" resize window to remove black borders\n" " resize window to remove black borders\n"
"\n" "\n"
@@ -134,48 +129,48 @@ static void usage(const char *arg0) {
" Middle-click\n" " Middle-click\n"
" click on HOME\n" " click on HOME\n"
"\n" "\n"
" " CTRL_OR_CMD "+b\n" " Ctrl+b\n"
" " CTRL_OR_CMD "+Backspace\n" " Ctrl+Backspace\n"
" Right-click (when screen is on)\n" " Right-click (when screen is on)\n"
" click on BACK\n" " click on BACK\n"
"\n" "\n"
" " CTRL_OR_CMD "+s\n" " Ctrl+s\n"
" click on APP_SWITCH\n" " click on APP_SWITCH\n"
"\n" "\n"
" Ctrl+m\n" " Ctrl+m\n"
" click on MENU\n" " click on MENU\n"
"\n" "\n"
" " CTRL_OR_CMD "+Up\n" " Ctrl+Up\n"
" click on VOLUME_UP\n" " click on VOLUME_UP\n"
"\n" "\n"
" " CTRL_OR_CMD "+Down\n" " Ctrl+Down\n"
" click on VOLUME_DOWN\n" " click on VOLUME_DOWN\n"
"\n" "\n"
" " CTRL_OR_CMD "+p\n" " Ctrl+p\n"
" click on POWER (turn screen on/off)\n" " click on POWER (turn screen on/off)\n"
"\n" "\n"
" Right-click (when screen is off)\n" " Right-click (when screen is off)\n"
" power on\n" " power on\n"
"\n" "\n"
" " CTRL_OR_CMD "+o\n" " Ctrl+o\n"
" turn device screen off (keep mirroring)\n" " turn device screen off (keep mirroring)\n"
"\n" "\n"
" " CTRL_OR_CMD "+n\n" " Ctrl+n\n"
" expand notification panel\n" " expand notification panel\n"
"\n" "\n"
" " CTRL_OR_CMD "+Shift+n\n" " Ctrl+Shift+n\n"
" collapse notification panel\n" " collapse notification panel\n"
"\n" "\n"
" " CTRL_OR_CMD "+c\n" " Ctrl+c\n"
" copy device clipboard to computer\n" " copy device clipboard to computer\n"
"\n" "\n"
" " CTRL_OR_CMD "+v\n" " Ctrl+v\n"
" paste computer clipboard to device\n" " paste computer clipboard to device\n"
"\n" "\n"
" " CTRL_OR_CMD "+Shift+v\n" " Ctrl+Shift+v\n"
" copy computer clipboard to device\n" " copy computer clipboard to device\n"
"\n" "\n"
" " CTRL_OR_CMD "+i\n" " Ctrl+i\n"
" enable/disable FPS counter (print frames/second in logs)\n" " enable/disable FPS counter (print frames/second in logs)\n"
"\n" "\n"
" Drag & drop APK file\n" " Drag & drop APK file\n"
@@ -328,7 +323,7 @@ parse_args(struct args *args, int argc, char *argv[]) {
{"push-target", required_argument, NULL, {"push-target", required_argument, NULL,
OPT_PUSH_TARGET}, OPT_PUSH_TARGET},
{"record", required_argument, NULL, 'r'}, {"record", required_argument, NULL, 'r'},
{"record-format", required_argument, NULL, 'F'}, {"record-format", required_argument, NULL, 'f'},
{"render-expired-frames", no_argument, NULL, {"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES}, OPT_RENDER_EXPIRED_FRAMES},
{"serial", required_argument, NULL, 's'}, {"serial", required_argument, NULL, 's'},

View File

@@ -2,7 +2,6 @@
#include <stdio.h> #include <stdio.h>
#include "config.h"
#include "log.h" #include "log.h"
#ifdef __WINDOWS__ #ifdef __WINDOWS__

View File

@@ -17,8 +17,6 @@
typedef int socket_t; typedef int socket_t;
#endif #endif
#include "config.h"
bool bool
net_init(void); net_init(void);

View File

@@ -1,77 +0,0 @@
// generic intrusive FIFO queue
#ifndef QUEUE_H
#define QUEUE_H
#include <stdbool.h>
#include <stddef.h>
#include <SDL2/SDL_assert.h>
#include "config.h"
// To define a queue type of "struct foo":
// struct queue_foo QUEUE(struct foo);
#define QUEUE(TYPE) { \
TYPE *first; \
TYPE *last; \
}
#define queue_init(PQ) \
(void) ((PQ)->first = (PQ)->last = NULL)
#define queue_is_empty(PQ) \
!(PQ)->first
// NEXTFIELD is the field in the ITEM type used for intrusive linked-list
//
// For example:
// struct foo {
// int value;
// struct foo *next;
// };
//
// // define the type "struct my_queue"
// struct my_queue QUEUE(struct foo);
//
// struct my_queue queue;
// queue_init(&queue);
//
// struct foo v1 = { .value = 42 };
// struct foo v2 = { .value = 27 };
//
// queue_push(&queue, next, v1);
// queue_push(&queue, next, v2);
//
// struct foo *foo;
// queue_take(&queue, next, &foo);
// assert(foo->value == 42);
// queue_take(&queue, next, &foo);
// assert(foo->value == 27);
// assert(queue_is_empty(&queue));
//
// push a new item into the queue
#define queue_push(PQ, NEXTFIELD, ITEM) \
(void) ({ \
(ITEM)->NEXTFIELD = NULL; \
if (queue_is_empty(PQ)) { \
(PQ)->first = (PQ)->last = (ITEM); \
} else { \
(PQ)->last->NEXTFIELD = (ITEM); \
(PQ)->last = (ITEM); \
} \
})
// take the next item and remove it from the queue (the queue must not be empty)
// the result is stored in *(PITEM)
// (without typeof(), we could not store a local variable having the correct
// type so that we can "return" it)
#define queue_take(PQ, NEXTFIELD, PITEM) \
(void) ({ \
SDL_assert(!queue_is_empty(PQ)); \
*(PITEM) = (PQ)->first; \
(PQ)->first = (PQ)->first->NEXTFIELD; \
})
// no need to update (PQ)->last if the queue is left empty:
// (PQ)->last is undefined if !(PQ)->first anyway
#endif

View File

@@ -5,7 +5,6 @@
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "config.h"
#include "net.h" #include "net.h"
// receive events from the device // receive events from the device

View File

@@ -3,8 +3,8 @@
#include <libavutil/time.h> #include <libavutil/time.h>
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include "config.h"
#include "compat.h" #include "compat.h"
#include "config.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
@@ -33,15 +33,11 @@ record_packet_new(const AVPacket *packet) {
if (!rec) { if (!rec) {
return NULL; return NULL;
} }
// av_packet_ref() does not initialize all fields in old FFmpeg versions
// See <https://github.com/Genymobile/scrcpy/issues/707>
av_init_packet(&rec->packet);
if (av_packet_ref(&rec->packet, packet)) { if (av_packet_ref(&rec->packet, packet)) {
SDL_free(rec); SDL_free(rec);
return NULL; return NULL;
} }
rec->next = NULL;
return rec; return rec;
} }
@@ -52,12 +48,59 @@ record_packet_delete(struct record_packet *rec) {
} }
static void static void
recorder_queue_clear(struct recorder_queue *queue) { recorder_queue_init(struct recorder_queue *queue) {
while (!queue_is_empty(queue)) { queue->first = NULL;
struct record_packet *rec; // queue->last is undefined if queue->first == NULL
queue_take(queue, next, &rec); }
record_packet_delete(rec);
static inline bool
recorder_queue_is_empty(struct recorder_queue *queue) {
return !queue->first;
}
static bool
recorder_queue_push(struct recorder_queue *queue, const AVPacket *packet) {
struct record_packet *rec = record_packet_new(packet);
if (!rec) {
LOGC("Could not allocate record packet");
return false;
} }
rec->next = NULL;
if (recorder_queue_is_empty(queue)) {
queue->first = queue->last = rec;
} else {
// chain rec after the (current) last packet
queue->last->next = rec;
// the last packet is now rec
queue->last = rec;
}
return true;
}
static inline struct record_packet *
recorder_queue_take(struct recorder_queue *queue) {
SDL_assert(!recorder_queue_is_empty(queue));
struct record_packet *rec = queue->first;
SDL_assert(rec);
queue->first = rec->next;
// no need to update queue->last if the queue is left empty:
// queue->last is undefined if queue->first == NULL
return rec;
}
static void
recorder_queue_clear(struct recorder_queue *queue) {
struct record_packet *rec = queue->first;
while (rec) {
struct record_packet *current = rec;
rec = rec->next;
record_packet_delete(current);
}
queue->first = NULL;
} }
bool bool
@@ -86,13 +129,12 @@ recorder_init(struct recorder *recorder,
return false; return false;
} }
queue_init(&recorder->queue); recorder_queue_init(&recorder->queue);
recorder->stopped = false; recorder->stopped = false;
recorder->failed = false; recorder->failed = false;
recorder->format = format; recorder->format = format;
recorder->declared_frame_size = declared_frame_size; recorder->declared_frame_size = declared_frame_size;
recorder->header_written = false; recorder->header_written = false;
recorder->previous = NULL;
return true; return true;
} }
@@ -254,50 +296,25 @@ run_recorder(void *data) {
for (;;) { for (;;) {
mutex_lock(recorder->mutex); mutex_lock(recorder->mutex);
while (!recorder->stopped && queue_is_empty(&recorder->queue)) { while (!recorder->stopped &&
recorder_queue_is_empty(&recorder->queue)) {
cond_wait(recorder->queue_cond, recorder->mutex); cond_wait(recorder->queue_cond, recorder->mutex);
} }
// if stopped is set, continue to process the remaining events (to // if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping // finish the recording) before actually stopping
if (recorder->stopped && queue_is_empty(&recorder->queue)) { if (recorder->stopped && recorder_queue_is_empty(&recorder->queue)) {
mutex_unlock(recorder->mutex); mutex_unlock(recorder->mutex);
struct record_packet *last = recorder->previous;
if (last) {
// assign an arbitrary duration to the last packet
last->packet.duration = 100000;
bool ok = recorder_write(recorder, &last->packet);
if (!ok) {
// failing to write the last frame is not very serious, no
// future frame may depend on it, so the resulting file
// will still be valid
LOGW("Could not record last packet");
}
record_packet_delete(last);
}
break; break;
} }
struct record_packet *rec; struct record_packet *rec = recorder_queue_take(&recorder->queue);
queue_take(&recorder->queue, next, &rec);
mutex_unlock(recorder->mutex); mutex_unlock(recorder->mutex);
// recorder->previous is only written from this thread, no need to lock bool ok = recorder_write(recorder, &rec->packet);
struct record_packet *previous = recorder->previous; record_packet_delete(rec);
recorder->previous = rec;
if (!previous) {
// we just received the first packet
continue;
}
// we now know the duration of the previous packet
previous->packet.duration = rec->packet.pts - previous->packet.pts;
bool ok = recorder_write(recorder, &previous->packet);
record_packet_delete(previous);
if (!ok) { if (!ok) {
LOGE("Could not record packet"); LOGE("Could not record packet");
@@ -352,15 +369,9 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
return false; return false;
} }
struct record_packet *rec = record_packet_new(packet); bool ok = recorder_queue_push(&recorder->queue, packet);
if (!rec) {
LOGC("Could not allocate record packet");
return false;
}
queue_push(&recorder->queue, next, rec);
cond_signal(recorder->queue_cond); cond_signal(recorder->queue_cond);
mutex_unlock(recorder->mutex); mutex_unlock(recorder->mutex);
return true; return ok;
} }

View File

@@ -6,9 +6,7 @@
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "config.h"
#include "common.h" #include "common.h"
#include "queue.h"
enum recorder_format { enum recorder_format {
RECORDER_FORMAT_MP4 = 1, RECORDER_FORMAT_MP4 = 1,
@@ -20,7 +18,10 @@ struct record_packet {
struct record_packet *next; struct record_packet *next;
}; };
struct recorder_queue QUEUE(struct record_packet); struct recorder_queue {
struct record_packet *first;
struct record_packet *last; // undefined if first is NULL
};
struct recorder { struct recorder {
char *filename; char *filename;
@@ -35,12 +36,6 @@ struct recorder {
bool stopped; // set on recorder_stop() by the stream reader bool stopped; // set on recorder_stop() by the stream reader
bool failed; // set on packet write failure bool failed; // set on packet write failure
struct recorder_queue queue; struct recorder_queue queue;
// we can write a packet only once we received the next one so that we can
// set its duration (next_pts - current_pts)
// "previous" is only accessed from the recorder thread, so it does not
// need to be protected by the mutex
struct record_packet *previous;
}; };
bool bool

View File

@@ -7,7 +7,6 @@
#include <sys/time.h> #include <sys/time.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
#include "command.h" #include "command.h"
#include "common.h" #include "common.h"
#include "compat.h" #include "compat.h"
@@ -181,11 +180,6 @@ handle_event(SDL_Event *event, bool control) {
input_manager_process_mouse_button(&input_manager, &event->button, input_manager_process_mouse_button(&input_manager, &event->button,
control); control);
break; break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(&input_manager, &event->tfinger);
break;
case SDL_DROPFILE: { case SDL_DROPFILE: {
if (!control) { if (!control) {
break; break;
@@ -303,6 +297,7 @@ scrcpy(const struct scrcpy_options *options) {
bool video_buffer_initialized = false; bool video_buffer_initialized = false;
bool file_handler_initialized = false; bool file_handler_initialized = false;
bool recorder_initialized = false; bool recorder_initialized = false;
bool stream_initialized = false;
bool stream_started = false; bool stream_started = false;
bool controller_initialized = false; bool controller_initialized = false;
bool controller_started = false; bool controller_started = false;
@@ -364,7 +359,10 @@ scrcpy(const struct scrcpy_options *options) {
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
stream_init(&stream, server.video_socket, dec, rec); if (!stream_init(&stream, server.video_socket, dec, rec)) {
goto end;
}
stream_initialized = true;
// now we consumed the header values, the socket receives the video stream // now we consumed the header values, the socket receives the video stream
// start the stream // start the stream
@@ -443,6 +441,9 @@ end:
if (stream_started) { if (stream_started) {
stream_join(&stream); stream_join(&stream);
} }
if (stream_initialized) {
stream_destroy(&stream);
}
if (controller_started) { if (controller_started) {
controller_join(&controller); controller_join(&controller);
} }

View File

@@ -5,8 +5,6 @@
#include <stdint.h> #include <stdint.h>
#include <recorder.h> #include <recorder.h>
#include "config.h"
struct scrcpy_options { struct scrcpy_options {
const char *serial; const char *serial;
const char *crop; const char *crop;

View File

@@ -3,7 +3,6 @@
#include <string.h> #include <string.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
#include "common.h" #include "common.h"
#include "compat.h" #include "compat.h"
#include "icon.xpm" #include "icon.xpm"

View File

@@ -5,7 +5,6 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "config.h"
#include "common.h" #include "common.h"
struct video_buffer; struct video_buffer;

View File

@@ -4,7 +4,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
#include "command.h" #include "command.h"
#include "net.h" #include "net.h"

View File

@@ -10,8 +10,6 @@
#include <SDL2/SDL_stdinc.h> #include <SDL2/SDL_stdinc.h>
#include "config.h"
size_t size_t
xstrncpy(char *dest, const char *src, size_t n) { xstrncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;

View File

@@ -3,8 +3,6 @@
#include <stddef.h> #include <stddef.h>
#include "config.h"
// like strncpy, except: // like strncpy, except:
// - it copies at most n-1 chars // - it copies at most n-1 chars
// - the dest string is nul-terminated // - the dest string is nul-terminated

View File

@@ -8,8 +8,8 @@
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#include "compat.h" #include "compat.h"
#include "config.h"
#include "buffer_util.h" #include "buffer_util.h"
#include "decoder.h" #include "decoder.h"
#include "events.h" #include "events.h"
@@ -17,7 +17,7 @@
#include "log.h" #include "log.h"
#include "recorder.h" #include "recorder.h"
#define BUFSIZE 0x10000 #define STREAM_BUFSIZE 0x10000
#define HEADER_SIZE 12 #define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1) #define NO_PTS UINT64_C(-1)
@@ -37,7 +37,8 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) {
// It is followed by <packet_size> bytes containing the packet/frame. // It is followed by <packet_size> bytes containing the packet/frame.
uint8_t header[HEADER_SIZE]; uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE); ssize_t r =
buffered_reader_recv_all(&stream->buffered_reader, header, HEADER_SIZE);
if (r < HEADER_SIZE) { if (r < HEADER_SIZE) {
return false; return false;
} }
@@ -51,7 +52,7 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) {
return false; return false;
} }
r = net_recv_all(stream->socket, packet->data, len); r = buffered_reader_recv_all(&stream->buffered_reader, packet->data, len);
if (r < len) { if (r < len) {
av_packet_unref(packet); av_packet_unref(packet);
return false; return false;
@@ -267,13 +268,23 @@ end:
return 0; return 0;
} }
void bool
stream_init(struct stream *stream, socket_t socket, stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder) { struct decoder *decoder, struct recorder *recorder) {
if (!buffered_reader_init(&stream->buffered_reader, socket,
STREAM_BUFSIZE)) {
return false;
}
stream->socket = socket; stream->socket = socket;
stream->decoder = decoder, stream->decoder = decoder,
stream->recorder = recorder; stream->recorder = recorder;
stream->has_pending = false; stream->has_pending = false;
return true;
}
void
stream_destroy(struct stream *stream) {
buffered_reader_destroy(&stream->buffered_reader);
} }
bool bool

View File

@@ -7,13 +7,14 @@
#include <SDL2/SDL_atomic.h> #include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "config.h" #include "buffered_reader.h"
#include "net.h" #include "net.h"
struct video_buffer; struct video_buffer;
struct stream { struct stream {
socket_t socket; socket_t socket;
struct buffered_reader buffered_reader;
struct video_buffer *video_buffer; struct video_buffer *video_buffer;
SDL_Thread *thread; SDL_Thread *thread;
struct decoder *decoder; struct decoder *decoder;
@@ -26,10 +27,13 @@ struct stream {
AVPacket pending; AVPacket pending;
}; };
void bool
stream_init(struct stream *stream, socket_t socket, stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder); struct decoder *decoder, struct recorder *recorder);
void
stream_destroy(struct stream *stream);
bool bool
stream_start(struct stream *stream); stream_start(struct stream *stream);

View File

@@ -7,8 +7,6 @@
#include "command.h" #include "command.h"
#include "config.h"
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h> #include <limits.h>

View File

@@ -2,8 +2,6 @@
#include <unistd.h> #include <unistd.h>
#include "config.h"
bool bool
net_init(void) { net_init(void) {
// do nothing // do nothing

View File

@@ -1,6 +1,5 @@
#include "net.h" #include "net.h"
#include "config.h"
#include "log.h" #include "log.h"
bool bool

View File

@@ -5,7 +5,6 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "config.h"
#include "log.h" #include "log.h"
struct index { struct index {

View File

@@ -3,8 +3,6 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
SDL_Surface * SDL_Surface *
read_xpm(char *xpm[]); read_xpm(char *xpm[]);

View File

@@ -4,7 +4,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include "config.h"
#include "fps_counter.h" #include "fps_counter.h"
// forward declarations // forward declarations

View File

@@ -99,41 +99,6 @@ static void test_serialize_inject_mouse_event(void) {
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
#include <stdio.h>
static void test_serialize_inject_touch_event(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = AMOTION_EVENT_ACTION_DOWN,
.finger_id = 0x1234567887654321L,
.position = {
.point = {
.x = 100,
.y = 200,
},
.screen_size = {
.width = 1080,
.height = 1920,
},
},
.pressure = 1.0f,
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 24);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
0x00, // AKEY_EVENT_ACTION_DOWN
0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // finger id
0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200
0x04, 0x38, 0x07, 0x80, // 1080 1920
0xff, 0xff, // pressure
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_inject_scroll_event(void) { static void test_serialize_inject_scroll_event(void) {
struct control_msg msg = { struct control_msg msg = {
@@ -272,7 +237,6 @@ int main(void) {
test_serialize_inject_text(); test_serialize_inject_text();
test_serialize_inject_text_long(); test_serialize_inject_text_long();
test_serialize_inject_mouse_event(); test_serialize_inject_mouse_event();
test_serialize_inject_touch_event();
test_serialize_inject_scroll_event(); test_serialize_inject_scroll_event();
test_serialize_back_or_screen_on(); test_serialize_back_or_screen_on();
test_serialize_expand_notification_panel(); test_serialize_expand_notification_panel();

View File

@@ -1,38 +0,0 @@
#include <assert.h>
#include <queue.h>
struct foo {
int value;
struct foo *next;
};
static void test_queue(void) {
struct my_queue QUEUE(struct foo) queue;
queue_init(&queue);
assert(queue_is_empty(&queue));
struct foo v1 = { .value = 42 };
struct foo v2 = { .value = 27 };
queue_push(&queue, next, &v1);
queue_push(&queue, next, &v2);
struct foo *foo;
assert(!queue_is_empty(&queue));
queue_take(&queue, next, &foo);
assert(foo->value == 42);
assert(!queue_is_empty(&queue));
queue_take(&queue, next, &foo);
assert(foo->value == 27);
assert(queue_is_empty(&queue));
}
int main(void) {
test_queue();
return 0;
}

View File

@@ -7,7 +7,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.4.2' classpath 'com.android.tools.build:gradle:3.3.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@@ -15,6 +15,6 @@ cpu = 'i686'
endian = 'little' endian = 'little'
[properties] [properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.1.4-win32-shared' prebuilt_ffmpeg_shared = 'ffmpeg-4.1.3-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.1.4-win32-dev' prebuilt_ffmpeg_dev = 'ffmpeg-4.1.3-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.10/i686-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.8/i686-w64-mingw32'

View File

@@ -15,6 +15,6 @@ cpu = 'x86_64'
endian = 'little' endian = 'little'
[properties] [properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.1.4-win64-shared' prebuilt_ffmpeg_shared = 'ffmpeg-4.1.3-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.1.4-win64-dev' prebuilt_ffmpeg_dev = 'ffmpeg-4.1.3-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.10/x86_64-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.8/x86_64-w64-mingw32'

Binary file not shown.

View File

@@ -1,5 +1,6 @@
#Thu Apr 18 11:45:59 CEST 2019
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip

116
gradlew vendored
View File

@@ -1,20 +1,4 @@
#!/usr/bin/env sh #!/usr/bin/env bash
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
############################################################################## ##############################################################################
## ##
@@ -22,6 +6,42 @@
## ##
############################################################################## ##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" PRG="$0"
@@ -40,46 +60,6 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`" APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -105,7 +85,7 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n` MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -170,19 +150,11 @@ if $cygwin ; then
esac esac
fi fi
# Escape application args # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
save () { function splitJvmOpts() {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done JVM_OPTS=("$@")
echo " "
} }
APP_ARGS=$(save "$@") eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
# Collect all arguments for the java command, following the shell quoting and substitution rules exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

30
gradlew.bat vendored
View File

@@ -1,19 +1,3 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@@ -24,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
@@ -62,9 +46,10 @@ echo location of your Java installation.
goto fail goto fail
:init :init
@rem Get command-line arguments, handling Windows variants @rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
@@ -75,6 +60,11 @@ set _SKIP=2
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute :execute
@rem Setup the command line @rem Setup the command line

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '1.10', version: '1.9',
meson_version: '>= 0.37', meson_version: '>= 0.37',
default_options: 'c_std=c11') default_options: 'c_std=c11')

View File

@@ -10,31 +10,31 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
prepare-ffmpeg-shared-win32: prepare-ffmpeg-shared-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.1.4-win32-shared.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.1.3-win32-shared.zip \
596608277f6b937c3dea7c46e854665d75b3de56790bae07f655ca331440f003 \ 8ea472d673370d5e87517a75587abfa6f189ee4f82e8da21fdbc49d0db0c1a89 \
ffmpeg-4.1.4-win32-shared ffmpeg-4.1.3-win32-shared
prepare-ffmpeg-dev-win32: prepare-ffmpeg-dev-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.1.4-win32-dev.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.1.3-win32-dev.zip \
a80c86e263cfad26e202edfa5e6e939a2c88843ae26f031d3e0d981a39fd03fb \ e16d3150b6ccf0b71908f5b964cb8c051d79053c8f5cd6d777d617ab4f03613a \
ffmpeg-4.1.4-win32-dev ffmpeg-4.1.3-win32-dev
prepare-ffmpeg-shared-win64: prepare-ffmpeg-shared-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.1.4-win64-shared.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.1.3-win64-shared.zip \
a90889871de2cab8a79b392591313a188189a353f69dde1db98aebe20b280989 \ 0b974578e07d974c4bafb36c7ed0b46e46b001d38b149455089c13b57ddefe5d \
ffmpeg-4.1.4-win64-shared ffmpeg-4.1.3-win64-shared
prepare-ffmpeg-dev-win64: prepare-ffmpeg-dev-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.1.4-win64-dev.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.1.3-win64-dev.zip \
6c9d53f9e94ce1821e975ec668e5b9d6e9deb4a45d0d7e30264685d3dfbbb068 \ 334b473467db096a5b74242743592a73e120a137232794508e4fc55593696a5b \
ffmpeg-4.1.4-win64-dev ffmpeg-4.1.3-win64-dev
prepare-sdl2: prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.10-mingw.tar.gz \ @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \
a90a7cddaec4996f4d7be6d80c57ec69b062e132bffc513965f99217f603274a \ ffff7305d634aff5e1df5b7bb935435c3a02c8b03ad94a1a2be9169a558a7961 \
SDL2-2.0.10 SDL2-2.0.8
prepare-adb: prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.2-windows.zip \ @./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.1-windows.zip \
d78f02e5e2c9c4c1d046dcd4e6fbdf586e5f57ef66eb0da5c2b49d745d85d5ee \ 2334f92cf571fd2d9bf6ff7c637765bee5d8323e0bd8e051e15927d87b54b4e8 \
platform-tools platform-tools

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 11 versionCode 10
versionName "1.10" versionName "1.9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@@ -6,7 +6,6 @@ if prebuilt_server == ''
build_always: true, # gradle is responsible for tracking source changes build_always: true, # gradle is responsible for tracking source changes
output: 'scrcpy-server.jar', output: 'scrcpy-server.jar',
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')], command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
console: true,
install: true, install: true,
install_dir: 'share/scrcpy') install_dir: 'share/scrcpy')
else else

View File

@@ -8,14 +8,13 @@ public final class ControlMessage {
public static final int TYPE_INJECT_KEYCODE = 0; public static final int TYPE_INJECT_KEYCODE = 0;
public static final int TYPE_INJECT_TEXT = 1; public static final int TYPE_INJECT_TEXT = 1;
public static final int TYPE_INJECT_MOUSE_EVENT = 2; public static final int TYPE_INJECT_MOUSE_EVENT = 2;
public static final int TYPE_INJECT_TOUCH_EVENT = 3; public static final int TYPE_INJECT_SCROLL_EVENT = 3;
public static final int TYPE_INJECT_SCROLL_EVENT = 4; public static final int TYPE_BACK_OR_SCREEN_ON = 4;
public static final int TYPE_BACK_OR_SCREEN_ON = 5; public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 6; public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6;
public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 7; public static final int TYPE_GET_CLIPBOARD = 7;
public static final int TYPE_GET_CLIPBOARD = 8; public static final int TYPE_SET_CLIPBOARD = 8;
public static final int TYPE_SET_CLIPBOARD = 9; public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
private int type; private int type;
private String text; private String text;
@@ -23,8 +22,6 @@ public final class ControlMessage {
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_* private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_*
private int keycode; // KeyEvent.KEYCODE_* private int keycode; // KeyEvent.KEYCODE_*
private int buttons; // MotionEvent.BUTTON_* private int buttons; // MotionEvent.BUTTON_*
private long fingerId;
private float pressure;
private Position position; private Position position;
private int hScroll; private int hScroll;
private int vScroll; private int vScroll;
@@ -33,70 +30,60 @@ public final class ControlMessage {
} }
public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) { public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) {
ControlMessage msg = new ControlMessage(); ControlMessage event = new ControlMessage();
msg.type = TYPE_INJECT_KEYCODE; event.type = TYPE_INJECT_KEYCODE;
msg.action = action; event.action = action;
msg.keycode = keycode; event.keycode = keycode;
msg.metaState = metaState; event.metaState = metaState;
return msg; return event;
} }
public static ControlMessage createInjectText(String text) { public static ControlMessage createInjectText(String text) {
ControlMessage msg = new ControlMessage(); ControlMessage event = new ControlMessage();
msg.type = TYPE_INJECT_TEXT; event.type = TYPE_INJECT_TEXT;
msg.text = text; event.text = text;
return msg; return event;
} }
public static ControlMessage createInjectMouseEvent(int action, int buttons, Position position) { public static ControlMessage createInjectMouseEvent(int action, int buttons, Position position) {
ControlMessage msg = new ControlMessage(); ControlMessage event = new ControlMessage();
msg.type = TYPE_INJECT_MOUSE_EVENT; event.type = TYPE_INJECT_MOUSE_EVENT;
msg.action = action; event.action = action;
msg.buttons = buttons; event.buttons = buttons;
msg.position = position; event.position = position;
return msg; return event;
}
public static ControlMessage createInjectTouchEvent(int action, long fingerId, Position position, float pressure) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_TOUCH_EVENT;
msg.action = action;
msg.fingerId = fingerId;
msg.pressure = pressure;
msg.position = position;
return msg;
} }
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) { public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) {
ControlMessage msg = new ControlMessage(); ControlMessage event = new ControlMessage();
msg.type = TYPE_INJECT_SCROLL_EVENT; event.type = TYPE_INJECT_SCROLL_EVENT;
msg.position = position; event.position = position;
msg.hScroll = hScroll; event.hScroll = hScroll;
msg.vScroll = vScroll; event.vScroll = vScroll;
return msg; return event;
} }
public static ControlMessage createSetClipboard(String text) { public static ControlMessage createSetClipboard(String text) {
ControlMessage msg = new ControlMessage(); ControlMessage event = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD; event.type = TYPE_SET_CLIPBOARD;
msg.text = text; event.text = text;
return msg; return event;
} }
/** /**
* @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants * @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
*/ */
public static ControlMessage createSetScreenPowerMode(int mode) { public static ControlMessage createSetScreenPowerMode(int mode) {
ControlMessage msg = new ControlMessage(); ControlMessage event = new ControlMessage();
msg.type = TYPE_SET_SCREEN_POWER_MODE; event.type = TYPE_SET_SCREEN_POWER_MODE;
msg.action = mode; event.action = mode;
return msg; return event;
} }
public static ControlMessage createEmpty(int type) { public static ControlMessage createEmpty(int type) {
ControlMessage msg = new ControlMessage(); ControlMessage event = new ControlMessage();
msg.type = type; event.type = type;
return msg; return event;
} }
public int getType() { public int getType() {
@@ -123,14 +110,6 @@ public final class ControlMessage {
return buttons; return buttons;
} }
public long getFingerId() {
return fingerId;
}
public float getPressure() {
return pressure;
}
public Position getPosition() { public Position getPosition() {
return position; return position;
} }

View File

@@ -10,7 +10,6 @@ public class ControlMessageReader {
private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9; private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
private static final int INJECT_MOUSE_EVENT_PAYLOAD_LENGTH = 17; private static final int INJECT_MOUSE_EVENT_PAYLOAD_LENGTH = 17;
private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 21;
private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
@@ -63,9 +62,6 @@ public class ControlMessageReader {
case ControlMessage.TYPE_INJECT_MOUSE_EVENT: case ControlMessage.TYPE_INJECT_MOUSE_EVENT:
msg = parseInjectMouseEvent(); msg = parseInjectMouseEvent();
break; break;
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
msg = parseInjectTouchEvent();
break;
case ControlMessage.TYPE_INJECT_SCROLL_EVENT: case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
msg = parseInjectScrollEvent(); msg = parseInjectScrollEvent();
break; break;
@@ -134,20 +130,6 @@ public class ControlMessageReader {
return ControlMessage.createInjectMouseEvent(action, buttons, position); return ControlMessage.createInjectMouseEvent(action, buttons, position);
} }
private ControlMessage parseInjectTouchEvent() {
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
return null;
}
int action = toUnsigned(buffer.get());
long fingerId = buffer.getLong();
Position position = readPosition(buffer);
// 16 bits fixed-point
int pressureInt = toUnsigned(buffer.getShort());
// convert it to a float between 0 and 1 (0x1p16f is 2^16 as float)
float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f);
return ControlMessage.createInjectTouchEvent(action, fingerId, position, pressure);
}
private ControlMessage parseInjectScrollEvent() { private ControlMessage parseInjectScrollEvent() {
if (buffer.remaining() < INJECT_SCROLL_EVENT_PAYLOAD_LENGTH) { if (buffer.remaining() < INJECT_SCROLL_EVENT_PAYLOAD_LENGTH) {
return null; return null;

View File

@@ -13,8 +13,6 @@ import java.io.IOException;
public class Controller { public class Controller {
private static final int MAX_FINGERS = 10;
private final Device device; private final Device device;
private final DesktopConnection connection; private final DesktopConnection connection;
private final DeviceMessageSender sender; private final DeviceMessageSender sender;
@@ -22,55 +20,35 @@ public class Controller {
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
private long lastMouseDown; private long lastMouseDown;
private final MotionEvent.PointerProperties[] mousePointerProperties = {new MotionEvent.PointerProperties()}; private final MotionEvent.PointerProperties[] pointerProperties = {new MotionEvent.PointerProperties()};
private final MotionEvent.PointerCoords[] mousePointerCoords = {new MotionEvent.PointerCoords()}; private final MotionEvent.PointerCoords[] pointerCoords = {new MotionEvent.PointerCoords()};
private long lastTouchDown;
private final FingersState fingersState = new FingersState(MAX_FINGERS);
private final MotionEvent.PointerProperties[] touchPointerProperties = new MotionEvent.PointerProperties[MAX_FINGERS];
private final MotionEvent.PointerCoords[] touchPointerCoords = new MotionEvent.PointerCoords[MAX_FINGERS];
public Controller(Device device, DesktopConnection connection) { public Controller(Device device, DesktopConnection connection) {
this.device = device; this.device = device;
this.connection = connection; this.connection = connection;
initMousePointer(); initPointer();
initTouchPointers();
sender = new DeviceMessageSender(connection); sender = new DeviceMessageSender(connection);
} }
private void initMousePointer() { private void initPointer() {
MotionEvent.PointerProperties props = mousePointerProperties[0]; MotionEvent.PointerProperties props = pointerProperties[0];
props.id = 0; props.id = 0;
props.toolType = MotionEvent.TOOL_TYPE_FINGER; props.toolType = MotionEvent.TOOL_TYPE_FINGER;
MotionEvent.PointerCoords coords = mousePointerCoords[0]; MotionEvent.PointerCoords coords = pointerCoords[0];
coords.orientation = 0; coords.orientation = 0;
coords.pressure = 1; coords.pressure = 1;
coords.size = 1; coords.size = 1;
} }
private void initTouchPointers() { private void setPointerCoords(Point point) {
for (int i = 0; i < MAX_FINGERS; ++i) { MotionEvent.PointerCoords coords = pointerCoords[0];
MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
props.toolType = MotionEvent.TOOL_TYPE_FINGER;
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
coords.orientation = 0;
coords.size = 1;
touchPointerProperties[i] = props;
touchPointerCoords[i] = coords;
}
}
private void setMousePointerCoords(Point point) {
MotionEvent.PointerCoords coords = mousePointerCoords[0];
coords.x = point.getX(); coords.x = point.getX();
coords.y = point.getY(); coords.y = point.getY();
} }
private void setScroll(int hScroll, int vScroll) { private void setScroll(int hScroll, int vScroll) {
MotionEvent.PointerCoords coords = mousePointerCoords[0]; MotionEvent.PointerCoords coords = pointerCoords[0];
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll); coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
} }
@@ -112,9 +90,6 @@ public class Controller {
case ControlMessage.TYPE_INJECT_MOUSE_EVENT: case ControlMessage.TYPE_INJECT_MOUSE_EVENT:
injectMouse(msg.getAction(), msg.getButtons(), msg.getPosition()); injectMouse(msg.getAction(), msg.getButtons(), msg.getPosition());
break; break;
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
injectTouch(msg.getAction(), msg.getFingerId(), msg.getPosition(), msg.getPressure());
break;
case ControlMessage.TYPE_INJECT_SCROLL_EVENT: case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
break; break;
@@ -183,52 +158,8 @@ public class Controller {
// ignore event // ignore event
return false; return false;
} }
setMousePointerCoords(point); setPointerCoords(point);
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, action, 1, mousePointerProperties, mousePointerCoords, 0, buttons, 1f, 1f, 0, 0, MotionEvent event = MotionEvent.obtain(lastMouseDown, now, action, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, 0, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0);
return injectEvent(event);
}
private boolean injectTouch(int action, long fingerId, Position position, float pressure) {
long now = SystemClock.uptimeMillis();
Point point = device.getPhysicalPoint(position);
if (point == null) {
// ignore event
return false;
}
int fingerIndex = fingersState.getFingerIndex(fingerId);
if (fingerIndex == -1) {
Ln.w("Too many fingers for touch event");
return false;
}
Finger finger = fingersState.get(fingerIndex);
finger.setPoint(point);
finger.setPressure(pressure);
finger.setUp(action == MotionEvent.ACTION_UP);
// FAIL: action_up will always remove the finger, and the event will not be written!
int pointerCount = fingersState.update(touchPointerProperties, touchPointerCoords);
fingersState.cleanUp();
Ln.d("pointerCount = " + pointerCount);
for (int i = 0; i < pointerCount; ++i) {
Ln.d("props = " + touchPointerProperties[i].id);
Ln.d("coords = " + touchPointerCoords[i].x + "," + touchPointerCoords[i].y);
}
if (pointerCount > 1) {
if (action == MotionEvent.ACTION_UP) {
action = MotionEvent.ACTION_POINTER_UP | (fingerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
} else if (action == MotionEvent.ACTION_DOWN) {
action = MotionEvent.ACTION_POINTER_DOWN | (fingerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
} else if (action == MotionEvent.ACTION_DOWN) {
lastTouchDown = now;
}
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, touchPointerProperties, touchPointerCoords, 0, 0, 1f, 1f, 0, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0); InputDevice.SOURCE_TOUCHSCREEN, 0);
return injectEvent(event); return injectEvent(event);
} }
@@ -240,9 +171,9 @@ public class Controller {
// ignore event // ignore event
return false; return false;
} }
setMousePointerCoords(point); setPointerCoords(point);
setScroll(hScroll, vScroll); setScroll(hScroll, vScroll);
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, MotionEvent.ACTION_SCROLL, 1, mousePointerProperties, mousePointerCoords, 0, 0, 1f, 1f, 0, MotionEvent event = MotionEvent.obtain(lastMouseDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, 0,
0, InputDevice.SOURCE_MOUSE, 0); 0, InputDevice.SOURCE_MOUSE, 0);
return injectEvent(event); return injectEvent(event);
} }

View File

@@ -1,47 +0,0 @@
package com.genymobile.scrcpy;
public class Finger {
private final long id;
private final int localId;
private Point point;
private float pressure;
private boolean up;
public Finger(long id, int localId) {
this.id = id;
this.localId = localId;
}
public long getId() {
return id;
}
public int getLocalId() {
return localId;
}
public Point getPoint() {
return point;
}
public void setPoint(Point point) {
this.point = point;
}
public float getPressure() {
return pressure;
}
public void setPressure(float pressure) {
this.pressure = pressure;
}
public boolean isUp() {
return up;
}
public void setUp(boolean up) {
this.up = up;
}
}

View File

@@ -1,101 +0,0 @@
package com.genymobile.scrcpy;
import android.view.MotionEvent;
import java.util.ArrayList;
import java.util.List;
public class FingersState {
private final List<Finger> fingers = new ArrayList<>();
private int maxFingers;
public FingersState(int maxFingers) {
this.maxFingers = maxFingers;
}
private int indexOf(long id) {
for (int i = 0; i < fingers.size(); ++i) {
Finger finger = fingers.get(i);
if (finger.getId() == id) {
return i;
}
}
return -1;
}
private boolean isLocalIdAvailable(int localId) {
for (int i = 0; i < fingers.size(); ++i) {
Finger finger = fingers.get(i);
if (finger.getLocalId() == localId) {
return false;
}
}
return true;
}
private int nextUnusedLocalId() {
for (int localId = 0; localId < maxFingers; ++localId) {
if (isLocalIdAvailable(localId)) {
return localId;
}
}
return -1;
}
public Finger get(int index) {
return fingers.get(index);
}
public int getFingerIndex(long id) {
int index = indexOf(id);
if (index != -1) {
// already exists, return it
return index;
}
if (fingers.size() >= maxFingers) {
// it's full
return -1;
}
// id 0 is reserved for mouse events
int localId = nextUnusedLocalId();
if (localId == -1) {
throw new AssertionError("fingers.size() < maxFingers implies that a local id is available");
}
Finger finger = new Finger(id, localId);
fingers.add(finger);
// return the index of the finger
return fingers.size() - 1;
}
/**
* Initialize the motion event parameters.
*
* @param props the pointer properties
* @param coords the pointer coordinates
* @return The number of items initialized (the number of fingers).
*/
public int update(MotionEvent.PointerProperties[] props, MotionEvent.PointerCoords[] coords) {
for (int i = 0; i < fingers.size(); ++i) {
Finger finger = fingers.get(i);
// id 0 is reserved for mouse events
props[i].id = finger.getLocalId();
Point point = finger.getPoint();
coords[i].x = point.getX();
coords[i].y = point.getY();
coords[i].pressure = finger.getPressure();
}
return fingers.size();
}
public void cleanUp() {
for (int i = fingers.size() - 1; i >= 0; --i) {
Finger finger = fingers.get(i);
if (finger.isUp()) {
fingers.remove(i);
}
}
}
}

View File

@@ -14,7 +14,7 @@ public final class WindowManager {
try { try {
Class<?> cls = manager.getClass(); Class<?> cls = manager.getClass();
try { try {
return (Integer) cls.getMethod("getRotation").invoke(manager); return (Integer) manager.getClass().getMethod("getRotation").invoke(manager);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
// method changed since this commit: // method changed since this commit:
// https://android.googlesource.com/platform/frameworks/base/+/8ee7285128c3843401d4c4d0412cd66e86ba49e3%5E%21/#F2 // https://android.googlesource.com/platform/frameworks/base/+/8ee7285128c3843401d4c4d0412cd66e86ba49e3%5E%21/#F2

View File

@@ -77,63 +77,24 @@ public class ControlMessageReaderTest {
} }
@Test @Test
@SuppressWarnings("checkstyle:MagicNumber")
public void testParseMouseEvent() throws IOException { public void testParseMouseEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader(); ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_MOUSE_EVENT); dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(MotionEvent.ACTION_DOWN); dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeInt(MotionEvent.BUTTON_PRIMARY); dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(100); dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeInt(200);
dos.writeShort(1080);
dos.writeShort(1920);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet)); reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next(); ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_MOUSE_EVENT, event.getType()); Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons()); Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
Assert.assertEquals(100, event.getPosition().getPoint().getX()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
Assert.assertEquals(200, event.getPosition().getPoint().getY());
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
}
@Test
@SuppressWarnings("checkstyle:MagicNumber")
public void testParseTouchEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_TOUCH_EVENT);
dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeLong(-42); // fingerId
dos.writeInt(100);
dos.writeInt(200);
dos.writeShort(1080);
dos.writeShort(1920);
dos.writeShort(0xffff); // pressure
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_TOUCH_EVENT, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(-42, event.getFingerId());
Assert.assertEquals(100, event.getPosition().getPoint().getX());
Assert.assertEquals(200, event.getPosition().getPoint().getY());
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
Assert.assertEquals(1f, event.getPressure(), 0f); // must be exact
} }
@Test @Test