Add --list-apps
Add an option to list all apps installed on the device: scrcpy --list-apps PR #5370 <https://github.com/Genymobile/scrcpy/pull/5370>
This commit is contained in:
parent
408a388fc5
commit
9c9d92fb1c
@ -33,6 +33,7 @@ _scrcpy() {
|
|||||||
--keyboard=
|
--keyboard=
|
||||||
--kill-adb-on-close
|
--kill-adb-on-close
|
||||||
--legacy-paste
|
--legacy-paste
|
||||||
|
--list-apps
|
||||||
--list-camera-sizes
|
--list-camera-sizes
|
||||||
--list-cameras
|
--list-cameras
|
||||||
--list-displays
|
--list-displays
|
||||||
|
@ -40,6 +40,7 @@ arguments=(
|
|||||||
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
||||||
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
||||||
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
||||||
|
'--list-apps[List Android apps installed on the device]'
|
||||||
'--list-camera-sizes[List the valid camera capture sizes]'
|
'--list-camera-sizes[List the valid camera capture sizes]'
|
||||||
'--list-cameras[List cameras available on the device]'
|
'--list-cameras[List cameras available on the device]'
|
||||||
'--list-displays[List displays available on the device]'
|
'--list-displays[List displays available on the device]'
|
||||||
|
@ -227,6 +227,10 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
|
|||||||
|
|
||||||
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-list\-apps
|
||||||
|
List Android apps installed on the device.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-list\-camera\-sizes
|
.B \-\-list\-camera\-sizes
|
||||||
List the valid camera capture sizes.
|
List the valid camera capture sizes.
|
||||||
|
@ -103,6 +103,7 @@ enum {
|
|||||||
OPT_AUDIO_DUP,
|
OPT_AUDIO_DUP,
|
||||||
OPT_GAMEPAD,
|
OPT_GAMEPAD,
|
||||||
OPT_NEW_DISPLAY,
|
OPT_NEW_DISPLAY,
|
||||||
|
OPT_LIST_APPS,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -443,6 +444,11 @@ static const struct sc_option options[] = {
|
|||||||
"This is a workaround for some devices not behaving as "
|
"This is a workaround for some devices not behaving as "
|
||||||
"expected when setting the device clipboard programmatically.",
|
"expected when setting the device clipboard programmatically.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_LIST_APPS,
|
||||||
|
.longopt = "list-apps",
|
||||||
|
.text = "List Android apps installed on the device.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_LIST_CAMERAS,
|
.longopt_id = OPT_LIST_CAMERAS,
|
||||||
.longopt = "list-cameras",
|
.longopt = "list-cameras",
|
||||||
@ -2611,6 +2617,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_LIST_CAMERA_SIZES:
|
case OPT_LIST_CAMERA_SIZES:
|
||||||
opts->list |= SC_OPTION_LIST_CAMERA_SIZES;
|
opts->list |= SC_OPTION_LIST_CAMERA_SIZES;
|
||||||
break;
|
break;
|
||||||
|
case OPT_LIST_APPS:
|
||||||
|
opts->list |= SC_OPTION_LIST_APPS;
|
||||||
|
break;
|
||||||
case OPT_REQUIRE_AUDIO:
|
case OPT_REQUIRE_AUDIO:
|
||||||
opts->require_audio = true;
|
opts->require_audio = true;
|
||||||
break;
|
break;
|
||||||
|
@ -304,6 +304,7 @@ struct scrcpy_options {
|
|||||||
#define SC_OPTION_LIST_DISPLAYS 0x2
|
#define SC_OPTION_LIST_DISPLAYS 0x2
|
||||||
#define SC_OPTION_LIST_CAMERAS 0x4
|
#define SC_OPTION_LIST_CAMERAS 0x4
|
||||||
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
||||||
|
#define SC_OPTION_LIST_APPS 0x10
|
||||||
uint8_t list;
|
uint8_t list;
|
||||||
bool window;
|
bool window;
|
||||||
bool mouse_hover;
|
bool mouse_hover;
|
||||||
|
@ -371,6 +371,9 @@ execute_server(struct sc_server *server,
|
|||||||
if (params->list & SC_OPTION_LIST_CAMERA_SIZES) {
|
if (params->list & SC_OPTION_LIST_CAMERA_SIZES) {
|
||||||
ADD_PARAM("list_camera_sizes=true");
|
ADD_PARAM("list_camera_sizes=true");
|
||||||
}
|
}
|
||||||
|
if (params->list & SC_OPTION_LIST_APPS) {
|
||||||
|
ADD_PARAM("list_apps=true");
|
||||||
|
}
|
||||||
|
|
||||||
#undef ADD_PARAM
|
#undef ADD_PARAM
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ public class Options {
|
|||||||
private boolean listDisplays;
|
private boolean listDisplays;
|
||||||
private boolean listCameras;
|
private boolean listCameras;
|
||||||
private boolean listCameraSizes;
|
private boolean listCameraSizes;
|
||||||
|
private boolean listApps;
|
||||||
|
|
||||||
// Options not used by the scrcpy client, but useful to use scrcpy-server directly
|
// Options not used by the scrcpy client, but useful to use scrcpy-server directly
|
||||||
private boolean sendDeviceMeta = true; // send device name and size
|
private boolean sendDeviceMeta = true; // send device name and size
|
||||||
@ -213,7 +214,7 @@ public class Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean getList() {
|
public boolean getList() {
|
||||||
return listEncoders || listDisplays || listCameras || listCameraSizes;
|
return listEncoders || listDisplays || listCameras || listCameraSizes || listApps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getListEncoders() {
|
public boolean getListEncoders() {
|
||||||
@ -232,6 +233,10 @@ public class Options {
|
|||||||
return listCameraSizes;
|
return listCameraSizes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getListApps() {
|
||||||
|
return listApps;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getSendDeviceMeta() {
|
public boolean getSendDeviceMeta() {
|
||||||
return sendDeviceMeta;
|
return sendDeviceMeta;
|
||||||
}
|
}
|
||||||
@ -395,6 +400,9 @@ public class Options {
|
|||||||
case "list_camera_sizes":
|
case "list_camera_sizes":
|
||||||
options.listCameraSizes = Boolean.parseBoolean(value);
|
options.listCameraSizes = Boolean.parseBoolean(value);
|
||||||
break;
|
break;
|
||||||
|
case "list_apps":
|
||||||
|
options.listApps = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
case "camera_id":
|
case "camera_id":
|
||||||
if (!value.isEmpty()) {
|
if (!value.isEmpty()) {
|
||||||
options.cameraId = value;
|
options.cameraId = value;
|
||||||
|
@ -292,6 +292,11 @@ public final class Server {
|
|||||||
Workarounds.apply();
|
Workarounds.apply();
|
||||||
Ln.i(LogUtils.buildCameraListMessage(options.getListCameraSizes()));
|
Ln.i(LogUtils.buildCameraListMessage(options.getListCameraSizes()));
|
||||||
}
|
}
|
||||||
|
if (options.getListApps()) {
|
||||||
|
Workarounds.apply();
|
||||||
|
Ln.i("Processing Android apps... (this may take some time)");
|
||||||
|
Ln.i(LogUtils.buildAppListMessage());
|
||||||
|
}
|
||||||
// Just print the requested data, do not mirror
|
// Just print the requested data, do not mirror
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.genymobile.scrcpy.device;
|
package com.genymobile.scrcpy.device;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.AndroidVersions;
|
import com.genymobile.scrcpy.AndroidVersions;
|
||||||
|
import com.genymobile.scrcpy.FakeContext;
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
||||||
import com.genymobile.scrcpy.wrappers.DisplayControl;
|
import com.genymobile.scrcpy.wrappers.DisplayControl;
|
||||||
@ -9,6 +10,10 @@ import com.genymobile.scrcpy.wrappers.ServiceManager;
|
|||||||
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
||||||
import com.genymobile.scrcpy.wrappers.WindowManager;
|
import com.genymobile.scrcpy.wrappers.WindowManager;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
@ -17,6 +22,9 @@ import android.view.InputEvent;
|
|||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public final class Device {
|
public final class Device {
|
||||||
|
|
||||||
public static final int DISPLAY_ID_NONE = -1;
|
public static final int DISPLAY_ID_NONE = -1;
|
||||||
@ -202,4 +210,37 @@ public final class Device {
|
|||||||
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||||
return displayInfo.getRotation();
|
return displayInfo.getRotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<DeviceApp> listApps() {
|
||||||
|
List<DeviceApp> apps = new ArrayList<>();
|
||||||
|
PackageManager pm = FakeContext.get().getPackageManager();
|
||||||
|
for (ApplicationInfo appInfo : getLaunchableApps(pm)) {
|
||||||
|
String name = pm.getApplicationLabel(appInfo).toString();
|
||||||
|
boolean system = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
||||||
|
apps.add(new DeviceApp(appInfo.packageName, name, system));
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("QueryPermissionsNeeded")
|
||||||
|
private static List<ApplicationInfo> getLaunchableApps(PackageManager pm) {
|
||||||
|
List<ApplicationInfo> result = new ArrayList<>();
|
||||||
|
for (ApplicationInfo appInfo : pm.getInstalledApplications(PackageManager.GET_META_DATA)) {
|
||||||
|
if (appInfo.enabled && getLaunchIntent(pm, appInfo.packageName) != null) {
|
||||||
|
result.add(appInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent getLaunchIntent(PackageManager pm, String packageName) {
|
||||||
|
Intent launchIntent = pm.getLaunchIntentForPackage(packageName);
|
||||||
|
if (launchIntent != null) {
|
||||||
|
return launchIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pm.getLeanbackLaunchIntentForPackage(packageName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.genymobile.scrcpy.device;
|
||||||
|
|
||||||
|
public final class DeviceApp {
|
||||||
|
|
||||||
|
private final String packageName;
|
||||||
|
private final String name;
|
||||||
|
private final boolean system;
|
||||||
|
|
||||||
|
public DeviceApp(String packageName, String name, boolean system) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.name = name;
|
||||||
|
this.system = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackageName() {
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSystem() {
|
||||||
|
return system;
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
package com.genymobile.scrcpy.util;
|
package com.genymobile.scrcpy.util;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.device.Device;
|
||||||
|
import com.genymobile.scrcpy.device.DeviceApp;
|
||||||
import com.genymobile.scrcpy.device.DisplayInfo;
|
import com.genymobile.scrcpy.device.DisplayInfo;
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.wrappers.DisplayManager;
|
import com.genymobile.scrcpy.wrappers.DisplayManager;
|
||||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.hardware.camera2.CameraAccessException;
|
import android.hardware.camera2.CameraAccessException;
|
||||||
import android.hardware.camera2.CameraCharacteristics;
|
import android.hardware.camera2.CameraCharacteristics;
|
||||||
@ -13,7 +16,9 @@ import android.hardware.camera2.params.StreamConfigurationMap;
|
|||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.util.Range;
|
import android.util.Range;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
@ -154,4 +159,53 @@ public final class LogUtils {
|
|||||||
}
|
}
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("QueryPermissionsNeeded")
|
||||||
|
public static String buildAppListMessage() {
|
||||||
|
StringBuilder builder = new StringBuilder("List of apps:");
|
||||||
|
|
||||||
|
List<DeviceApp> apps = Device.listApps();
|
||||||
|
|
||||||
|
// Sort by:
|
||||||
|
// 1. system flag (system apps are before non-system apps)
|
||||||
|
// 2. name
|
||||||
|
// 3. package name
|
||||||
|
// Comparator.comparing() was introduced in API 24, so it cannot be used here to simplify the code
|
||||||
|
Collections.sort(apps, (thisApp, otherApp) -> {
|
||||||
|
// System apps first
|
||||||
|
int cmp = -Boolean.compare(thisApp.isSystem(), otherApp.isSystem());
|
||||||
|
if (cmp != 0) {
|
||||||
|
return cmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmp = Objects.compare(thisApp.getName(), otherApp.getName(), String::compareTo);
|
||||||
|
if (cmp != 0) {
|
||||||
|
return cmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Objects.compare(thisApp.getPackageName(), otherApp.getPackageName(), String::compareTo);
|
||||||
|
});
|
||||||
|
|
||||||
|
final int column = 30;
|
||||||
|
for (DeviceApp app : apps) {
|
||||||
|
String name = app.getName();
|
||||||
|
int padding = column - name.length();
|
||||||
|
builder.append("\n ");
|
||||||
|
if (app.isSystem()) {
|
||||||
|
builder.append("* ");
|
||||||
|
} else {
|
||||||
|
builder.append("- ");
|
||||||
|
|
||||||
|
}
|
||||||
|
builder.append(name);
|
||||||
|
if (padding > 0) {
|
||||||
|
builder.append(String.format("%" + padding + "s", " "));
|
||||||
|
} else {
|
||||||
|
builder.append("\n ").append(String.format("%" + column + "s", " "));
|
||||||
|
}
|
||||||
|
builder.append(" [").append(app.getPackageName()).append(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user