aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/Settings.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/Settings.java')
-rw-r--r--app/src/main/java/invalid/lena/scrcpy/Settings.java119
1 files changed, 105 insertions, 14 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/Settings.java b/app/src/main/java/invalid/lena/scrcpy/Settings.java
index c4bb83b..fab6ed1 100644
--- a/app/src/main/java/invalid/lena/scrcpy/Settings.java
+++ b/app/src/main/java/invalid/lena/scrcpy/Settings.java
@@ -18,16 +18,32 @@ public final class Settings {
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";
- public static final String DEFAULT_AUDIO_CODEC = "raw";
- public static final int DEFAULT_MAX_SIZE = 0;
- public static final int DEFAULT_VIDEO_BIT_RATE = 8_000_000;
+ public static final String DEFAULT_AUDIO_CODEC = "opus";
+ // Defaults sized for the worst link this is meant to work over, not
+ // the best. The constraint is the target's UPLINK: an uncapped
+ // stream at 8 Mbit/s is fine on a LAN and unusable over a VPN or a
+ // home broadband uplink, which is a supported way to reach a target.
+ // 1080p at 4 Mbit/s looks fine on a phone and fits an ordinary
+ // 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;
- // Hidden while mirroring by default: an always-on bar over the video
- // is intrusive. It still auto-shows when not CONNECTED so Reconnect
- // stays reachable. Record lives in the bar, so enable this to keep it.
- public static final boolean DEFAULT_STATUS_BAR = false;
+ // 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
+ // can read whatever is copied on this device and write anything it
+ // likes back - and because there was previously no way to decline.
+ public static final boolean DEFAULT_CLIPBOARD = true;
private Settings() {}
@@ -36,58 +52,133 @@ public final class Settings {
}
public static String videoCodec(Context ctx) {
- return prefs(ctx).getString(VIDEO_CODEC, DEFAULT_VIDEO_CODEC);
+ String value = string(ctx, VIDEO_CODEC, DEFAULT_VIDEO_CODEC);
+ return "h264".equals(value) || "h265".equals(value) || "av1".equals(value)
+ ? value : DEFAULT_VIDEO_CODEC;
}
public static String audioCodec(Context ctx) {
- return prefs(ctx).getString(AUDIO_CODEC, DEFAULT_AUDIO_CODEC);
+ String value = string(ctx, AUDIO_CODEC, DEFAULT_AUDIO_CODEC);
+ return "opus".equals(value) || "raw".equals(value)
+ ? value : DEFAULT_AUDIO_CODEC;
}
public static int maxSize(Context ctx) {
- return prefs(ctx).getInt(MAX_SIZE, DEFAULT_MAX_SIZE);
+ int value = integer(ctx, MAX_SIZE, DEFAULT_MAX_SIZE);
+ switch (value) {
+ case 0: case 480: case 720: case 1080: case 1440: case 2160:
+ return value;
+ default:
+ return DEFAULT_MAX_SIZE;
+ }
}
public static int videoBitRate(Context ctx) {
- return prefs(ctx).getInt(VIDEO_BIT_RATE, DEFAULT_VIDEO_BIT_RATE);
+ int value = integer(ctx, VIDEO_BIT_RATE, DEFAULT_VIDEO_BIT_RATE);
+ switch (value) {
+ case 1_000_000: case 2_000_000: case 4_000_000:
+ case 8_000_000: case 16_000_000:
+ return value;
+ default:
+ return DEFAULT_VIDEO_BIT_RATE;
+ }
}
public static int maxFps(Context ctx) {
- return prefs(ctx).getInt(MAX_FPS, DEFAULT_MAX_FPS);
+ 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");
+ }
prefs(ctx).edit().putString(VIDEO_CODEC, v).apply();
}
public static void setAudioCodec(Context ctx, String a) {
+ if (!"opus".equals(a) && !"raw".equals(a)) {
+ throw new IllegalArgumentException("invalid audio codec");
+ }
prefs(ctx).edit().putString(AUDIO_CODEC, a).apply();
}
public static void setMaxSize(Context ctx, int v) {
+ if (v != 0 && v != 480 && v != 720 && v != 1080 && v != 1440 && v != 2160) {
+ throw new IllegalArgumentException("invalid maximum size");
+ }
prefs(ctx).edit().putInt(MAX_SIZE, v).apply();
}
public static void setVideoBitRate(Context ctx, int v) {
+ if (v != 1_000_000 && v != 2_000_000 && v != 4_000_000
+ && v != 8_000_000 && v != 16_000_000) {
+ throw new IllegalArgumentException("invalid video bit rate");
+ }
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 prefs(ctx).getBoolean(HINT_BACK_SHOWN, false);
+ return bool(ctx, HINT_BACK_SHOWN, false);
}
public static void setHintBackShown(Context ctx, boolean v) {
prefs(ctx).edit().putBoolean(HINT_BACK_SHOWN, v).apply();
}
+ public static boolean clipboardSync(Context ctx) {
+ return bool(ctx, CLIPBOARD, DEFAULT_CLIPBOARD);
+ }
+
+ public static void setClipboardSync(Context ctx, boolean v) {
+ prefs(ctx).edit().putBoolean(CLIPBOARD, v).apply();
+ }
+
public static boolean showStatusBar(Context ctx) {
- return prefs(ctx).getBoolean(STATUS_BAR, DEFAULT_STATUS_BAR);
+ 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);
+ return value == null ? fallback : value;
+ } catch (ClassCastException e) {
+ Log.w("settings: %s has the wrong type", key);
+ return fallback;
+ }
+ }
+
+ private static int integer(Context ctx, String key, int fallback) {
+ try {
+ return prefs(ctx).getInt(key, fallback);
+ } catch (ClassCastException e) {
+ Log.w("settings: %s has the wrong type", key);
+ return fallback;
+ }
+ }
+
+ private static boolean bool(Context ctx, String key, boolean fallback) {
+ try {
+ return prefs(ctx).getBoolean(key, fallback);
+ } catch (ClassCastException e) {
+ Log.w("settings: %s has the wrong type", key);
+ return fallback;
+ }
+ }
}