diff options
| -rw-r--r-- | app/build.gradle | 1 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/ControlMessages.java | 5 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Controller.java | 17 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Mirror.java | 61 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Server.java | 2 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Session.java | 10 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Settings.java | 35 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/SettingsActivity.java | 40 | ||||
| -rw-r--r-- | app/src/main/res/layout/mirror.xml | 40 | ||||
| -rw-r--r-- | app/src/main/res/layout/settings.xml | 71 | ||||
| -rw-r--r-- | app/src/main/res/values/strings.xml | 13 | ||||
| -rw-r--r-- | fastlane/metadata/android/en-US/changelogs/5.txt | 2 | ||||
| -rw-r--r-- | fastlane/metadata/android/en-US/full_description.txt | 2 | ||||
| -rw-r--r-- | fastlane/metadata/android/en-US/images/phoneScreenshots/1.png | bin | 146272 -> 147797 bytes | |||
| -rw-r--r-- | fastlane/metadata/android/en-US/images/phoneScreenshots/2.png | bin | 163722 -> 163329 bytes |
15 files changed, 60 insertions, 239 deletions
diff --git a/app/build.gradle b/app/build.gradle index c75bfc0..219bf62 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,6 @@ android { buildTypes { debug { applicationIdSuffix '.debug' - versionNameSuffix '-debug' ndk { abiFilters 'arm64-v8a', 'x86_64' } diff --git a/app/src/main/java/invalid/lena/scrcpy/ControlMessages.java b/app/src/main/java/invalid/lena/scrcpy/ControlMessages.java index 6455a65..2a593d0 100644 --- a/app/src/main/java/invalid/lena/scrcpy/ControlMessages.java +++ b/app/src/main/java/invalid/lena/scrcpy/ControlMessages.java @@ -32,11 +32,6 @@ public final class ControlMessages { public static final int ACTION_DOWN = 0; public static final int ACTION_UP = 1; - // AOSP keycodes, mirrored rather than imported so this stays - // android-free. KeyEvent.KEYCODE_HOME / KEYCODE_APP_SWITCH. - public static final int KEYCODE_HOME = 3; - public static final int KEYCODE_APP_SWITCH = 187; - public static final int TOUCH_MSG_LEN = 32; // 1 + 1 + 8 + 4 + 4 + 2 + 2 + 2 + 4 + 4 public static final int KEY_MSG_LEN = 14; // 1 + 1 + 4 + 4 + 4 diff --git a/app/src/main/java/invalid/lena/scrcpy/Controller.java b/app/src/main/java/invalid/lena/scrcpy/Controller.java index 3cc321c..59dc84d 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Controller.java +++ b/app/src/main/java/invalid/lena/scrcpy/Controller.java @@ -189,23 +189,6 @@ public final class Controller implements ControlStream.InboundSink { sender.accept(ControlMessages.backOrScreenOn(ControlMessages.ACTION_UP)); } - // Home and Recents go as keycodes, not as gestures. On a - // gesture-navigation source a swipe from the bottom edge is claimed - // by the source's own gesture detector and never reaches this app, - // and unlike the side edges that area cannot be released with - // setSystemGestureExclusionRects - the system always keeps it. So - // the target's navigation is unreachable by forwarding touches, on - // any modern source device, and has to be driven explicitly. - public void onHome() { - sendKeycode(ControlMessages.ACTION_DOWN, ControlMessages.KEYCODE_HOME, 0, 0); - sendKeycode(ControlMessages.ACTION_UP, ControlMessages.KEYCODE_HOME, 0, 0); - } - - public void onRecents() { - sendKeycode(ControlMessages.ACTION_DOWN, ControlMessages.KEYCODE_APP_SWITCH, 0, 0); - sendKeycode(ControlMessages.ACTION_UP, ControlMessages.KEYCODE_APP_SWITCH, 0, 0); - } - public void resetVideo() { sender.accept(ControlMessages.resetVideo()); Log.i("controller: reset video"); diff --git a/app/src/main/java/invalid/lena/scrcpy/Mirror.java b/app/src/main/java/invalid/lena/scrcpy/Mirror.java index bc9db34..51e861a 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Mirror.java +++ b/app/src/main/java/invalid/lena/scrcpy/Mirror.java @@ -11,13 +11,13 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; -import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsController; import android.view.WindowManager; @@ -65,6 +65,7 @@ public final class Mirror extends Activity { private State state = State.CONNECTING; private int connectedW, connectedH; private long connectedGeometryVersion; + private int gestureBottomInset; private SurfaceView surfaceView; @@ -114,26 +115,25 @@ public final class Mirror extends Activity { // video surface, so re-fit from here as well. root.addOnLayoutChangeListener( (view, l, t, r, b, ol, ot, or, ob) -> applyLetterbox()); + // Keep the target's bottom edge above the source's mandatory Home + // gesture area. A target gesture can then start on the mirrored + // handle instead of being claimed by the source system. + root.setOnApplyWindowInsetsListener((view, insets) -> { + int bottom = insets.getInsetsIgnoringVisibility( + WindowInsets.Type.mandatorySystemGestures()).bottom; + if (gestureBottomInset != bottom) { + gestureBottomInset = bottom; + applyLetterbox(); + } + return insets; + }); + root.requestApplyInsets(); statusBar = findViewById(R.id.status_bar); statusText = findViewById(R.id.status_text); reconnectBtn = findViewById(R.id.reconnect); insetStatusBar(); reconnectBtn.setOnClickListener(view -> reconnect()); - // Back, Home and Recents as explicit controls. On a - // gesture-navigation source the target's own navigation cannot be - // reached by forwarding touches: the source claims the bottom - // edge swipe for itself and, unlike the side edges, that region - // cannot be released with setSystemGestureExclusionRects. - findViewById(R.id.nav_back).setOnClickListener(view -> { - if (session != null) session.onBack(); - }); - findViewById(R.id.nav_home).setOnClickListener(view -> { - if (session != null) session.onHome(); - }); - findViewById(R.id.nav_recents).setOnClickListener(view -> { - if (session != null) session.onRecents(); - }); updateStatusBar(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -220,22 +220,29 @@ public final class Mirror extends Activity { View v = surfaceView; if (v == null || root == null || session == null) return; int cw = root.getWidth(), ch = root.getHeight(); + int availableH = ch - gestureBottomInset; int tw = connectedW, th = connectedH; - if (cw <= 0 || ch <= 0 || tw <= 0 || th <= 0) return; + if (cw <= 0 || availableH <= 0 || tw <= 0 || th <= 0) return; - float scale = Math.min(cw / (float) tw, ch / (float) th); + float scale = Math.min(cw / (float) tw, availableH / (float) th); int w = Math.min(cw, Math.max(1, Math.round(tw * scale))); - int h = Math.min(ch, Math.max(1, Math.round(th * scale))); + int h = Math.min(availableH, Math.max(1, Math.round(th * scale))); + int x = (cw - w) / 2; + int y = (availableH - h) / 2; - ViewGroup.LayoutParams lp = v.getLayoutParams(); - if (lp.width != w || lp.height != h) { + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams(); + if (lp.width != w || lp.height != h || lp.leftMargin != x + || lp.topMargin != y || lp.gravity != (Gravity.TOP | Gravity.START)) { lp.width = w; lp.height = h; + lp.leftMargin = x; + lp.topMargin = y; + lp.gravity = Gravity.TOP | Gravity.START; v.setLayoutParams(lp); // re-layout re-enters here, then converges - Log.i("mirror: letterbox %dx%d -> %dx%d in %dx%d", tw, th, w, h, cw, ch); + Log.i("mirror: letterbox %dx%d -> %dx%d in %dx%d gesture_bottom=%d", + tw, th, w, h, cw, ch, gestureBottomInset); } - session.setViewport(connectedGeometryVersion, - (cw - w) / 2, (ch - h) / 2, w, h); + session.setViewport(connectedGeometryVersion, x, y, w, h); } private void detachSurface() { @@ -377,12 +384,10 @@ public final class Mirror extends Activity { statusText.setText(s); reconnectBtn.setVisibility(state == State.DISCONNECTED ? View.VISIBLE : View.GONE); - // Hide the whole bar while actively mirroring if the user opted - // out; keep it up whenever not CONNECTED so the status and the - // Reconnect button stay reachable. + // Status is only needed during bring-up and after a terminal error. + // Target navigation remains inside the mirrored frame. if (statusBar != null) { - boolean show = Settings.showStatusBar(this) || state != State.CONNECTED; - statusBar.setVisibility(show ? View.VISIBLE : View.GONE); + statusBar.setVisibility(state == State.CONNECTED ? View.GONE : View.VISIBLE); } } diff --git a/app/src/main/java/invalid/lena/scrcpy/Server.java b/app/src/main/java/invalid/lena/scrcpy/Server.java index dbc6738..f4d9b93 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Server.java +++ b/app/src/main/java/invalid/lena/scrcpy/Server.java @@ -183,7 +183,6 @@ public final class Server { String audioCodec = Settings.audioCodec(ctx); int maxSize = Settings.maxSize(ctx); int videoBitR = Settings.videoBitRate(ctx); - int maxFps = Settings.maxFps(ctx); List<String> args = new ArrayList<>(); args.add("CLASSPATH=" + REMOTE_PATH); args.add("app_process"); @@ -199,7 +198,6 @@ public final class Server { args.add("audio_codec=" + audioCodec); args.add("max_size=" + maxSize); args.add("video_bit_rate=" + videoBitR); - if (maxFps > 0) args.add("max_fps=" + maxFps); // Off means off at the source: the server never sends the // target's clipboard, rather than us receiving and discarding it. args.add("clipboard_autosync=" + Settings.clipboardSync(ctx)); diff --git a/app/src/main/java/invalid/lena/scrcpy/Session.java b/app/src/main/java/invalid/lena/scrcpy/Session.java index 73cecb7..2e8e005 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Session.java +++ b/app/src/main/java/invalid/lena/scrcpy/Session.java @@ -178,16 +178,6 @@ public final class Session { if (c != null) c.onBack(); } - public void onHome() { - Controller c = controller; - if (c != null) c.onHome(); - } - - public void onRecents() { - Controller c = controller; - if (c != null) c.onRecents(); - } - public void syncClipboard() { Controller c = controller; if (c != null) c.syncLocalClipboard(); diff --git a/app/src/main/java/invalid/lena/scrcpy/Settings.java b/app/src/main/java/invalid/lena/scrcpy/Settings.java index fab6ed1..70cf46a 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Settings.java +++ b/app/src/main/java/invalid/lena/scrcpy/Settings.java @@ -15,9 +15,7 @@ public final class Settings { public static final String AUDIO_CODEC = "audio_codec"; public static final String MAX_SIZE = "max_size"; // px, long edge; 0 = no cap public static final String VIDEO_BIT_RATE = "video_bit_rate"; // bits/sec - public static final String MAX_FPS = "max_fps"; // fps; 0 = unlimited public static final String HINT_BACK_SHOWN = "hint_back_shown"; // first-run UI hint - public static final String STATUS_BAR = "status_bar"; // show status bar while mirroring public static final String CLIPBOARD = "clipboard"; // two-way clipboard sync public static final String DEFAULT_VIDEO_CODEC = "h264"; @@ -30,14 +28,6 @@ public final class Settings { // uplink; raise both in Settings when both devices are on the LAN. public static final int DEFAULT_MAX_SIZE = 1080; public static final int DEFAULT_VIDEO_BIT_RATE = 4_000_000; - public static final int DEFAULT_MAX_FPS = 0; - // Shown by default. It carries Back, Home and Recents, which are the - // only way to reach the target's navigation: a swipe from the - // source's bottom edge is taken by the source's own gesture - // detector and never reaches us. Hiding it is a deliberate choice - // for an unobstructed picture, not the default, because a control - // the user cannot find is a control that does not exist. - public static final boolean DEFAULT_STATUS_BAR = true; // On by default: it is a headline feature and the target is one the // user deliberately paired with. It is a setting because that trust // is not absolute - a compromised target @@ -84,16 +74,6 @@ public final class Settings { } } - public static int maxFps(Context ctx) { - int value = integer(ctx, MAX_FPS, DEFAULT_MAX_FPS); - switch (value) { - case 0: case 30: case 60: case 90: case 120: - return value; - default: - return DEFAULT_MAX_FPS; - } - } - public static void setVideoCodec(Context ctx, String v) { if (!"h264".equals(v) && !"h265".equals(v) && !"av1".equals(v)) { throw new IllegalArgumentException("invalid video codec"); @@ -123,13 +103,6 @@ public final class Settings { prefs(ctx).edit().putInt(VIDEO_BIT_RATE, v).apply(); } - public static void setMaxFps(Context ctx, int v) { - if (v != 0 && v != 30 && v != 60 && v != 90 && v != 120) { - throw new IllegalArgumentException("invalid maximum frame rate"); - } - prefs(ctx).edit().putInt(MAX_FPS, v).apply(); - } - public static boolean hintBackShown(Context ctx) { return bool(ctx, HINT_BACK_SHOWN, false); } @@ -146,14 +119,6 @@ public final class Settings { prefs(ctx).edit().putBoolean(CLIPBOARD, v).apply(); } - public static boolean showStatusBar(Context ctx) { - return bool(ctx, STATUS_BAR, DEFAULT_STATUS_BAR); - } - - public static void setShowStatusBar(Context ctx, boolean v) { - prefs(ctx).edit().putBoolean(STATUS_BAR, v).apply(); - } - private static String string(Context ctx, String key, String fallback) { try { String value = prefs(ctx).getString(key, fallback); diff --git a/app/src/main/java/invalid/lena/scrcpy/SettingsActivity.java b/app/src/main/java/invalid/lena/scrcpy/SettingsActivity.java index b650bc4..fce776b 100644 --- a/app/src/main/java/invalid/lena/scrcpy/SettingsActivity.java +++ b/app/src/main/java/invalid/lena/scrcpy/SettingsActivity.java @@ -1,11 +1,14 @@ package invalid.lena.scrcpy; import android.app.Activity; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.os.Bundle; import android.view.WindowInsets; import android.widget.CheckBox; import android.widget.RadioButton; import android.widget.RadioGroup; +import android.widget.TextView; // Codec choice + streaming knobs. All persisted to SharedPreferences // via Settings on click; the next session bringup reads them and @@ -17,13 +20,12 @@ public final class SettingsActivity extends Activity { super.onCreate(saved); setContentView(R.layout.settings); Ui.padForInsets(findViewById(R.id.root), WindowInsets.Type.systemBars()); + showVersion(); RadioGroup videoGroup = findViewById(R.id.video_codec); RadioGroup audioGroup = findViewById(R.id.audio_codec); RadioGroup maxSizeGroup = findViewById(R.id.max_size); RadioGroup bitRateGroup = findViewById(R.id.video_bit_rate); - RadioGroup maxFpsGroup = findViewById(R.id.max_fps); - CheckBox statusBarBox = findViewById(R.id.show_status_bar); switch (Settings.videoCodec(this)) { case "h265": ((RadioButton) findViewById(R.id.video_h265)).setChecked(true); break; @@ -49,14 +51,6 @@ public final class SettingsActivity extends Activity { case 16_000_000: ((RadioButton) findViewById(R.id.bit_rate_16m)).setChecked(true); break; default: ((RadioButton) findViewById(R.id.bit_rate_4m)).setChecked(true); } - switch (Settings.maxFps(this)) { - case 30: ((RadioButton) findViewById(R.id.max_fps_30)).setChecked(true); break; - case 60: ((RadioButton) findViewById(R.id.max_fps_60)).setChecked(true); break; - case 90: ((RadioButton) findViewById(R.id.max_fps_90)).setChecked(true); break; - case 120: ((RadioButton) findViewById(R.id.max_fps_120)).setChecked(true); break; - default: ((RadioButton) findViewById(R.id.max_fps_0)).setChecked(true); - } - videoGroup.setOnCheckedChangeListener((g, id) -> { String v = "h264"; if (id == R.id.video_h265) v = "h265"; @@ -98,16 +92,6 @@ public final class SettingsActivity extends Activity { Log.i("settings: video_bit_rate=%d", v); }); - maxFpsGroup.setOnCheckedChangeListener((g, id) -> { - int v = 0; - if (id == R.id.max_fps_30) v = 30; - else if (id == R.id.max_fps_60) v = 60; - else if (id == R.id.max_fps_90) v = 90; - else if (id == R.id.max_fps_120) v = 120; - Settings.setMaxFps(this, v); - Log.i("settings: max_fps=%d", v); - }); - findViewById(R.id.licenses).setOnClickListener(v -> startActivity(new android.content.Intent(this, Licenses.class))); @@ -118,10 +102,16 @@ public final class SettingsActivity extends Activity { Log.i("settings: clipboard=%b", checked); }); - statusBarBox.setChecked(Settings.showStatusBar(this)); - statusBarBox.setOnCheckedChangeListener((b, checked) -> { - Settings.setShowStatusBar(this, checked); - Log.i("settings: status_bar=%b", checked); - }); + } + + @SuppressWarnings("deprecation") + private void showVersion() { + try { + PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0); + ((TextView) findViewById(R.id.version)).setText(getString( + R.string.version_format, info.versionName, info.getLongVersionCode())); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("installed package is missing", e); + } } } diff --git a/app/src/main/res/layout/mirror.xml b/app/src/main/res/layout/mirror.xml index d2355a8..0e1768a 100644 --- a/app/src/main/res/layout/mirror.xml +++ b/app/src/main/res/layout/mirror.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Video surface with optional target navigation and reconnect controls. --> +<!-- Video surface with transient connection status. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root" @@ -38,44 +38,6 @@ android:singleLine="true" android:ellipsize="end" android:text="@string/connecting"/> - - - <Button - android:id="@+id/nav_back" - style="@style/Button.Secondary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/space_sm" - android:minWidth="0dp" - android:minHeight="@dimen/touch_min" - android:paddingHorizontal="@dimen/space_md" - android:paddingVertical="@dimen/space_xs" - android:text="@string/nav_back"/> - - <Button - android:id="@+id/nav_home" - style="@style/Button.Secondary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/space_sm" - android:minWidth="0dp" - android:minHeight="@dimen/touch_min" - android:paddingHorizontal="@dimen/space_md" - android:paddingVertical="@dimen/space_xs" - android:text="@string/nav_home"/> - - <Button - android:id="@+id/nav_recents" - style="@style/Button.Secondary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/space_sm" - android:minWidth="0dp" - android:minHeight="@dimen/touch_min" - android:paddingHorizontal="@dimen/space_md" - android:paddingVertical="@dimen/space_xs" - android:text="@string/nav_recents"/> - <Button android:id="@+id/reconnect" style="@style/Button.Secondary" diff --git a/app/src/main/res/layout/settings.xml b/app/src/main/res/layout/settings.xml index aa2f117..0b70de3 100644 --- a/app/src/main/res/layout/settings.xml +++ b/app/src/main/res/layout/settings.xml @@ -16,13 +16,19 @@ <TextView android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/space_lg" android:text="@string/settings" android:textColor="@color/on_surface" android:textSize="22sp" android:textStyle="bold"/> <TextView + android:id="@+id/version" + style="@style/Caption" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/space_lg"/> + + <TextView style="@style/Note" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -228,62 +234,6 @@ <LinearLayout style="@style/Card" android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <TextView - style="@style/SectionHeader" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/max_fps"/> - - <RadioGroup - android:id="@+id/max_fps" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <RadioButton - android:id="@+id/max_fps_0" - style="@style/RadioOption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/max_fps_unlimited"/> - - <RadioButton - android:id="@+id/max_fps_30" - style="@style/RadioOption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/max_fps_30"/> - - <RadioButton - android:id="@+id/max_fps_60" - style="@style/RadioOption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/max_fps_60"/> - - <RadioButton - android:id="@+id/max_fps_90" - style="@style/RadioOption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/max_fps_90"/> - - <RadioButton - android:id="@+id/max_fps_120" - style="@style/RadioOption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/max_fps_120"/> - - </RadioGroup> - - </LinearLayout> - - <LinearLayout - style="@style/Card" - android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="0dp"> @@ -300,13 +250,6 @@ android:layout_height="wrap_content" android:text="@string/clipboard_sync"/> - <CheckBox - android:id="@+id/show_status_bar" - style="@style/RadioOption" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/show_status_bar"/> - </LinearLayout> <!-- Required, not decorative: the bundled SPAKE2 library is diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9b7e03b..ac1c22d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,6 +20,7 @@ <string name="saved_devices">Saved devices</string> <string name="notif_session_active">Mirroring active</string> <string name="settings">Settings</string> + <string name="version_format">Version %1$s (%2$d)</string> <string name="licenses">Open-source licenses</string> <string name="licenses_unavailable">Notices unavailable.</string> <string name="video_codec">Video codec</string> @@ -43,25 +44,15 @@ <string name="bit_rate_4m">4 Mbps (default)</string> <string name="bit_rate_8m">8 Mbps</string> <string name="bit_rate_16m">16 Mbps</string> - <string name="max_fps">Max fps</string> - <string name="max_fps_unlimited">Unlimited (default)</string> - <string name="max_fps_30">30</string> - <string name="max_fps_60">60</string> - <string name="max_fps_90">90</string> - <string name="max_fps_120">120</string> <string name="reconnect">Reconnect</string> - <string name="nav_back">Back</string> - <string name="nav_home">Home</string> - <string name="nav_recents">Recents</string> <string name="connecting">connecting…</string> <string name="disconnected">disconnected</string> <string name="session_error">Session error: %1$s</string> <string name="session_stop_timeout">The old session did not stop. Reconnect was cancelled.</string> - <string name="hint_back">Back goes to the target. Press Back twice to leave. Home and Recents are in the bar above.</string> + <string name="hint_back">Back goes to the target. Press Back twice to leave.</string> <string name="pairing">Pairing…</string> <string name="settings_apply_on_reconnect">Changes apply on the next connection.</string> <string name="display">Display</string> <string name="clipboard_sync">Sync clipboard with the target</string> - <string name="show_status_bar">Show status bar (IP and resolution) while mirroring</string> <string name="no_devices_yet">No devices paired yet - fill the fields above and tap Pair.</string> </resources> diff --git a/fastlane/metadata/android/en-US/changelogs/5.txt b/fastlane/metadata/android/en-US/changelogs/5.txt index 522da36..7fd0edf 100644 --- a/fastlane/metadata/android/en-US/changelogs/5.txt +++ b/fastlane/metadata/android/en-US/changelogs/5.txt @@ -1,4 +1,4 @@ Fix aspect ratio, rotation, touch mapping, cancellation, and reconnects. Prevent Opus packet loss and recover video immediately under decoder pressure. -Use a gesture-safe viewport with optional controls, and show the installed +Use a gesture-safe viewport, and show the installed version in Settings. Require Wireless ADB TLS and update to scrcpy 4.1. diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index c406c1a..9c0a98e 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -11,7 +11,7 @@ Features: * Live video mirroring with selectable codec (H.264, H.265, or AV1) * Audio forwarding with selectable codec (raw or Opus) * Multi-touch, soft keys, and two-way clipboard sync -* Adjustable max size, bit rate, and frame rate +* Adjustable max size and bit rate * Automatic reconnection when the link drops The app pushes the scrcpy server, built from Genymobile's scrcpy sources, to the diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png Binary files differindex 903b7be..1a0127e 100644 --- a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png +++ b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png Binary files differindex 2d2b81e..0c2c5fc 100644 --- a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png +++ b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png |