Compare commits

..

30 Commits

Author SHA1 Message Date
Romain Vimont
a9275f2555 Use the physical size for the renderer
Position and scale the frames manually instead of relying on the
renderer "logical size".

This will allow to draw elements having a size independant of the device
frame.
2019-10-20 21:09:30 +02:00
Romain Vimont
c160825854 Handle window resizing in screen
Only the screen knows what to do when the window is resized.

This paves the way to do other actions on window resizing.
2019-10-20 16:21:10 +02:00
Romain Vimont
fd9f41548a Pass screen to mouse event converters
Mouse events coordinates depend on the screen size and location, so the
converter need to access the screen.

The fact that it needs the position or the size is an internal detail,
so pass a pointer to the whole screen structure.
2019-10-20 16:21:10 +02:00
Romain Vimont
56527c0808 Move event conversion to input_manager
Only keep helper functions separated.

This will help to convert coordinates internally when necessary.
2019-10-20 16:09:00 +02:00
Romain Vimont
1315c2b00d Rename "input_manager" variables to "im"
It is used a lot, a short name improves readability.
2019-10-20 16:09:00 +02:00
Romain Vimont
f6c8460ebb Rename window size functions for clarity
Now, get_window_size() returns the current window size (fullscreen or
not), while get_windowed_window_size() always returned the windowed size
(the size when fullscreen is disabled).
2019-10-20 16:08:16 +02:00
Romain Vimont
c33a147fd0 Fix "turn screen off" on Android Q
Call getInternalDisplayToken(), which retrieve the id of the first
physical display (which is not necessarily 0 anymore).

Fixes <https://github.com/Genymobile/scrcpy/issues/835>
2019-10-17 23:25:00 +02:00
Romain Vimont
8b33c6c108 Adapt copy-paste methods for Android 10
The methods getPrimaryClip() and setPrimaryClip() expect an additional
parameter since Android 10.

Fixes <https://github.com/Genymobile/scrcpy/issues/796>.
2019-10-17 22:21:47 +02:00
Romain Vimont
5b7a0cd8e9 Extract String literal to static constant 2019-10-17 22:11:39 +02:00
Romain Vimont
bab9361948 Do not crash on control error
Some devices do not have some methods that we invoke via reflection, or
their call do not return the expected value. In that case, do not crash
the whole controller.
2019-10-17 22:09:54 +02:00
Romain Vimont
6220456def Merge mouse and touch events
Both are handled the very same way on the device.
2019-10-03 20:37:49 +02:00
Romain Vimont
7e1d52c119 Rename "touch pointer" to "pointer"
There are only touch pointers now, mouse pointers have been removed.
2019-10-03 20:27:28 +02:00
Romain Vimont
280d5b718c Use common pointers for mouse and touch
The mouse is a pointer like any other.
2019-10-03 20:05:29 +02:00
Romain Vimont
30168f0428 Ignore duplicate mouse events
In SDL, a touch event may simulate an identical mouse event. Since we
already handle touch event, ignore these duplicates.
2019-10-03 20:05:29 +02:00
Romain Vimont
b5a2d99bc2 Send touch events from the client
On SDL touch events, send control messages to the server.
2019-10-03 20:05:29 +02:00
Romain Vimont
f765aae352 Inject touch events on the server
On receiving an "inject touch" control message, update the local
pointers state and inject touches.
2019-10-03 20:05:29 +02:00
Romain Vimont
77f876e29c Add "inject touch" control message
Add a control message type in the protocol to forward touch events to
the device.
2019-10-03 20:05:27 +02:00
Romain Vimont
d90549d1e6 Rename "pointer" to "mouse pointer"
This will help to distinguish them from "touch pointers".
2019-10-02 21:40:26 +02:00
Romain Vimont
810ff80ba7 Add buffer_write64be()
Add a function to write 64 bits in big-endian from a uint64_t.
2019-10-02 21:40:26 +02:00
Romain Vimont
1f8ba1ca79 Include config.h everywhere
Ref: <https://github.com/Genymobile/scrcpy/issues/829>

Suggested-by: Louis Kruger <louisk@gmail.com>
2019-09-29 22:39:53 +02:00
Louis Kruger
129dabcfa4 Include config.h to fix HIDPI support
Ref: <https://github.com/Genymobile/scrcpy/issues/829>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2019-09-29 22:39:47 +02:00
Romain Vimont
f510f1de1c Remove "make" from build dependencies
The project is built with meson+ninja.
2019-09-28 14:46:44 +02:00
Romain Vimont
7d1932b907 Fix gradle warnings in tests 2019-09-28 12:23:54 +02:00
Yu-Chen Lin
795d103032 input_manager.c: Correct log
Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
2019-09-28 10:37:25 +08:00
Ta-da
513d1ac96d Fix option "record-format" related short opt 2019-09-27 10:04:41 +08:00
Romain Vimont
ffdbf5990b Rename event converter functions
Rename "XXX_from_sdl_to_android" to "convert_XXX", to avoid huge
function names.
2019-09-15 17:29:03 +02:00
Romain Vimont
9463850c24 Rename "convert.h" to "event_converter.h"
The filename gave no hint about what was converted.
2019-09-15 17:29:03 +02:00
Romain Vimont
6e38e0cbfe Rename variable names "event" to "msg"
Some variable names had not been renamed when "event" was renamed to
"message" (28980bbc90).
2019-09-15 17:29:03 +02:00
Romain Vimont
7040e8abc4 Fix control message reader test
The mouse event test actually tested a key event control message.
2019-09-15 17:29:03 +02:00
Romain Vimont
da5b0ec0d5 Improve FAQ 2019-08-14 22:32:46 +02:00
61 changed files with 933 additions and 384 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 make gcc git pkg-config meson ninja-build \ sudo apt install gcc git pkg-config meson ninja-build \
libavcodec-dev libavformat-dev libavutil-dev \ libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev libsdl2-dev

44
FAQ.md
View File

@@ -1,10 +1,5 @@
# 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.
@@ -20,9 +15,13 @@ 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
### Mouse clicks do not work ### I can only mirror, I cannot interact with the device
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
@@ -43,6 +42,16 @@ 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.
@@ -50,3 +59,26 @@ 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

@@ -3,10 +3,10 @@ src = [
'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',

View File

@@ -4,6 +4,8 @@
#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;
@@ -18,6 +20,12 @@ 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];

View File

@@ -5,6 +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); // struct cbuf_int CBUF(int, 20);
// //

View File

