Compare commits
1 Commits
meizu
...
mainlooper
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da4c93582 |
@@ -144,7 +144,12 @@ handle_event(SDL_Event *event, bool control) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
screen_handle_window_event(&screen, &event->window);
|
switch (event->window.event) {
|
||||||
|
case SDL_WINDOWEVENT_EXPOSED:
|
||||||
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
|
screen_render(&screen);
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_TEXTINPUT:
|
case SDL_TEXTINPUT:
|
||||||
if (!control) {
|
if (!control) {
|
||||||
@@ -213,7 +218,6 @@ event_loop(bool display, bool control) {
|
|||||||
case EVENT_RESULT_STOPPED_BY_USER:
|
case EVENT_RESULT_STOPPED_BY_USER:
|
||||||
return true;
|
return true;
|
||||||
case EVENT_RESULT_STOPPED_BY_EOS:
|
case EVENT_RESULT_STOPPED_BY_EOS:
|
||||||
LOGW("Device disconnected");
|
|
||||||
return false;
|
return false;
|
||||||
case EVENT_RESULT_CONTINUE:
|
case EVENT_RESULT_CONTINUE:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -30,28 +30,23 @@ get_window_size(SDL_Window *window) {
|
|||||||
// get the windowed window size
|
// get the windowed window size
|
||||||
static struct size
|
static struct size
|
||||||
get_windowed_window_size(const struct screen *screen) {
|
get_windowed_window_size(const struct screen *screen) {
|
||||||
if (screen->fullscreen || screen->maximized) {
|
if (screen->fullscreen) {
|
||||||
return screen->windowed_window_size;
|
return screen->windowed_window_size;
|
||||||
}
|
}
|
||||||
return get_window_size(screen->window);
|
return get_window_size(screen->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the windowed window size if fullscreen and maximized are disabled
|
|
||||||
static void
|
|
||||||
apply_windowed_size(struct screen *screen) {
|
|
||||||
if (!screen->fullscreen && !screen->maximized) {
|
|
||||||
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width,
|
|
||||||
screen->windowed_window_size.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the window size to be applied when fullscreen is disabled
|
// set the window size to be applied when fullscreen is disabled
|
||||||
static void
|
static void
|
||||||
set_window_size(struct screen *screen, struct size new_size) {
|
set_window_size(struct screen *screen, struct size new_size) {
|
||||||
// setting the window size during fullscreen is implementation defined,
|
// setting the window size during fullscreen is implementation defined,
|
||||||
// so apply the resize only after fullscreen is disabled
|
// so apply the resize only after fullscreen is disabled
|
||||||
|
if (screen->fullscreen) {
|
||||||
|
// SDL_SetWindowSize will be called when fullscreen will be disabled
|
||||||
screen->windowed_window_size = new_size;
|
screen->windowed_window_size = new_size;
|
||||||
apply_windowed_size(screen);
|
} else {
|
||||||
|
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||||
@@ -199,8 +194,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen->windowed_window_size = window_size;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +287,10 @@ screen_render(struct screen *screen) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_switch_fullscreen(struct screen *screen) {
|
screen_switch_fullscreen(struct screen *screen) {
|
||||||
|
if (!screen->fullscreen) {
|
||||||
|
// going to fullscreen, store the current windowed window size
|
||||||
|
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)) {
|
||||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||||
@@ -301,7 +298,11 @@ screen_switch_fullscreen(struct screen *screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
screen->fullscreen = !screen->fullscreen;
|
screen->fullscreen = !screen->fullscreen;
|
||||||
apply_windowed_size(screen);
|
if (!screen->fullscreen) {
|
||||||
|
// fullscreen disabled, restore expected windowed window size
|
||||||
|
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width,
|
||||||
|
screen->windowed_window_size.height);
|
||||||
|
}
|
||||||
|
|
||||||
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
||||||
screen_render(screen);
|
screen_render(screen);
|
||||||
@@ -309,75 +310,20 @@ screen_switch_fullscreen(struct screen *screen) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_resize_to_fit(struct screen *screen) {
|
screen_resize_to_fit(struct screen *screen) {
|
||||||
if (screen->fullscreen) {
|
if (!screen->fullscreen) {
|
||||||
return;
|
struct size optimal_size = get_optimal_window_size(screen,
|
||||||
}
|
screen->frame_size);
|
||||||
|
SDL_SetWindowSize(screen->window, optimal_size.width,
|
||||||
if (screen->maximized) {
|
optimal_size.height);
|
||||||
SDL_RestoreWindow(screen->window);
|
|
||||||
screen->maximized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct size optimal_size =
|
|
||||||
get_optimal_window_size(screen, screen->frame_size);
|
|
||||||
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
|
||||||
LOGD("Resized to optimal size");
|
LOGD("Resized to optimal size");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
screen_resize_to_pixel_perfect(struct screen *screen) {
|
screen_resize_to_pixel_perfect(struct screen *screen) {
|
||||||
if (screen->fullscreen) {
|
if (!screen->fullscreen) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (screen->maximized) {
|
|
||||||
SDL_RestoreWindow(screen->window);
|
|
||||||
screen->maximized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_SetWindowSize(screen->window, screen->frame_size.width,
|
SDL_SetWindowSize(screen->window, screen->frame_size.width,
|
||||||
screen->frame_size.height);
|
screen->frame_size.height);
|
||||||
LOGD("Resized to pixel-perfect");
|
LOGD("Resized to pixel-perfect");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
screen_handle_window_event(struct screen *screen,
|
|
||||||
const SDL_WindowEvent *event) {
|
|
||||||
switch (event->event) {
|
|
||||||
case SDL_WINDOWEVENT_EXPOSED:
|
|
||||||
screen_render(screen);
|
|
||||||
break;
|
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
||||||
if (!screen->fullscreen && !screen->maximized) {
|
|
||||||
// Backup the previous size: if we receive the MAXIMIZED event,
|
|
||||||
// then the new size must be ignored (it's the maximized size).
|
|
||||||
// We could not rely on the window flags due to race conditions
|
|
||||||
// (they could be updated asynchronously, at least on X11).
|
|
||||||
screen->windowed_window_size_backup =
|
|
||||||
screen->windowed_window_size;
|
|
||||||
|
|
||||||
// Save the windowed size, so that it is available once the
|
|
||||||
// window is maximized or fullscreen is enabled.
|
|
||||||
screen->windowed_window_size = get_window_size(screen->window);
|
|
||||||
}
|
|
||||||
screen_render(screen);
|
|
||||||
break;
|
|
||||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
|
||||||
// The backup size must be non-nul.
|
|
||||||
SDL_assert(screen->windowed_window_size_backup.width);
|
|
||||||
SDL_assert(screen->windowed_window_size_backup.height);
|
|
||||||
// Revert the last size, it was updated while screen was maximized.
|
|
||||||
screen->windowed_window_size = screen->windowed_window_size_backup;
|
|
||||||
#ifdef DEBUG
|
|
||||||
// Reset the backup to invalid values to detect unexpected usage
|
|
||||||
screen->windowed_window_size_backup.width = 0;
|
|
||||||
screen->windowed_window_size_backup.height = 0;
|
|
||||||
#endif
|
|
||||||
screen->maximized = true;
|
|
||||||
break;
|
|
||||||
case SDL_WINDOWEVENT_RESTORED:
|
|
||||||
screen->maximized = false;
|
|
||||||
apply_windowed_size(screen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,10 @@ struct screen {
|
|||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
SDL_Texture *texture;
|
SDL_Texture *texture;
|
||||||
struct size frame_size;
|
struct size frame_size;
|
||||||
// The window size the last time it was not maximized or fullscreen.
|
//used only in fullscreen mode to know the windowed window size
|
||||||
struct size windowed_window_size;
|
struct size windowed_window_size;
|
||||||
// Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be
|
|
||||||
// able to revert the size to its non-maximized value.
|
|
||||||
struct size windowed_window_size_backup;
|
|
||||||
bool has_frame;
|
bool has_frame;
|
||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
bool maximized;
|
|
||||||
bool no_window;
|
bool no_window;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,13 +34,8 @@ struct screen {
|
|||||||
.width = 0, \
|
.width = 0, \
|
||||||
.height = 0, \
|
.height = 0, \
|
||||||
}, \
|
}, \
|
||||||
.windowed_window_size_backup = { \
|
|
||||||
.width = 0, \
|
|
||||||
.height = 0, \
|
|
||||||
}, \
|
|
||||||
.has_frame = false, \
|
.has_frame = false, \
|
||||||
.fullscreen = false, \
|
.fullscreen = false, \
|
||||||
.maximized = false, \
|
|
||||||
.no_window = false, \
|
.no_window = false, \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +76,4 @@ 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);
|
||||||
|
|
||||||
// react to window events
|
|
||||||
void
|
|
||||||
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
#endif
|
#endif
|
||||||
"/", // unused
|
"/", // unused
|
||||||
"com.genymobile.scrcpy.Server",
|
"com.genymobile.scrcpy.Server",
|
||||||
SCRCPY_VERSION,
|
|
||||||
max_size_string,
|
max_size_string,
|
||||||
bit_rate_string,
|
bit_rate_string,
|
||||||
server->tunnel_forward ? "true" : "false",
|
server->tunnel_forward ? "true" : "false",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.media.MediaCodec;
|
|||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
@@ -54,8 +55,10 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
|
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
|
||||||
Workarounds.prepareMainLooper();
|
// Some devices internally create a Handler when creating an input Surface, causing an exception:
|
||||||
Workarounds.fillAppInfo();
|
// "Can't create handler inside thread that has not called Looper.prepare()"
|
||||||
|
// <https://github.com/Genymobile/scrcpy/issues/240>
|
||||||
|
Looper.prepareMainLooper();
|
||||||
|
|
||||||
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
|
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
|
||||||
device.setRotationListener(this);
|
device.setRotationListener(this);
|
||||||
|
|||||||
@@ -67,39 +67,29 @@ public final class Server {
|
|||||||
|
|
||||||
@SuppressWarnings("checkstyle:MagicNumber")
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
private static Options createOptions(String... args) {
|
private static Options createOptions(String... args) {
|
||||||
if (args.length < 1) {
|
if (args.length != 6) {
|
||||||
throw new IllegalArgumentException("Missing client version");
|
throw new IllegalArgumentException("Expecting 6 parameters");
|
||||||
}
|
|
||||||
|
|
||||||
String clientVersion = args[0];
|
|
||||||
if (!clientVersion.equals(BuildConfig.VERSION_NAME)) {
|
|
||||||
throw new IllegalArgumentException("The server version (" + clientVersion + ") does not match the client "
|
|
||||||
+ "(" + BuildConfig.VERSION_NAME + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length != 7) {
|
|
||||||
throw new IllegalArgumentException("Expecting 7 parameters");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Options options = new Options();
|
Options options = new Options();
|
||||||
|
|
||||||
int maxSize = Integer.parseInt(args[1]) & ~7; // multiple of 8
|
int maxSize = Integer.parseInt(args[0]) & ~7; // multiple of 8
|
||||||
options.setMaxSize(maxSize);
|
options.setMaxSize(maxSize);
|
||||||
|
|
||||||
int bitRate = Integer.parseInt(args[2]);
|
int bitRate = Integer.parseInt(args[1]);
|
||||||
options.setBitRate(bitRate);
|
options.setBitRate(bitRate);
|
||||||
|
|
||||||
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
|
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
|
||||||
boolean tunnelForward = Boolean.parseBoolean(args[3]);
|
boolean tunnelForward = Boolean.parseBoolean(args[2]);
|
||||||
options.setTunnelForward(tunnelForward);
|
options.setTunnelForward(tunnelForward);
|
||||||
|
|
||||||
Rect crop = parseCrop(args[4]);
|
Rect crop = parseCrop(args[3]);
|
||||||
options.setCrop(crop);
|
options.setCrop(crop);
|
||||||
|
|
||||||
boolean sendFrameMeta = Boolean.parseBoolean(args[5]);
|
boolean sendFrameMeta = Boolean.parseBoolean(args[4]);
|
||||||
options.setSendFrameMeta(sendFrameMeta);
|
options.setSendFrameMeta(sendFrameMeta);
|
||||||
|
|
||||||
boolean control = Boolean.parseBoolean(args[6]);
|
boolean control = Boolean.parseBoolean(args[5]);
|
||||||
options.setControl(control);
|
options.setControl(control);
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.os.Looper;
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
|
|
||||||
public final class Workarounds {
|
|
||||||
private Workarounds() {
|
|
||||||
// not instantiable
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void prepareMainLooper() {
|
|
||||||
// Some devices internally create a Handler when creating an input Surface, causing an exception:
|
|
||||||
// "Can't create handler inside thread that has not called Looper.prepare()"
|
|
||||||
// <https://github.com/Genymobile/scrcpy/issues/240>
|
|
||||||
//
|
|
||||||
// Use Looper.prepareMainLooper() instead of Looper.prepare() to avoid a NullPointerException:
|
|
||||||
// "Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue'
|
|
||||||
// on a null object reference"
|
|
||||||
// <https://github.com/Genymobile/scrcpy/issues/921>
|
|
||||||
Looper.prepareMainLooper();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
|
||||||
public static void fillAppInfo() {
|
|
||||||
try {
|
|
||||||
// ActivityThread activityThread = new ActivityThread();
|
|
||||||
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
|
|
||||||
Constructor<?> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
|
|
||||||
activityThreadConstructor.setAccessible(true);
|
|
||||||
Object activityThread = activityThreadConstructor.newInstance();
|
|
||||||
|
|
||||||
// ActivityThread.sCurrentActivityThread = activityThread;
|
|
||||||
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
|
|
||||||
sCurrentActivityThreadField.setAccessible(true);
|
|
||||||
sCurrentActivityThreadField.set(null, activityThread);
|
|
||||||
|
|
||||||
// ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
|
|
||||||
Class<?> appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
|
|
||||||
Constructor<?> appBindDataConstructor = appBindDataClass.getDeclaredConstructor();
|
|
||||||
appBindDataConstructor.setAccessible(true);
|
|
||||||
Object appBindData = appBindDataConstructor.newInstance();
|
|
||||||
|
|
||||||
ApplicationInfo applicationInfo = new ApplicationInfo();
|
|
||||||
applicationInfo.packageName = "com.genymobile.scrcpy";
|
|
||||||
|
|
||||||
// appBindData.appInfo = applicationInfo;
|
|
||||||
Field appInfo = appBindDataClass.getDeclaredField("appInfo");
|
|
||||||
appInfo.setAccessible(true);
|
|
||||||
appInfo.set(appBindData, applicationInfo);
|
|
||||||
|
|
||||||
// activityThread.mBoundApplication = appBindData;
|
|
||||||
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
|
||||||
mBoundApplicationField.setAccessible(true);
|
|
||||||
mBoundApplicationField.set(activityThread, appBindData);
|
|
||||||
} catch (Throwable throwable) {
|
|
||||||
// this is a workaround, so failing is not an error
|
|
||||||
Ln.w("Could not fill app info: " + throwable.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user