commit
bce7e33a5b
Binary file not shown.
Binary file not shown.
@ -0,0 +1,84 @@
|
||||
#include <QX11Info>
|
||||
|
||||
#include <xcb/xproto.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "xmousetap.h"
|
||||
|
||||
XMouseTap::XMouseTap() {}
|
||||
|
||||
XMouseTap::~XMouseTap() {}
|
||||
|
||||
void XMouseTap::initMouseEventTap() {}
|
||||
|
||||
void XMouseTap::quitMouseEventTap() {}
|
||||
|
||||
static void find_grab_window_recursive(xcb_connection_t *dpy, xcb_window_t window,
|
||||
QRect rc, int16_t offset_x, int16_t offset_y,
|
||||
xcb_window_t *grab_window, uint32_t *grab_window_size) {
|
||||
xcb_query_tree_cookie_t tree_cookie;
|
||||
xcb_query_tree_reply_t *tree;
|
||||
tree_cookie = xcb_query_tree(dpy, window);
|
||||
tree = xcb_query_tree_reply(dpy, tree_cookie, NULL);
|
||||
|
||||
xcb_window_t *children = xcb_query_tree_children(tree);
|
||||
for (int i = 0; i < xcb_query_tree_children_length(tree); i++) {
|
||||
xcb_get_geometry_cookie_t gg_cookie;
|
||||
xcb_get_geometry_reply_t *gg;
|
||||
gg_cookie = xcb_get_geometry(dpy, children[i]);
|
||||
gg = xcb_get_geometry_reply(dpy, gg_cookie, NULL);
|
||||
|
||||
if (gg->x + offset_x <= rc.left() && gg->x + offset_x + gg->width >= rc.right() &&
|
||||
gg->y + offset_y <= rc.top() && gg->y + offset_y + gg->height >= rc.bottom()) {
|
||||
if (!*grab_window || gg->width * gg->height <= *grab_window_size) {
|
||||
*grab_window = children[i];
|
||||
*grab_window_size = gg->width * gg->height;
|
||||
}
|
||||
}
|
||||
|
||||
find_grab_window_recursive(dpy, children[i], rc,
|
||||
gg->x + offset_x, gg->y + offset_y,
|
||||
grab_window, grab_window_size);
|
||||
|
||||
free(gg);
|
||||
}
|
||||
|
||||
free(tree);
|
||||
}
|
||||
|
||||
void XMouseTap::enableMouseEventTap(QRect rc, bool enabled) {
|
||||
if (enabled && rc.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_connection_t *dpy = QX11Info::connection();
|
||||
|
||||
if (enabled) {
|
||||
// We grab the top-most smallest window
|
||||
xcb_window_t grab_window = 0;
|
||||
uint32_t grab_window_size = 0;
|
||||
|
||||
find_grab_window_recursive(dpy, QX11Info::appRootWindow(QX11Info::appScreen()),
|
||||
rc, 0, 0, &grab_window, &grab_window_size);
|
||||
|
||||
if (grab_window) {
|
||||
xcb_grab_pointer_cookie_t grab_cookie;
|
||||
xcb_grab_pointer_reply_t *grab;
|
||||
grab_cookie = xcb_grab_pointer(dpy, /* owner_events = */ 1,
|
||||
grab_window, /* event_mask = */ 0,
|
||||
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
|
||||
grab_window, XCB_NONE, XCB_CURRENT_TIME);
|
||||
grab = xcb_grab_pointer_reply(dpy, grab_cookie, NULL);
|
||||
|
||||
free(grab);
|
||||
}
|
||||
} else {
|
||||
xcb_void_cookie_t ungrab_cookie;
|
||||
xcb_generic_error_t *error;
|
||||
ungrab_cookie = xcb_ungrab_pointer_checked(dpy, XCB_CURRENT_TIME);
|
||||
error = xcb_request_check(dpy, ungrab_cookie);
|
||||
|
||||
free(error);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
#ifndef XMOUSETAP_H
|
||||
#define XMOUSETAP_H
|
||||
|
||||
#include "mousetap.h"
|
||||
|
||||
class XMouseTap : public MouseTap
|
||||
{
|
||||
public:
|
||||
XMouseTap();
|
||||
virtual ~XMouseTap();
|
||||
|
||||
void initMouseEventTap() override;
|
||||
void quitMouseEventTap() override;
|
||||
void enableMouseEventTap(QRect rc, bool enabled) override;
|
||||
};
|
||||
|
||||
#endif // XMOUSETAP_H
|
||||
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2008, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.content;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IOnPrimaryClipChangedListener {
|
||||
void dispatchPrimaryClipChanged();
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.ContentProvider;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Handle the cleanup of scrcpy, even if the main process is killed.
|
||||
* <p>
|
||||
* This is useful to restore some state when scrcpy is closed, even on device disconnection (which kills the scrcpy process).
|
||||
*/
|
||||
public final class CleanUp {
|
||||
|
||||
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
||||
|
||||
private CleanUp() {
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
public static void configure(boolean disableShowTouches, int restoreStayOn) throws IOException {
|
||||
boolean needProcess = disableShowTouches || restoreStayOn != -1;
|
||||
if (needProcess) {
|
||||
startProcess(disableShowTouches, restoreStayOn);
|
||||
} else {
|
||||
// There is no additional clean up to do when scrcpy dies
|
||||
unlinkSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private static void startProcess(boolean disableShowTouches, int restoreStayOn) throws IOException {
|
||||
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(restoreStayOn)};
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||
builder.environment().put("CLASSPATH", SERVER_PATH);
|
||||
builder.start();
|
||||
}
|
||||
|
||||
private static void unlinkSelf() {
|
||||
try {
|
||||
new File(SERVER_PATH).delete();
|
||||
} catch (Exception e) {
|
||||
Ln.e("Could not unlink server", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String... args) {
|
||||
unlinkSelf();
|
||||
|
||||
try {
|
||||
// Wait for the server to die
|
||||
System.in.read();
|
||||
} catch (IOException e) {
|
||||
// Expected when the server is dead
|
||||
}
|
||||
|
||||
Ln.i("Cleaning up");
|
||||
|
||||
boolean disableShowTouches = Boolean.parseBoolean(args[0]);
|
||||
int restoreStayOn = Integer.parseInt(args[1]);
|
||||
|
||||
if (disableShowTouches || restoreStayOn != -1) {
|
||||
ServiceManager serviceManager = new ServiceManager();
|
||||
try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) {
|
||||
if (disableShowTouches) {
|
||||
Ln.i("Disabling \"show touches\"");
|
||||
settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0");
|
||||
}
|
||||
if (restoreStayOn != -1) {
|
||||
Ln.i("Restoring \"stay awake\"");
|
||||
settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(restoreStayOn));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CodecOption {
|
||||
private String key;
|
||||
private Object value;
|
||||
|
||||
public CodecOption(String key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static List<CodecOption> parse(String codecOptions) {
|
||||
if ("-".equals(codecOptions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<CodecOption> result = new ArrayList<>();
|
||||
|
||||
boolean escape = false;
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
for (char c : codecOptions.toCharArray()) {
|
||||
switch (c) {
|
||||
case '\\':
|
||||
if (escape) {
|
||||
buf.append('\\');
|
||||
escape = false;
|
||||
} else {
|
||||
escape = true;
|
||||
}
|
||||
break;
|
||||
case ',':
|
||||
if (escape) {
|
||||
buf.append(',');
|
||||
escape = false;
|
||||
} else {
|
||||
// This comma is a separator between codec options
|
||||
String codecOption = buf.toString();
|
||||
result.add(parseOption(codecOption));
|
||||
// Clear buf
|
||||
buf.setLength(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
buf.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (buf.length() > 0) {
|
||||
String codecOption = buf.toString();
|
||||
result.add(parseOption(codecOption));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static CodecOption parseOption(String option) {
|
||||
int equalSignIndex = option.indexOf('=');
|
||||
if (equalSignIndex == -1) {
|
||||
throw new IllegalArgumentException("'=' expected");
|
||||
}
|
||||
String keyAndType = option.substring(0, equalSignIndex);
|
||||
if (keyAndType.length() == 0) {
|
||||
throw new IllegalArgumentException("Key may not be null");
|
||||
}
|
||||
|
||||
String key;
|
||||
String type;
|
||||
|
||||
int colonIndex = keyAndType.indexOf(':');
|
||||
if (colonIndex != -1) {
|
||||
key = keyAndType.substring(0, colonIndex);
|
||||
type = keyAndType.substring(colonIndex + 1);
|
||||
} else {
|
||||
key = keyAndType;
|
||||
type = "int"; // assume int by default
|
||||
}
|
||||
|
||||
Object value;
|
||||
String valueString = option.substring(equalSignIndex + 1);
|
||||
switch (type) {
|
||||
case "int":
|
||||
value = Integer.parseInt(valueString);
|
||||
break;
|
||||
case "long":
|
||||
value = Long.parseLong(valueString);
|
||||
break;
|
||||
case "float":
|
||||
value = Float.parseFloat(valueString);
|
||||
break;
|
||||
case "string":
|
||||
value = valueString;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid codec option type (int, long, float, str): " + type);
|
||||
}
|
||||
|
||||
return new CodecOption(key, value);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public class InvalidDisplayIdException extends RuntimeException {
|
||||
|
||||
private final int displayId;
|
||||
private final int[] availableDisplayIds;
|
||||
|
||||
public InvalidDisplayIdException(int displayId, int[] availableDisplayIds) {
|
||||
super("There is no display having id " + displayId);
|
||||
this.displayId = displayId;
|
||||
this.availableDisplayIds = availableDisplayIds;
|
||||
}
|
||||
|
||||
public int getDisplayId() {
|
||||
return displayId;
|
||||
}
|
||||
|
||||
public int[] getAvailableDisplayIds() {
|
||||
return availableDisplayIds;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import com.genymobile.scrcpy.Ln;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ActivityManager {
|
||||
|
||||
private final IInterface manager;
|
||||
private Method getContentProviderExternalMethod;
|
||||
private boolean getContentProviderExternalMethodLegacy;
|
||||
private Method removeContentProviderExternalMethod;
|
||||
|
||||
public ActivityManager(IInterface manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
private Method getGetContentProviderExternalMethod() throws NoSuchMethodException {
|
||||
if (getContentProviderExternalMethod == null) {
|
||||
try {
|
||||
getContentProviderExternalMethod = manager.getClass()
|
||||
.getMethod("getContentProviderExternal", String.class, int.class, IBinder.class, String.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// old version
|
||||
getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class);
|
||||
getContentProviderExternalMethodLegacy = true;
|
||||
}
|
||||
}
|
||||
return getContentProviderExternalMethod;
|
||||
}
|
||||
|
||||
private Method getRemoveContentProviderExternalMethod() throws NoSuchMethodException {
|
||||
if (removeContentProviderExternalMethod == null) {
|
||||
removeContentProviderExternalMethod = manager.getClass().getMethod("removeContentProviderExternal", String.class, IBinder.class);
|
||||
}
|
||||
return removeContentProviderExternalMethod;
|
||||
}
|
||||
|
||||
private ContentProvider getContentProviderExternal(String name, IBinder token) {
|
||||
try {
|
||||
Method method = getGetContentProviderExternalMethod();
|
||||
Object[] args;
|
||||
if (!getContentProviderExternalMethodLegacy) {
|
||||
// new version
|
||||
args = new Object[]{name, ServiceManager.USER_ID, token, null};
|
||||
} else {
|
||||
// old version
|
||||
args = new Object[]{name, ServiceManager.USER_ID, token};
|
||||
}
|
||||
// ContentProviderHolder providerHolder = getContentProviderExternal(...);
|
||||
Object providerHolder = method.invoke(manager, args);
|
||||
if (providerHolder == null) {
|
||||
return null;
|
||||
}
|
||||
// IContentProvider provider = providerHolder.provider;
|
||||
Field providerField = providerHolder.getClass().getDeclaredField("provider");
|
||||
providerField.setAccessible(true);
|
||||
Object provider = providerField.get(providerHolder);
|
||||
if (provider == null) {
|
||||
return null;
|
||||
}
|
||||
return new ContentProvider(this, provider, name, token);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | NoSuchFieldException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void removeContentProviderExternal(String name, IBinder token) {
|
||||
try {
|
||||
Method method = getRemoveContentProviderExternalMethod();
|
||||
method.invoke(manager, name, token);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
|
||||
public ContentProvider createSettingsProvider() {
|
||||
return getContentProviderExternal("settings", new Binder());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import com.genymobile.scrcpy.Ln;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ContentProvider implements Closeable {
|
||||
|
||||
public static final String TABLE_SYSTEM = "system";
|
||||
public static final String TABLE_SECURE = "secure";
|
||||
public static final String TABLE_GLOBAL = "global";
|
||||
|
||||
// See android/providerHolder/Settings.java
|
||||
private static final String CALL_METHOD_GET_SYSTEM = "GET_system";
|
||||
private static final String CALL_METHOD_GET_SECURE = "GET_secure";
|
||||
private static final String CALL_METHOD_GET_GLOBAL = "GET_global";
|
||||
|
||||
private static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
|
||||
private static final String CALL_METHOD_PUT_SECURE = "PUT_secure";
|
||||
private static final String CALL_METHOD_PUT_GLOBAL = "PUT_global";
|
||||
|
||||
private static final String CALL_METHOD_USER_KEY = "_user";
|
||||
|
||||
private static final String NAME_VALUE_TABLE_VALUE = "value";
|
||||
|
||||
private final ActivityManager manager;
|
||||
// android.content.IContentProvider
|
||||
private final Object provider;
|
||||
private final String name;
|
||||
private final IBinder token;
|
||||
|
||||
private Method callMethod;
|
||||
private boolean callMethodLegacy;
|
||||
|
||||
ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
|
||||
this.manager = manager;
|
||||
this.provider = provider;
|
||||
this.name = name;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
private Method getCallMethod() throws NoSuchMethodException {
|
||||
if (callMethod == null) {
|
||||
try {
|
||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// old version
|
||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
|
||||
callMethodLegacy = true;
|
||||
}
|
||||
}
|
||||
return callMethod;
|
||||
}
|
||||
|
||||
private Bundle call(String callMethod, String arg, Bundle extras) {
|
||||
try {
|
||||
Method method = getCallMethod();
|
||||
Object[] args;
|
||||
if (!callMethodLegacy) {
|
||||
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
|
||||
} else {
|
||||
args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
|
||||
}
|
||||
return (Bundle) method.invoke(provider, args);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
manager.removeContentProviderExternal(name, token);
|
||||
}
|
||||
|
||||
private static String getGetMethod(String table) {
|
||||
switch (table) {
|
||||
case TABLE_SECURE:
|
||||
return CALL_METHOD_GET_SECURE;
|
||||
case TABLE_SYSTEM:
|
||||
return CALL_METHOD_GET_SYSTEM;
|
||||
case TABLE_GLOBAL:
|
||||
return CALL_METHOD_GET_GLOBAL;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid table: " + table);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPutMethod(String table) {
|
||||
switch (table) {
|
||||
case TABLE_SECURE:
|
||||
return CALL_METHOD_PUT_SECURE;
|
||||
case TABLE_SYSTEM:
|
||||
return CALL_METHOD_PUT_SYSTEM;
|
||||
case TABLE_GLOBAL:
|
||||
return CALL_METHOD_PUT_GLOBAL;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid table: " + table);
|
||||
}
|
||||
}
|
||||
|
||||
public String getValue(String table, String key) {
|
||||
String method = getGetMethod(table);
|
||||
Bundle arg = new Bundle();
|
||||
arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
|
||||
Bundle bundle = call(method, key, arg);
|
||||
if (bundle == null) {
|
||||
return null;
|
||||
}
|
||||
return bundle.getString("value");
|
||||
}
|
||||
|
||||
public void putValue(String table, String key, String value) {
|
||||
String method = getPutMethod(table);
|
||||
Bundle arg = new Bundle();
|
||||
arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
|
||||
arg.putString(NAME_VALUE_TABLE_VALUE, value);
|
||||
call(method, key, arg);
|
||||
}
|
||||
|
||||
public String getAndPutValue(String table, String key, String value) {
|
||||
String oldValue = getValue(table, key);
|
||||
if (!value.equals(oldValue)) {
|
||||
putValue(table, key, value);
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Loading…
Reference in new issue