@@ -5,6 +5,7 @@
#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,6 +33,8 @@
#endif #endif
#include "config.h"
# define NO_EXIT_CODE -1 # define NO_EXIT_CODE -1
enum process_result { enum process_result {

View File

@@ -3,6 +3,8 @@
#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,7 +1,9 @@
#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"
@@ -23,6 +25,16 @@ 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;
@@ -37,11 +49,15 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]); CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len; return 1 + len;
} }
case CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT: case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
buf[1] = msg->inject_mouse_event.action; buf[1] = msg->inject_touch_event.action;
buffer_write32be(&buf[2], msg->inject_mouse_event.buttons); buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
write_position(&buf[6], &msg->inject_mouse_event.position); write_position(&buf[10], &msg->inject_touch_event.position);
return 18; uint16_t pressure =
to_fixed_point_16(msg->inject_touch_event.pressure);
buffer_write16be(&buf[22], pressure);
buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
return 28;
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,6 +5,7 @@
#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"
@@ -14,10 +15,12 @@
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \ #define CONTROL_MSG_SERIALIZED_MAX_SIZE \
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH) (3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
#define POINTER_ID_MOUSE UINT64_C(-1);
enum control_msg_type { 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_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,
@@ -47,8 +50,10 @@ struct control_msg {
struct { struct {
enum android_motionevent_action action; enum android_motionevent_action action;
enum android_motionevent_buttons buttons; enum android_motionevent_buttons buttons;
uint64_t pointer_id;
struct position position; struct position position;
} inject_mouse_event; float pressure;
} inject_touch_event;
struct { struct {
struct position position; struct position position;
int32_t hscroll; int32_t hscroll;

View File

@@ -5,6 +5,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 "cbuf.h" #include "cbuf.h"
#include "control_msg.h" #include "control_msg.h"
#include "net.h" #include "net.h"

View File

@@ -1,41 +0,0 @@
#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 "compat.h"
#include "config.h" #include "config.h"
#include "compat.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,6 +4,8 @@
#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,4 +1,6 @@
#include "device.h" #include "device.h"
#include "config.h"
#include "log.h" #include "log.h"
bool bool

View File

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

View File

@@ -3,6 +3,7 @@
#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,6 +5,8 @@
#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,9 +1,11 @@
#include "convert.h" #include "event_converter.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
static bool bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
switch (from) { switch (from) {
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
@@ -31,8 +33,7 @@ autocomplete_metastate(enum android_metastate metastate) {
return metastate; return metastate;
} }
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;
if (mod & KMOD_LSHIFT) { if (mod & KMOD_LSHIFT) {
@@ -73,7 +74,7 @@ convert_meta_state(SDL_Keymod mod) {
return autocomplete_metastate(metastate); return autocomplete_metastate(metastate);
} }
static bool bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) { convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
switch (from) { switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER); MAP(SDLK_RETURN, AKEYCODE_ENTER);
@@ -127,16 +128,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
} }
} }
static bool enum android_motionevent_buttons
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
FAIL;
}
}
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) { convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0; enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) { if (state & SDL_BUTTON_LMASK) {
@@ -158,71 +150,20 @@ convert_mouse_buttons(uint32_t state) {
} }
bool bool
input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
struct control_msg *to) { switch (from) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { FAIL;
return false;
} }
uint16_t mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod)) {
return false;
}
to->inject_keycode.metastate = convert_meta_state(mod);
return true;
} }
bool bool
mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
struct size screen_size, switch (from) {
struct control_msg *to) { MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT; MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
if (!convert_mouse_action(from->type, &to->inject_mouse_event.action)) { FAIL;
return false;
} }
to->inject_mouse_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
to->inject_mouse_event.position.screen_size = screen_size;
to->inject_mouse_event.position.point.x = from->x;
to->inject_mouse_event.position.point.y = from->y;
return true;
}
bool
mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct size screen_size,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT;
to->inject_mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_mouse_event.buttons = convert_mouse_buttons(from->state);
to->inject_mouse_event.position.screen_size = screen_size;
to->inject_mouse_event.position.point.x = from->x;
to->inject_mouse_event.position.point.y = from->y;
return true;
}
bool
mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
struct position position,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
to->inject_scroll_event.position = position;
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
// SDL behavior seems inconsistent between horizontal and vertical scrolling
// so reverse the horizontal
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
to->inject_scroll_event.hscroll = -mul * from->x;
to->inject_scroll_event.vscroll = mul * from->y;
return true;
} }

28
app/src/event_converter.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef CONVERT_H
#define CONVERT_H
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "config.h"
#include "control_msg.h"
bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
enum android_metastate
convert_meta_state(SDL_Keymod mod);
bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod);
enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state);
bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to);
bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to);
#endif

View File

@@ -5,6 +5,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 "cbuf.h" #include "cbuf.h"
#include "command.h" #include "command.h"

View File

