scrcpy icon indicating copy to clipboard operation
scrcpy copied to clipboard

Draft: Add virtual display feature

Open rom1v opened this issue 1 year ago • 18 comments

This PR adds the possibility to start a new virtual display instead of mirroring the screen.

Implementation

There are 3 steps:

  1. refactors
  2. rework event handling for virtual displays
  3. add virtual display features

Refactors

The (7) firsts commits refactor to move code from Device to Controller and ScreenCapture, to prepare the next commits.

Events handling

The commit Inject display-related events to virtual display aims to fix #4598 and #5137 (based on the work by @mengyanshou in #5137 and #5214), also necessary for making virtual display events work.

Basically, for event injection, there are two display ids:

  • the source display id (--display-id=…, default to 0 for the main display)
  • the id of the virtual display used for mirroring

(In case the ScreenCapture uses the "SurfaceControl API", then both ids are equals, but this is an implementation detail.)

In order to make events work correctly in all cases:

  • the virtual display id must be used for events relative to the display (mouse and touch events with coordinates);
  • the source display id must be used for other events (like key events).

Virtual display

Then, based on the discussions in #1887 and the work by @yume-chan and @anirudhb:

  • https://github.com/yume-chan/scrcpy/commits/feat/virtual-display-3/ (https://github.com/Genymobile/scrcpy/issues/1887#issuecomment-1405184608)
  • https://github.com/anirudhb/scrcpy/commits/virtual-display/ (https://github.com/Genymobile/scrcpy/issues/1887#issuecomment-2240359603)

The commit Add virtual display feature implements a first version of virtual display support:

scrcpy --new-display=1920x1080
scrcpy --new-display=1920x1080/420  # force 420 dpi
scrcpy --new-display         # use the default screen size and density
scrcpy --new-display=/240    # use the default screen size and 240 dpi
scrcpy --new-display -m1920  # use the default screen size and density, scaled to fit in 1920

The size is fixed (not resizable).

On a Pixel 8, the virtual display contains a menu, so it is possible to open an app easily.

On other devices, it may be empty. In that case, you can start your app manually. Find the new display id is scrcpy logs (let's say it's 42), and start an app manually:

adb shell am start -a android.settings.SETTINGS --display 42

To start an app manually:

adb shell cmd package resolve-activity --brief org.mozilla.firefox | tail -n 1  # get the activity name
adb shell am start -S -n org.mozilla.firefox/.App --display 5

TODO

Start app

The feature really needs a new command to start an app, for example:

# not implemented, just an idea
scrcpy --new-display --start-app=firefox

I might need to rework how arguments are passed to the server (to start an app "My gallery" for example), because currently it does not support spaces or other special characters (see bec3321fff4c6dc3b3dbc61fdc6fd98913988a78).

I don't know if the expected behavior is to just have a virtual display and open any app (like on Pixel 8, where there is a "launcher"), or expose something like a "single app" feature.

It probably depends on the device (I don't know if it's possible to limit a single app on Pixel 8 for example, or to navigate to other apps on device without a launcher). What do you think? (also ref discussion https://github.com/Genymobile/scrcpy/issues/1887#issuecomment-1407012535 @4nric)

Detect failure

If I run scrcpy --new-display on a Nexus 5 with Android 6, it does not fail, it just waits indefinitely for a frame. How to detect that "it works"? Should we only use checks based on the Android version?


Draft v2

Start app

Here is a draft v2, with new options to list and start apps:

scrcpy --list-apps
scrcpy --new-display --start-app=org.mozilla.firefox  # start app by package name
scrcpy --new-display --start-app=firefox              # start app by name
scrcpy --start-app=firefox   # also works for real display mirroring
scrcpy --start-app=+firefox  # prefix by '+' to force-stop before starting the app

There is a performance issue on some devices to retrieve the app names (but not package names), because loadLabel() must be called for each app). As a consequence, --list-apps may take several seconds, and selecting an application by name may also take time. However, starting via package name is quick. Any solution welcome.

Binary

Here is a Windows binary:

draft v1 (old)

rom1v avatar Oct 12 '24 07:10 rom1v

Hi @rom1v, binary file cannot be downloaded

huynhtanloc2612 avatar Oct 12 '24 08:10 huynhtanloc2612

