diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 802dab5e..1b81d05e 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -498,10 +498,18 @@ Default is "lalt,lsuper" (left-Alt or left-Super). .BI "\-\-start\-app " name Start an Android app, by its exact package name. +Add a '?' prefix to select an app whose name starts with the given name, case-insensitive (retrieving app names on the device may take some time): + + scrcpy --start-app=?firefox + Add a '+' prefix to force-stop before starting the app: scrcpy --new-display --start-app=+org.mozilla.firefox +Both prefixes can be used, in that order: + + scrcpy --start-app=+?firefox + .TP .B \-t, \-\-show\-touches Enable "show touches" on start, restore the initial value on exit. diff --git a/app/src/cli.c b/app/src/cli.c index d715a385..4b9be5d8 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -812,8 +812,14 @@ static const struct sc_option options[] = { .longopt = "start-app", .argdesc = "name", .text = "Start an Android app, by its exact package name.\n" + "Add a '?' prefix to select an app whose name starts with the " + "given name, case-insensitive (retrieving app names on the " + "device may take some time):\n" + " scrcpy --start-app=?firefox\n" "Add a '+' prefix to force-stop before starting the app:\n" - " scrcpy --new-display --start-app=+org.mozilla.firefox", + " scrcpy --new-display --start-app=+org.mozilla.firefox\n" + "Both prefixes can be used, in that order:\n" + " scrcpy --start-app=+?firefox", }, { .shortopt = 't', diff --git a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java index b3ab34c3..0fdb6064 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java @@ -8,6 +8,7 @@ import com.genymobile.scrcpy.device.DeviceApp; import com.genymobile.scrcpy.device.Point; import com.genymobile.scrcpy.device.Position; import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.LogUtils; import com.genymobile.scrcpy.video.VirtualDisplayListener; import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.InputManager; @@ -23,6 +24,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import java.io.IOException; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -599,10 +601,31 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener { name = name.substring(1); } - DeviceApp app = Device.findByPackageName(name); - if (app == null) { - Ln.w("No app found for package \"" + name + "\""); - return; + DeviceApp app; + boolean searchByName = name.startsWith("?"); + if (searchByName) { + name = name.substring(1); + + Ln.i("Processing Android apps... (this may take some time)"); + List apps = Device.findByName(name); + if (apps.isEmpty()) { + Ln.w("No app found for name \"" + name + "\""); + return; + } + + if (apps.size() > 1) { + String title = "No unique app found for name \"" + name + "\":"; + Ln.w(LogUtils.buildAppListMessage(title, apps)); + return; + } + + app = apps.get(0); + } else { + app = Device.findByPackageName(name); + if (app == null) { + Ln.w("No app found for package \"" + name + "\""); + return; + } } int startAppDisplayId = getStartAppDisplayId(); diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Device.java b/server/src/main/java/com/genymobile/scrcpy/device/Device.java index f51a433e..a2699076 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java @@ -27,6 +27,7 @@ import android.view.KeyEvent; import java.util.ArrayList; import java.util.List; +import java.util.Locale; public final class Device { @@ -264,6 +265,23 @@ public final class Device { return null; } + @SuppressLint("QueryPermissionsNeeded") + public static List findByName(String searchName) { + List result = new ArrayList<>(); + searchName = searchName.toLowerCase(Locale.getDefault()); + + PackageManager pm = FakeContext.get().getPackageManager(); + for (ApplicationInfo appInfo : getLaunchableApps(pm)) { + String name = pm.getApplicationLabel(appInfo).toString(); + if (name.toLowerCase(Locale.getDefault()).startsWith(searchName)) { + boolean system = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + result.add(new DeviceApp(appInfo.packageName, name, system)); + } + } + + return result; + } + public static void startApp(String packageName, int displayId, boolean forceStop) { PackageManager pm = FakeContext.get().getPackageManager();