@@ -3,6 +3,7 @@
#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,6 +7,8 @@
#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,37 +1,12 @@
#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"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
// coordinates (as provided in SDL mouse events)
//
// See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport;
float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport);
SDL_RenderGetScale(renderer, &scale_x, &scale_y);
*x = (int) (*x / scale_x) - viewport.x;
*y = (int) (*y / scale_y) - viewport.y;
}
static struct point
get_mouse_point(struct screen *screen) {
int x;
int y;
SDL_GetMouseState(&x, &y);
convert_to_renderer_coordinates(screen->renderer, &x, &y);
return (struct point) {
.x = x,
.y = y,
};
}
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1; static const int ACTION_UP = 1 << 1;
@@ -102,7 +77,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 'turn screen on'"); LOGW("Could not request 'press back or turn screen on'");
} }
} }
@@ -210,7 +185,7 @@ clipboard_paste(struct controller *controller) {
} }
void void
input_manager_process_text_input(struct input_manager *input_manager, input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) { const SDL_TextInputEvent *event) {
char c = event->text[0]; char c = event->text[0];
if (isalpha(c) || c == ' ') { if (isalpha(c) || c == ' ') {
@@ -225,14 +200,32 @@ input_manager_process_text_input(struct input_manager *input_manager,
LOGW("Could not strdup input text"); LOGW("Could not strdup input text");
return; return;
} }
if (!controller_push_msg(input_manager->controller, &msg)) { if (!controller_push_msg(im->controller, &msg)) {
SDL_free(msg.inject_text.text); SDL_free(msg.inject_text.text);
LOGW("Could not request 'inject text'"); LOGW("Could not request 'inject text'");
} }
} }
static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
return false;
}
uint16_t mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod)) {
return false;
}
to->inject_keycode.metastate = convert_meta_state(mod);
return true;
}
void void
input_manager_process_key(struct input_manager *input_manager, input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event, const SDL_KeyboardEvent *event,
bool control) { bool control) {
// control: indicates the state of the command-line option --no-control // control: indicates the state of the command-line option --no-control
@@ -259,7 +252,7 @@ input_manager_process_key(struct input_manager *input_manager,
return; return;
} }
struct controller *controller = input_manager->controller; struct controller *controller = im->controller;
// capture all Ctrl events // capture all Ctrl events
if (ctrl || cmd) { if (ctrl || cmd) {
@@ -334,23 +327,23 @@ input_manager_process_key(struct input_manager *input_manager,
return; return;
case SDLK_f: case SDLK_f:
if (!shift && cmd && !repeat && down) { if (!shift && cmd && !repeat && down) {
screen_switch_fullscreen(input_manager->screen); screen_switch_fullscreen(im->screen);
} }
return; return;
case SDLK_x: case SDLK_x:
if (!shift && cmd && !repeat && down) { if (!shift && cmd && !repeat && down) {
screen_resize_to_fit(input_manager->screen); screen_resize_to_fit(im->screen);
} }
return; return;
case SDLK_g: case SDLK_g:
if (!shift && cmd && !repeat && down) { if (!shift && cmd && !repeat && down) {
screen_resize_to_pixel_perfect(input_manager->screen); screen_resize_to_pixel_perfect(im->screen);
} }
return; return;
case SDLK_i: case SDLK_i:
if (!shift && cmd && !repeat && down) { if (!shift && cmd && !repeat && down) {
struct fps_counter *fps_counter = struct fps_counter *fps_counter =
input_manager->video_buffer->fps_counter; im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter); switch_fps_counter_state(fps_counter);
} }
return; return;
@@ -373,56 +366,131 @@ input_manager_process_key(struct input_manager *input_manager,
} }
struct control_msg msg; struct control_msg msg;
if (input_key_from_sdl_to_android(event, &msg)) { if (convert_input_key(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'");
} }
} }
} }
static bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
return true;
}
void void
input_manager_process_mouse_motion(struct input_manager *input_manager, input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) { const SDL_MouseMotionEvent *event) {
if (!event->state) { if (!event->state) {
// 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 (mouse_motion_from_sdl_to_android(event, if (convert_mouse_motion(event, im->screen, &msg)) {
input_manager->screen->frame_size, if (!controller_push_msg(im->controller, &msg)) {
&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'");
} }
} }
} }
static bool static bool
is_outside_device_screen(struct input_manager *input_manager, int x, int y) convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
{ struct control_msg *to) {
return x < 0 || x >= input_manager->screen->frame_size.width || to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
y < 0 || y >= input_manager->screen->frame_size.height;
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
return false;
}
struct size frame_size = screen->frame_size;
// SDL touch event coordinates are normalized in the range [0; 1]
float x = from->x * frame_size.width;
float y = from->y * frame_size.height;
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = frame_size;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
} }
void void
input_manager_process_mouse_button(struct input_manager *input_manager, input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) {
struct control_msg msg;
if (convert_touch(event, im->screen, &msg)) {
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
}
static bool
is_outside_device_screen(struct input_manager *im, int x, int y)
{
return x < 0 || x >= im->screen->frame_size.width ||
y < 0 || y >= im->screen->frame_size.height;
}
static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
return true;
}
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event, const SDL_MouseButtonEvent *event,
bool control) { bool control) {
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
return;
}
if (event->type == SDL_MOUSEBUTTONDOWN) { if (event->type == SDL_MOUSEBUTTONDOWN) {
if (control && event->button == SDL_BUTTON_RIGHT) { if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(input_manager->controller); press_back_or_turn_screen_on(im->controller);
return; return;
} }
if (control && event->button == SDL_BUTTON_MIDDLE) { if (control && event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP); action_home(im->controller, ACTION_DOWN | ACTION_UP);
return; return;
} }
// double-click on black borders resize to fit the device screen // double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
bool outside = bool outside =
is_outside_device_screen(input_manager, event->x, event->y); is_outside_device_screen(im, event->x, event->y);
if (outside) { if (outside) {
screen_resize_to_fit(input_manager->screen); screen_resize_to_fit(im->screen);
return; return;
} }
} }
@@ -434,25 +502,47 @@ input_manager_process_mouse_button(struct input_manager *input_manager,
} }
struct control_msg msg; struct control_msg msg;
if (mouse_button_from_sdl_to_android(event, if (convert_mouse_button(event, im->screen, &msg)) {
input_manager->screen->frame_size, if (!controller_push_msg(im->controller, &msg)) {
&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'");
} }
} }
} }
void static bool
input_manager_process_mouse_wheel(struct input_manager *input_manager, convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
const SDL_MouseWheelEvent *event) { struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relatice to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = { struct position position = {
.screen_size = input_manager->screen->frame_size, .screen_size = screen->frame_size,
.point = get_mouse_point(input_manager->screen), .point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y),
}; };
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
to->inject_scroll_event.position = position;
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
// SDL behavior seems inconsistent between horizontal and vertical scrolling
// so reverse the horizontal
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
to->inject_scroll_event.hscroll = -mul * from->x;
to->inject_scroll_event.vscroll = mul * from->y;
return true;
}
void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) {
struct control_msg msg; struct control_msg msg;
if (mouse_wheel_from_sdl_to_android(event, position, &msg)) { if (convert_mouse_wheel(event, im->screen, &msg)) {
if (!controller_push_msg(input_manager->controller, &msg)) { if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse wheel event'"); LOGW("Could not request 'inject mouse wheel event'");
} }
} }

View File

@@ -3,6 +3,7 @@
#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"
@@ -16,25 +17,29 @@ struct input_manager {
}; };
void void
input_manager_process_text_input(struct input_manager *input_manager, input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event); const SDL_TextInputEvent *event);
void void
input_manager_process_key(struct input_manager *input_manager, input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event, const SDL_KeyboardEvent *event,
bool control); bool control);
void void
input_manager_process_mouse_motion(struct input_manager *input_manager, input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event); const SDL_MouseMotionEvent *event);
void void
input_manager_process_mouse_button(struct input_manager *input_manager, input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event);
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event, const SDL_MouseButtonEvent *event,
bool control); bool control);
void void
input_manager_process_mouse_wheel(struct input_manager *input_manager, input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event); const SDL_MouseWheelEvent *event);
#endif #endif

View File