@huynhtanloc2612 Thanks, fixed.

rom1v avatar Oct 12 '24 08:10 rom1v

@rom1v There is a minor bug which is that the primary screen does not turn on again after closing scrcpy tool (something like when using with the option --no-cleanup) My command: scrcpy -SK --new-display=1920x1080/200

huynhtanloc2612 avatar Oct 12 '24 09:10 huynhtanloc2612

tested this on mac(m1pro) - works!

Steps I followed -

  • clone and switch to branch
  • install dependencies
  • build
  • install
  • tested for -this issue

vaddisrinivas avatar Oct 12 '24 16:10 vaddisrinivas

Thank you for your contribution.

Test platform: Mac(M2)/Meizu 21

Test way:

git fetch origin pull/5370/head:pr-5370
git checkout pr-5370
meson setup x --buildtype=release --strip -Db_lto=true
ninja -Cx  # DO NOT RUN AS ROOT
./run x --new-display=1920x1080

I mainly tested the following scenarios:

  • Without using the --new-display option, using scrcpy --display xxx to render the virtual display screen, the click events work normally.
  • Using scrcpy --new-display=1920x1080, the created virtual display can be clicked normally.

Due to this flag

VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS

On the Meizu device I tested, the Launcher will start on this virtual display. This is not an issue, but rather an unexpected benefit.

Here is a screenshot.

截屏2024-10-14 20 49 52

By the way, I have ported scrcpy to Android.

Once I complete the remaining work, I invite you to experience this software. Thank you very much.

mengyanshou avatar Oct 14 '24 12:10 mengyanshou

A small issue: IME will not appear on the virtual display unless the following setting is applied:

adb shell settings put global force_desktop_mode_on_external_displays 1

This is likely because the ImePolicy is not set to DISPLAY_IME_POLICY_LOCAL, as indicated by the getImePolicy() method.


Additionally, on some devices, the launcher displays incorrectly on non-default screens.

eiyooooo avatar Oct 14 '24 14:10 eiyooooo

Additionally, on some devices, the launcher displays incorrectly on non-default screens.

Launcher I tested that works: Lawnchair (playstore, github). Another instance of the launcher can be opened on secondary displays without additional flags. Good enough for quickly launching apps for now

scrcpy --new-display
adb shell am start -n app.lawnchair.play/app.lawnchair.LawnchairLauncher --display 12

https://github.com/user-attachments/assets/85058b48-8086-43ab-99e9-cfd41efbfcd4

4nric avatar Oct 15 '24 09:10 4nric

@rom1v thank you for working on this feature!

I'm getting permission issue on android 12. @yume-chan's proof-of-concept works on this device

[server] INFO: Device: [samsung] samsung SM-G975F (Android 12)
[server] ERROR: Could not create display
java.lang.SecurityException: Requires ADD_TRUSTED_DISPLAY permission to create a trusted virtual display.
        at android.os.Parcel.createExceptionOrNull(Parcel.java:2438)
        at android.os.Parcel.createException(Parcel.java:2422)
        at android.os.Parcel.readException(Parcel.java:2405)
        at android.os.Parcel.readException(Parcel.java:2347)
        at android.hardware.display.IDisplayManager$Stub$Proxy.createVirtualDisplay(IDisplayManager.java:2020)
        at android.hardware.display.DisplayManagerGlobal.createVirtualDisplay(DisplayManagerGlobal.java:694)
        at android.hardware.display.DisplayManagerGlobal.createVirtualDisplay(DisplayManagerGlobal.java:664)
        at android.hardware.display.DisplayManager.createVirtualDisplay(DisplayManager.java:1390)
        at android.hardware.display.DisplayManager.createVirtualDisplay(DisplayManager.java:1357)
        at android.hardware.display.DisplayManager.createVirtualDisplay(DisplayManager.java:1302)
        at com.genymobile.scrcpy.wrappers.DisplayManager.createNewVirtualDisplay(DisplayManager.java:138)
        at com.genymobile.scrcpy.video.NewDisplayCapture.start(NewDisplayCapture.java:95)
        at com.genymobile.scrcpy.video.SurfaceEncoder.streamCapture(SurfaceEncoder.java:89)
        at com.genymobile.scrcpy.video.SurfaceEncoder.lambda$start$0$com-genymobile-scrcpy-video-SurfaceEncoder(SurfaceEncoder.java:279)
        at com.genymobile.scrcpy.video.SurfaceEncoder$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
        at java.lang.Thread.run(Thread.java:920)
