From db241065d1e1b0678da9b8e0785a73f329db0e98 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Oct 2024 15:49:25 +0200 Subject: [PATCH] Add option to start an app by its name By adding the '?' prefix, the app is searched by its name instead of its package name (it may take some time to retrieve the app names on the device): scrcpy --start-app=?firefox An app matches if its label starts with the given name, case-insensitive. If '+' is also passed to force-stop the app before starting, then the prefixes must be in that order: scrcpy --start-app=+?firefox --- app/scrcpy.1 | 8 +++++ app/src/cli.c | 8 ++++- .../genymobile/scrcpy/control/Controller.java | 31 ++++++++++++++++--- .../com/genymobile/scrcpy/device/Device.java | 20 ++++++++++++ 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 541a3f3b..c29e1f86 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -497,10 +497,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 (it may take some time to retrieve the app names on the device): + + 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 01b4c6fc..abacdc84 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -811,8 +811,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 (it may take some time to " + "retrieve the app names on the device):\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 8c6505c0..b7298e3a 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 8621e36b..47279511 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 { @@ -263,6 +264,25 @@ 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)) { + if (appInfo.enabled) { + 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, appInfo.enabled)); + } + } + } + + return result; + } + public static void startApp(String packageName, int displayId, boolean forceStop) { PackageManager pm = FakeContext.get().getPackageManager();