@@ -4,6 +4,7 @@
#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 "compat.h"
#include "config.h" #include "config.h"
#include "compat.h"
#include "log.h" #include "log.h"
#include "recorder.h" #include "recorder.h"
@@ -328,7 +328,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,6 +2,7 @@
#include <stdio.h> #include <stdio.h>
#include "config.h"
#include "log.h" #include "log.h"
#ifdef __WINDOWS__ #ifdef __WINDOWS__

View File

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

View File

@@ -6,6 +6,8 @@
#include <stddef.h> #include <stddef.h>
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include "config.h"
// To define a queue type of "struct foo": // To define a queue type of "struct foo":
// struct queue_foo QUEUE(struct foo); // struct queue_foo QUEUE(struct foo);
#define QUEUE(TYPE) { \ #define QUEUE(TYPE) { \

View File

@@ -5,6 +5,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 "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 "compat.h"
#include "config.h" #include "config.h"
#include "compat.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"

View File

@@ -6,6 +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" #include "queue.h"

View File

@@ -7,6 +7,7 @@
#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"
@@ -144,9 +145,11 @@ handle_event(SDL_Event *event, bool control) {
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
switch (event->window.event) { switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen); screen_render(&screen);
break; break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_window_resized(&screen);
break;
} }
break; break;
case SDL_TEXTINPUT: case SDL_TEXTINPUT:
@@ -180,6 +183,11 @@ 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;

View File

@@ -5,6 +5,8 @@
#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,6 +3,7 @@
#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"
@@ -15,7 +16,7 @@
// get the window size in a struct size // get the window size in a struct size
static struct size static struct size
get_native_window_size(SDL_Window *window) { get_window_size(SDL_Window *window) {
int width; int width;
int height; int height;
SDL_GetWindowSize(window, &width, &height); SDL_GetWindowSize(window, &width, &height);
@@ -28,11 +29,11 @@ get_native_window_size(SDL_Window *window) {
// get the windowed window size // get the windowed window size
static struct size static struct size
get_window_size(const struct screen *screen) { get_windowed_window_size(const struct screen *screen) {
if (screen->fullscreen) { if (screen->fullscreen) {
return screen->windowed_window_size; return screen->windowed_window_size;
} }
return get_native_window_size(screen->window); return get_window_size(screen->window);
} }
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
@@ -111,8 +112,8 @@ get_optimal_size(struct size current_size, struct size frame_size) {
// same as get_optimal_size(), but read the current size from the window // same as get_optimal_size(), but read the current size from the window
static inline struct size static inline struct size
get_optimal_window_size(const struct screen *screen, struct size frame_size) { get_optimal_window_size(const struct screen *screen, struct size frame_size) {
struct size current_size = get_window_size(screen); struct size windowed_size = get_windowed_window_size(screen);
return get_optimal_size(current_size, frame_size); return get_optimal_size(windowed_size, frame_size);
} }
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
@@ -121,6 +122,32 @@ get_initial_optimal_size(struct size frame_size) {
return get_optimal_size(frame_size, frame_size); return get_optimal_size(frame_size, frame_size);
} }
static void
update_frame_rect(struct screen *screen) {
int ww;
int wh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
// 32 bits because we need to multiply two 16 bits values
uint32_t fw = screen->frame_size.width;
uint32_t fh = screen->frame_size.height;
SDL_Rect *rect = &screen->rect;
bool keep_width = fw * wh > fh * ww;
if (keep_width) {
rect->x = 0;
rect->w = ww;
rect->h = ww * fh / fw;
rect->y = (wh - rect->h) / 2;
} else {
rect->y = 0;
rect->h = wh;
rect->w = wh * fw / fh;
rect->x = (ww - rect->w) / 2;
}
}
void void
screen_init(struct screen *screen) { screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER; *screen = (struct screen) SCREEN_INITIALIZER;
@@ -169,13 +196,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false; return false;
} }
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width,
frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen);
return false;
}
SDL_Surface *icon = read_xpm(icon_xpm); SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) { if (icon) {
SDL_SetWindowIcon(screen->window, icon); SDL_SetWindowIcon(screen->window, icon);
@@ -219,20 +239,14 @@ static bool
prepare_for_frame(struct screen *screen, struct size new_frame_size) { prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) { || screen->frame_size.height != new_frame_size.height) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
new_frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false;
}
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
struct size current_size = get_window_size(screen); struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = { struct size target_size = {
(uint32_t) current_size.width * new_frame_size.width (uint32_t) windowed_size.width * new_frame_size.width
/ screen->frame_size.width, / screen->frame_size.width,
(uint32_t) current_size.height * new_frame_size.height (uint32_t) windowed_size.height * new_frame_size.height
/ screen->frame_size.height, / screen->frame_size.height,
}; };
target_size = get_optimal_size(target_size, new_frame_size); target_size = get_optimal_size(target_size, new_frame_size);
@@ -270,6 +284,7 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
mutex_unlock(vb->mutex); mutex_unlock(vb->mutex);
return false; return false;
} }
update_frame_rect(screen);
update_texture(screen, frame); update_texture(screen, frame);
mutex_unlock(vb->mutex); mutex_unlock(vb->mutex);
@@ -277,10 +292,16 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
return true; return true;
} }
void
screen_window_resized(struct screen *screen) {
update_frame_rect(screen);
screen_render(screen);
}
void void
screen_render(struct screen *screen) { screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
SDL_RenderPresent(screen->renderer); SDL_RenderPresent(screen->renderer);
} }
@@ -288,7 +309,7 @@ void
screen_switch_fullscreen(struct screen *screen) { screen_switch_fullscreen(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
// going to fullscreen, store the current windowed window size // going to fullscreen, store the current windowed window size
screen->windowed_window_size = get_native_window_size(screen->window); screen->windowed_window_size = get_window_size(screen->window);
} }
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) { if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
@@ -326,3 +347,11 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
LOGD("Resized to pixel-perfect"); LOGD("Resized to pixel-perfect");
} }
} }
struct point
screen_convert_to_frame_coords(struct screen *screen, float x, float y) {
struct point out;
out.x = (x - screen->rect.x) * screen->frame_size.width / screen->rect.w;
out.y = (y - screen->rect.y) * screen->frame_size.height / screen->rect.h;
return out;
}

View File