Caused by: android.os.RemoteException: Remote stack trace:
        at com.android.server.display.DisplayManagerService$BinderService.createVirtualDisplay(DisplayManagerService.java:4070)
        at android.hardware.display.IDisplayManager$Stub.onTransact(IDisplayManager.java:887)
        at android.os.Binder.execTransactInternal(Binder.java:1215)
        at android.os.Binder.execTransact(Binder.java:1179)

[server] ERROR: Exception on thread Thread[video,5,main]
java.lang.AssertionError: Could not create display
        at com.genymobile.scrcpy.video.NewDisplayCapture.start(NewDisplayCapture.java:100)
        at com.genymobile.scrcpy.video.SurfaceEncoder.streamCapture(SurfaceEncoder.java:89)
        at com.genymobile.scrcpy.video.SurfaceEncoder.lambda$start$0$com-genymobile-scrcpy-video-SurfaceEncoder(SurfaceEncoder.java:279)
        at com.genymobile.scrcpy.video.SurfaceEncoder$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
        at java.lang.Thread.run(Thread.java:920)
INFO: Renderer: direct3d
WARN: Device disconnected
ERROR: Demuxer 'audio': stream disabled due to connection error

4nric avatar Oct 15 '24 10:10 4nric

We probably need to remove this flag for Android < 13 (according to c7b04a6b19a86b11b8c857a5fb775b0e16b50ebf):

diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
index 0622998ed..9440199ce 100644
--- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
+++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
@@ -85,7 +85,6 @@ public class NewDisplayCapture extends SurfaceCapture {
                     | VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
                     | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
                     | VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
-                    | VIRTUAL_DISPLAY_FLAG_TRUSTED
                     | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
                     | VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
                     | VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED

rom1v avatar Oct 15 '24 10:10 rom1v

We probably need to remove this flag for Android < 13 (according to c7b04a6):

diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
index 0622998ed..9440199ce 100644
--- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
+++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
@@ -85,7 +85,6 @@ public class NewDisplayCapture extends SurfaceCapture {
                     | VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
                     | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
                     | VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
-                    | VIRTUAL_DISPLAY_FLAG_TRUSTED
                     | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
                     | VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
                     | VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED

~~According to my research, the flags VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP, VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED, and VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED require at least Android 12L (Build.VERSION_CODES.S_V2) to be used.~~

~~The flags VIRTUAL_DISPLAY_FLAG_TRUSTED, VIRTUAL_DISPLAY_FLAG_OWN_FOCUS and VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP require at least Android 13 (Build.VERSION_CODES.TIRAMISU) to be used.~~

eiyooooo avatar Oct 15 '24 11:10 eiyooooo

@eiyooooo The flag exists, but apparently in Android 12, shell does not have permission to create a virtual display with this flag enabled.

rom1v avatar Oct 15 '24 13:10 rom1v

@eiyooooo The flag exists, but apparently in Android 12, shell does not have permission to create a virtual display with this flag enabled.

Yes, at least Android 13 is required to use the VIRTUAL_DISPLAY_FLAG_TRUSTED flag.

eiyooooo avatar Oct 15 '24 13:10 eiyooooo

Yes, at least Android 12L is required to use the VIRTUAL_DISPLAY_FLAG_TRUSTED flag.

If you don't pass it, what happens?

rom1v avatar Oct 15 '24 13:10 rom1v

Yes, at least Android 12L is required to use the VIRTUAL_DISPLAY_FLAG_TRUSTED flag.

If you don't pass it, what happens?

Based on my tests, the flags VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS, VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP, VIRTUAL_DISPLAY_FLAG_OWN_FOCUS, and VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP require a trusted virtual display to function properly.

eiyooooo avatar Oct 15 '24 14:10 eiyooooo

scrcpy.exe --new-display
scrcpy 2.7 <https://github.com/Genymobile/scrcpy>
INFO: ADB device found:
INFO:     -->   (usb)  R5CN210NXME                     device  SM_G986U1
C:\q\software\scrcpy-win64-v2.4\scrcpy-win64-v2.7-35-g765e...file pushed, 0 skipped. 106.6 MB/s (72764 bytes in 0.001s)
[server] INFO: Device: [samsung] samsung SM-G986U1 (Android 13)
INFO: Renderer: direct3d
INFO: Texture: 1920x1080
[server] INFO: New display id: 2
adb shell am start --display 2 -n com.samsung.android.app.notes/com.samsung.android.app.notes.memolist.MemoListActivity
Starting: Intent { cmp=com.samsung.android.app.notes/.memolist.MemoListActivity }

Exception occurred while executing 'start':
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference
        at com.android.server.wm.MultiTaskingTaskLaunchParamsModifier.calculate(MultiTaskingTaskLaunchParamsModifier.java:228)
        at com.android.server.wm.MultiTaskingTaskLaunchParamsModifier.onCalculate(MultiTaskingTaskLaunchParamsModifier.java:154)
        at com.android.server.wm.LaunchParamsController.calculate(LaunchParamsController.java:142)
        at com.android.server.wm.ActivityStarter.computeLaunchParams(ActivityStarter.java:3110)
        at com.android.server.wm.ActivityStarter.startActivityInner(ActivityStarter.java:2658)
        at com.android.server.wm.ActivityStarter.startActivityUnchecked(ActivityStarter.java:2470)
        at com.android.server.wm.ActivityStarter.executeRequest(ActivityStarter.java:1808)
        at com.android.server.wm.ActivityStarter.execute(ActivityStarter.java:1026)
        at com.android.server.wm.ActivityTaskManagerService.startActivityAsUser(ActivityTaskManagerService.java:1943)
        at com.android.server.wm.ActivityTaskManagerService.startActivityAsUser(ActivityTaskManagerService.java:1816)
        at com.android.server.am.ActivityManagerService.startActivityAsUserWithFeature(ActivityManagerService.java:3747)        at com.android.server.am.ActivityManagerShellCommand.runStartActivity(ActivityManagerShellCommand.java:656)
        at com.android.server.am.ActivityManagerShellCommand.onCommand(ActivityManagerShellCommand.java:222)
        at com.android.modules.utils.BasicShellCommandHandler.exec(BasicShellCommandHandler.java:97)
        at android.os.ShellCommand.exec(ShellCommand.java:38)
        at com.android.server.am.ActivityManagerService.onShellCommand(ActivityManagerService.java:11276)
        at android.os.Binder.shellCommand(Binder.java:1085)
        at android.os.Binder.onTransact(Binder.java:903)
        at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:5643)
        at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3135)
        at android.os.Binder.execTransactInternal(Binder.java:1321)
        at android.os.Binder.execTransact(Binder.java:1280)

On the S20+ (Android 13), after multiple attempts to create virtual displays using scrcpy.exe --new-display, the display ID always remains 2, and apps fail to start in the virtual display created by scrcpy when the VIRTUAL_DISPLAY_FLAG_OWN_FOCUS flag is set. 😞 However, when this flag is not set, everything works fine.

eiyooooo avatar Oct 16 '24 08:10 eiyooooo

I added features to list and start apps. I updated the main post, and also published a binary for this new version.

Please test and give feedback :wink:

rom1v avatar Oct 19 '24 17:10 rom1v

@eiyooooo I added a commit to fix flags depending on Android versions: 5ee42eab85a3aac28d2cbcced001f011f5cd3be2

However, I have no device with Android 12 or 12L to test, so don't hesitate to suggest a patch with the correct flags.

rom1v avatar Oct 19 '24 17:10 rom1v

I added a commit to fix flags depending on Android versions: 5ee42ea

However, I have no device with Android 12 or 12L to test, so don't hesitate to suggest a patch with the correct flags.

After digging deeper, I found that Android 12L still encounters the following issue:

java.lang.SecurityException: Requires ADD_TRUSTED_DISPLAY permission to create a trusted virtual display.

References:

Therefore, I think this part of the code:

https://github.com/Genymobile/scrcpy/blob/5ee42eab85a3aac28d2cbcced001f011f5cd3be2/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java#L89

