Compare commits
10
Commits
v1.0
..
hidpiscale
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae87885ffa | ||
|
|
65021416b3 | ||
|
|
305e76ee6e | ||
|
|
ef0029d10e | ||
|
|
36c68c4802 | ||
|
|
a82f665114 | ||
|
|
f88cc91a51 | ||
|
|
38e4b3e39a | ||
|
|
64efe2c07d | ||
|
|
93e9fb496c |
+19
-19
@@ -32,7 +32,7 @@ The server is a Java application (with a [`public static void main(String...
|
|||||||
args)`][main] method), compiled against the Android framework, and executed as
|
args)`][main] method), compiled against the Android framework, and executed as
|
||||||
`shell` on the Android device.
|
`shell` on the Android device.
|
||||||
|
|
||||||
[main]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/Server.java#L61
|
[main]: server/src/main/java/com/genymobile/scrcpy/Server.java#L61
|
||||||
|
|
||||||
To run such a Java application, the classes must be [_dexed_][dex] (typically,
|
To run such a Java application, the classes must be [_dexed_][dex] (typically,
|
||||||
to `classes.dex`). If `my.package.MainClass` is the main class, compiled to
|
to `classes.dex`). If `my.package.MainClass` is the main class, compiled to
|
||||||
@@ -57,7 +57,7 @@ build system, the server is built to an (unsigned) APK (renamed to
|
|||||||
|
|
||||||
### Hidden methods
|
### Hidden methods
|
||||||
|
|
||||||
Although compiled against the Android framework, [hidden] methods and classes are
|
Albeit compiled against the Android framework, [hidden] methods and classes are
|
||||||
not directly accessible (and they may differ from one Android version to
|
not directly accessible (and they may differ from one Android version to
|
||||||
another).
|
another).
|
||||||
|
|
||||||
@@ -65,8 +65,8 @@ They can be called using reflection though. The communication with hidden
|
|||||||
components is provided by [_wrappers_ classes][wrappers] and [aidl].
|
components is provided by [_wrappers_ classes][wrappers] and [aidl].
|
||||||
|
|
||||||
[hidden]: https://stackoverflow.com/a/31908373/1987178
|
[hidden]: https://stackoverflow.com/a/31908373/1987178
|
||||||
[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers
|
[wrappers]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/wrappers
|
||||||
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/aidl/android/view
|
[aidl]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/aidl/android/view
|
||||||
|
|
||||||
|
|
||||||
### Threading
|
### Threading
|
||||||
@@ -89,9 +89,9 @@ The video is encoded using the [`MediaCodec`] API. The codec takes its input
|
|||||||
from a [surface] associated to the display, and writes the resulting H.264
|
from a [surface] associated to the display, and writes the resulting H.264
|
||||||
stream to the provided output stream (the socket connected to the client).
|
stream to the provided output stream (the socket connected to the client).
|
||||||
|
|
||||||
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
|
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
|
||||||
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
|
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
|
||||||
[surface]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L63-L64
|
[surface]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L63-L64
|
||||||
|
|
||||||
On device [rotation], the codec, surface and display are reinitialized, and a
|
On device [rotation], the codec, surface and display are reinitialized, and a
|
||||||
new video stream is produced.
|
new video stream is produced.
|
||||||
@@ -105,8 +105,8 @@ because it avoids to send unnecessary frames, but there are drawbacks:
|
|||||||
Both problems are [solved][repeat] by the flag
|
Both problems are [solved][repeat] by the flag
|
||||||
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
|
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
|
||||||
|
|
||||||
[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L89-L92
|
[rotation]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L89-L92
|
||||||
[repeat]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L125-L126
|
[repeat]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L125-L126
|
||||||
[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER
|
[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER
|
||||||
|
|
||||||
|
|
||||||
@@ -124,11 +124,11 @@ All of them may need to inject input events to the system. To do so, they use
|
|||||||
the _hidden_ method [`InputManager.injectInputEvent`] (exposed by our
|
the _hidden_ method [`InputManager.injectInputEvent`] (exposed by our
|
||||||
[`InputManager` wrapper][inject-wrapper]).
|
[`InputManager` wrapper][inject-wrapper]).
|
||||||
|
|
||||||
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/EventController.java#L70
|
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/EventController.java#L70
|
||||||
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
|
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
|
||||||
[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html
|
[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html
|
||||||
[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857
|
[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857
|
||||||
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -195,8 +195,8 @@ When a new decoded frame is available, the decoder _swaps_ the decoding and
|
|||||||
rendering frame (with proper synchronization). Thus, it immediatly starts
|
rendering frame (with proper synchronization). Thus, it immediatly starts
|
||||||
to decode a new frame while the main thread renders the last one.
|
to decode a new frame while the main thread renders the last one.
|
||||||
|
|
||||||
[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/decoder.c
|
[decoder]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/decoder.c
|
||||||
[frames]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/frames.h
|
[frames]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/frames.h
|
||||||
|
|
||||||
|
|
||||||
### Controller
|
### Controller
|
||||||
@@ -211,10 +211,10 @@ events_ to a blocking queue hold by the controller. On its own thread, the
|
|||||||
controller takes events from the queue, that it serializes and sends to the
|
controller takes events from the queue, that it serializes and sends to the
|
||||||
client.
|
client.
|
||||||
|
|
||||||
[controller]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controller.h
|
[controller]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/controller.h
|
||||||
[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controlevent.h
|
[controlevent]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/controlevent.h
|
||||||
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/inputmanager.h
|
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/inputmanager.h
|
||||||
[convert]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/convert.h
|
[convert]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/convert.h
|
||||||
|
|
||||||
|
|
||||||
### UI and event loop
|
### UI and event loop
|
||||||
@@ -225,9 +225,9 @@ thread.
|
|||||||
Events are handled in the [event loop], which either updates the [screen] or
|
Events are handled in the [event loop], which either updates the [screen] or
|
||||||
delegates to the [input manager][inputmanager].
|
delegates to the [input manager][inputmanager].
|
||||||
|
|
||||||
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c
|
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/scrcpy.c
|
||||||
[event loop]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c#L38
|
[event loop]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/scrcpy.c#L38
|
||||||
[screen]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/screen.h
|
[screen]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/screen.h
|
||||||
|
|
||||||
|
|
||||||
## Hack
|
## Hack
|
||||||
|
|||||||
@@ -50,15 +50,8 @@ Install the required packages from your package manager (here, for Debian):
|
|||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
For Windows, for simplicity, a prebuilt archive with all the dependencies
|
For Windows, for simplicity, a prebuilt package with all the dependencies
|
||||||
(including `adb`) is available:
|
(including `adb`) is available: TODO.
|
||||||
|
|
||||||
- [`scrcpy-windows-with-deps-v1.0.zip`][direct-windows-with-deps].
|
|
||||||
_(SHA-256: TODO)_
|
|
||||||
|
|
||||||
[direct-windows-with-deps]: https://github.com/Genymobile/scrcpy/releases/download/v1.0/scrcpy-windows-with-deps-v1.0.zip
|
|
||||||
|
|
||||||
_(It's just a portable version including _dll_ copied from MSYS2.)_
|
|
||||||
|
|
||||||
Instead, you may want to build it manually. You need [MSYS2] to build the
|
Instead, you may want to build it manually. You need [MSYS2] to build the
|
||||||
project. From an MSYS2 terminal, install the required packages:
|
project. From an MSYS2 terminal, install the required packages:
|
||||||
@@ -135,12 +128,7 @@ Just remove them to "uninstall" the application.
|
|||||||
|
|
||||||
Since the server binary, that will be pushed to the Android device, does not
|
Since the server binary, that will be pushed to the Android device, does not
|
||||||
depend on your system and architecture, you may want to use the prebuilt binary
|
depend on your system and architecture, you may want to use the prebuilt binary
|
||||||
instead:
|
instead: [`scrcpy-server.jar`](TODO).
|
||||||
|
|
||||||
- [`scrcpy-server-v1.0.jar`][direct-scrcpy-server].
|
|
||||||
_(SHA-256: TODO)_
|
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.0/scrcpy-server-v1.0.jar
|
|
||||||
|
|
||||||
In that case, the build does not require Java or the Android SDK.
|
In that case, the build does not require Java or the Android SDK.
|
||||||
|
|
||||||
@@ -194,7 +182,6 @@ If several devices are listed in `adb devices`, you must specify the _serial_:
|
|||||||
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
|
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
|
||||||
| click on `POWER` | `Ctrl`+`p` |
|
| click on `POWER` | `Ctrl`+`p` |
|
||||||
| turn screen on | _Right-click_ |
|
| turn screen on | _Right-click_ |
|
||||||
| paste computer clipboard to device | `Ctrl`+`v` |
|
|
||||||
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
|
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -8,6 +8,7 @@ src = [
|
|||||||
'src/device.c',
|
'src/device.c',
|
||||||
'src/fpscounter.c',
|
'src/fpscounter.c',
|
||||||
'src/frames.c',
|
'src/frames.c',
|
||||||
|
'src/hidpi.c',
|
||||||
'src/inputmanager.c',
|
'src/inputmanager.c',
|
||||||
'src/lockutil.c',
|
'src/lockutil.c',
|
||||||
'src/net.c',
|
'src/net.c',
|
||||||
@@ -42,7 +43,7 @@ conf = configuration_data()
|
|||||||
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
|
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
|
||||||
|
|
||||||
# the version, updated on release
|
# the version, updated on release
|
||||||
conf.set_quoted('SCRCPY_VERSION', '1.0')
|
conf.set_quoted('SCRCPY_VERSION', '0.1')
|
||||||
|
|
||||||
# the prefix used during configuration (meson --prefix=PREFIX)
|
# the prefix used during configuration (meson --prefix=PREFIX)
|
||||||
conf.set_quoted('PREFIX', get_option('prefix'))
|
conf.set_quoted('PREFIX', get_option('prefix'))
|
||||||
@@ -80,9 +81,6 @@ conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
|
|||||||
# SKIP_FRAMES improves latency at the cost of framerate
|
# SKIP_FRAMES improves latency at the cost of framerate
|
||||||
conf.set('SKIP_FRAMES', get_option('skip_frames'))
|
conf.set('SKIP_FRAMES', get_option('skip_frames'))
|
||||||
|
|
||||||
# enable High DPI support
|
|
||||||
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
|
|
||||||
|
|
||||||
configure_file(configuration: conf, output: 'config.h')
|
configure_file(configuration: conf, output: 'config.h')
|
||||||
|
|
||||||
executable('scrcpy', src, dependencies: dependencies, install: true)
|
executable('scrcpy', src, dependencies: dependencies, install: true)
|
||||||
|
|||||||
+2
-13
@@ -36,11 +36,10 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
|
|||||||
// write length (1 byte) + date (non nul-terminated)
|
// write length (1 byte) + date (non nul-terminated)
|
||||||
size_t len = strlen(event->text_event.text);
|
size_t len = strlen(event->text_event.text);
|
||||||
if (len > TEXT_MAX_LENGTH) {
|
if (len > TEXT_MAX_LENGTH) {
|
||||||
// injecting a text takes time, so limit the text length
|
|
||||||
len = TEXT_MAX_LENGTH;
|
len = TEXT_MAX_LENGTH;
|
||||||
}
|
}
|
||||||
buf[1] = (Uint8) len;
|
buf[1] = (Uint8) len;
|
||||||
memcpy(&buf[2], event->text_event.text, len);
|
memcpy(&buf[2], &event->text_event.text, len);
|
||||||
return 2 + len;
|
return 2 + len;
|
||||||
}
|
}
|
||||||
case CONTROL_EVENT_TYPE_MOUSE:
|
case CONTROL_EVENT_TYPE_MOUSE:
|
||||||
@@ -62,12 +61,6 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void control_event_destroy(struct control_event *event) {
|
|
||||||
if (event->type == CONTROL_EVENT_TYPE_TEXT) {
|
|
||||||
SDL_free(event->text_event.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue) {
|
SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue) {
|
||||||
return queue->head == queue->tail;
|
return queue->head == queue->tail;
|
||||||
}
|
}
|
||||||
@@ -84,11 +77,7 @@ SDL_bool control_event_queue_init(struct control_event_queue *queue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void control_event_queue_destroy(struct control_event_queue *queue) {
|
void control_event_queue_destroy(struct control_event_queue *queue) {
|
||||||
int i = queue->tail;
|
// nothing to do in the current implementation
|
||||||
while (i != queue->head) {
|
|
||||||
control_event_destroy(&queue->data[i]);
|
|
||||||
i = (i + 1) % CONTROL_EVENT_QUEUE_SIZE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event) {
|
SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
#define CONTROL_EVENT_QUEUE_SIZE 64
|
#define CONTROL_EVENT_QUEUE_SIZE 64
|
||||||
#define SERIALIZED_EVENT_MAX_SIZE 33
|
#define SERIALIZED_EVENT_MAX_SIZE 33
|
||||||
#define TEXT_MAX_LENGTH 256
|
#define TEXT_MAX_LENGTH 31
|
||||||
|
|
||||||
enum control_event_type {
|
enum control_event_type {
|
||||||
CONTROL_EVENT_TYPE_KEYCODE,
|
CONTROL_EVENT_TYPE_KEYCODE,
|
||||||
@@ -31,7 +31,7 @@ struct control_event {
|
|||||||
enum android_metastate metastate;
|
enum android_metastate metastate;
|
||||||
} keycode_event;
|
} keycode_event;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char text[TEXT_MAX_LENGTH + 1]; // nul-terminated string
|
||||||
} text_event;
|
} text_event;
|
||||||
struct {
|
struct {
|
||||||
enum android_motionevent_action action;
|
enum android_motionevent_action action;
|
||||||
@@ -68,6 +68,4 @@ SDL_bool control_event_queue_is_full(const struct control_event_queue *queue);
|
|||||||
SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event);
|
SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event);
|
||||||
SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event);
|
SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event);
|
||||||
|
|
||||||
void control_event_destroy(struct control_event *event);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -65,9 +65,7 @@ static int run_controller(void *data) {
|
|||||||
}
|
}
|
||||||
struct control_event event;
|
struct control_event event;
|
||||||
while (control_event_queue_take(&controller->queue, &event)) {
|
while (control_event_queue_take(&controller->queue, &event)) {
|
||||||
SDL_bool ok = process_event(controller, &event);
|
if (!process_event(controller, &event)) {
|
||||||
control_event_destroy(&event);
|
|
||||||
if (!ok) {
|
|
||||||
LOGD("Cannot write event to socket");
|
LOGD("Cannot write event to socket");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-4
@@ -136,6 +136,7 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
|
|||||||
|
|
||||||
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
||||||
struct size screen_size,
|
struct size screen_size,
|
||||||
|
struct hidpi_scale *hidpi_scale,
|
||||||
struct control_event *to) {
|
struct control_event *to) {
|
||||||
to->type = CONTROL_EVENT_TYPE_MOUSE;
|
to->type = CONTROL_EVENT_TYPE_MOUSE;
|
||||||
|
|
||||||
@@ -145,21 +146,30 @@ SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
|||||||
|
|
||||||
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
|
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
|
||||||
to->mouse_event.position.screen_size = screen_size;
|
to->mouse_event.position.screen_size = screen_size;
|
||||||
to->mouse_event.position.point.x = (Uint16) from->x;
|
|
||||||
to->mouse_event.position.point.y = (Uint16) from->y;
|
Sint32 x = from->x;
|
||||||
|
Sint32 y = from->y;
|
||||||
|
hidpi_unscale_coordinates(hidpi_scale, &x, &y);
|
||||||
|
to->mouse_event.position.point.x = (Uint16) x;
|
||||||
|
to->mouse_event.position.point.y = (Uint16) y;
|
||||||
|
|
||||||
return SDL_TRUE;
|
return SDL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
|
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
|
||||||
struct size screen_size,
|
struct size screen_size,
|
||||||
|
struct hidpi_scale *hidpi_scale,
|
||||||
struct control_event *to) {
|
struct control_event *to) {
|
||||||
to->type = CONTROL_EVENT_TYPE_MOUSE;
|
to->type = CONTROL_EVENT_TYPE_MOUSE;
|
||||||
to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
|
to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
|
||||||
to->mouse_event.buttons = convert_mouse_buttons(from->state);
|
to->mouse_event.buttons = convert_mouse_buttons(from->state);
|
||||||
to->mouse_event.position.screen_size = screen_size;
|
to->mouse_event.position.screen_size = screen_size;
|
||||||
to->mouse_event.position.point.x = from->x;
|
|
||||||
to->mouse_event.position.point.y = from->y;
|
Sint32 x = from->x;
|
||||||
|
Sint32 y = from->y;
|
||||||
|
hidpi_unscale_coordinates(hidpi_scale, &x, &y);
|
||||||
|
to->mouse_event.position.point.x = (Uint16) x;
|
||||||
|
to->mouse_event.position.point.y = (Uint16) y;
|
||||||
|
|
||||||
return SDL_TRUE;
|
return SDL_TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
#include <SDL2/SDL_stdinc.h>
|
||||||
#include <SDL2/SDL_events.h>
|
#include <SDL2/SDL_events.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include "controlevent.h"
|
#include "controlevent.h"
|
||||||
|
#include "hidpi.h"
|
||||||
|
|
||||||
struct complete_mouse_motion_event {
|
struct complete_mouse_motion_event {
|
||||||
SDL_MouseMotionEvent *mouse_motion_event;
|
SDL_MouseMotionEvent *mouse_motion_event;
|
||||||
@@ -19,12 +22,14 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
|
|||||||
struct control_event *to);
|
struct control_event *to);
|
||||||
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
||||||
struct size screen_size,
|
struct size screen_size,
|
||||||
|
struct hidpi_scale *hidpi_scale,
|
||||||
struct control_event *to);
|
struct control_event *to);
|
||||||
|
|
||||||
// the video size may be different from the real device size, so we need the size
|
// 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
|
// to which the absolute position apply, to scale it accordingly
|
||||||
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
|
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
|
||||||
struct size screen_size,
|
struct size screen_size,
|
||||||
|
struct hidpi_scale *hidpi_scale,
|
||||||
struct control_event *to);
|
struct control_event *to);
|
||||||
|
|
||||||
// on Android, a scroll event requires the current mouse position
|
// on Android, a scroll event requires the current mouse position
|
||||||
|
|||||||
+22
-19
@@ -40,33 +40,37 @@ static void notify_stopped(void) {
|
|||||||
|
|
||||||
static int run_decoder(void *data) {
|
static int run_decoder(void *data) {
|
||||||
struct decoder *decoder = data;
|
struct decoder *decoder = data;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
LOGE("H.264 decoder not found");
|
LOGE("H.264 decoder not found");
|
||||||
goto run_end;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
||||||
if (!codec_ctx) {
|
if (!codec_ctx) {
|
||||||
LOGC("Could not allocate decoder context");
|
LOGC("Could not allocate decoder context");
|
||||||
goto run_end;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
|
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
|
||||||
LOGE("Could not open H.264 codec");
|
LOGE("Could not open H.264 codec");
|
||||||
|
ret = -1;
|
||||||
goto run_finally_free_codec_ctx;
|
goto run_finally_free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVFormatContext *format_ctx = avformat_alloc_context();
|
AVFormatContext *format_ctx = avformat_alloc_context();
|
||||||
if (!format_ctx) {
|
if (!format_ctx) {
|
||||||
LOGC("Could not allocate format context");
|
LOGC("Could not allocate format context");
|
||||||
|
ret = -1;
|
||||||
goto run_finally_close_codec;
|
goto run_finally_close_codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char *buffer = av_malloc(BUFSIZE);
|
unsigned char *buffer = av_malloc(BUFSIZE);
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
LOGC("Could not allocate buffer");
|
LOGC("Could not allocate buffer");
|
||||||
|
ret = -1;
|
||||||
goto run_finally_free_format_ctx;
|
goto run_finally_free_format_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +80,7 @@ static int run_decoder(void *data) {
|
|||||||
// avformat_open_input takes ownership of 'buffer'
|
// avformat_open_input takes ownership of 'buffer'
|
||||||
// so only free the buffer before avformat_open_input()
|
// so only free the buffer before avformat_open_input()
|
||||||
av_free(buffer);
|
av_free(buffer);
|
||||||
|
ret = -1;
|
||||||
goto run_finally_free_format_ctx;
|
goto run_finally_free_format_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +88,7 @@ static int run_decoder(void *data) {
|
|||||||
|
|
||||||
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
|
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
|
||||||
LOGE("Could not open video stream");
|
LOGE("Could not open video stream");
|
||||||
|
ret = -1;
|
||||||
goto run_finally_free_avio_ctx;
|
goto run_finally_free_avio_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,21 +100,7 @@ static int run_decoder(void *data) {
|
|||||||
while (!av_read_frame(format_ctx, &packet) && !avio_ctx->eof_reached) {
|
while (!av_read_frame(format_ctx, &packet) && !avio_ctx->eof_reached) {
|
||||||
// the new decoding/encoding API has been introduced by:
|
// the new decoding/encoding API has been introduced by:
|
||||||
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
|
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 0)
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 37, 0)
|
||||||
int ret;
|
|
||||||
if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) {
|
|
||||||
LOGE("Could not send video packet: %d", ret);
|
|
||||||
goto run_quit;
|
|
||||||
}
|
|
||||||
ret = avcodec_receive_frame(codec_ctx, decoder->frames->decoding_frame);
|
|
||||||
if (!ret) {
|
|
||||||
// a frame was received
|
|
||||||
push_frame(decoder);
|
|
||||||
} else if (ret != AVERROR(EAGAIN)) {
|
|
||||||
LOGE("Could not receive video frame: %d", ret);
|
|
||||||
goto run_quit;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
while (packet.size > 0) {
|
while (packet.size > 0) {
|
||||||
int got_picture;
|
int got_picture;
|
||||||
int len = avcodec_decode_video2(codec_ctx, decoder->frames->decoding_frame, &got_picture, &packet);
|
int len = avcodec_decode_video2(codec_ctx, decoder->frames->decoding_frame, &got_picture, &packet);
|
||||||
@@ -122,6 +114,18 @@ static int run_decoder(void *data) {
|
|||||||
packet.size -= len;
|
packet.size -= len;
|
||||||
packet.data += len;
|
packet.data += len;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
int ret;
|
||||||
|
if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) {
|
||||||
|
LOGE("Could not send video packet: %d", ret);
|
||||||
|
goto run_quit;
|
||||||
|
}
|
||||||
|
if ((ret = avcodec_receive_frame(codec_ctx, decoder->frames->decoding_frame)) < 0) {
|
||||||
|
LOGE("Could not receive video frame: %d", ret);
|
||||||
|
goto run_quit;
|
||||||
|
}
|
||||||
|
|
||||||
|
push_frame(decoder);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,8 +142,7 @@ run_finally_close_codec:
|
|||||||
run_finally_free_codec_ctx:
|
run_finally_free_codec_ctx:
|
||||||
avcodec_free_context(&codec_ctx);
|
avcodec_free_context(&codec_ctx);
|
||||||
notify_stopped();
|
notify_stopped();
|
||||||
run_end:
|
return ret;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket) {
|
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket) {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include "hidpi.h"
|
||||||
|
|
||||||
|
void hidpi_get_scale(struct screen *screen, struct hidpi_scale *scale) {
|
||||||
|
SDL_GL_GetDrawableSize(screen->window, &scale->horizontal.num, &scale->vertical.num);
|
||||||
|
SDL_GetWindowSize(screen->window, &scale->horizontal.div, &scale->vertical.div);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hidpi_unscale_coordinates(struct hidpi_scale *scale, Sint32 *x, Sint32 *y) {
|
||||||
|
// to unscale, we devide by the ratio (so num and div are reversed)
|
||||||
|
if (scale->horizontal.num) {
|
||||||
|
*x = ((Sint64) *x) * scale->horizontal.div / scale->horizontal.num;
|
||||||
|
}
|
||||||
|
if (scale->vertical.num) {
|
||||||
|
*y = ((Sint64) *y) * scale->vertical.div / scale->vertical.num;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef HIDPI_H
|
||||||
|
#define HIDPI_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "screen.h"
|
||||||
|
|
||||||
|
// rational number p/q
|
||||||
|
struct rational {
|
||||||
|
int num;
|
||||||
|
int div;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hidpi_scale {
|
||||||
|
struct rational horizontal; // drawable.width / window.width
|
||||||
|
struct rational vertical; // drawable.height / window.height
|
||||||
|
};
|
||||||
|
|
||||||
|
void hidpi_get_scale(struct screen *screen, struct hidpi_scale *hidpi_scale);
|
||||||
|
|
||||||
|
// mouse location need to be "unscaled" if hidpi is enabled
|
||||||
|
// <https://nlguillemot.wordpress.com/2016/12/11/high-dpi-rendering/>
|
||||||
|
void hidpi_unscale_coordinates(struct hidpi_scale *hidpi_scale, Sint32 *x, Sint32 *y);
|
||||||
|
|
||||||
|
#endif
|
||||||
+43
-48
@@ -1,6 +1,7 @@
|
|||||||
#include "inputmanager.h"
|
#include "inputmanager.h"
|
||||||
|
|
||||||
#include "convert.h"
|
#include "convert.h"
|
||||||
|
#include "hidpi.h"
|
||||||
#include "lockutil.h"
|
#include "lockutil.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
@@ -18,10 +19,19 @@ static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct point get_mouse_point(struct screen *screen) {
|
static struct point get_mouse_point(struct screen *screen) {
|
||||||
int x;
|
int mx;
|
||||||
int y;
|
int my;
|
||||||
SDL_GetMouseState(&x, &y);
|
SDL_GetMouseState(&mx, &my);
|
||||||
convert_to_renderer_coordinates(screen->renderer, &x, &y);
|
convert_to_renderer_coordinates(screen->renderer, &mx, &my);
|
||||||
|
|
||||||
|
struct hidpi_scale hidpi_scale;
|
||||||
|
hidpi_get_scale(screen, &hidpi_scale);
|
||||||
|
|
||||||
|
// SDL sometimes uses "int", sometimes "Sint32"
|
||||||
|
Sint32 x = mx;
|
||||||
|
Sint32 y = my;
|
||||||
|
hidpi_unscale_coordinates(&hidpi_scale, &x, &y);
|
||||||
|
|
||||||
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
|
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
|
||||||
return (struct point) {
|
return (struct point) {
|
||||||
.x = (Uint16) x,
|
.x = (Uint16) x,
|
||||||
@@ -36,11 +46,14 @@ static SDL_bool is_ctrl_down(void) {
|
|||||||
|
|
||||||
static void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
|
static void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
|
||||||
// send DOWN event
|
// send DOWN event
|
||||||
struct control_event control_event;
|
struct control_event control_event = {
|
||||||
control_event.type = CONTROL_EVENT_TYPE_KEYCODE;
|
.type = CONTROL_EVENT_TYPE_KEYCODE,
|
||||||
control_event.keycode_event.action = AKEY_EVENT_ACTION_DOWN;
|
.keycode_event = {
|
||||||
control_event.keycode_event.keycode = keycode;
|
.action = AKEY_EVENT_ACTION_DOWN,
|
||||||
control_event.keycode_event.metastate = 0;
|
.keycode = keycode,
|
||||||
|
.metastate = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (!controller_push_event(controller, &control_event)) {
|
if (!controller_push_event(controller, &control_event)) {
|
||||||
LOGW("Cannot send %s (DOWN)", name);
|
LOGW("Cannot send %s (DOWN)", name);
|
||||||
@@ -79,10 +92,12 @@ static inline void action_volume_down(struct controller *controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void turn_screen_on(struct controller *controller) {
|
static void turn_screen_on(struct controller *controller) {
|
||||||
struct control_event control_event;
|
struct control_event control_event = {
|
||||||
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
|
.type = CONTROL_EVENT_TYPE_COMMAND,
|
||||||
control_event.command_event.action = CONTROL_EVENT_COMMAND_SCREEN_ON;
|
.command_event = {
|
||||||
|
.action = CONTROL_EVENT_COMMAND_SCREEN_ON,
|
||||||
|
},
|
||||||
|
};
|
||||||
if (!controller_push_event(controller, &control_event)) {
|
if (!controller_push_event(controller, &control_event)) {
|
||||||
LOGW("Cannot turn screen on");
|
LOGW("Cannot turn screen on");
|
||||||
}
|
}
|
||||||
@@ -100,27 +115,6 @@ static void switch_fps_counter_state(struct frames *frames) {
|
|||||||
mutex_unlock(frames->mutex);
|
mutex_unlock(frames->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clipboard_paste(struct controller *controller) {
|
|
||||||
char *text = SDL_GetClipboardText();
|
|
||||||
if (!text) {
|
|
||||||
LOGW("Cannot get clipboard text: %s", SDL_GetError());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!*text) {
|
|
||||||
// empty text
|
|
||||||
SDL_free(text);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct control_event control_event;
|
|
||||||
control_event.type = CONTROL_EVENT_TYPE_TEXT;
|
|
||||||
control_event.text_event.text = text;
|
|
||||||
if (!controller_push_event(controller, &control_event)) {
|
|
||||||
SDL_free(text);
|
|
||||||
LOGW("Cannot send clipboard paste event");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void input_manager_process_text_input(struct input_manager *input_manager,
|
void input_manager_process_text_input(struct input_manager *input_manager,
|
||||||
const SDL_TextInputEvent *event) {
|
const SDL_TextInputEvent *event) {
|
||||||
if (is_ctrl_down()) {
|
if (is_ctrl_down()) {
|
||||||
@@ -137,11 +131,8 @@ void input_manager_process_text_input(struct input_manager *input_manager,
|
|||||||
|
|
||||||
struct control_event control_event;
|
struct control_event control_event;
|
||||||
control_event.type = CONTROL_EVENT_TYPE_TEXT;
|
control_event.type = CONTROL_EVENT_TYPE_TEXT;
|
||||||
control_event.text_event.text = SDL_strdup(event->text);
|
strncpy(control_event.text_event.text, event->text, TEXT_MAX_LENGTH);
|
||||||
if (!control_event.text_event.text) {
|
control_event.text_event.text[TEXT_MAX_LENGTH] = '\0';
|
||||||
LOGW("Cannot strdup input text");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!controller_push_event(input_manager->controller, &control_event)) {
|
if (!controller_push_event(input_manager->controller, &control_event)) {
|
||||||
LOGW("Cannot send text event");
|
LOGW("Cannot send text event");
|
||||||
}
|
}
|
||||||
@@ -149,24 +140,23 @@ void input_manager_process_text_input(struct input_manager *input_manager,
|
|||||||
|
|
||||||
void input_manager_process_key(struct input_manager *input_manager,
|
void input_manager_process_key(struct input_manager *input_manager,
|
||||||
const SDL_KeyboardEvent *event) {
|
const SDL_KeyboardEvent *event) {
|
||||||
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
|
SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
|
||||||
|
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||||
|
SDL_bool repeat = event->repeat;
|
||||||
|
|
||||||
// capture all Ctrl events
|
// capture all Ctrl events
|
||||||
if (ctrl) {
|
if (ctrl) {
|
||||||
SDL_bool repeat = event->repeat;
|
|
||||||
|
|
||||||
// only consider keydown events, and ignore repeated events
|
// only consider keydown events, and ignore repeated events
|
||||||
if (repeat || event->type != SDL_KEYDOWN) {
|
if (repeat || event->type != SDL_KEYDOWN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
|
||||||
if (shift) {
|
if (shift) {
|
||||||
// currently, there is no shortcut implying SHIFT
|
// currently, there is no shortcut implying SHIFT
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case SDLK_h:
|
case SDLK_h:
|
||||||
action_home(input_manager->controller);
|
action_home(input_manager->controller);
|
||||||
@@ -181,9 +171,6 @@ void input_manager_process_key(struct input_manager *input_manager,
|
|||||||
case SDLK_p:
|
case SDLK_p:
|
||||||
action_power(input_manager->controller);
|
action_power(input_manager->controller);
|
||||||
return;
|
return;
|
||||||
case SDLK_v:
|
|
||||||
clipboard_paste(input_manager->controller);
|
|
||||||
return;
|
|
||||||
case SDLK_f:
|
case SDLK_f:
|
||||||
screen_switch_fullscreen(input_manager->screen);
|
screen_switch_fullscreen(input_manager->screen);
|
||||||
return;
|
return;
|
||||||
@@ -215,8 +202,12 @@ void 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct hidpi_scale hidpi_scale;
|
||||||
|
hidpi_get_scale(input_manager->screen, &hidpi_scale);
|
||||||
|
|
||||||
struct control_event control_event;
|
struct control_event control_event;
|
||||||
if (mouse_motion_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
|
if (mouse_motion_from_sdl_to_android(event, input_manager->screen->frame_size, &hidpi_scale, &control_event)) {
|
||||||
if (!controller_push_event(input_manager->controller, &control_event)) {
|
if (!controller_push_event(input_manager->controller, &control_event)) {
|
||||||
LOGW("Cannot send mouse motion event");
|
LOGW("Cannot send mouse motion event");
|
||||||
}
|
}
|
||||||
@@ -229,8 +220,12 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
|
|||||||
turn_screen_on(input_manager->controller);
|
turn_screen_on(input_manager->controller);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct hidpi_scale hidpi_scale;
|
||||||
|
hidpi_get_scale(input_manager->screen, &hidpi_scale);
|
||||||
|
|
||||||
struct control_event control_event;
|
struct control_event control_event;
|
||||||
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
|
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &hidpi_scale, &control_event)) {
|
||||||
if (!controller_push_event(input_manager->controller, &control_event)) {
|
if (!controller_push_event(input_manager->controller, &control_event)) {
|
||||||
LOGW("Cannot send mouse button event");
|
LOGW("Cannot send mouse button event");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,9 +82,6 @@ static void usage(const char *arg0) {
|
|||||||
" Right-click\n"
|
" Right-click\n"
|
||||||
" turn screen on\n"
|
" turn screen on\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+v\n"
|
|
||||||
" paste computer clipboard to device\n"
|
|
||||||
"\n"
|
|
||||||
" Ctrl+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",
|
||||||
|
|||||||
+2
-5
@@ -140,12 +140,9 @@ SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, s
|
|||||||
screen->frame_size = frame_size;
|
screen->frame_size = frame_size;
|
||||||
|
|
||||||
struct size window_size = get_initial_optimal_size(frame_size);
|
struct size window_size = get_initial_optimal_size(frame_size);
|
||||||
Uint32 window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
|
|
||||||
#ifdef HIDPI_SUPPORT
|
|
||||||
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
|
|
||||||
#endif
|
|
||||||
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||||
window_size.width, window_size.height, window_flags);
|
window_size.width, window_size.height,
|
||||||
|
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||||
if (!screen->window) {
|
if (!screen->window) {
|
||||||
LOGC("Could not create window: %s", SDL_GetError());
|
LOGC("Could not create window: %s", SDL_GetError());
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
@@ -3,4 +3,3 @@ option('build_server', type: 'boolean', value: true, description: 'Build the ser
|
|||||||
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
||||||
option('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')
|
option('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')
|
||||||
option('skip_frames', type: 'boolean', value: true, description: 'Always display the most recent frame')
|
option('skip_frames', type: 'boolean', value: true, description: 'Always display the most recent frame')
|
||||||
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
|
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ public class ControlEventReader {
|
|||||||
private static final int SCROLL_PAYLOAD_LENGTH = 16;
|
private static final int SCROLL_PAYLOAD_LENGTH = 16;
|
||||||
private static final int COMMAND_PAYLOAD_LENGTH = 1;
|
private static final int COMMAND_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
private static final int TEXT_MAX_LENGTH = 256;
|
private static final int MAX_TEXT_LENGTH = 32;
|
||||||
private static final int RAW_BUFFER_SIZE = 128;
|
private static final int RAW_BUFFER_SIZE = 128;
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
private final byte[] textBuffer = new byte[TEXT_MAX_LENGTH];
|
private final byte[] textBuffer = new byte[MAX_TEXT_LENGTH];
|
||||||
|
|
||||||
public ControlEventReader() {
|
public ControlEventReader() {
|
||||||
// invariant: the buffer is always in "get" mode
|
// invariant: the buffer is always in "get" mode
|
||||||
|
|||||||
@@ -93,12 +93,14 @@ public class EventController {
|
|||||||
return injectKeyEvent(action, keycode, 0, metaState);
|
return injectKeyEvent(action, keycode, 0, metaState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectChar(char c) {
|
private boolean injectText(String text) {
|
||||||
String decomposed = KeyComposition.decompose(c);
|
return injectText(text, true);
|
||||||
char[] chars = decomposed != null ? decomposed.toCharArray() : new char[] {c};
|
}
|
||||||
KeyEvent[] events = charMap.getEvents(chars);
|
|
||||||
|
private boolean injectText(String text, boolean decomposeOnFailure) {
|
||||||
|
KeyEvent[] events = charMap.getEvents(text.toCharArray());
|
||||||
if (events == null) {
|
if (events == null) {
|
||||||
return false;
|
return decomposeOnFailure ? injectDecomposition(text) : false;
|
||||||
}
|
}
|
||||||
for (KeyEvent event : events) {
|
for (KeyEvent event : events) {
|
||||||
if (!injectEvent(event)) {
|
if (!injectEvent(event)) {
|
||||||
@@ -108,9 +110,10 @@ public class EventController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectText(String text) {
|
private boolean injectDecomposition(String text) {
|
||||||
for (char c : text.toCharArray()) {
|
for (char c : text.toCharArray()) {
|
||||||
if (!injectChar(c)) {
|
String composedText = KeyComposition.decompose(c);
|
||||||
|
if (composedText == null || !injectText(composedText, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.util.Map;
|
|||||||
* This is useful for injecting key events to generate the expected character ({@link android.view.KeyCharacterMap#getEvents(char[])}
|
* This is useful for injecting key events to generate the expected character ({@link android.view.KeyCharacterMap#getEvents(char[])}
|
||||||
* KeyCharacterMap.getEvents()} returns {@code null} with input {@code "é"} but works with input {@code "\u0301e"}).
|
* KeyCharacterMap.getEvents()} returns {@code null} with input {@code "é"} but works with input {@code "\u0301e"}).
|
||||||
* <p>
|
* <p>
|
||||||
* See <a href="https://source.android.com/devices/input/key-character-map-files#behaviors">diacritical dead key characters</a>.
|
* See <a href="https://source.android.com/devices/input/key-character-map-files#key-declarations">diacritical dead key characters</a>.
|
||||||
*/
|
*/
|
||||||
public final class KeyComposition {
|
public final class KeyComposition {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user