@@ -5,6 +5,7 @@
#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;
@@ -16,6 +17,7 @@ struct screen {
struct size frame_size; struct size frame_size;
//used only in fullscreen mode to know the windowed window size //used only in fullscreen mode to know the windowed window size
struct size windowed_window_size; struct size windowed_window_size;
struct SDL_Rect rect;
bool has_frame; bool has_frame;
bool fullscreen; bool fullscreen;
bool no_window; bool no_window;
@@ -33,6 +35,12 @@ struct screen {
.width = 0, \ .width = 0, \
.height = 0, \ .height = 0, \
}, \ }, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \ .has_frame = false, \
.fullscreen = false, \ .fullscreen = false, \
.no_window = false, \ .no_window = false, \
@@ -59,6 +67,10 @@ screen_destroy(struct screen *screen);
bool bool
screen_update_frame(struct screen *screen, struct video_buffer *vb); screen_update_frame(struct screen *screen, struct video_buffer *vb);
// update content after window resizing
void
screen_window_resized(struct screen *screen);
// render the texture to the renderer // render the texture to the renderer
void void
screen_render(struct screen *screen); screen_render(struct screen *screen);
@@ -75,4 +87,9 @@ screen_resize_to_fit(struct screen *screen);
void void
screen_resize_to_pixel_perfect(struct screen *screen); screen_resize_to_pixel_perfect(struct screen *screen);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels (float to allow partial pixels)
struct point
screen_convert_to_frame_coords(struct screen *screen, float x, float y);
#endif #endif

View File

@@ -4,6 +4,7 @@
#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,6 +10,8 @@
#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,6 +3,8 @@
#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 "compat.h"
#include "config.h" #include "config.h"
#include "compat.h"
#include "buffer_util.h" #include "buffer_util.h"
#include "decoder.h" #include "decoder.h"
#include "events.h" #include "events.h"

View File

@@ -7,6 +7,7 @@
#include <SDL2/SDL_atomic.h> #include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "config.h"
#include "net.h" #include "net.h"
struct video_buffer; struct video_buffer;

View File

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

View File

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

View File

@@ -5,6 +5,7 @@
#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,6 +3,8 @@
#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,6 +4,7 @@
#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