should be updated to:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {

eiyooooo avatar Oct 19 '24 18:10 eiyooooo

@eiyooooo OK.

Commit that granted Shell the ADD_TRUSTED_DISPLAY permission

Indeed, it has been merged in Android 13:

# in aosp/framework_base
git tag --contains 990e3429636382175ca8e7bf04df054f48fbd130

I first added readable Android version constants (already merged into dev): 3acffaae57238ee47e05f97f8e762a04550fdad8 (the Android way to select a version was a mess, I had to check the mapping every time).

Then I set the TRUSTED and other flags only since Android 13: e05be0ba17a1a48275eef19d92f1c729b35a0c6d

rom1v avatar Oct 20 '24 11:10 rom1v

I first added readable Android version constants (already merged into dev): 3acffaa (the Android way to select a version was a mess, I had to check the mapping every time).

@rom1v By the way, Android 15 has been released, so you'll need to add a new Android version constant. 😄

public static final int API_35_ANDROID_15 = Build.VERSION_CODES.VANILLA_ICE_CREAM;

eiyooooo avatar Oct 20 '24 11:10 eiyooooo

@eiyooooo Done: a46150f753c47d0fb180040448629d229ae74581

rom1v avatar Oct 20 '24 13:10 rom1v

Given that loading the app names may take time (several seconds), by default, I reworked so that --start-app only accepts package names by default:

scrpcy --start-app=org.mozilla.firefox

For convenience, however, it is still possible to explicitly select an app by its name (but with some delay) by adding a ? prefix:

scrcpy --start-app=?firefox

Like in the previous version, a + prefix force-stops the app before starting:

scrcpy --new-display --start-app=+org.mozilla.firefox   # by package name
scrcpy --new-display --start-app=+?firefox              # by name

rom1v avatar Oct 20 '24 14:10 rom1v

Seems like we need to also call getLeanbackLaunchIntentForPackage (https://developer.android.com/reference/android/content/pm/PackageManager#getLeanbackLaunchIntentForPackage(java.lang.String)) for Android TV apps (https://stackoverflow.com/questions/30446052/getlaunchintentforpackage-is-null-for-some-apps).

You can also call them in listApps to only return launchable apps.

yume-chan avatar Oct 20 '24 14:10 yume-chan

Seems like we need to also call getLeanbackLaunchIntentForPackage (https://developer.android.com/reference/android/content/pm/PackageManager#getLeanbackLaunchIntentForPackage(java.lang.String)) for Android TV apps (https://stackoverflow.com/questions/30446052/getlaunchintentforpackage-is-null-for-some-apps).

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 f98988e8c..73f2d312c 100644
--- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java
+++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java
@@ -261,7 +261,11 @@ public final class Device {
     }
 
     public static void startApp(String packageName, int displayId, boolean forceStop) {
-        Intent launchIntent = FakeContext.get().getPackageManager().getLaunchIntentForPackage(packageName);
+        PackageManager pm = FakeContext.get().getPackageManager();
+
+        // Use Leanback launch intent for TV
+        boolean hasLeanback = pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+        Intent launchIntent = hasLeanback ? pm.getLeanbackLaunchIntentForPackage(packageName) : pm.getLaunchIntentForPackage(packageName);
         if (launchIntent == null) {
             Ln.w("Cannot create launch intent for app " + packageName);
             return;

?

Or should we only use it if the "normal" launch intent failed? (I have no Android TV to test)

You can also call them in listApps to only return launchable apps.

Oh, that's a great filter to only get relevant apps! I will do that.

Maybe there is something "lighter" to know if an app is launchable without actually creating the intent? (I have not found such a method).

rom1v avatar Oct 20 '24 15:10 rom1v

Or should we only use it if the "normal" launch intent failed? (I have no Android TV to test)

From that StackOverflow question, it's up to the app to have whether "normal" launch intent or lean launch intent, so normal apps on Android TVs should also use "normal" launch intent, and we should try both on all devices.

Maybe there is something "lighter" to know if an app is launchable without actually creating the intent?

Settings app (https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java;l=371-374;drc=2825589cb6ad9cdb4fa50367916cdad5614f0e40) (and launcher apps, I believe) also use getLaunchIntentForPackage to determine if an app is launchable, so it should be the only way.

yume-chan avatar Oct 20 '24 15:10 yume-chan

:+1:

Then I will do something like:

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 f98988e8c..25005081b 100644
--- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java
+++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java
@@ -261,7 +261,7 @@ public final class Device {
     }
 
     public static void startApp(String packageName, int displayId, boolean forceStop) {
-        Intent launchIntent = FakeContext.get().getPackageManager().getLaunchIntentForPackage(packageName);
+        Intent launchIntent = getLaunchIntent(packageName);
         if (launchIntent == null) {
             Ln.w("Cannot create launch intent for app " + packageName);
             return;
@@ -283,4 +283,14 @@ public final class Device {
         am.startActivity(launchIntent, options);
     }
 
+    public static Intent getLaunchIntent(String packageName) {
+        PackageManager pm = FakeContext.get().getPackageManager();
+
+        Intent launchIntent = pm.getLaunchIntentForPackage(packageName);
+        if (launchIntent != null) {
+            return launchIntent;
+        }
+
+        return pm.getLeanbackLaunchIntentForPackage(packageName);
+    }
 }

and

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 25005081b..de5675898 100644
--- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java
+++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java
@@ -219,7 +219,9 @@ public final class Device {
         List<DeviceApp> apps = new ArrayList<>();
         PackageManager pm = FakeContext.get().getPackageManager();
         for (ApplicationInfo appInfo : pm.getInstalledApplications(PackageManager.GET_META_DATA)) {
-            apps.add(toApp(pm, appInfo));
+            if (getLaunchIntent(appInfo.packageName) != null) {
+                apps.add(toApp(pm, appInfo));
+            }
         }
 
         return apps;
@@ -250,10 +252,12 @@ public final class Device {
 
         PackageManager pm = FakeContext.get().getPackageManager();
         for (ApplicationInfo appInfo : pm.getInstalledApplications(PackageManager.GET_META_DATA)) {
-            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));
+            if (getLaunchIntent(appInfo.packageName) != null) {
+                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));
+                }
             }
         }
 

rom1v avatar Oct 20 '24 15:10 rom1v

Done :heavy_check_mark:

Btw, I don't know if the audio output is related to a specific display. For example, if I run Firefox in a new virtual display, and VLC on my physical device, I still get the VLC audio through scrcpy (I would like the audio of Firefox in scrcpy, and the audio of VLC on the physical device).

rom1v avatar Oct 20 '24 15:10 rom1v

I don't know if the audio output is related to a specific display

I don't know that either.

Audio Policy API can capture audio from specific apps using RULE_MATCH_UID, replacing here:

https://github.com/Genymobile/scrcpy/blob/665ccb32f5306ebd866dc0d99f4d08ed2aeb91c3/server/src/main/java/com/genymobile/scrcpy/audio/AudioPlaybackCapture.java#L47-L50

https://github.com/legendsayantan/ShizuTools/blob/f0783bd76e87b28e8cb59e38ae8493f31f2a27e2/app/src/main/java/com/legendsayantan/adbtools/lib/PlayBackThread.kt#L62-L71 is an app that implements per-app volume control using Media Projection API (which uses Audio Policy API internally) (I know this app before, but I haven't tried it).

It would work for the "single app mode", but if user launches another app on the virtual display later, it's complex to track and create new rules for them.

yume-chan avatar Oct 20 '24 16:10 yume-chan

I reworked some commits. I pushed a new version and here is a new binary:

I removed the Draft: prefix, I consider that it is ready to be merged.

One question: I don't know if a new display works on Android 10, so I'm not sure 10fab000758d00bcd70767764b9a0fd13a0b256c is correct.

Reviews and tests welcome :wink:

This PR refactors a lot of things related to displays and events, so if you could use this version even without using the virtual display feature, that would be helpful to detect issues :sunglasses:

rom1v avatar Oct 23 '24 17:10 rom1v

i dont know if this is intended but if you do "scrcpy.exe --new-display=1920x1080/240 --max-size=1280" the stream resolution is 1080p instead of 720p as its ignoring the max size option.

streaming in high res increases the delay by a lot while choosing a lower virtual display res decreases the quality in games. so the perfect solution would be to allow high VD res while streaming in a lower res.

tl;dr: "--new-display" should respect the "--max-size" option.

ghoste0001 avatar Oct 25 '24 22:10 ghoste0001