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 (retrieving app names on the device may take some time):

    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

PR #5370 <https://github.com/Genymobile/scrcpy/pull/5370>
This commit is contained in:
Romain Vimont 2024-10-20 15:49:25 +02:00
parent dd20efa41c
commit 566b5be0f6
4 changed files with 60 additions and 5 deletions

View File

@ -498,10 +498,18 @@ Default is "lalt,lsuper" (left-Alt or left-Super).
.BI "\-\-start\-app " name .BI "\-\-start\-app " name
Start an Android app, by its exact package 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: Add a '+' prefix to force-stop before starting the app:
scrcpy --new-display --start-app=+org.mozilla.firefox scrcpy --new-display --start-app=+org.mozilla.firefox
Both prefixes can be used, in that order:
scrcpy --start-app=+?firefox
.TP .TP
.B \-t, \-\-show\-touches .B \-t, \-\-show\-touches
Enable "show touches" on start, restore the initial value on exit. Enable "show touches" on start, restore the initial value on exit.

View File

@ -812,8 +812,14 @@ static const struct sc_option options[] = {
.longopt = "start-app", .longopt = "start-app",
.argdesc = "name", .argdesc = "name",
.text = "Start an Android app, by its exact package name.\n" .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" "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', .shortopt = 't',

View File

@ -8,6 +8,7 @@ import com.genymobile.scrcpy.device.DeviceApp;
import com.genymobile.scrcpy.device.Point; import com.genymobile.scrcpy.device.Point;
import com.genymobile.scrcpy.device.Position; import com.genymobile.scrcpy.device.Position;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.video.VirtualDisplayListener; import com.genymobile.scrcpy.video.VirtualDisplayListener;
import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
@ -23,6 +24,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -599,10 +601,31 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
name = name.substring(1); name = name.substring(1);
} }
DeviceApp app = Device.findByPackageName(name); DeviceApp app;
if (app == null) { boolean searchByName = name.startsWith("?");
Ln.w("No app found for package \"" + name + "\""); if (searchByName) {
return; name = name.substring(1);
Ln.i("Processing Android apps... (this may take some time)");
List<DeviceApp> 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(); int startAppDisplayId = getStartAppDisplayId();

View File

@ -27,6 +27,7 @@ import android.view.KeyEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
public final class Device { public final class Device {
@ -264,6 +265,23 @@ public final class Device {
return null; return null;
} }
@SuppressLint("QueryPermissionsNeeded")
public static List<DeviceApp> findByName(String searchName) {
List<DeviceApp> 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) { public static void startApp(String packageName, int displayId, boolean forceStop) {
PackageManager pm = FakeContext.get().getPackageManager(); PackageManager pm = FakeContext.get().getPackageManager();