@@ -67,35 +67,39 @@ static void test_serialize_inject_text_long(void) {
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
static void test_serialize_inject_mouse_event(void) { static void test_serialize_inject_touch_event(void) {
struct control_msg msg = { struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT, .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_mouse_event = { .inject_touch_event = {
.action = AMOTION_EVENT_ACTION_DOWN, .action = AMOTION_EVENT_ACTION_DOWN,
.buttons = AMOTION_EVENT_BUTTON_PRIMARY, .pointer_id = 0x1234567887654321L,
.position = { .position = {
.point = { .point = {
.x = 260, .x = 100,
.y = 1026, .y = 200,
}, },
.screen_size = { .screen_size = {
.width = 1080, .width = 1080,
.height = 1920, .height = 1920,
}, },
}, },
.pressure = 1.0f,
.buttons = AMOTION_EVENT_BUTTON_PRIMARY,
}, },
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 18); assert(size == 28);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT, CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
0x00, // AKEY_EVENT_ACTION_DOWN 0x00, // AKEY_EVENT_ACTION_DOWN
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY 0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // pointer id
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200
0x04, 0x38, 0x07, 0x80, // 1080 1920 0x04, 0x38, 0x07, 0x80, // 1080 1920
0xff, 0xff, // pressure
0x00, 0x00, 0x00, 0x01 // AMOTION_EVENT_BUTTON_PRIMARY
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
@@ -236,7 +240,7 @@ int main(void) {
test_serialize_inject_keycode(); test_serialize_inject_keycode();
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_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

@@ -7,7 +7,7 @@ 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_TOUCH_EVENT = 2;
public static final int TYPE_INJECT_SCROLL_EVENT = 3; public static final int TYPE_INJECT_SCROLL_EVENT = 3;
public static final int TYPE_BACK_OR_SCREEN_ON = 4; public static final int TYPE_BACK_OR_SCREEN_ON = 4;
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5; public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
@@ -22,6 +22,8 @@ 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 pointerId;
private float pressure;
private Position position; private Position position;
private int hScroll; private int hScroll;
private int vScroll; private int vScroll;
@@ -30,60 +32,63 @@ 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 event = new ControlMessage(); ControlMessage msg = new ControlMessage();
event.type = TYPE_INJECT_KEYCODE; msg.type = TYPE_INJECT_KEYCODE;
event.action = action; msg.action = action;
event.keycode = keycode; msg.keycode = keycode;
event.metaState = metaState; msg.metaState = metaState;
return event; return msg;
} }
public static ControlMessage createInjectText(String text) { public static ControlMessage createInjectText(String text) {
ControlMessage event = new ControlMessage(); ControlMessage msg = new ControlMessage();
event.type = TYPE_INJECT_TEXT; msg.type = TYPE_INJECT_TEXT;
event.text = text; msg.text = text;
return event; return msg;
} }
public static ControlMessage createInjectMouseEvent(int action, int buttons, Position position) { public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure,
ControlMessage event = new ControlMessage(); int buttons) {
event.type = TYPE_INJECT_MOUSE_EVENT; ControlMessage msg = new ControlMessage();
event.action = action; msg.type = TYPE_INJECT_TOUCH_EVENT;
event.buttons = buttons; msg.action = action;
event.position = position; msg.pointerId = pointerId;
return event; msg.pressure = pressure;
msg.position = position;
msg.buttons = buttons;
return msg;
} }
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) { public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) {
ControlMessage event = new ControlMessage(); ControlMessage msg = new ControlMessage();
event.type = TYPE_INJECT_SCROLL_EVENT; msg.type = TYPE_INJECT_SCROLL_EVENT;
event.position = position; msg.position = position;
event.hScroll = hScroll; msg.hScroll = hScroll;
event.vScroll = vScroll; msg.vScroll = vScroll;
return event; return msg;
} }
public static ControlMessage createSetClipboard(String text) { public static ControlMessage createSetClipboard(String text) {
ControlMessage event = new ControlMessage(); ControlMessage msg = new ControlMessage();
event.type = TYPE_SET_CLIPBOARD; msg.type = TYPE_SET_CLIPBOARD;
event.text = text; msg.text = text;
return event; return msg;
} }
/** /**
* @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 event = new ControlMessage(); ControlMessage msg = new ControlMessage();
event.type = TYPE_SET_SCREEN_POWER_MODE; msg.type = TYPE_SET_SCREEN_POWER_MODE;
event.action = mode; msg.action = mode;
return event; return msg;
} }
public static ControlMessage createEmpty(int type) { public static ControlMessage createEmpty(int type) {
ControlMessage event = new ControlMessage(); ControlMessage msg = new ControlMessage();
event.type = type; msg.type = type;
return event; return msg;
} }
public int getType() { public int getType() {
@@ -110,6 +115,14 @@ public final class ControlMessage {
return buttons; return buttons;
} }
public long getPointerId() {
return pointerId;
}
public float getPressure() {
return pressure;
}
public Position getPosition() { public Position getPosition() {
return position; return position;
} }

View File

@@ -10,6 +10,7 @@ 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;
@@ -59,8 +60,8 @@ public class ControlMessageReader {
case ControlMessage.TYPE_INJECT_TEXT: case ControlMessage.TYPE_INJECT_TEXT:
msg = parseInjectText(); msg = parseInjectText();
break; break;
case ControlMessage.TYPE_INJECT_MOUSE_EVENT: case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
msg = parseInjectMouseEvent(); msg = parseInjectTouchEvent();
break; break;
case ControlMessage.TYPE_INJECT_SCROLL_EVENT: case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
msg = parseInjectScrollEvent(); msg = parseInjectScrollEvent();
@@ -120,14 +121,20 @@ public class ControlMessageReader {
return ControlMessage.createInjectText(text); return ControlMessage.createInjectText(text);
} }
private ControlMessage parseInjectMouseEvent() { @SuppressWarnings("checkstyle:MagicNumber")
if (buffer.remaining() < INJECT_MOUSE_EVENT_PAYLOAD_LENGTH) { private ControlMessage parseInjectTouchEvent() {
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
return null; return null;
} }
int action = toUnsigned(buffer.get()); int action = toUnsigned(buffer.get());
int buttons = buffer.getInt(); long pointerId = buffer.getLong();
Position position = readPosition(buffer); Position position = readPosition(buffer);
return ControlMessage.createInjectMouseEvent(action, buttons, position); // 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);
int buttons = buffer.getInt();
return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons);
} }
private ControlMessage parseInjectScrollEvent() { private ControlMessage parseInjectScrollEvent() {

View File

@@ -19,38 +19,32 @@ 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 lastTouchDown;
private final MotionEvent.PointerProperties[] pointerProperties = {new MotionEvent.PointerProperties()}; private final PointersState pointersState = new PointersState();
private final MotionEvent.PointerCoords[] pointerCoords = {new MotionEvent.PointerCoords()}; private final MotionEvent.PointerProperties[] pointerProperties =
new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
private final MotionEvent.PointerCoords[] pointerCoords =
new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
public Controller(Device device, DesktopConnection connection) { public Controller(Device device, DesktopConnection connection) {
this.device = device; this.device = device;
this.connection = connection; this.connection = connection;
initPointer(); initPointers();
sender = new DeviceMessageSender(connection); sender = new DeviceMessageSender(connection);
} }
private void initPointer() { private void initPointers() {
MotionEvent.PointerProperties props = pointerProperties[0]; for (int i = 0; i < PointersState.MAX_POINTERS; ++i) {
props.id = 0; MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
props.toolType = MotionEvent.TOOL_TYPE_FINGER; props.toolType = MotionEvent.TOOL_TYPE_FINGER;
MotionEvent.PointerCoords coords = pointerCoords[0]; MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
coords.orientation = 0; coords.orientation = 0;
coords.pressure = 1;
coords.size = 1; coords.size = 1;
}
private void setPointerCoords(Point point) { pointerProperties[i] = props;
MotionEvent.PointerCoords coords = pointerCoords[0]; pointerCoords[i] = coords;
coords.x = point.getX();
coords.y = point.getY();
} }
private void setScroll(int hScroll, int vScroll) {
MotionEvent.PointerCoords coords = pointerCoords[0];
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
} }
@SuppressWarnings("checkstyle:MagicNumber") @SuppressWarnings("checkstyle:MagicNumber")
@@ -87,8 +81,8 @@ public class Controller {
case ControlMessage.TYPE_INJECT_TEXT: case ControlMessage.TYPE_INJECT_TEXT:
injectText(msg.getText()); injectText(msg.getText());
break; break;
case ControlMessage.TYPE_INJECT_MOUSE_EVENT: case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
injectMouse(msg.getAction(), msg.getButtons(), msg.getPosition()); injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), 0);
break; 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());
@@ -148,19 +142,42 @@ public class Controller {
return successCount; return successCount;
} }
private boolean injectMouse(int action, int buttons, Position position) { private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
if (action == MotionEvent.ACTION_DOWN) {
lastMouseDown = now;
}
Point point = device.getPhysicalPoint(position); Point point = device.getPhysicalPoint(position);
if (point == null) { if (point == null) {
// ignore event // ignore event
return false; return false;
} }
setPointerCoords(point);
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, action, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, 0, 0, int pointerIndex = pointersState.getPointerIndex(pointerId);
InputDevice.SOURCE_TOUCHSCREEN, 0); if (pointerIndex == -1) {
Ln.w("Too many pointers for touch event");
return false;
}
Pointer pointer = pointersState.get(pointerIndex);
pointer.setPoint(point);
pointer.setPressure(pressure);
pointer.setUp(action == MotionEvent.ACTION_UP);
int pointerCount = pointersState.update(pointerProperties, pointerCoords);
if (pointerCount == 1) {
if (action == MotionEvent.ACTION_DOWN) {
lastTouchDown = now;
}
} else {
// secondary pointers must use ACTION_POINTER_* ORed with the pointerIndex
if (action == MotionEvent.ACTION_UP) {
action = MotionEvent.ACTION_POINTER_UP | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
} else if (action == MotionEvent.ACTION_DOWN) {
action = MotionEvent.ACTION_POINTER_DOWN | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
}
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties,
pointerCoords, 0, 0, 1f, 1f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
return injectEvent(event); return injectEvent(event);
} }
@@ -171,23 +188,30 @@ public class Controller {
// ignore event // ignore event
return false; return false;
} }
setPointerCoords(point);
setScroll(hScroll, vScroll); MotionEvent.PointerProperties props = pointerProperties[0];
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, 0, props.id = 0;
0, InputDevice.SOURCE_MOUSE, 0);
MotionEvent.PointerCoords coords = pointerCoords[0];
coords.x = point.getX();
coords.y = point.getY();
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties,
pointerCoords, 0, 0, 1f, 1f, 0, 0, InputDevice.SOURCE_MOUSE, 0);
return injectEvent(event); return injectEvent(event);
} }
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD,
InputDevice.SOURCE_KEYBOARD); 0, 0, InputDevice.SOURCE_KEYBOARD);
return injectEvent(event); return injectEvent(event);
} }
private boolean injectKeycode(int keyCode) { private boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
} }
private boolean injectEvent(InputEvent event) { private boolean injectEvent(InputEvent event) {

View File

@@ -161,7 +161,11 @@ public final class Device {
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants * @param mode one of the {@code SCREEN_POWER_MODE_*} constants
*/ */
public void setScreenPowerMode(int mode) { public void setScreenPowerMode(int mode) {
IBinder d = SurfaceControl.getBuiltInDisplay(0); IBinder d = SurfaceControl.getBuiltInDisplay();
if (d == null) {
Ln.e("Could not get built-in display");
return;
}
SurfaceControl.setDisplayPowerMode(d, mode); SurfaceControl.setDisplayPowerMode(d, mode);
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
} }

View File

@@ -0,0 +1,55 @@
package com.genymobile.scrcpy;
public class Pointer {
/**
* Pointer id as received from the client.
*/
private final long id;
/**
* Local pointer id, using the lowest possible values to fill the {@link android.view.MotionEvent.PointerProperties PointerProperties}.
*/
private final int localId;
private Point point;
private float pressure;
private boolean up;
public Pointer(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

@@ -0,0 +1,103 @@
package com.genymobile.scrcpy;
import android.view.MotionEvent;
import java.util.ArrayList;
import java.util.List;
public class PointersState {
public static final int MAX_POINTERS = 10;
private final List<Pointer> pointers = new ArrayList<>();
private int indexOf(long id) {
for (int i = 0; i < pointers.size(); ++i) {
Pointer pointer = pointers.get(i);
if (pointer.getId() == id) {
return i;
}
}
return -1;
}
private boolean isLocalIdAvailable(int localId) {
for (int i = 0; i < pointers.size(); ++i) {
Pointer pointer = pointers.get(i);
if (pointer.getLocalId() == localId) {
return false;
}
}
return true;
}
private int nextUnusedLocalId() {
for (int localId = 0; localId < MAX_POINTERS; ++localId) {
if (isLocalIdAvailable(localId)) {
return localId;
}
}
return -1;
}
public Pointer get(int index) {
return pointers.get(index);
}
public int getPointerIndex(long id) {
int index = indexOf(id);
if (index != -1) {
// already exists, return it
return index;
}
if (pointers.size() >= MAX_POINTERS) {
// it's full
return -1;
}
// id 0 is reserved for mouse events
int localId = nextUnusedLocalId();
if (localId == -1) {
throw new AssertionError("pointers.size() < maxFingers implies that a local id is available");
}
Pointer pointer = new Pointer(id, localId);
pointers.add(pointer);
// return the index of the pointer
return pointers.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 pointers).
*/
public int update(MotionEvent.PointerProperties[] props, MotionEvent.PointerCoords[] coords) {
int count = pointers.size();
for (int i = 0; i < count; ++i) {
Pointer pointer = pointers.get(i);
// id 0 is reserved for mouse events
props[i].id = pointer.getLocalId();
Point point = pointer.getPoint();
coords[i].x = point.getX();
coords[i].y = point.getY();
coords[i].pressure = pointer.getPressure();
}
cleanUp();
return count;
}
/**
* Remove all pointers which are UP.
*/
private void cleanUp() {
for (int i = pointers.size() - 1; i >= 0; --i) {
Pointer pointer = pointers.get(i);
if (pointer.isUp()) {
pointers.remove(i);
}
}
}
}

View File

@@ -1,44 +1,102 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.content.ClipData; import android.content.ClipData;
import android.os.Build;
import android.os.IInterface; import android.os.IInterface;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
public class ClipboardManager { public class ClipboardManager {
private static final String PACKAGE_NAME = "com.android.shell";
private static final int USER_ID = 0;
private final IInterface manager; private final IInterface manager;
private final Method getPrimaryClipMethod; private Method getPrimaryClipMethod;
private final Method setPrimaryClipMethod; private Method setPrimaryClipMethod;
public ClipboardManager(IInterface manager) { public ClipboardManager(IInterface manager) {
this.manager = manager; this.manager = manager;
}
private Method getGetPrimaryClipMethod() {
if (getPrimaryClipMethod == null) {
try { try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class); getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class); } else {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
}
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new AssertionError(e); Ln.e("Could not find method", e);
}
}
return getPrimaryClipMethod;
}
private Method getSetPrimaryClipMethod() {
if (setPrimaryClipMethod == null) {
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
} else {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class,
String.class, int.class);
}
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
}
return setPrimaryClipMethod;
}
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException,
IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return (ClipData) method.invoke(manager, PACKAGE_NAME);
}
return (ClipData) method.invoke(manager, PACKAGE_NAME, USER_ID);
}
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData) throws InvocationTargetException,
IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, clipData, PACKAGE_NAME);
} else {
method.invoke(manager, clipData, PACKAGE_NAME, USER_ID);
} }
} }
public CharSequence getText() { public CharSequence getText() {
Method method = getGetPrimaryClipMethod();
if (method == null) {
return null;
}
try { try {
ClipData clipData = (ClipData) getPrimaryClipMethod.invoke(manager, "com.android.shell"); ClipData clipData = getPrimaryClip(method, manager);
if (clipData == null || clipData.getItemCount() == 0) { if (clipData == null || clipData.getItemCount() == 0) {
return null; return null;
} }
return clipData.getItemAt(0).getText(); return clipData.getItemAt(0).getText();
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e); Ln.e("Could not invoke " + method.getName(), e);
return null;
} }
} }
public void setText(CharSequence text) { public void setText(CharSequence text) {
Method method = getSetPrimaryClipMethod();
if (method == null) {
return;
}
ClipData clipData = ClipData.newPlainText(null, text); ClipData clipData = ClipData.newPlainText(null, text);
try { try {
setPrimaryClipMethod.invoke(manager, clipData, "com.android.shell"); setPrimaryClip(method, manager, clipData);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e); Ln.e("Could not invoke " + method.getName(), e);
} }
} }
} }

View File

@@ -1,5 +1,7 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.os.IInterface; import android.os.IInterface;
import android.view.InputEvent; import android.view.InputEvent;
@@ -13,22 +15,33 @@ public final class InputManager {
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
private final IInterface manager; private final IInterface manager;
private final Method injectInputEventMethod; private Method injectInputEventMethod;
public InputManager(IInterface manager) { public InputManager(IInterface manager) {
this.manager = manager; this.manager = manager;
}
private Method getInjectInputEventMethod() {
if (injectInputEventMethod == null) {
try { try {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class); injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new AssertionError(e); Ln.e("Could not find method", e);
} }
} }
return injectInputEventMethod;
}
public boolean injectInputEvent(InputEvent inputEvent, int mode) { public boolean injectInputEvent(InputEvent inputEvent, int mode) {
Method method = getInjectInputEventMethod();
if (method == null) {
return false;
}
try { try {
return (Boolean) injectInputEventMethod.invoke(manager, inputEvent, mode); return (Boolean) method.invoke(manager, inputEvent, mode);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e); Ln.e("Could not invoke " + method.getName(), e);
return false;
} }
} }
} }

View File

@@ -1,5 +1,7 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Build; import android.os.Build;
import android.os.IInterface; import android.os.IInterface;
@@ -9,24 +11,35 @@ import java.lang.reflect.Method;
public final class PowerManager { public final class PowerManager {
private final IInterface manager; private final IInterface manager;
private final Method isScreenOnMethod; private Method isScreenOnMethod;
public PowerManager(IInterface manager) { public PowerManager(IInterface manager) {
this.manager = manager; this.manager = manager;
}
private Method getIsScreenOnMethod() {
if (isScreenOnMethod == null) {
try { try {
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future @SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn"; String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
isScreenOnMethod = manager.getClass().getMethod(methodName); isScreenOnMethod = manager.getClass().getMethod(methodName);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new AssertionError(e); Ln.e("Could not find method", e);
} }
} }
return isScreenOnMethod;
}
public boolean isScreenOn() { public boolean isScreenOn() {
Method method = getIsScreenOnMethod();
if (method == null) {
return false;
}
try { try {
return (Boolean) isScreenOnMethod.invoke(manager); return (Boolean) method.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e); Ln.e("Could not invoke " + method.getName(), e);
return false;
} }
} }
} }

View File

@@ -17,35 +17,49 @@ public class StatusBarManager {
this.manager = manager; this.manager = manager;
} }
public void expandNotificationsPanel() { private Method getExpandNotificationsPanelMethod() {
if (expandNotificationsPanelMethod == null) { if (expandNotificationsPanelMethod == null) {
try { try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel"); expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.expandNotificationsPanel() is not available on this device"); Ln.e("Could not find method", e);
return;
} }
} }
try { return expandNotificationsPanelMethod;
expandNotificationsPanelMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke ServiceBarManager.expandNotificationsPanel()", e);
}
} }
public void collapsePanels() { private Method getCollapsePanelsMethod() {
if (collapsePanelsMethod == null) { if (collapsePanelsMethod == null) {
try { try {
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels"); collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.collapsePanels() is not available on this device"); Ln.e("Could not find method", e);
}
}
return collapsePanelsMethod;
}
public void expandNotificationsPanel() {
Method method = getExpandNotificationsPanelMethod();
if (method == null) {
return; return;
} }
try {
method.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e);
}
}
public void collapsePanels() {
Method method = getCollapsePanelsMethod();
if (method == null) {
return;
} }
try { try {
collapsePanelsMethod.invoke(manager); method.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke ServiceBarManager.collapsePanels()", e); Ln.e("Could not invoke " + method.getName(), e);
} }
} }
} }

View File

@@ -1,11 +1,16 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.view.Surface; import android.view.Surface;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressLint("PrivateApi") @SuppressLint("PrivateApi")
public final class SurfaceControl { public final class SurfaceControl {
@@ -23,6 +28,9 @@ public final class SurfaceControl {
} }
} }
private static Method getBuiltInDisplayMethod;
private static Method setDisplayPowerModeMethod;
private SurfaceControl() { private SurfaceControl() {
// only static methods // only static methods
} }
@@ -76,24 +84,62 @@ public final class SurfaceControl {
} }
} }
public static IBinder getBuiltInDisplay(int builtInDisplayId) { private static Method getGetBuiltInDisplayMethod() {
if (getBuiltInDisplayMethod == null) {
try { try {
// the method signature has changed in Android Q // the method signature has changed in Android Q
// <https://github.com/Genymobile/scrcpy/issues/586> // <https://github.com/Genymobile/scrcpy/issues/586>
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return (IBinder) CLASS.getMethod("getBuiltInDisplay", int.class).invoke(null, builtInDisplayId); getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
} else {
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
} }
return (IBinder) CLASS.getMethod("getPhysicalDisplayToken", long.class).invoke(null, builtInDisplayId); } catch (NoSuchMethodException e) {
} catch (Exception e) { Ln.e("Could not find method", e);
throw new AssertionError(e); }
}
return getBuiltInDisplayMethod;
}
public static IBinder getBuiltInDisplay() {
Method method = getGetBuiltInDisplayMethod();
if (method == null) {
return null;
}
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// call getBuiltInDisplay(0)
return (IBinder) method.invoke(null, 0);
}
// call getInternalDisplayToken()
return (IBinder) method.invoke(null);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e);
return null;
} }
} }
public static void setDisplayPowerMode(IBinder displayToken, int mode) { private static Method getSetDisplayPowerModeMethod() {
if (setDisplayPowerModeMethod == null) {
try { try {
CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class).invoke(null, displayToken, mode); setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class);
} catch (Exception e) { } catch (NoSuchMethodException e) {
throw new AssertionError(e); Ln.e("Could not find method", e);
}
}
return setDisplayPowerModeMethod;
}
public static void setDisplayPowerMode(IBinder displayToken, int mode) {
Method method = getSetDisplayPowerModeMethod();
if (method == null) {
return;
}
try {
method.invoke(null, displayToken, mode);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e);
} }
} }

View File

@@ -77,24 +77,36 @@ public class ControlMessageReaderTest {
} }
@Test @Test
public void testParseMouseEvent() throws IOException { @SuppressWarnings("checkstyle:MagicNumber")
public void testParseTouchEvent() 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_KEYCODE); dos.writeByte(ControlMessage.TYPE_INJECT_TOUCH_EVENT);
dos.writeByte(MotionEvent.ACTION_DOWN); dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeLong(-42); // pointerId
dos.writeInt(100);
dos.writeInt(200);
dos.writeShort(1080);
dos.writeShort(1920);
dos.writeShort(0xffff); // pressure
dos.writeInt(MotionEvent.BUTTON_PRIMARY); dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(KeyEvent.META_CTRL_ON);
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_KEYCODE, event.getType()); Assert.assertEquals(ControlMessage.TYPE_INJECT_TOUCH_EVENT, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode()); Assert.assertEquals(-42, event.getPointerId());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); 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
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons());
} }
@Test @Test